Seed relay fetches plain overviews by default
Test and deploy / test (push) Successful in 1m6s
Test and deploy / deploy (push) Successful in 1m43s

For 2026 a.b.ath articles Eweka's gzip replies have a long tail (p90 12.4 s
per 20k vs 3.9 s plain; 6.1k vs 10.5k headers/s). SEED_NNTP_COMPRESS turns
compression back on; plain replies are deflated for nodes asynchronously.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Opus 5.5 committed 2026-10-06 15:49:21 +02:00
1 parent 7893876435
commit 3e6b5bed56
5 files changed
+21 -5

No files matched your search

+3 -2
View File
@@ -31,7 +31,8 @@ class SeedFetchPool {
}
/**
* A failure is retried once without compression, on a connection of its
* Pooled connections fetch compressed only with SEED_NNTP_COMPRESS (plain is
* faster for recent articles). A failure is retried once without compression, on a connection of its
* own: either the pooled connection was dead (dropped while idle), or the
* server stalled mid-reply in compressed mode. Eweka does that for some
* chunks: it stops after most of the stream and never sends the rest
@@ -105,7 +106,7 @@ class SeedFetchPool {
if (this.open < this.size) {
this.open++
try {
return { client: await this.connect(true), usedAt: Date.now() }
return { client: await this.connect(indexerConfig.seed.nntpCompress), usedAt: Date.now() }
} catch (error) {
this.open--
this.waiters.shift()?.()
+6
View File
@@ -72,6 +72,12 @@ export default {
* answered" in 10 minutes came in 0.3 s on a fresh connection.
*/
nntpTimeoutMs: env.get('SEED_NNTP_TIMEOUT_MS', 90 * 1000),
/**
* Ask the news server for gzip-compressed overviews. Off: for 2026 articles Eweka's
* compressed replies have a long tail (p90 12.4 s per 20k, against 3.9 s plain; 6.1k
* against 10.5k headers/s, LOG.md 2026-10-06 (9)). Nodes get gzip from the relay either way.
*/
nntpCompress: env.get('SEED_NNTP_COMPRESS', false),
},
backfill: {
+7 -2
View File
@@ -1,6 +1,9 @@
import { promisify } from 'node:util'
import net from 'node:net'
import tls from 'node:tls'
import { deflateSync, unzipSync } from 'node:zlib'
import { deflate, deflateSync, unzipSync } from 'node:zlib'
const deflateAsync = promisify(deflate)
export interface XoverClientOptions {
host: string
@@ -100,7 +103,9 @@ export class XoverClient {
const reply = await this.command(`XOVER ${from}-${to}`)
if (reply.startsWith('420') || reply.startsWith('423')) return deflateSync(Buffer.alloc(0))
await this.expect(reply, ['224'])
return reply.includes('COMPRESS=GZIP') ? (await this.compressedRaw()).raw : deflateSync(Buffer.from(await this.plainBody()))
if (reply.includes('COMPRESS=GZIP')) return (await this.compressedRaw()).raw
// Plain replies (the relay's default): compressed off the event loop, at the fastest level.
return deflateAsync(Buffer.from(await this.plainBody()), { level: 1 })
})
}
+1
View File
@@ -83,6 +83,7 @@ export default await Env.create(new URL('../', import.meta.url), {
NNTP_TLS_SERVERNAME: Env.schema.string.optional(),
SEED_NNTP_CONNECTIONS: Env.schema.number.optional(),
SEED_NNTP_TIMEOUT_MS: Env.schema.number.optional(),
SEED_NNTP_COMPRESS: Env.schema.boolean.optional(),
// Indexer (config/indexer.ts)
BACKFILL_MAX_QUEUED: Env.schema.number.optional(),
+4 -1
View File
@@ -26,7 +26,9 @@ test.group('SeedFetchPool', () => {
})
})
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve))
const saved = { host: nntpConfig.host, port: nntpConfig.port, secure: nntpConfig.secure, connections: indexerConfig.seed.nntpConnections }
const saved = { host: nntpConfig.host, port: nntpConfig.port, secure: nntpConfig.secure, connections: indexerConfig.seed.nntpConnections, compress: indexerConfig.seed.nntpCompress }
// The fallback from a compressed pooled connection (SEED_NNTP_COMPRESS on).
indexerConfig.seed.nntpCompress = true
Object.assign(nntpConfig, { host: '127.0.0.1', port: (server.address() as net.AddressInfo).port, secure: false })
indexerConfig.seed.nntpConnections = 2
try {
@@ -39,6 +41,7 @@ test.group('SeedFetchPool', () => {
SeedFetchPool.closeIdle()
Object.assign(nntpConfig, { host: saved.host, port: saved.port, secure: saved.secure })
indexerConfig.seed.nntpConnections = saved.connections
indexerConfig.seed.nntpCompress = saved.compress
server.close()
}
})