control-plane-api: don't lock injected ops collections during publications#3164
control-plane-api: don't lock injected ops collections during publications#3164skord wants to merge 2 commits into
Conversation
…tions
Every publication injects ops.us-central1.v1/{logs,stats} into its build so
the runtime knows where to write telemetry. Those collections then landed in
the passthrough set that verify_unchanged_revisions locks with FOR UPDATE,
so every publication in the fleet took a row lock on the same two live_specs
rows, serializing all publishes globally (estuary/sre#54).
The publication does not modify these collections and has no correctness
dependency on their last_pub_id, so skip locking and revision-checking them.
They remain injected into the build, and a publication which actually drafts
an ops collection is still locked and validated through the normal
drafted-spec path.
Adds a regression test which bumps the ops collections' last_pub_id between
an unrelated publication's build and commit, and asserts the commit still
succeeds; without this change it fails with a BuildIdLockFailure on the two
ops rows.
…ed specs
Review follow-ups to the previous commit:
The lock-skip of injected ops collections was by name alone, so it also
dropped the revision check when a drafted spec genuinely reads an ops
collection (as the L2 reporting derivations do). Now the skip only applies
to ops collections which are not a dependency of any drafted spec in the
build, so a genuinely-referenced ops collection is locked and revision
checked like any other dependency.
Also: document on get_ops_collection_names that injection and lock-skipping
must stay in agreement if it becomes data-plane-dependent, remove a dead
`if !expect_pub_id.is_zero() {}` leftover from an earlier refactor
(passthrough rows always have a non-zero pub id), and simplify the
regression test's draft construction and helper. The test now also covers
the referenced-ops case: a derivation reading ops.us-central1.v1/logs gets
a lock failure for exactly that collection when it changes mid-publication,
while the merely-injected stats collection remains unlocked.
|
Pushed review follow-ups in 53178e6:
Full |
There was a problem hiding this comment.
This is the critical part:
Never lock or revision-check ops collections which are present only through injection.
resolve_live_specsinjects ops.us-central1.v1/{logs,stats} into every build so the runtime knows where to write telemetry; the publication does not modify them and has no correctness dependency on theirlast_pub_id
So long as we can convince ourselves that builds don't actually depend on the contents of the ops collections, but instead just injects them as an implementation detail to enable finding them later in order to write logs/stats, then I think this PR is safe to deploy, otherwise it's not.
The check exists because built specs embed content from the live specs they were validated against, so a passthrough spec changing between build and commit means you'd commit output validated against stale inputs. For that to matter, something must actually consume the embedded copy. For the injected ops collections, nothing does:
- The per-task ops journal templates used at activation are fetched fresh from the current
live_specsrow at activation time, not from the publication's build DB. - The runtime's ops publisher doesn't read the ops collection spec either: it writes to
labels.LogsJournal/labels.StatsJournalfrom the shard's labeling (stamped at activation) using a placeholder collection spec. - Nothing in the commit records ops state: they're not in any task's
reads_from/writes_to, so they don't participate in dependency hashes,live_spec_flows, ornotify_dependents.
This says to me that we can merge this PR, and also that the real fix is to remove this injection entirely. I asked Claude to do an investigation into the history of its presence and whether we can get rid of it, and the answer appears to be yes:
Why the injection exists (June 2024, cpv2). In the cpv2-era runtime, the ops publisher held real *pf.CollectionSpecs: opsLogsSpec/opsStatsSpec fields, ops.ValidateLogsCollection(opsLogsSpec) at startup, with the specs loaded from the task's build DB (cpv2 go/runtime/ops_publisher.go). The reactor genuinely could not route telemetry without the ops collections present in every build, so resolve_live_specs injected them, and the comment "the resulting build will not function properly in the data plane without them" was true when written (commit 3ef202e).
When it stopped being true (August 2024, federated data planes, f1502ba). That work moved ops journal resolution out of the build entirely: activation stamps LOGS_JOURNAL/STATS_JOURNAL shard labels from journal templates fetched fresh from live_specs at activation time, and the runtime was rewritten to write to the label-named journals with a placeholder spec. LogsJournal doesn't exist anywhere in the cpv2 tree and the spec-loading ops publisher doesn't exist in the current one; they're two halves of the same migration. The injection and its comment just survived it.
The decisive evidence that it's vestigial: federated data planes already run without it. A task in ops/dp/public/aws-us-east-1-c1 uses that plane's ops collections via data_planes.ops_logs_name, and those collections are never injected into any build. Meanwhile the pair we do inject (ops.us-central1.v1/*) is the wrong pair for every task outside the legacy plane. So for most of the fleet we've been injecting, fetching, building, and globally locking two collections their builds don't even nominally relate to.
|
@jgraettinger I/claude did some investigation and wrote up my findings above ☝️ but this is a "real" change in behavior of the build process, which I am not intimately familiar with. Would you mind taking a look as well? |
Problem
Every publication takes
FOR UPDATEon thelive_specsrows ofops.us-central1.v1/logsandops.us-central1.v1/stats(estuary/sre#54).resolve_live_specsinjects those two collections into every build so the runtime knows where to write telemetry, and they then land in the passthrough set thatverify_unchanged_revisionslocks and revision-checks. The result is that all publications across the fleet serialize on the same two rows.Change
verify_unchanged_revisionsnow removes the injected ops collections from its expected set before locking. The publication does not modify them and has no correctness dependency on theirlast_pub_id, so there is nothing to lock or verify:update_live_specs.LockFailureis produced for them.Testing
test_injected_ops_collections_are_not_locked: publishes the ops collections, builds an unrelated publication, republishes the ops collections to bump theirlast_pub_id, then commits the first build. Without the fix the commit fails with aBuildIdLockFailureon the two ops rows (verified); with the fix it succeeds. The test also asserts the ops collections remain present in the build output.cargo nextest run -p control-plane-api -p agent: 221 passed, 1 skipped (live Stripe test, skipped in CI as well).After deploy, the
pg_locksblocking tree should no longer showops.us-central1.v1/logs/statsas the contendedlive_specstuples, and concurrent publications of different tenants should no longer wait on each other.