1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! The CDC seam: a trait, deliberately with no v1 implementation.
//!
//! Archival is the ingestion path (§8.1), and that is not a placeholder. The
//! comparison is closer than it looks — logical decoding reads the WAL and touches
//! no heap pages, which is a real advantage on a busy primary — but it does not
//! solve the expensive half. Replicating rows out of PostgreSQL does not *remove*
//! them, and the hot tier has to stay bounded either way. Both designs need the
//! purge, and the purge is where the cost actually is; once it is a partition
//! drop, what remains of CDC's advantage does not pay for a replication slot that
//! can fill the primary's disk when a consumer stalls.
//!
//! # So why does this file exist
//!
//! Because the *shape* of the alternative is what makes it cheap to adopt later,
//! and expensive to retrofit. If lake latency ever has to drop below an hour, the
//! question should be "which strategy is configured" rather than "how do we
//! restructure ingestion". The trait costs a definition; discovering its absence
//! costs a rewrite.
//!
//! It is feature-gated (`cdc`), unbuilt, and unscheduled. [`rustcdc`] implements
//! this shape against PostgreSQL logical replication when the requirement
//! arrives.
//!
//! [`rustcdc`]: https://github.com/hupe1980/rustcdc
//!
//! # What a source owes the store
//!
//! The two obligations are the ones §8.4 already places on any transport, plus
//! one that is specific to streaming:
//!
//! 1. **Batches carry the storage schema.** A source decodes rows; it does not
//! get to invent columns. Anything it cannot map belongs upstream.
//! 2. **Positions are opaque and monotonic.** The store never interprets one; it
//! stores it and hands it back. An LSN, a Kafka offset and a file cursor are
//! all positions, and none of them is comparable to another.
//! 3. **A checkpoint means durable in the lake, not consumed.** Checkpointing
//! before the Iceberg commit is the same class of bug as dropping a partition
//! before it — and it is why `checkpoint` is a separate call rather than an
//! acknowledgement folded into the stream.
use async_trait;
use Stream;
use crateRecordBatch;
use crateResult;
/// An opaque, source-defined stream position.
///
/// Not interpreted, not compared across sources, and not parsed. A PostgreSQL
/// LSN, a Kafka offset and a byte cursor are all positions; giving the store any
/// structure to reason about would make it responsible for semantics only the
/// source knows.
;
/// A batch of changes, with the position that follows them.
/// A stream of changes out of the hot tier.
///
/// The seam archival would be swapped for, not an addition to it: both produce
/// batches in the storage schema, and both must respect the tiering invariant.