Skip to content

Add fault tolerance support during migration - #35

Open
kPsarakis wants to merge 20 commits into
mainfrom
ft-state-migration
Open

kPsarakis wants to merge 20 commits into
mainfrom
ft-state-migration

Conversation

@kPsarakis

@kPsarakis kPsarakis commented Mar 21, 2026 •

Copy link
Copy Markdown
Member

Summary

Makes state migration fault tolerant. Before this, a worker crash during a migration reverted to the pre-migration snapshot and retried from scratch. Now migration progress is checkpointed into snapshots: a crash after the checkpoint resumes the transfer with the new layout, a crash before it reverts to the old layout and retries the migration. The branch is rebased onto main and also fixes several correctness and latency problems in the migration path, some of which predate it.

Migration algorithm

A migration starts when a client submits a graph whose operators have different partition counts. The coordinator runs one migration at a time. It forces a snapshot at the next epoch boundary (the revert point if the migration fails before its checkpoint), assigns the new partitions to workers, and sends every worker InitMigration with the new graph, its assignment and the partition locations. Partitions are pre-created up to the maximum parallelism; those at or above an operator's partition count are shadow partitions. The migration then runs in three phases. Steps marked (new) are added by this PR; the rest is on main.

Phase A: rehash, protocol running. Each worker rehashes the keys of its partitions with the new partitioner, in a process pool and without network I/O, and records which keys must move and to which partition. It does not apply the new plan yet. Each worker reports MigrationRepartitioningDone. Once all have, the coordinator sets the checkpoint metadata (new) and asks the protocol to stop at the next epoch boundary.

Phase B: switch over, protocol stopped. Each worker:

  1. Waits for the protocol to stop at the epoch boundary, with every response flushed. It is never cancelled mid-epoch (new).
  2. Applies the plan (DNS, peers, assignments) and creates its new partitions.
  3. Rehashes the keys created during Phase A.
  4. Sends each destination the metadata of the keys it will receive (key → source worker and old partition). The destination keeps it as remote_keys, so it knows where to find a key that hasn't arrived yet.
  5. Takes the migration checkpoint snapshot (new).
  6. Reports MigrationInitDone with its epoch and transaction counters and its ingress and egress offsets.

The coordinator merges the counters and offsets (max per partition). Once the checkpoint is complete on every worker (new), it publishes the new graph, so the other clients switch partitioners (the client that submitted the migration switched when it submitted it), and sends MigrationDone with the merged counters. Each worker then rebuilds the protocol for its new assignment, reusing its Kafka clients (new), and starts it.

Phase C: transfer, protocol running. A background sender on each worker moves its keys to their new partitions in batches, alongside normal processing. A batch to a partition on the same worker is applied in place (new); a batch to a peer stays unacknowledged until the peer has applied it. Each epoch drains the in-flight batches before its processing-done barrier and caps how many keys the sender moves (ASYNC_MIGRATION_KEYS_PER_EPOCH) (new). Transactions don't wait for the transfer:

  • A key that hasn't arrived is fetched on demand: moved directly from a local partition, or requested from its source worker (RequestRemoteKey / ReceiveRemoteKey). If the sender already took it into a batch, the transaction waits for that batch (new).
  • A request addressed to a partition that doesn't hold its key is rerouted by the consuming worker to the partition that does, as a proxied call if that partition is on a peer (new). This covers requests routed by the old graph and still in Kafka, and requests the submitting client routes by the new graph before the switch-over, which land on shadow partitions.

When a worker has sent every key and every batch is acknowledged, it reports MigrationDone with the epoch its last key landed in. Once every worker has, the migration is finished. Snapshots keep the checkpoint metadata until one is cut after that epoch (new).

Fault tolerance

Checkpoint. In Phase B, after the protocol stops and the hash metadata is exchanged, every worker takes a snapshot flagged as the migration checkpoint. The coordinator attaches the migration metadata (new graph, partition locations) to the sequencer file of that snapshot and of every later one, until a snapshot is cut after the last migrated key landed. It publishes the new graph and sends MigrationDone only once the checkpoint is complete on every worker.

Recovery. On recovery the coordinator reads the latest complete snapshot:

  • With the checkpoint metadata: recover with the new layout. Each worker re-derives the keys it still has to send from its snapshot state, and the workers exchange the matching remote_keys (all-to-all, on ReadyAfterRecovery) before the protocol starts. The coordinator keeps tracking the resumed transfer, so a second crash during it resumes again.
  • Without it, while a migration was running: revert to the old layout and retry the migration after the first post-recovery snapshot.

Snapshots during the transfer. Migrated keys are recorded as tombstones at the source and as inserts at the destination. A batch never straddles a snapshot cut: each epoch drains in-flight batches (acknowledged by the destination once applied) before its processing-done barrier, and the sender resumes after the cleanup barrier. The flush to the snapshot process swaps the delta map before sending, so writes landing during the flush are not lost.

Exactly-once output. After a recovery, every worker scans all output partitions (a replayed request may now run on a different worker) and suppresses responses already sent. A clean migration stop needs no scan: it stops at an epoch boundary with every response flushed.

Requests for migrated keys. A request consumed for a key that now belongs to a partition on another worker stays in the consuming worker's epoch and runs on the owner as a proxied call. The owner returns the whole outcome (response, abort, chain participants, fallback read/write-set change) in one ProxyResult, which the consuming worker awaits before it can commit, and the response goes to the request's ingress partition. So an ingress partition's offset is only ever advanced by the worker consuming it, in order.

Stale messages. The coordinator bumps an incarnation number on every recovery. Migration batches, remote-key requests/replies, proxied calls and their results, and the recovery hash exchange carry it, and messages from another incarnation are dropped.

Fixes beyond the original change

Correctness:

  • Keys could be lost across a snapshot cut: async batches were in flight across it, and the delta flush cleared writes that landed while it awaited.
  • After a resume, destinations had no remote_keys, so transactions read keys still at their source as missing.
  • A transaction could read a migrating key as missing when the async sender had already taken it into a batch (local move, or a None reply to a remote fetch); it now waits for the batch.
  • Keys whose value is None were dropped by the transfer.
  • The checkpoint metadata was cleared on MigrationDone, so a snapshot cut before the last key landed but completed afterwards lost it; and any snapshot completing after Phase B began was taken as the checkpoint.
  • A snapshot started while the previous one was uploading wrote its files under the same id, and failed uploads were reported as complete. Snapshots now get their id at start and are reported strictly in order.
  • Phase B force-stopped a protocol still running after 15 s, possibly mid-commit. A barrier or migration message the coordinator cannot deliver now hands the worker to recovery instead, after one heartbeat limit: by then the heartbeat check has removed a dead worker (and rescheduled its partitions), so only a live one is recovered as a participant. Recovered at once, a dead worker stayed a participant and recovery waited on it forever.
  • A destination could receive a batch for a partition it had not created yet.
  • Requests consumed for a migrated key were forwarded into the owner's epoch, so two workers advanced one ingress partition's offset, each by max, and forwarded requests could run out of order over the socket pool. A snapshot or the Phase B stop could then record an offset past requests not yet run, which recovery or the rebuilt consumer skipped (the likely cause of 27 missed responses in one CI run of the external-kill test). They now run as proxied calls from the consuming worker.
  • A request rescheduled to a later epoch (a fallback read/write-set change, or a concurrency abort with fallback off) was already behind the epoch's ingress offset, and only the sequencer held it. A recovery from a snapshot cut in between, or the protocol rebuilt after a Phase B stop, never ran it. Snapshots, MigrationInitDone and MigrationDone now carry these pending requests, and the protocol that takes over sequences them before consuming anything, routed to the partition that holds the key now (possibly on another worker, after a recovery).
  • The coordinator folded each worker's snapshot report into the completed offsets on arrival, so a worker's report of snapshot N+1 arriving before another worker's report of N put N+1's offsets in N's sequencer file, past requests N's state does not contain. Each sequencer file is now written from its own snapshot's reports.
  • Recovery started a partition without an offset in the snapshot (e.g. a shadow partition, recovering from the data-load snapshot) at the latest offset, skipping requests written to it before and during the failure.
  • Compaction (off by default) merged deltas in S3 listing order, kept a stale first delta, and never dropped tombstones.
  • The migration YCSB / TPC-C and scalability demos never sent InitDataComplete, so those E2E tests ran on an empty store.

Latency (the stall after Phase B):

  • The rebuilt protocol reuses the egress producer and the ingress consumer instead of creating new Kafka clients, and skips the dedup scan after a clean stop.
  • exists() scanned all pending migration keys for every incoming message (about 3 ms per call with 60k keys pending, which kept the event loop busy for seconds during Phase C). It is now O(1).
  • Local batches are applied in place instead of being sent over TCP to the same worker, and the transfer is spread over epochs (ASYNC_MIGRATION_KEYS_PER_EPOCH, default 3000) so no single epoch serializes the whole share into its snapshot delta.

Performance

YCSB, 4000 tx/s, 500k keys, 4 → 8 partitions, interleaved runs on the same machine:

main this PR
p99 / max latency during the migration 1199–1793 / 1301–1898 ms 540–947 / 620–1081 ms
lowest throughput around the migration 239–556 tx/s 2409–3634 tx/s
steady-state p50 / p99 unchanged unchanged

The remaining spike is mostly the Phase B stop, which includes waiting for the checkpoint snapshot (about 100 ms).

Diagnostics

A stalled epoch stalls every worker at the next barrier and nothing is logged. Workers now warn every EPOCH_STALL_WARN_S (10 s) while one epoch wait lasts, naming the wait (transactions, chains, WAL write, a barrier, fallback, migration batch acks, snapshot delta, responses to Kafka, snapshot request), the unfinished chains and the pending proxied calls. The coordinator warns when no barrier completes for BARRIER_STALL_WARN_S (10 s) while some workers wait, naming the workers it waits on.

Wire format changes

New message types SnapMigrationReassign (44), AsyncMigrationAck (45), RecoveryMigrationHashes (46), RunFunProxy (47), ProxyResult (48); WrongPartitionRequest (26) is gone. AsyncMigration, RequestRemoteKey, ReceiveRemoteKey, MigrationInitDone, MigrationDone, SnapID, SnapTakeSnapshot and InitRecovery carry extra fields. The sequencer file has a fifth element (the migration metadata) and a sixth (the pending requests); 4- and 5-element files still load.

Testing

  • Unit tests for each fix above, including the coordinator recovery paths through the real _perform_recovery.
  • A regression test for the forwarding bug runs Kafka records through the real ingress and sequencer and checks that the recorded ingress offset never passes a request that has not run; it fails on the forwarding code. Workers log Proxied calls this epoch: N when they sent any, so the E2E worker logs show whether that path ran. In a local run of the migration E2E suite, 236 of the 239 proxied calls came from the external-kill test: its recovery put shadow partition 5 and old partition 1, which holds partition 5's keys under the old layout, on different workers, so every request the submitting client sent to partition 5 crossed workers. On the forwarding code, that is where two workers advanced one partition's offset.
  • E2E (tests/e2e/test_e2e_migration_ft_ycsb.py): baseline migration, a kill before the post-migration snapshot (resume from the checkpoint), a kill after it, a kill well after the migration, and an external kill during Phase A (revert and retry). Besides duplicates and missed responses, every key's stored record is now compared with the loaded dataset at the end.
  • Unit tests for the pending requests: a rescheduled request is behind the offset and carried at the cut, it runs once and before newly consumed requests after a restore, it goes to the partition that holds its key now, and a sequencer file never takes a later snapshot's report. The E2E runs did not reschedule a request across a cut, so that path is covered by the unit tests only.
  • Which phase a kill lands in depends on timing; the docstrings note the path observed on a typical machine.

🤖 Generated with Claude Code

@kPsarakis kPsarakis self-assigned this Mar 21, 2026
@codecov

codecov Bot commented Mar 21, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.50000% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.12%. Comparing base (0979587) to head (657f01c).

