Repository navigation
Conversation
… stop Each run numbered an hour's cans from 0 and PUT them unconditionally, and took any hour that was over even if the fastpath was still writing to it. When measurements reach the spool after their hour was uploaded (a fastpath backlog, or the reuploader re-sending old measurements, which keep their uid and so their hour), the next run wrote the same keys again and replaced the earlier cans, losing their measurements from the current view and leaving their jsonl rows pointing at other measurements. Scanning every version under raw/ in ooni-data-eu-fra for 2026 found 35,131 measurements that exist only in replaced versions (Feb 23, Mar 11, Apr 10, Apr 11, Apr 23, Apr 25, Jul 14). - Number a run's cans after the highest index already on S3 for the hour, cc, test and collector, and skip any index whose key exists. - Upload an hour's directory only once nothing has been written to it for quiet_minutes (default 5, in api-uploader.conf). - Delete the POSTs only after the cans are uploaded and their jsonl rows inserted, so a failed upload is retried by the next run instead of losing them. - Keep measurements that arrive during a run for the next one, delete only stale .tmp files, and remove an hour's directory only after it has been empty for a day: removing it right after an upload could land between the fastpath's mkdir and its write. - Lock the spool so a run started by hand cannot overlap the timer's.
Runs ooni_api_uploader.py against the fastpath image production runs, a versioned S3 (MinIO) and ClickHouse 24.8, in the uploader's production runtime (Ubuntu 22.04 packages). It submits real measurements the way the ooniprobe service does and creates the situations that replaced cans in ooni-data-eu-fra: measurements written after their hour was uploaded, a full fastpath queue failing over to the failed-reports bucket, the reuploader (master and production's version) re-sending into uploaded hours, S3 unreachable during an upload, and overlapping runs. Every measurement is then looked up as oonimeasurements does (jsonl -> s3path, linenum) and its body compared; the run fails on any missing or wrong measurement, replaced S3 object, or leftover in the spool or failed bucket. The uploader before this branch fails it (120 of 270 measurements found, 4 objects replaced); this branch passes.
The workflow reads production's reuploader from ooni/backend at the
commit in its image tag (8b35a38f). That commit is only on #1219's
branch, which was squash-merged, so the checkout did not have it
("fatal: invalid object name '8b35a38f'"). Fetch refs/pull/*/head too.
aagbsn
marked this pull request as draft
October 20, 2026 09:37
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.
The problem
ooni_api_uploader.pynumbered each run's cans from 0 (<hour>_<cc>_<test>.n<collector>.0.tar.gz/.jsonl.gz) and PUT them without checking. It also took any hour that was over, even while the fastpath was still writing to it. When measurements reach the spool after their hour was uploaded, the next run writes the same keys again and replaces the earlier cans. That happens with a fastpath backlog, and with the reuploader re-sending old measurements, which keep their uid and so their hour. The replaced cans' measurements disappear from the current view of the bucket, and theirjsonlrows point at other measurements' bodies.The change
quiet_minutes(default 5, inapi-uploader.conf)..tmpfiles. Remove an hour's directory only after it has been empty for a day: removing it right after an upload could land between the fastpath'smkdirand its write.flockon the spool, so a run started by hand cannot overlap the timer's (the timer itself never overlaps,Type=oneshot).late_hour_count,can_index_taken,hourdir_busy,hourdir_kept.The test
.github/workflows/test_uploader_e2e.ymlrunstests/uploader_e2e/(README there). It uses:ansible/host_vars/fastpath.prod.ooni.io/vars.yml);ooni-data-eu-fraand the failed-reports bucket;tf/environments/prod/main.tf).It submits real measurements the way the ooniprobe service does, then exercises: