Skip to content

fix: rebuild the time-series date index after large eTUFF loads - #461

Merged
lewismc merged 7 commits into
mainfrom
fix/timeseries-date-index
Oct 18, 2026
Merged

lewismc merged 7 commits into
mainfrom
fix/timeseries-date-index

Conversation

@lewismc

@lewismc lewismc commented Oct 13, 2026

Copy link
Copy Markdown
Member

@renato2099 this PR will be updated once #437 is merged into main. This PR include #437 but really all that will change in this PR is the SQL files. I also want your take on refactoring the SQL files into one file rather than two. We can discuss that separately.

Cross posting from Slack: I'm sharing some results from smoke testing ingest performance of the feat/async-ingest branch and some local modifications to the tagbase schema. The local changes will land in a separate PR after we finish review of feat/async-ingest branch.

Large eTUFF files now finish in about 27 minutes. The previous file of this size took 1 hour 5 minutes.

A file with at least one million time-series rows no longer updates the date index on every row. Tagbase drops that index for the load, writes the measurements, links them to positions, and builds the index once before the load is visible. Smaller files are unchanged.

I loaded five ICCAT bluefin tuna files, 715–792 MiB, one at a time into a database that already held the earlier smoke-test data. Job time includes parsing and the database write. All five succeeded, and the date index was present again after each one.

• 2024BFT07891A_264262_23P2947.txt — 714.8 MiB, 59 min, 8% shorter than 1:04:45
• 2024BFT07903A_265548_24P0087.txt — 764.4 MiB, 1 hr 4 min, 1% shorter
• 2024BFT07902A_265536_24P0021.txt — 766.5 MiB, 27 min, 59% shorter
• 2024BFT07799A_264255_23P2610.txt — 788.8 MiB, 27 min, 58% shorter
• 2024BFT07801A_264258_23P2820.txt — 791.5 MiB, 27 min, 58% shorter

The previous file was 2024BFT07901A_265012_23P2911.txt (714.0 MiB, 1:04:45).

The last three files agree. Each wrote about 18–19 million time-series rows. Rebuilding the date index took about 12 seconds.

Why the first two files were still about an hour: parsing (~7 min) and position linking (~4 min) took the same time on every file. The time-series insert did not. On the first two files it ran for 34–42 minutes at about 35 MB/min and spent most of that time reading data pages from disk. On the last three it finished in 2–7 minutes at 200–600 MB/min and stayed on CPU, with almost no data-page reads.

All five files already had the date index removed during the load, so that change does not explain the split. New rows are written in file order, while the primary key is ordered by submission, tag, variable, then time, and its pages are packed full. Those inserts were splitting index pages that did not stay in the 256 MB database cache. After the first file, autovacuum cleaned data_time_series. The second file started during that cleanup and was the slowest insert. After the cleanup, the next three inserts touched a small set of cached primary-key pages, and the disk reads went away.

Use the later three runs as the time to expect for another file of this size on this database. During a large load, the date index is unavailable until that load commits.

POST/GET /ingest return 202 with a job id instead of holding the
request open while every archive member is processed, which caused
nginx 504s on large zips. Poll GET /ingest/jobs/{job_id} for status.
One worker, started by docker-entrypoint.sh beside Gunicorn, runs
queued jobs from the new ingest_job table. Clients that read the old
200 Ingest200 body must poll the job instead.
POST /ingest/jobs streams a .txt, .zip, .tar.gz, or .tgz up to 2 GiB and
returns 202. One worker drains the ingest_job queue. Synchronous /ingest
still finishes inside the request and rejects bodies over 100 MiB.

The eTUFF migration matches histogram rows without joining every
time-series row, and resolves position times with one indexed lookup.
submission.filename stores the eTUFF name. Finished jobs are purged by
cron. fswatch and the upload page use the job API. Archive extraction
uses the Python standard library.
The job upload opened its staging file inside an async function, and
repeated error strings were duplicated literals.
bash 5 treated a zero-second read as another copy of the previous
path, so later drops never reached the job API. The watcher also
exited on startup because its state volume was not writable.
The contract test read the pending list as soon as the second POST
returned, before the watcher finished writing that job.
Maintaining data_time_series_date_time_index on every row held a large
load to about 29 MB/min of heap once existing data already covered
2011–2026. Submissions of at least one million time-series rows now
drop that index for the insert and create it once, in the same
transaction, after positions are linked.
@lewismc lewismc added this to the 0.18.0 milestone Oct 13, 2026
@lewismc
lewismc requested a review from renato2099 October 13, 2026 02:15
@lewismc lewismc self-assigned this Oct 13, 2026
Merge origin/main, including queued-upload ingest from #437.
Keep the deferred time-series date index rebuild in both schema files.
@lewismc

lewismc commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

@renato2099 this is the most important performance improvement before we shift SQL stored procedures to Python.

@lewismc lewismc changed the title Fix/timeseries date index fix: rebuild the time-series date index after large eTUFF loads Oct 18, 2026
@lewismc
lewismc force-pushed the fix/timeseries-date-index branch from 24e8f08 to 8da2ee2 Compare October 18, 2026 04:12
@sonarqubecloud

Copy link
Copy Markdown

@lewismc
lewismc merged commit 06341f8 into main Oct 18, 2026
40 checks passed
@lewismc
lewismc deleted the fix/timeseries-date-index branch October 18, 2026 04:15
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