Skip to content

Streams: records written after a re-enable can carry the earlier stream's view type #698

Open

Description

@LeeroyHannigan

What happens

A write's stream record takes its view type and its images from the StreamCapture the engine builds from the table's key info, which each server caches (crates/server/src/key_info_cache.rs, soft TTL 30 seconds by default, [auth.cache] soft_ttl_seconds). Only the server that runs UpdateTable invalidates its entry (crates/engine/src/update_table.rs:241). The write path reads the table's open shards when it records: on PostgreSQL inside its data transaction (crates/storage-postgres/src/data/tx_helpers.rs:331-337), with the images and the record's StreamViewType taken from the capture (tx_helpers.rs:297-304, :390).

With #515 (open), a disable closes the stream's shards and a later enable creates a new stream with new shards and the requested view type. A server holding the table's key info from before the change writes into the new stream's open shards with the earlier view type: a stream created as KEYS_ONLY gets records carrying both images, or a NEW_AND_OLD_IMAGES stream gets records with keys only, until that server's cached key info expires. MongoDB and Cassandra build the record from the same cached key info.

On Cassandra a transacted write's stream record is built at commit (crates/storage-cassandra/src/data/transactions.rs, transact_stream_statement, from #576): it draws its sequence number from the server's clock then (stream_util::stream_record_statement), while its view type and images come from the capture built when the request arrived. The commit path does not read the shard rows, so, from reading the code, a TransactWriteItems prepared under one stream and committed after a disable and an enable writes its record into the new stream with the earlier view type, on one server too.

On one server the window for a single-item write is the request's own duration; on a deployment with more than one server it is the key-info cache's soft TTL on every server that did not run the change. Before #515 the same staleness wrote the earlier view type into the same stream after its view type changed in place.

How to see it

Two PostgreSQL servers on one database, built from #515's head. Create a table with a NEW_AND_OLD_IMAGES stream through server 1, write one item through server 2 to load its key info, then through server 1 disable the stream and enable it with KEYS_ONLY, and write through server 2 every 200 ms for 40 seconds. Measured:

Change Writes Records in the new stream with the earlier view type Last of them
NEW_AND_OLD_IMAGES, then KEYS_ONLY 186 140 (both images); 46 keys only 29.87 s after the enable
KEYS_ONLY, then NEW_AND_OLD_IMAGES 186 141 (keys only); 45 with both images 30.17 s after the enable

Every write was recorded. On one server, eight writers against eight disable and enable cycles that alternate the two view types gave 0 records with the wrong view type on SQLite (488 records) and on PostgreSQL (628).

What the service does

Not measured. The service records every write it acknowledges while a disable is in progress in the closed stream before its shard's EndingSequenceNumber (measured 2026-10-24 with 569 writes against one disable), and a write after that records nothing; which view type a record written right after a re-enable carries there was not measured.

What a fix needs

On SQLite, PostgreSQL and MongoDB the write path knows the shard it records into, and a labelled shard id carries the stream's view type (stream_lineage::parse_shard_id), so the write path can drop the images the shard's view type excludes and rebuild the capture when the shard's view type needs an image the capture did not take. Cassandra writes its records without reading the shard rows, so it needs the same check at read time, against the record's own StreamViewType and the stream's.

Related: #576's "Composition with #515" section describes another consequence of the same prepare-to-commit window (a transacted write acknowledged before a disable is readable in no stream) and asks whoever merges second to measure transacted writers; #579 is the recovery path that writes no record at all. Neither covers the view type. Searched open and closed issues for stream view type after a disable and enable, TransactWriteItems stream records, and Cassandra transacted stream records; none describes this.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions