fix: cache preload

feat: Fetch only newest relay list updates
This commit is contained in:
Kieran 2023-05-25 10:31:47 +01:00
parent 2ccee7bd7c
commit 988416f353
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
13 changed files with 79 additions and 51 deletions

View File

@ -11,8 +11,8 @@ class DMCache extends FeedCache<RawEvent> {
return of.id;
}
override async preload(): Promise<void> {
await super.preload();
override async preload(follows?: Array<string>): Promise<void> {
await super.preload(follows);
// load all dms to memory
await this.buffer([...this.onTable]);
}

View File

@ -12,8 +12,8 @@ class EventInteractionCache extends FeedCache<EventInteraction> {
return sha256(of.event + of.by);
}
override async preload(): Promise<void> {
await super.preload();
override async preload(follows?: Array<string>): Promise<void> {
await super.preload(follows);
const data = window.localStorage.getItem("zap-cache");
if (data) {

View File

@ -12,18 +12,18 @@ interface HookFilter {
export default abstract class FeedCache<TCached> {
#name: string;
#table: Table<TCached>;
#hooks: Array<HookFilter> = [];
#snapshot: Readonly<Array<TCached>> = [];
#changed = true;
#hits = 0;
#miss = 0;
protected table: Table<TCached>;
protected onTable: Set<string> = new Set();
protected cache: Map<string, TCached> = new Map();
constructor(name: string, table: Table<TCached>) {
this.#name = name;
this.#table = table;
this.table = table;
setInterval(() => {
debug(this.#name)(
"%d loaded, %d on-disk, %d hooks, %d% hit",
@ -35,9 +35,9 @@ export default abstract class FeedCache<TCached> {
}, 30_000);
}
async preload() {
async preload(follows?: Array<string>) {
if (db.ready) {
const keys = await this.#table.toCollection().primaryKeys();
const keys = await this.table.toCollection().primaryKeys();
this.onTable = new Set<string>(keys.map(a => a as string));
}
}
@ -75,7 +75,7 @@ export default abstract class FeedCache<TCached> {
async get(key?: string) {
if (key && !this.cache.has(key) && db.ready) {
const cached = await this.#table.get(key);
const cached = await this.table.get(key);
if (cached) {
this.cache.set(this.key(cached), cached);
this.notifyChange([key]);
@ -88,7 +88,7 @@ export default abstract class FeedCache<TCached> {
async bulkGet(keys: Array<string>) {
const missing = keys.filter(a => !this.cache.has(a));
if (missing.length > 0 && db.ready) {
const cached = await this.#table.bulkGet(missing);
const cached = await this.table.bulkGet(missing);
cached.forEach(a => {
if (a) {
this.cache.set(this.key(a), a);
@ -105,7 +105,7 @@ export default abstract class FeedCache<TCached> {
const k = this.key(obj);
this.cache.set(k, obj);
if (db.ready) {
await this.#table.put(obj);
await this.table.put(obj);
this.onTable.add(k);
}
this.notifyChange([k]);
@ -113,7 +113,7 @@ export default abstract class FeedCache<TCached> {
async bulkSet(obj: Array<TCached>) {
if (db.ready) {
await this.#table.bulkPut(obj);
await this.table.bulkPut(obj);
obj.forEach(a => this.onTable.add(this.key(a)));
}
obj.forEach(v => this.cache.set(this.key(v), v));
@ -164,7 +164,7 @@ export default abstract class FeedCache<TCached> {
key: a,
}));
const start = unixNowMs();
const fromCache = await this.#table.bulkGet(mapped.filter(a => a.has).map(a => a.key));
const fromCache = await this.table.bulkGet(mapped.filter(a => a.has).map(a => a.key));
const fromCacheFiltered = fromCache.filter(a => a !== undefined).map(a => unwrap(a));
fromCacheFiltered.forEach(a => {
this.cache.set(this.key(a), a);
@ -184,7 +184,7 @@ export default abstract class FeedCache<TCached> {
}
async clear() {
await this.#table.clear();
await this.table.clear();
this.cache.clear();
this.onTable.clear();
}

View File

@ -18,6 +18,14 @@ class UserProfileCache extends FeedCache<MetadataCache> {
return of.pubkey;
}
override async preload(follows?: Array<string>): Promise<void> {
await super.preload(follows);
// load follows profiles
if (follows) {
await this.buffer(follows);
}
}
async search(q: string): Promise<Array<MetadataCache>> {
if (db.ready) {
// on-disk cache will always have more data

View File

@ -10,6 +10,19 @@ class UsersRelaysCache extends FeedCache<UsersRelays> {
return of.pubkey;
}
override async preload(follows?: Array<string>): Promise<void> {
await super.preload(follows);
if (follows) {
await this.buffer(follows);
}
}
newest(): number {
let ret = 0;
this.cache.forEach(v => (ret = v.created_at > ret ? v.created_at : ret));
return ret;
}
takeSnapshot(): Array<UsersRelays> {
return [...this.cache.values()];
}

View File

@ -52,11 +52,14 @@ export function mapEventToProfile(ev: RawEvent) {
}
}
export async function preload() {
await UserCache.preload();
await DmCache.preload();
await InteractionCache.preload();
await UserRelays.preload();
export async function preload(follows?: Array<string>) {
const preloads = [
UserCache.preload(follows),
DmCache.preload(follows),
InteractionCache.preload(follows),
UserRelays.preload(follows),
];
await Promise.all(preloads);
}
export { UserCache, DmCache };

View File

@ -21,6 +21,7 @@ export interface RelayMetrics {
export interface UsersRelays {
pubkey: HexKey;
created_at: number;
relays: FullRelaySettings[];
}

View File

@ -1,5 +1,6 @@
import { useEffect, useMemo } from "react";
import { TaggedRawEvent, Lists, EventKind } from "@snort/nostr";
import debug from "debug";
import { bech32ToHex, getNewest, getNewestEventTagsByKey, unwrap } from "SnortUtils";
import { makeNotification, sendNotification } from "Notifications";
@ -41,6 +42,7 @@ export default function useLoginFeed() {
.limit(1);
const dmSince = DmCache.newest();
debug("LoginFeed")("Loading dms since %s", new Date(dmSince * 1000).toISOString());
b.withFilter().authors([pubKey]).kinds([EventKind.DirectMessage]).since(dmSince);
b.withFilter().kinds([EventKind.DirectMessage]).tag("p", [pubKey]).since(dmSince);
return b;

View File

@ -1,20 +1,24 @@
import { useMemo } from "react";
import { HexKey, FullRelaySettings, TaggedRawEvent, RelaySettings, EventKind } from "@snort/nostr";
import debug from "debug";
import { sanitizeRelayUrl } from "SnortUtils";
import { PubkeyReplaceableNoteStore, RequestBuilder } from "System";
import useRequestBuilder from "Hooks/useRequestBuilder";
import { UserRelays } from "Cache/UserRelayCache";
interface RelayList {
pubkey: string;
created: number;
created_at: number;
relays: FullRelaySettings[];
}
export default function useRelaysFeedFollows(pubkeys: HexKey[]): Array<RelayList> {
const sub = useMemo(() => {
const b = new RequestBuilder(`relays:follows`);
b.withFilter().authors(pubkeys).kinds([EventKind.Relays, EventKind.ContactList]);
const since = UserRelays.newest();
debug("LoginFeed")("Loading relay lists since %s", new Date(since * 1000).toISOString());
b.withFilter().authors(pubkeys).kinds([EventKind.Relays, EventKind.ContactList]).since(since);
return b;
}, [pubkeys]);
@ -22,7 +26,7 @@ export default function useRelaysFeedFollows(pubkeys: HexKey[]): Array<RelayList
return notes.map(ev => {
return {
pubkey: ev.pubkey,
created: ev.created_at,
created_at: ev.created_at,
relays: ev.tags
.map(a => {
return {
@ -45,7 +49,7 @@ export default function useRelaysFeedFollows(pubkeys: HexKey[]): Array<RelayList
const relays: Record<string, RelaySettings> = JSON.parse(ev.content);
return {
pubkey: ev.pubkey,
created: ev.created_at,
created_at: ev.created_at,
relays: Object.entries(relays)
.map(([k, v]) => {
return {
@ -61,7 +65,7 @@ export default function useRelaysFeedFollows(pubkeys: HexKey[]): Array<RelayList
}
return {
pubkey: ev.pubkey,
created: 0,
created_at: 0,
relays: [],
};
});

View File

@ -14,9 +14,7 @@ import useLoginFeed from "Feed/LoginFeed";
import { totalUnread } from "Pages/MessagesPage";
import useModeration from "Hooks/useModeration";
import { NoteCreator } from "Element/NoteCreator";
import { db } from "Db";
import useEventPublisher from "Feed/EventPublisher";
import { preload } from "Cache";
import { useDmCache } from "Hooks/useDmsCache";
import { mapPlanName } from "./subscribe";
import useLogin from "Hooks/useLogin";
@ -111,29 +109,6 @@ export default function Layout() {
};
}, [preferences.theme]);
useEffect(() => {
// check DB support then init
db.isAvailable().then(async a => {
db.ready = a;
if (a) {
await preload();
}
console.debug(`Using db: ${a ? "IndexedDB" : "In-Memory"}`);
try {
if ("registerProtocolHandler" in window.navigator) {
window.navigator.registerProtocolHandler(
"web+nostr",
`${window.location.protocol}//${window.location.host}/%s`
);
console.info("Registered protocol handler for 'web+nostr'");
}
} catch (e) {
console.error("Failed to register protocol handler", e);
}
});
}, []);
return (
<div className={pageClass}>
{!shouldHideHeader && (

View File

@ -247,7 +247,7 @@ export class Query implements QueryBase {
return false;
}
if ((q.relays?.length ?? 0) === 0 && c.Ephemeral) {
this.#log("Cant send non-specific REQ to ephemeral connection");
this.#log("Cant send non-specific REQ to ephemeral connection %o", q.relays);
return false;
}
if (q.filters.some(a => a.search) && !c.SupportsNip(Nips.Search)) {

View File

@ -221,7 +221,7 @@ export class NostrSystem extends ExternalStore<SystemSnapshot> {
if (rb.options?.leaveOpen) {
q.leaveOpen = rb.options.leaveOpen;
}
if (rb.options?.relays) {
if (rb.options?.relays && (rb.options?.relays?.length ?? 0) > 0) {
q.relays = rb.options.relays;
}

View File

@ -32,6 +32,9 @@ import Thread from "Element/Thread";
import { SubscribeRoutes } from "Pages/subscribe";
import ZapPoolPage from "Pages/ZapPool";
import DebugPage from "Pages/Debug";
import { db } from "Db";
import { preload } from "Cache";
import { LoginStore } from "Login";
// @ts-ignore
window.__webpack_nonce__ = "ZmlhdGphZiBzYWlkIHNub3J0LnNvY2lhbCBpcyBwcmV0dHkgZ29vZCwgd2UgbWFkZSBpdCE=";
@ -42,6 +45,25 @@ export const router = createBrowserRouter([
{
element: <Layout />,
errorElement: <ErrorPage />,
loader: async () => {
db.ready = await db.isAvailable();
if (db.ready) {
await preload(LoginStore.takeSnapshot().follows.item);
}
try {
if ("registerProtocolHandler" in window.navigator) {
window.navigator.registerProtocolHandler(
"web+nostr",
`${window.location.protocol}//${window.location.host}/%s`
);
console.info("Registered protocol handler for 'web+nostr'");
}
} catch (e) {
console.error("Failed to register protocol handler", e);
}
return null;
},
children: [
...RootRoutes,
{