Files with missing lines Patch % Lines
worker/egress/styx_kafka_batch_egress.py 69.56% 21 Missing ⚠️
coordinator/coordinator_metadata.py 97.80% 2 Missing ⚠️
styx-package/styx/common/tcp_networking.py 83.33% 2 Missing ⚠️
styx-package/styx/common/stateful_function.py 95.83% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main      #35      +/-   ##
==========================================
+ Coverage   89.73%   91.12%   +1.39%     
==========================================
  Files          45       46       +1     
  Lines        2707     2952     +245     
==========================================
+ Hits         2429     2690     +261     
+ Misses        278      262      -16     
Flag Coverage Δ
coordinator 96.34% <98.37%> (+2.91%) ⬆️
integration 9.89% <22.75%> (+0.98%) ⬆️
styx-package 87.88% <93.18%> (+0.02%) ⬆️
worker 90.92% <88.84%> (+7.15%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
coordinator/aria_sync_metadata.py 100.00% <100.00%> (ø)
coordinator/migration_metadata.py 100.00% <100.00%> (ø)
coordinator/snapshot_compactor.py 100.00% <100.00%> (ø)
coordinator/worker_pool.py 94.35% <100.00%> (+0.13%) ⬆️
styx-package/styx/common/logging.py 100.00% <100.00%> (ø)
styx-package/styx/common/message_types.py 100.00% <100.00%> (ø)
worker/fault_tolerance/async_snapshots.py 98.65% <100.00%> (-0.48%) ⬇️
worker/ingress/styx_kafka_ingress.py 100.00% <100.00%> (+6.97%) ⬆️
worker/operator_state/aria/in_memory_state.py 100.00% <100.00%> (+1.34%) ⬆️
worker/sequencer/pending_requests.py 100.00% <100.00%> (ø)
... and 4 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

kPsarakis and others added 8 commits October 24, 2026 14:52
The async migration sender ran independently of the epoch, so a batch could
be in flight across a snapshot cut: the source's tombstone landed in one
snapshot and the destination's insert in the next, and recovery from the
first lost the key. The epoch now drains in-flight batches (acknowledged by
the destination once applied) before its processing-done barrier and lets
the sender resume after the cleanup barrier. The sender also waits for the
protocol to start, so peers have loaded their state first.

The delta flush cleared the delta map after awaiting the send, wiping
migration writes that landed during the await. It now swaps the map out
before sending.

After a post-checkpoint recovery the destinations had no remote_keys, so a
transaction on a key still at its source read it as missing. Workers now
exchange the re-derived hash metadata on ReadyAfterRecovery and start the
protocol only once every peer has reported.

New partitions are created in Phase B before MigrationInitDone, so a peer
that rebuilds first cannot send batches to a partition that does not exist
yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… snapshotted

A transaction that needed a migrating key could read it as missing: when
the async sender had already taken the key into a batch, the local move
found nothing and the remote source answered None, and in both cases the
transaction ran without waiting for the batch. It now waits for the batch:
the local move reports whether it moved the key, and a None response no
longer wakes the waiter. Concurrent waiters on one key no longer crash on
cleanup.

The checkpoint was cleared on MigrationDone, so a snapshot cut before the
last key landed but completed afterwards was saved without it, and recovery
from it stranded keys at their old partitions. MigrationDone now carries
the epoch of each worker's last batch, and the checkpoint stays on
snapshots up to the latest such epoch. The coordinator always checks the
latest snapshot for a checkpoint on recovery, restores the checkpoint and
migration tracking while the transfer resumes, and retries a newer
migration that had not reached its own checkpoint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Migration treated a key holding None as absent: the async sender popped
and dropped it, and the on-demand path reported it as already gone. The
destination still listed the key in remote_keys, so a transaction on it
waited for a batch that never came. get_key_to_migrate now reports
whether it found the key separately from its value, None-valued keys are
sent like any other, and the remote reply carries the found flag.

The migration YCSB, migration TPC-C and scalability demos wrote their
initial data to S3 but never sent InitDataComplete, so workers deployed
from snapshot -1 with empty state and the E2E tests ran against an empty
store. They now notify the coordinator like the other demos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… batches in place

Every migration rebuilt the protocol with a new idempotent Kafka producer
and then scanned all output partitions for duplicates before sending any
response; together these held responses back for 300-600 ms after Phase B.
A graceful (migration) stop now flushes the egress but keeps its producer,
and the rebuilt protocol takes it over. The dedup scan runs only after a
recovery: a clean migration stop flushes every response at an epoch
boundary, and the new protocol resumes from those offsets.

The async sender also serialized batches for partitions on its own worker
and sent them over TCP to itself. Those are now applied in place, with no
acknowledgement needed since nothing is in flight. Remote batches default
to 500 keys so each sender step stays short.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sender moved a worker's whole share in a burst, so all of it landed in
one epoch's snapshot delta and was serialized on that epoch's critical path
(about 200 ms for 60k keys). It now moves at most
ASYNC_MIGRATION_KEYS_PER_EPOCH keys (default 3000) per epoch, refilling at
each epoch end, or after a short wait when no epoch is running.
Transactions do not wait for the transfer: they fetch keys on demand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rebuilt protocol created a new Kafka consumer, and the first epoch
after a migration waited about 180 ms at its first barrier for the slowest
worker's consumer to connect and fetch. A graceful (migration) stop now
keeps the consumer connected, and the rebuilt protocol reassigns it to the
new partitions and seeks each to its stop offset, which also discards any
records it had prefetched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
exists() scanned the whole keys_to_send set of a partition on every call,
and the ingress calls it for every message while a migration is running.
With 60k keys pending, each call took about 3 ms, so a backlog of 2,000
messages kept the event loop busy for seconds and epochs barely ran during
Phase C (this predates the PR). exists() now uses an index of the pending
keys, built once per partition and reset when a new transfer is registered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kPsarakis and others added 10 commits October 24, 2026 20:07
The new worker tests import worker_service, which imports uvloop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eckpoint, escalate lost barrier replies

Snapshots: the snapshot id advanced only when every file of a snapshot had
uploaded, so a snapshot started while the previous one was still uploading
wrote its files under the same id (overwriting the earlier deltas) and
overwrote its offsets; failed uploads were also reported as complete. Each
snapshot now gets its id at start and its own metadata, failed uploads
are not reported, and completions are reported to the coordinator strictly
in id order.

Stale messages: after a recovery, migration batches, remote-key requests
and replies from a protocol of before the recovery could still be applied.
The coordinator now bumps an incarnation number on every recovery; these
messages carry it and are dropped when it does not match.

Checkpoint tag: any snapshot completing after Phase B began got the
checkpoint and was taken as the checkpoint, including snapshots cut
before Phase B. The workers now flag the checkpoint snapshot, and only it
and later snapshots carry the checkpoint.

Stop timeout: Phase B force-stopped a protocol still running after 15 s,
possibly cutting an epoch mid-commit, then continued the migration. It now
keeps waiting. A barrier or migration message the coordinator cannot
deliver hands the worker to recovery instead, which is what the timeout
was guarding against.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
inspect.getsource() cannot read Cython-compiled functions, which the
hot-path CI job uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… check

- Remove the debug 10 s timeout on every barrier wait.
- Lower per-message, per-snapshot and step-tracing warnings to debug.
- Log a failed graph publication instead of dropping the future's error.
- Compaction merged each partition's deltas in S3 listing order (10.bin
  before 2.bin), kept a partition's first delta when it was not 0.bin (so it
  was re-applied stale on load), and never dropped tombstones. It now merges
  in snapshot-id order into the partition's latest file, deletes the older
  ones, and only then drops tombstones (compaction is off by default).
- FT E2E: names and docstrings now say which recovery path each kill hits.
- Coordinator FT tests drive the real _perform_recovery instead of
  re-implementing its migration check.
- FT E2E: after the run, every key's stored record is fingerprinted and
  compared with the loaded dataset, so a lost or emptied key fails the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Recovery kept only the input offsets present in the snapshot. When the
snapshot did not cover every assigned partition (a shadow partition, after
recovering from the snapshot written at data load), that partition's
consumer was never seeked and started at the latest offset, skipping the
requests clients wrote to it before and during the failure. Seen in CI as
194 missed responses in the external-kill migration test. Recovery now
defaults missing offsets to -1, and the ingress seeks every assigned
partition, from the beginning when it has no offset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
calculate_metrics writes missed_requests.csv, and the FT tests copy their
results directory into logs/, which CI uploads, so a failure that only
reproduces in CI can be traced to the requests involved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A request consumed for a key that migrated to a peer's partition was
forwarded into the peer's epoch (WrongPartitionRequest). The ingress
partition's offset was then advanced by two workers, each taking the max,
and over a 16-socket pool the forwarded requests could run out of order: a
snapshot or Phase B stop could record an offset past requests one of them
had not run yet, and recovery or the rebuilt consumer skipped them.

The request now stays in the consuming worker's epoch and runs on the owner
as a proxied call (RunFunProxy). The owner runs its chain against itself and
returns the whole outcome in one ProxyResult (response, abort, participants,
fallback read/write-set change), which the root awaits before it can commit.
Responses go to the request's ingress partition, which the root owns. Both
messages carry the incarnation, since a replay reuses t_ids.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…xied calls

