Commit Graph
15 Commits
Author SHA1 Message Date
Daan MeijerandClaude Opus 5.5 9ae15ed26f Seed batches: test chunked inserts with a lowered chunk size
Test and deploy / test (push) Successful in 1m57s
Test and deploy / deploy (push) Successful in 4m7s
The 6,000-file test took 29 s and failed CI; 120 files in chunks of 50
cover the same path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:20:17 +02:00
Daan MeijerandClaude Opus 5.5 953bbe9e39 Seed batches: insert files and binaries in chunks of 2,000 rows
Test and deploy / test (push) Failing after 3m16s
Test and deploy / deploy (push) Skipped
A batch of a dense range (over ~5,400 files) exceeded Postgres' 65,535
bind parameters per statement, so its post failed with a 500 until the
range was given up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:01:32 +02:00
Daan MeijerandClaude Opus 5.5 8d86f7d489 Seed fragments: keep only those near the range edges, unstaged
Test and deploy / test (push) Successful in 1m5s
Test and deploy / deploy (push) Successful in 1m48s
About 5% of seeded headers are fragments. They went through staging,
where they waited 30 minutes for parts that were never coming (the
neighbouring range is worked on by another node, often hours later) and
expired: the staging table grew to 22 GB and the NVMe part of the database
reached its 60 GB limit, which stopped leasing and the backfill. Files cut
by a range edge can still be completed by the neighbouring range, so
fragments within 50k articles of an edge go straight into binaries and
parts; the rest, files with articles missing for good, are dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:30:24 +02:00
Daan MeijerandClaude Opus 5.5 530a752cca Seed: finished ranges marked done; relay XOVER timeout 10 minutes
Test and deploy / test (push) Successful in 1m37s
Test and deploy / deploy (push) Successful in 2m10s
- A range reported to its end but not finished (its node went away at
  the very end) was leased again with start past end; nodes asked for
  "from 49208000000 to 49207999999", got 400, and the range failed after
  five leases. The orchestrator now marks such ranges done before
  leasing, and runRange reports them done without fetching.
- Some old ranges come so slowly that one XOVER of 20k headers takes over
  two minutes: five ranges failed on "NNTP reply timed out". The relay
  pool's timeout is SEED_NNTP_TIMEOUT_MS (default 10 minutes, the node's
  HTTP timeout).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:21:30 +02:00
Daan MeijerandClaude Opus 5.5 f1773744c0 Partition files by age (id)
Test and deploy / test (push) Successful in 54s
Test and deploy / deploy (push) Successful in 7m42s
New rows went into free space scattered through the 9.6 GB heap on the HDD
(left behind by inspection's updates): one random HDD read per insert, and
seed batches took a median 13 s to store. The existing table becomes the
first partition as it is (a CHECK scan, no rewrite; indexes, its foreign
key and the triggers are taken over by the partitioned parent); new
partitions of 4M ids go in the same tablespace and stay in memory while
they fill.

- Every partition has a unique file_key index; FileStore.insert looks keys
  up in all partitions first (a key can't be unique across partitions).
- FilePartitions.ensure() (gc:scheduler) keeps two partitions ahead.
- Stats count files from the partitions; seed:node waits ~20 minutes for
  the API before giving a range up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:57:51 +02:00
Daan MeijerandClaude Opus 5.5 41859636d3 Seed relay: nodes get their headers through the seed API
Test and deploy / test (push) Successful in 1m28s
Test and deploy / deploy (push) Successful in 2m5s
The seed API keeps a pool of NNTP connections (SEED_NNTP_CONNECTIONS) and
serves GET /api/v1/seed/ranges/:id/chunk?from=&to= for leased ranges:
the gzip stream the news server sent, passed on unopened. seed:node fetches
that way by default (SEED_FETCH=relay), so a node needs only a token and
SEED_CONNECTIONS; SEED_FETCH=direct keeps its own NNTP connections. All
fetching then comes from the indexer's IP (the account allows two).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:24:39 +02:00
Daan MeijerandClaude Opus 5.5 3d5cfb2527 Seeding nodes lease first, connect for the lease, close when idle
Test and deploy / test (push) Successful in 1m36s
Test and deploy / deploy (push) Successful in 1m58s
A failed NNTP login gives the range back uncounted (no attempt used), and a
node with nothing to lease holds no connection. Replaces log-in-before-lease,
which kept idle connections and logged in again every minute while idle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:18:38 +02:00
Daan MeijerandClaude Opus 5.5 00f6187b8f Seed leases respect the backfill's disk limits
Test and deploy / test (push) Successful in 1m8s
Test and deploy / deploy (push) Successful in 1m37s
No new range while the database's NVMe part or the hdd tablespace is over
BACKFILL_MAX_NVME_GB / BACKFILL_MAX_HDD_GB; nodes ask again a minute later.
seed:status says when leasing is paused. The NVMe part was at 47 GB of 50
with a plan for all of a.b.ath queued.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:30:46 +02:00
Daan MeijerandClaude Opus 5.5 8c36744088 Seed orchestrator: remote nodes lease ranges and post results over HTTPS
Test and deploy / test (push) Successful in 1m4s
Test and deploy / deploy (push) Successful in 1m37s
- seed_nodes (hashed bearer tokens) and seed_ranges (planned ranges with
  lease, resume point and counts); migration …140.
- SeedOrchestrator: lease (oldest plan first, FOR UPDATE SKIP LOCKED; a
  lease without a batch for 15 min passes to the next node and resumes at
  next_id), batch (stores files and fragments, moves next_id), finish,
  release, plan (from below live indexing / the backfill / earlier plans
  down to N days back; takes over the group's backfill), status.
- Seed API under /api/v1/seed (lease, ranges/:id/batches|done|release,
  status) behind a node-token middleware; JSON bodies up to 64 MB after
  gzip.
- seed:node runs a node: no database or Redis, outgoing HTTPS only, N
  connections, gzip batches, retries for network errors and 5xx, drops a
  range on 409, gives its ranges back on SIGTERM. seed-node/ has a compose
  file that builds the image from this repository.
- seed:token, seed:plan, seed:status for the operator. Header parsing moved
  to lib/ingest and the range loop to lib/seed/runner, shared by seed:range
  and seed:node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:02:01 +02:00
Daan MeijerandClaude Opus 5.5 68eb9a44fd Edge aggregation: seed:range indexes history one row per file
Test and deploy / test (push) Successful in 1m36s
Test and deploy / deploy (push) Successful in 3m37s
A seeding node fetches a range over one NNTP connection, parses headers
with the live rules, groups them into files in memory (lib/aggregate) and
stores per complete file its release, the file and a completed binary
(SeedService), instead of one parts row per article. What doesn't complete
within the window goes through the regular ingest path as fragments.
`--dry-run` only fetches and aggregates, to measure a node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:41:18 +02:00
Daan MeijerandClaude Opus 5.5 7ca338d4f3 Extension filter via a releases.extensions column
Test and deploy / test (push) Successful in 1m28s
Test and deploy / deploy (push) Successful in 58m9s
The first version matched extensions in search_text, which was wrong and
slow: file names enter search_text as stems without extension (so plainly
posted .mkv files were missed), and matching the TOASTed text took 3-4 s
for common types.

- Migration …130: releases.extensions text[] (two to four characters with
  a letter; .7z.001 and .r00 count as 7z and rar), kept current by triggers
  on files (name, release) and on releases (archive listing), filled with
  one sequential pass over files, with a GIN index.
- Search: ext matches with `extensions && ?`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 19:54:38 +02:00
Daan MeijerandClaude Opus 5.5 e1392608d8 Search filters for size (MB) and file extensions
Test and deploy / test (push) Successful in 1m27s
Test and deploy / deploy (push) Successful in 2m9s
- API: min_size and max_size in MB, ext as a comma-separated list. An
  extension matches as a whole word in search_text (file and entry names,
  dots as spaces), so "iso" finds .iso files but not "ISOLATION", which a
  plain search word matched as a substring. The trigram index serves it.
- Web UI: a size range and an extension field in the filter row, kept in
  the URL like the other filters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 19:43:55 +02:00
Daan MeijerandClaude Opus 5.5 2178847513 Stop refilling parts of binaries that are already a file
Test and deploy / test (push) Successful in 52s
Test and deploy / deploy (push) Successful in 1m23s
Crossposts, reposts and backfill ranges keep bringing articles of files
that worker:file already created. Ingest inserted their header parts again,
and GC's copied-parts pass (which searched all binaries for parts on every
batch) removed only 150k-450k per sweep: parts grew to 68M rows, 56M of
them belonging to finished files, and the 23 GB table paused the backfill.

- Ingest skips header parts (no per-part CRC) for binaries with a file_id.
- GC drops copied parts with a cursor over binaries, 2000 per statement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 02:53:11 +02:00
Daan MeijerandClaude Opus 5.5 be8a3d4580 Spotnet import, header backfill, staging and counter grouping, faster search
Spotnet (CONCEPT D29):
- Index free.pt fully and import spots with their descriptions, categories
  and NZBs; a spot renames its release, since Spotnet names are more
  trustworthy than names derived from subjects.
- Find passwords in spot descriptions too, and tell "needs a password we
  have" from "needs a password we don't have".

Content inspection:
- List the contents of 7z (.7z.001) archives, SFV sets, nicovideo uploads
  and other archive-like files; mark yEncBin .bin containers as encrypted.
- Take real names from yEnc headers for randomly named files, and merge
  uploads that were split across posts.

Storage (D31, D32):
- Store segment lists zstd-compressed behind the FileStore accessor.
- group:backfill indexes older headers at low priority and pauses when
  the NVMe or HDD part of the database gets too large.

Grouping (D35, D36):
- Lone header segments wait in staged_segments until a second segment of
  their file arrives, which drops the per-article junk binaries.
- Counter grouping joins random-subject uploads by poster, [i/M] counter,
  part count and day.

Search and maintenance (D33, D34):
- A trigram-indexed search_text column, smaller indexes, estimated stats.
- Passes that clean up nonsense names; weekly index maintenance; GC budgets
  per kind.
- Web UI: grey out stale results while a search runs and cancel superseded
  requests. Count only up to 1,000 matches for the page, which made filter
  clicks take seconds (Newznab keeps the 10,000 cap).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 19:59:23 +02:00
Daan Meijer bcb9be5da9 implemented a lot of features 2026-09-29 00:32:01 +02:00