Skip to content

Fix uploader can overwrite - #515

Draft
aagbsn wants to merge 3 commits into
mainfrom
fix_uploader_can_overwrite
Draft

aagbsn wants to merge 3 commits into
mainfrom
fix_uploader_can_overwrite

Conversation

@aagbsn

@aagbsn aagbsn commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

The problem

ooni_api_uploader.py numbered 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 their jsonl rows point at other measurements' bodies.

The change

  • 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. The file name format is unchanged. The jsonl rows record the key actually used, so Explorer, the fastpath S3 feeder and ooni/data read the new cans as before.
  • 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 both cans are uploaded and the jsonl rows inserted.
  • Keep measurements that arrive during a run for the next one, and delete only stale .tmp files. 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.
  • flock on the spool, so a run started by hand cannot overlap the timer's (the timer itself never overlaps, Type=oneshot).
  • New metrics: late_hour_count, can_index_taken, hourdir_busy, hourdir_kept.

The test

.github/workflows/test_uploader_e2e.yml runs tests/uploader_e2e/ (README there). It uses:

  • the fastpath image production runs (from ansible/host_vars/fastpath.prod.ooni.io/vars.yml);
  • MinIO with versioning on, standing in for ooni-data-eu-fra and the failed-reports bucket;
  • ClickHouse 24.8.6.70;
  • the uploader in its production runtime (Ubuntu 22.04 apt packages);
  • the reuploader at master and at the version production runs (from tf/environments/prod/main.tf).

It submits real measurements the way the ooniprobe service does, then exercises:

  1. a normal hour;
  2. measurements written after their hour was uploaded (fastpath workers suspended, then resumed);
  3. a full fastpath queue failing over to the failed-reports bucket;
  4. the reuploader re-sending into uploaded hours;
  5. S3 unreachable during an upload, and two uploader runs at once.

… 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
aagbsn marked this pull request as draft October 20, 2026 09:37
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