Repository navigation
Ray uploads build their tables in the production database and publish them by rename - #267
Merged
Merged
Conversation
…lready in id order The 337M-row minnie upload failed its transfer twice (2026-10-28): pg_dump | psql streams 137 GB through the driver, and psql had a fixed 1200 s timeout. The Ray driver now transfers with Cloud SQL imports and exports (method "cloudsql", transfer_table_using_cloudsql), run by the database server: - the annotation table is imported from the processed CSV it was imported into staging from, which nothing changes afterwards (exported from staging if the CSV is gone); - the segmentation table is exported from staging in id order, then imported; - each export and import is recorded on the job (upload_cloudsql_ops, one kind per operation), so a rerun skips what finished, waits on or adopts what was started, and reruns what failed; the truncate only runs before a fresh import; - the row count is checked against staging before the indexes are built; - cleanup deletes the exported CSVs with the processed one. Celery keeps pg_dump | psql (UPLOAD_TRANSFER_METHOD), with the psql timeout now UPLOAD_TRANSFER_PSQL_TIMEOUT_SECONDS, default 6 h. cluster_staging_tables skips a table whose id correlation (after ANALYZE of id) is at least UPLOAD_CLUSTER_SKIP_CORRELATION (0.99). The annotation table is imported in id order, so its CLUSTER (6h13m on that upload, mostly rebuilding GIST indexes) changed nothing. Needs the Ray service account to have cloudsql.instances.export and the Cloud SQL service account to be able to write to the upload bucket (terraform-google-cave). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… them by rename The staging path built every table twice: the annotation indexes in staging for the spatial lookup and again in production after copying, with both copies on the instance's disk until the end (the 337M-row minnie upload, 2026-10-28: ~166 GB twice, ~6.5 h of ~22 h spent re-importing, re-indexing and re-exporting). New Ray jobs (params.build_in_production) build in the aligned volume database under temporary names (upl<hash>_<table>, production_build.py), with no metadata rows, so nothing in production lists them: - load_annotations: the table from its model, the Cloud SQL import, its indexes; - spatial_init/spatial_chunks: get_materialization_info takes the table's metadata directly (annotation_metadata), the segmentation table is created there; - finish_tables: the segmentation table clustered by id, its indexes, ANALYZE; - publish, one transaction: tables, indexes, constraints and the id sequence renamed to the names index_manager expects, the sequence set to max(id) (it stayed at 1), then the annotation and segmentation metadata rows. A name taken meanwhile raises PublishConflict and keeps the built tables. Cancels are refused from publish on. A failed or cancelled job (and the cleanup CronJob) drops only tables carrying its marker comment and no metadata row. Jobs started before keep the staging stages. A frozen version terminates every connection to the live database to copy it, so uploads give way: create_analysis_database records a clone request (upload_state.clone_requests) and waits, up to UPLOAD_CLONE_WAIT_SECONDS (2 h), until no upload holds the database (production_holds). The driver starts nothing new there while a request is open, drains the chunks in flight (run_bounded should_pause), closes its connections and resumes after the copy; workers close theirs after every chunk. The gate is on wherever the upload state database exists. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he job, not a metadata row The chunker's bounds query and each chunk's point query looked the schema up from annotation_table_metadata, which a temporary table does not have until publish. The bounds came back empty, the chunker fell back to one zero-volume chunk, and that chunk failed. Both now use the schema spatial_init already has, and spatial_init stops when the bounds can't be found instead of going on with a chunk that finds nothing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ce's ids are unsorted A source file with its own ids keeps the file's order through the processed CSV and the import; the minnie synapse files' ids are unsorted, so the 1M ltv7 test table was published with id correlation 0.0001. load_annotations now builds the primary key, clusters on it if the id correlation is below UPLOAD_CLUSTER_SKIP_CORRELATION, and only then builds the other indexes, so the CLUSTER rewrites one index instead of six. CLUSTER also gets the upload's maintenance_work_mem for its sort. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fcollman
force-pushed
the
ray-build-in-production
branch
from
October 29, 2026 06:44
6d0f604 to
da40993
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #266. This branch includes its commit: the GCS transfer and the CLUSTER skip, which the Celery staging path still uses.
Why
A Ray bulk upload today is built in
staging, then copied into production. On minnie's 337M-rowsynapses_300m_ray2that meant:The copied annotation table also came out unclustered (id correlation −0.0006), and its id sequence was left at 1.
What changes (Ray path only)
Build in production under temporary names. The tables are
upl<hash6>_<table>and its segmentation table, with no metadata rows until publish. The table's metadata travels in the job's params.Annotation table:
CLUSTERby id, only when the source's ids are unsorted (correlation belowUPLOAD_CLUSTER_SKIP_CORRELATION, 0.99).Spatial lookup: runs against the temporary table. It takes the schema from the job, since there is no metadata row.
Segmentation table: clustered by id, then indexed, before publish.
Publish (one transaction):
index_managerexpects;setvals the sequence tomax(id);Tables, indexes and metadata become visible together. Publish is idempotent on resume (a table comment marks the job).
Yields to frozen materialization.
create_analysis_databaserecords a clone request inupload_state.clone_requests. It waits until no upload holds the database: each driver pauses its new work, lets in-flight statements finish and drops its connections. The wait is bounded byUPLOAD_CLONE_WAIT_SECONDS(default 2h). Clones made mid-upload drop theupl…tables.Failure or cancel drops only the job's recorded
upl…tables.Name-length check: a job is refused at start if any derived temporary or final index name would exceed 63 bytes.
Celery uploads keep the staging path.
Expected effect on a 337M-row upload
About 22h → about 15.5h, because nothing is transferred and no index is built twice. There is one copy on disk instead of two.
Testing
vBuildInProd3), checked against the earlier staging-path run:max(id);uplleftovers.upltables;max(id);uplleftovers.Before a large run
UPLOAD_CLONE_WAIT_SECONDSshould exceed the longest single statement. ACLUSTERof a 337M-row annotation table can take hours.CLUSTERneeds about the table's size in free disk.🤖 Generated with Claude Code