The regression test feeds Kafka records through the real ingress and sequencer
into the consuming worker's epoch: a request for a migrated key, then a local
one. The offset it records must not pass a request that has not run. It fails
on the forwarding code (offset 41 recorded while request 40 waits in the
owner's sequencer).

Workers log how many proxied calls they sent in an epoch, when there were any,
so the E2E worker logs show whether the path ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A stalled epoch stalls every worker at the next barrier and nothing is logged:
a CI run of the kill-before-post-migration-snapshot test went silent for two
minutes after recovering. Each epoch wait (transactions, chains, the WAL
write, a barrier, fallback, migration batch acks) now records what it waits
on, and a watchdog warns every EPOCH_STALL_WARN_S (default 10 s) while one
lasts, with the unfinished chains and pending proxied calls. A ProxyResult
with no pending call, which would leave its call waiting forever, is now
logged as a warning instead of at debug.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… barriers

A worker a barrier or migration message could not be delivered to was handed
to recovery at once, but only the heartbeat check removes a worker from the
pool and reschedules its partitions. A dead one therefore stayed a
participant, got InitRecovery, and recovery waited on it forever (reproduced
locally: a worker exiting at the Phase B stop, under CPU-capped workers, failed
the SyncCleanup send first). Unreachable workers now wait HEARTBEAT_LIMIT: by
then the heartbeat check has removed a dead one, and the ones still in the pool
are alive and are recovered as participants.

The coordinator also warns when no barrier completes for BARRIER_STALL_WARN_S
(default 10 s) while some workers wait, naming the workers it waits on, and
workers mark the remaining epoch waits (snapshot delta, responses to Kafka,
snapshot request) for the stall watchdog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kPsarakis and others added 2 commits October 25, 2026 13:00
An epoch's ingress offset passed requests rescheduled to a later epoch (a
fallback read/write-set change, or a concurrency abort with fallback off): with
fallback the offset advanced past them, without it the max over the committed
ones did. They were only held in the sequencer, so a recovery from a snapshot
cut in between, or the protocol rebuilt after a Phase B stop, never ran them.

Offsets now advance past every request of the epoch, and the requests still in
the sequencer at or below their partition's recorded offset are pending. A
snapshot carries them (SnapTakeSnapshot, SnapID, a sixth element of the
sequencer file), as do MigrationInitDone and MigrationDone. The protocol that
takes over (after recovery, possibly on another worker, or after a migration)
sequences the ones of its ingress partitions before consuming anything, routed
to the partition that holds the key now.

The coordinator now writes each sequencer file from its own snapshot's reports.
It used to fold every report into the completed offsets on arrival, so a
worker's report of snapshot N+1 that came before another worker's report of N
put N+1's offsets in N's file, past requests N's state does not contain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…shot

retrieve_snapshot returns them as a seventh value; the startup path and the S3
integration tests still unpacked six.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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