Skip to content

v2.1.0

Choose a tag to compare

@cargopete cargopete released this 11 Aug 12:45
9651658

Be your own indexer. One static binary, one command, a live indexed API in under two minutes - no subgraph to author, no Postgres to run, no third-party data dependency, ever.

This is a binary swap. No schema change, no data migration, no flag changes. Stop, replace the binary, start.

What 2.1 is

2.0 moved tenancy into the runtime. 2.1 spends its budget on something less glamorous and more overdue: making the promises measurable, and fixing what measuring them exposed.

The headline numbers in this project used to be claims. They are now gates that fail the build, with both their healthy value and their failure value measured on the machine that enforces them.

The ≤2 GB per-cursor budget is now a gate

CLAUDE.md has promised ≤2 GB per active-chain cursor since the beginning. Nothing checked it.

It is now measured on the case most likely to break it - 20 nests on one cursor, the real 10-event Uniswap V4 PoolManager ABI, 200 logs per block, 240,200 rows, and 200 blocks of live tip-following after the backfill drains.

Peak 131 MB against a 2048 MB budget. At a 4x event rate, 198 MB. The at-tip figure comes in below the peak, which is the sealing design working rather than luck: RSS tracks the near-tip window and the per-window fan-out, not the size of history, because rows past finality are sealed to Parquet and leave the heap.

A second ceiling, 180 MB, catches regressions the 2 GB promise never would - a change could cost ten times the memory and still pass 2048.

Entity point-read latency is now a gate

p50 0.59-0.82µs on the CI runner, gated at 8µs.

The ceiling was not chosen for comfort. It was derived by replacing the B-tree seek in Store::get_entity with a linear scan and measuring what a real regression costs - 18.15µs - then placing the gate between the two.

Both gates were caught being weaker than claimed, before you could rely on them

The point-read ceiling shipped at 15µs on the strength of dev-box numbers. A throwaway probe put the regression back in and let CI judge it: the margin was 1.21x, not the 1.61x written down, because a cache-resident scan does not slow proportionally on a smaller machine. Re-baselined to 8µs from numbers measured where the gate runs.

The RAM ceiling got the same treatment and survived it - 322 MB on the runner against 323 MB on a 32-core box, because a retention leak holds the same bytes wherever it runs. CPU-bound regressions need re-measuring per machine; memory-bound ones largely do not.

Correctness

  • A factory nest over the provider's getLogs cap recovers instead of dying. A topic0-only union fetch that breaks the cap is now retried address-filtered, with the same in-block discovery the backfill path uses. Previously it quarantined the nest permanently, and the recovery already existed one path over. (#297)
  • A contract-free nest no longer asks for every log on the chain. (#429)
  • A corrupt sealed segment reduces the table instead of deleting it - and says so, rather than reporting an empty table as a healthy one. (#419)
  • Restart rebuild walks the hot store once, not three times per transfer table with nine parses per row. (#294)
  • An edited nest adopts its own data instead of re-indexing it when mounted into a running runtime. (#364)
  • A provider's own retry hint is honoured rather than guessed at, clamped so it can never undercut our pacing. (#361)
  • /explain's hot scan is bounded, as /sql already was. (#293)

Security

  • A deleted nest no longer leaves its provider credentials behind. undeclare_nest removed the nest and kept its secrets - unreachable through any surface that lists nests, and silently inherited by the next nest declared under the same name. (#426)
  • Both deploy recipes stopped shipping a published admin token. The Docker recipe was live rather than theoretical: the image binds 0.0.0.0:8288, so the documented -e flipped the admin surface on with a token published in this repository. (#292)
  • --no-admin means the routes are not mounted, not that they answer 404. (#292)
  • The control-API token guard is a route layer, so a new route cannot forget it. (#292)
  • The query-FE role derives its admin credential like every other role. (#292)

Isolation, tested rather than asserted

RFC-0021 promises that one chain's cursor cannot harm another. Two of its testing criteria had no coverage at all:

  • Cross-cursor stall isolation - one chain's provider going dark leaves its sibling advancing, and the stalled cursor catches up by itself on recovery. (#356)
  • Cross-cursor death isolation - a dying cursor is retired, not shared. (#387)

Upgrading

# stop, swap, start
nuthatch --version   # 2.1.0

Nothing to migrate. If you are on 1.x, read Upgrading to 2.0 first - that one does move data on disk.