Skip to content

Root ID updates: dense missing-root lookup, repairs that keep last_updated, and an update log - #263

Merged
fcollman merged 4 commits into
masterfrom
root-id-update-log-on-master
Oct 26, 2026
Merged

fcollman merged 4 commits into
masterfrom
root-id-update-log-on-master

Conversation

@fcollman

Copy link
Copy Markdown
Collaborator

The root ID update work that minnie and ltv7 have been running as vRootLog* images, on current master.

Changes

  • Dense missing root ID lookup streams and throttles its work and never moves last_updated.
  • Root ID lookups can do a full re-lookup, and the regular update fills NULL roots.
  • Repairs (full re-lookup, NULL fill) keep last_updated and look up roots as of it.
  • Root ID update log: every update_root_ids run is recorded as an append-only Delta table.

Relation to other branches

Conflict resolution

Every commit conflicted in tests/test_ingest_new_annotations.py: each edits the import list that #258 moved into a block that mocks modules only during the import. In each case I kept master's block and used the commit's import names.

Testing

Full suite on a fresh PostGIS: 575 passed, 2 skipped.

🤖 Generated with Claude Code

fcollman and others added 4 commits October 26, 2026 13:52
…dated

The dense lookup queued one chord over the whole span between the lowest and highest
row with a NULL root, 500 ids per task, all at once. A large span filled Redis; a span
of one row crashed every task ('int' object is not subscriptable) and any longer span
could not be sent at all (range is not JSON serializable). Each task also re-looked-up
every row in its range, not only the NULL ones, and the chain ended with
update_metadata, moving the table's last_updated to now: the next update_root_ids run
then skipped the roots of other rows that expired in between, leaving them stale.

It now streams the ids of rows with a supervoxel id and a NULL root, and queues small
batches as the process queue drains (batch_missing_root_ids_query, throttled), like the
sparse lookup but without its "table modified recently" check. Roots are looked up at
the table's last update, which stays as it is.

monitor_task_states polls only the tasks still pending, instead of every task every
0.2 s.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ar update

Two tools, both through update_root_ids (so both write only what changed and keep the
table's last_updated right):

- Full re-lookup, for a table whose roots may be stale without knowing which: every row
  is looked up from its supervoxel id (lookup_all_root_ids). New per-table endpoint
  /materialize/run/lookup_all_root_ids/datastack/<ds>/table/<table>; the existing
  update_roots?lookup_all_root_ids=true does it for a whole datastack.
- NULL roots: every update now looks up the rows with a supervoxel id and a NULL root in
  one chunk of their own (they used to be added to every expired-root chunk, and were
  skipped entirely when nothing had expired). The dense and sparse missing-root
  endpoints now run that update right away; the hourly and build chains no longer need
  the separate (gated) missing-root step.

update_root_ids_workflow queues its chunk tasks with at most UPDATE_ROOT_IDS_MAX_IN_FLIGHT
(default 20) unfinished at a time (run_with_limit) instead of one chord of all of them,
so a full re-lookup of a large table, in 50k-row chunks, cannot flood Redis.
get_new_root_ids writes only rows whose root changed. update_metadata runs only after
every chunk succeeded, as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…of it

A full re-lookup (lookup_all_root_ids) or a NULL fill (only_missing_roots, from the dense
and sparse missing-root endpoints) is a repair: it looks roots up as of the table's
last_updated (now if it has none) and leaves last_updated as it is, so the next regular
update still covers everything that expired since. The NULL fill no longer searches for
expired roots.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…y Delta table

One append-only Delta table per annotation table, root_id_updates/{datastack}/{table}/:
one commit per run (partitioned by run_id), rows (id, root_column, supervoxel_id,
old_root_id, new_root_id, start_time_stamp, end_time_stamp) sorted by (root_column, id),
id delta-encoded, zstd, 16k-row row groups; plus a _manifest table with one row per run,
including runs that changed nothing or failed, so the windows chain. Off unless
ROOT_ID_UPDATE_LOG names the datastack / tables. Only rows whose root changed are
recorded.

(The root log history up to vRootLog12, squashed onto the throttled root id lookups.)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@fcollman
fcollman merged commit 5e923e9 into master Oct 26, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant