From 9ea6c7b00b3a283639eba3d42712c0f8c60a274f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 29 Jun 2024 22:26:51 +0100 Subject: [PATCH 1/8] Add query timeouts --- deno.json | 2 +- deno.lock | 8 ++++---- src/controllers/nostr/relay.ts | 10 +++++++--- src/storages/EventsDB.ts | 19 +++++++++++-------- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/deno.json b/deno.json index 9c0cec5a..af21804a 100644 --- a/deno.json +++ b/deno.json @@ -26,7 +26,7 @@ "@hono/hono": "jsr:@hono/hono@^4.4.6", "@isaacs/ttlcache": "npm:@isaacs/ttlcache@^1.4.1", "@noble/secp256k1": "npm:@noble/secp256k1@^2.0.0", - "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.23.3", + "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.25.0", "@scure/base": "npm:@scure/base@^1.1.6", "@sentry/deno": "https://deno.land/x/sentry@7.112.2/index.mjs", "@soapbox/kysely-deno-sqlite": "jsr:@soapbox/kysely-deno-sqlite@^2.1.0", diff --git a/deno.lock b/deno.lock index 855f7353..f8e13dbd 100644 --- a/deno.lock +++ b/deno.lock @@ -12,7 +12,7 @@ "jsr:@nostrify/nostrify@^0.22.1": "jsr:@nostrify/nostrify@0.22.5", "jsr:@nostrify/nostrify@^0.22.4": "jsr:@nostrify/nostrify@0.22.4", "jsr:@nostrify/nostrify@^0.22.5": "jsr:@nostrify/nostrify@0.22.5", - "jsr:@nostrify/nostrify@^0.23.3": "jsr:@nostrify/nostrify@0.23.3", + "jsr:@nostrify/nostrify@^0.25.0": "jsr:@nostrify/nostrify@0.25.0", "jsr:@soapbox/kysely-deno-sqlite@^2.1.0": "jsr:@soapbox/kysely-deno-sqlite@2.2.0", "jsr:@soapbox/stickynotes@^0.4.0": "jsr:@soapbox/stickynotes@0.4.0", "jsr:@std/assert@^0.217.0": "jsr:@std/assert@0.217.0", @@ -136,8 +136,8 @@ "npm:zod@^3.23.8" ] }, - "@nostrify/nostrify@0.23.3": { - "integrity": "868b10dd094801e28f4982ef9815f0d43f2a807b6f8ad291c78ecb3eb291605a", + "@nostrify/nostrify@0.25.0": { + "integrity": "98f26f44e95ac87fc91b3f3809d38432e1a7f6aebf10380b2554b6f9526313c6", "dependencies": [ "jsr:@std/encoding@^0.224.1", "npm:@scure/base@^1.1.6", @@ -1420,7 +1420,7 @@ "jsr:@bradenmacdonald/s3-lite-client@^0.7.4", "jsr:@db/sqlite@^0.11.1", "jsr:@hono/hono@^4.4.6", - "jsr:@nostrify/nostrify@^0.23.3", + "jsr:@nostrify/nostrify@^0.25.0", "jsr:@soapbox/kysely-deno-sqlite@^2.1.0", "jsr:@soapbox/stickynotes@^0.4.0", "jsr:@std/assert@^0.225.1", diff --git a/src/controllers/nostr/relay.ts b/src/controllers/nostr/relay.ts index 4e624e9b..e86f1991 100644 --- a/src/controllers/nostr/relay.ts +++ b/src/controllers/nostr/relay.ts @@ -73,11 +73,15 @@ function connectStream(socket: WebSocket) { const pubsub = await Storages.pubsub(); try { - for (const event of await store.query(filters, { limit: FILTER_LIMIT })) { + for (const event of await store.query(filters, { limit: FILTER_LIMIT, timeout: 500 })) { send(['EVENT', subId, event]); } } catch (e) { - send(['CLOSED', subId, e.message]); + if (e instanceof RelayError) { + send(['CLOSED', subId, e.message]); + } else { + send(['CLOSED', subId, 'error: something went wrong']); + } controllers.delete(subId); return; } @@ -124,7 +128,7 @@ function connectStream(socket: WebSocket) { /** Handle COUNT. Return the number of events matching the filters. */ async function handleCount([_, subId, ...filters]: NostrClientCOUNT): Promise { const store = await Storages.db(); - const { count } = await store.count(filters); + const { count } = await store.count(filters, { timeout: 500 }); send(['COUNT', subId, { count, approximate: false }]); } diff --git a/src/storages/EventsDB.ts b/src/storages/EventsDB.ts index bd350173..c22e2567 100644 --- a/src/storages/EventsDB.ts +++ b/src/storages/EventsDB.ts @@ -51,7 +51,7 @@ class EventsDB implements NStore { } /** Insert an event (and its tags) into the database. */ - async event(event: NostrEvent, _opts?: { signal?: AbortSignal }): Promise { + async event(event: NostrEvent, opts?: { signal?: AbortSignal; timeout?: number }): Promise { event = purifyEvent(event); this.console.debug('EVENT', JSON.stringify(event)); dbEventCounter.inc({ kind: event.kind }); @@ -63,7 +63,7 @@ class EventsDB implements NStore { await this.deleteEventsAdmin(event); try { - await this.store.event(event); + await this.store.event(event, { timeout: opts?.timeout ?? 3000 }); } catch (e) { if (e.message === 'Cannot add a deleted event') { throw new RelayError('blocked', 'event deleted by user'); @@ -137,7 +137,10 @@ class EventsDB implements NStore { } /** Get events for filters from the database. */ - async query(filters: NostrFilter[], opts: { signal?: AbortSignal; limit?: number } = {}): Promise { + async query( + filters: NostrFilter[], + opts: { signal?: AbortSignal; timeout?: number; limit?: number } = {}, + ): Promise { filters = await this.expandFilters(filters); dbQueryCounter.inc(); @@ -160,28 +163,28 @@ class EventsDB implements NStore { this.console.debug('REQ', JSON.stringify(filters)); - return this.store.query(filters, opts); + return this.store.query(filters, { timeout: opts.timeout ?? 3000 }); } /** Delete events based on filters from the database. */ - async remove(filters: NostrFilter[], _opts?: { signal?: AbortSignal }): Promise { + async remove(filters: NostrFilter[], opts?: { signal?: AbortSignal; timeout?: number }): Promise { if (!filters.length) return Promise.resolve(); this.console.debug('DELETE', JSON.stringify(filters)); - return this.store.remove(filters); + return this.store.remove(filters, opts); } /** Get number of events that would be returned by filters. */ async count( filters: NostrFilter[], - opts: { signal?: AbortSignal } = {}, + opts: { signal?: AbortSignal; timeout?: number } = {}, ): Promise<{ count: number; approximate: boolean }> { if (opts.signal?.aborted) return Promise.reject(abortError()); if (!filters.length) return Promise.resolve({ count: 0, approximate: false }); this.console.debug('COUNT', JSON.stringify(filters)); - return this.store.count(filters); + return this.store.count(filters, { timeout: opts.timeout ?? 1000 }); } /** Return only the tags that should be indexed. */ From 96fe171d6595fcc7afc78d3ec245ff5373a60a51 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 1 Jul 2024 07:22:33 +0100 Subject: [PATCH 2/8] Use kysely_deno_postgres with simple transactions --- deno.json | 2 +- deno.lock | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/deno.json b/deno.json index af21804a..cd52a343 100644 --- a/deno.json +++ b/deno.json @@ -50,7 +50,7 @@ "iso-639-1": "npm:iso-639-1@2.1.15", "isomorphic-dompurify": "npm:isomorphic-dompurify@^2.11.0", "kysely": "npm:kysely@^0.27.3", - "kysely_deno_postgres": "https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/b4725e74ad6ca359ba0e370b55dbb8bb845a8a83/mod.ts", + "kysely_deno_postgres": "https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/c6869b9e12d74af78a846ad503d84493f5db9df4/mod.ts", "light-bolt11-decoder": "npm:light-bolt11-decoder", "linkify-plugin-hashtag": "npm:linkify-plugin-hashtag@^4.1.1", "linkify-string": "npm:linkify-string@^4.1.1", diff --git a/deno.lock b/deno.lock index f8e13dbd..1377bf48 100644 --- a/deno.lock +++ b/deno.lock @@ -1395,6 +1395,10 @@ "https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/b4725e74ad6ca359ba0e370b55dbb8bb845a8a83/mod.ts": "662438fd3909984bb8cbaf3fd44d2121e949d11301baf21d6c3f057ccf9887de", "https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/b4725e74ad6ca359ba0e370b55dbb8bb845a8a83/src/PostgreSQLDriver.ts": "ea5a523bceeed420858b744beeb95d48976cb2b0d3f519a68b65a8229036cf6a", "https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/b4725e74ad6ca359ba0e370b55dbb8bb845a8a83/src/PostgreSQLDriverDatabaseConnection.ts": "11e2fc10a3abb3d0729613c4b7cdb9cb73b597fd77353311bb6707c73a635fc5", + "https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/c6869b9e12d74af78a846ad503d84493f5db9df4/deps.ts": "b3dbecae69c30a5f161323b8c8ebd91d9af1eceb98fafab3091c7281a4b64fed", + "https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/c6869b9e12d74af78a846ad503d84493f5db9df4/mod.ts": "662438fd3909984bb8cbaf3fd44d2121e949d11301baf21d6c3f057ccf9887de", + "https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/c6869b9e12d74af78a846ad503d84493f5db9df4/src/PostgreSQLDriver.ts": "0f5d1bc2b24d4e0052e38ee289fb2f5e8e1470544f61aa2afe65e1059bf35dfb", + "https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/c6869b9e12d74af78a846ad503d84493f5db9df4/src/PostgreSQLDriverDatabaseConnection.ts": "e5d4e0fc9737c3ec253e679a51f5b43d2bb9a3386c147b7b1d14f4f5a5f734f1", "https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/f2948b86190a10faa293588775e162b3a8b52e70/deps.ts": "b3dbecae69c30a5f161323b8c8ebd91d9af1eceb98fafab3091c7281a4b64fed", "https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/f2948b86190a10faa293588775e162b3a8b52e70/mod.ts": "662438fd3909984bb8cbaf3fd44d2121e949d11301baf21d6c3f057ccf9887de", "https://gitlab.com/soapbox-pub/kysely-deno-postgres/-/raw/f2948b86190a10faa293588775e162b3a8b52e70/src/PostgreSQLDriver.ts": "ac1a39e86fd676973bce215e19db1f26b82408b8f2bb09a3601802974ea7cec6", From d062f6bbb670b686faddfc52d3edc79ce348e8ae Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 1 Jul 2024 08:44:01 +0100 Subject: [PATCH 3/8] Try lazy pool initialization --- src/db/adapters/DittoPostgres.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/adapters/DittoPostgres.ts b/src/db/adapters/DittoPostgres.ts index c06a262f..6df78b97 100644 --- a/src/db/adapters/DittoPostgres.ts +++ b/src/db/adapters/DittoPostgres.ts @@ -12,7 +12,7 @@ export class DittoPostgres { static getPool(): Pool { if (!this.pool) { - this.pool = new Pool(Conf.databaseUrl, Conf.pg.poolSize); + this.pool = new Pool(Conf.databaseUrl, Conf.pg.poolSize, true); } return this.pool; } From c3ffe7c7f76fc3f1710d50fd0f992c410f56f28a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 1 Jul 2024 08:53:03 +0100 Subject: [PATCH 4/8] EventsDB: fix limit being passed to NDatabase --- src/storages/EventsDB.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/storages/EventsDB.ts b/src/storages/EventsDB.ts index c22e2567..664be893 100644 --- a/src/storages/EventsDB.ts +++ b/src/storages/EventsDB.ts @@ -51,7 +51,7 @@ class EventsDB implements NStore { } /** Insert an event (and its tags) into the database. */ - async event(event: NostrEvent, opts?: { signal?: AbortSignal; timeout?: number }): Promise { + async event(event: NostrEvent, opts: { signal?: AbortSignal; timeout?: number } = {}): Promise { event = purifyEvent(event); this.console.debug('EVENT', JSON.stringify(event)); dbEventCounter.inc({ kind: event.kind }); @@ -63,7 +63,7 @@ class EventsDB implements NStore { await this.deleteEventsAdmin(event); try { - await this.store.event(event, { timeout: opts?.timeout ?? 3000 }); + await this.store.event(event, { ...opts, timeout: opts.timeout ?? 3000 }); } catch (e) { if (e.message === 'Cannot add a deleted event') { throw new RelayError('blocked', 'event deleted by user'); @@ -163,15 +163,15 @@ class EventsDB implements NStore { this.console.debug('REQ', JSON.stringify(filters)); - return this.store.query(filters, { timeout: opts.timeout ?? 3000 }); + return this.store.query(filters, { ...opts, timeout: opts.timeout ?? 3000 }); } /** Delete events based on filters from the database. */ - async remove(filters: NostrFilter[], opts?: { signal?: AbortSignal; timeout?: number }): Promise { + async remove(filters: NostrFilter[], opts: { signal?: AbortSignal; timeout?: number } = {}): Promise { if (!filters.length) return Promise.resolve(); this.console.debug('DELETE', JSON.stringify(filters)); - return this.store.remove(filters, opts); + return this.store.remove(filters, { ...opts, timeout: opts.timeout ?? 5000 }); } /** Get number of events that would be returned by filters. */ @@ -184,7 +184,7 @@ class EventsDB implements NStore { this.console.debug('COUNT', JSON.stringify(filters)); - return this.store.count(filters, { timeout: opts.timeout ?? 1000 }); + return this.store.count(filters, { ...opts, timeout: opts.timeout ?? 1000 }); } /** Return only the tags that should be indexed. */ From 092a20088a5ae82a89ce71b034b0b24e61b4b480 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 1 Jul 2024 09:18:42 +0100 Subject: [PATCH 5/8] Reduce timeouts --- src/controllers/nostr/relay.ts | 4 ++-- src/storages/EventsDB.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/controllers/nostr/relay.ts b/src/controllers/nostr/relay.ts index e86f1991..6d79b031 100644 --- a/src/controllers/nostr/relay.ts +++ b/src/controllers/nostr/relay.ts @@ -73,7 +73,7 @@ function connectStream(socket: WebSocket) { const pubsub = await Storages.pubsub(); try { - for (const event of await store.query(filters, { limit: FILTER_LIMIT, timeout: 500 })) { + for (const event of await store.query(filters, { limit: FILTER_LIMIT, timeout: 300 })) { send(['EVENT', subId, event]); } } catch (e) { @@ -128,7 +128,7 @@ function connectStream(socket: WebSocket) { /** Handle COUNT. Return the number of events matching the filters. */ async function handleCount([_, subId, ...filters]: NostrClientCOUNT): Promise { const store = await Storages.db(); - const { count } = await store.count(filters, { timeout: 500 }); + const { count } = await store.count(filters, { timeout: 100 }); send(['COUNT', subId, { count, approximate: false }]); } diff --git a/src/storages/EventsDB.ts b/src/storages/EventsDB.ts index 664be893..61b87b00 100644 --- a/src/storages/EventsDB.ts +++ b/src/storages/EventsDB.ts @@ -63,7 +63,7 @@ class EventsDB implements NStore { await this.deleteEventsAdmin(event); try { - await this.store.event(event, { ...opts, timeout: opts.timeout ?? 3000 }); + await this.store.event(event, { ...opts, timeout: opts.timeout ?? 1000 }); } catch (e) { if (e.message === 'Cannot add a deleted event') { throw new RelayError('blocked', 'event deleted by user'); @@ -163,7 +163,7 @@ class EventsDB implements NStore { this.console.debug('REQ', JSON.stringify(filters)); - return this.store.query(filters, { ...opts, timeout: opts.timeout ?? 3000 }); + return this.store.query(filters, { ...opts, timeout: opts.timeout ?? 1000 }); } /** Delete events based on filters from the database. */ @@ -171,7 +171,7 @@ class EventsDB implements NStore { if (!filters.length) return Promise.resolve(); this.console.debug('DELETE', JSON.stringify(filters)); - return this.store.remove(filters, { ...opts, timeout: opts.timeout ?? 5000 }); + return this.store.remove(filters, { ...opts, timeout: opts.timeout ?? 3000 }); } /** Get number of events that would be returned by filters. */ @@ -184,7 +184,7 @@ class EventsDB implements NStore { this.console.debug('COUNT', JSON.stringify(filters)); - return this.store.count(filters, { ...opts, timeout: opts.timeout ?? 1000 }); + return this.store.count(filters, { ...opts, timeout: opts.timeout ?? 500 }); } /** Return only the tags that should be indexed. */ From 4e150edb5b1f3bab308a1c3b5ff73f168c091206 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 1 Jul 2024 19:48:28 +0100 Subject: [PATCH 6/8] Actually enable query timeouts :facepalm: --- src/storages/EventsDB.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/storages/EventsDB.ts b/src/storages/EventsDB.ts index 61b87b00..69959fc0 100644 --- a/src/storages/EventsDB.ts +++ b/src/storages/EventsDB.ts @@ -45,6 +45,7 @@ class EventsDB implements NStore { constructor(private kysely: Kysely) { this.store = new NDatabase(kysely, { fts: Conf.db.dialect, + timeoutStrategy: Conf.db.dialect === 'postgres' ? 'setStatementTimeout' : undefined, indexTags: EventsDB.indexTags, searchText: EventsDB.searchText, }); From 3ae6d39ebc2f5f2aa80217ca682ec447b50ebc6b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 1 Jul 2024 20:29:06 +0100 Subject: [PATCH 7/8] Increase notifications endpoint timeout --- src/controllers/api/notifications.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/api/notifications.ts b/src/controllers/api/notifications.ts index d92ccf4a..51a20a05 100644 --- a/src/controllers/api/notifications.ts +++ b/src/controllers/api/notifications.ts @@ -78,7 +78,7 @@ async function renderNotifications( const store = c.get('store'); const pubkey = await c.get('signer')?.getPublicKey()!; const { signal } = c.req.raw; - const opts = { signal, limit: params.limit }; + const opts = { signal, limit: params.limit, timeout: 5000 }; const events = await store .query(filters, opts) From ae687bb52561298437a54cf36da61a242da18a95 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 1 Jul 2024 23:05:03 +0100 Subject: [PATCH 8/8] Increase timeouts --- src/controllers/api/notifications.ts | 2 +- src/controllers/nostr/relay.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/api/notifications.ts b/src/controllers/api/notifications.ts index 51a20a05..64a5a7ca 100644 --- a/src/controllers/api/notifications.ts +++ b/src/controllers/api/notifications.ts @@ -78,7 +78,7 @@ async function renderNotifications( const store = c.get('store'); const pubkey = await c.get('signer')?.getPublicKey()!; const { signal } = c.req.raw; - const opts = { signal, limit: params.limit, timeout: 5000 }; + const opts = { signal, limit: params.limit, timeout: 10_000 }; const events = await store .query(filters, opts) diff --git a/src/controllers/nostr/relay.ts b/src/controllers/nostr/relay.ts index 6d79b031..4d8ab2cb 100644 --- a/src/controllers/nostr/relay.ts +++ b/src/controllers/nostr/relay.ts @@ -73,7 +73,7 @@ function connectStream(socket: WebSocket) { const pubsub = await Storages.pubsub(); try { - for (const event of await store.query(filters, { limit: FILTER_LIMIT, timeout: 300 })) { + for (const event of await store.query(filters, { limit: FILTER_LIMIT, timeout: 1000 })) { send(['EVENT', subId, event]); } } catch (e) {