|
| 1 | +<!--- |
| 2 | + Copyright 2026-present Alibaba Inc. |
| 3 | +
|
| 4 | + Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + you may not use this file except in compliance with the License. |
| 6 | + You may obtain a copy of the License at |
| 7 | +
|
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +
|
| 10 | + Unless required by applicable law or agreed to in writing, software |
| 11 | + distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + See the License for the specific language governing permissions and |
| 14 | + limitations under the License. |
| 15 | +--> |
| 16 | + |
| 17 | +# [Proposal] Add Writer-local ReadLatest Support to Paimon C++ |
| 18 | + |
| 19 | +## 1. Motivation |
| 20 | + |
| 21 | +Paimon C++ writers already keep intermediate state before committing a snapshot. Records may stay |
| 22 | +in the active write buffer, be spilled into sorted runs, or be written as prepared files before the |
| 23 | +final snapshot commit. |
| 24 | + |
| 25 | +For embedded C++ applications, it is useful to read the latest state accepted by a writer before |
| 26 | +the snapshot is committed. Typical examples include validation and read-after-write flows owned by |
| 27 | +the same process. Today, applications that need this behavior usually maintain an external side |
| 28 | +buffer and duplicate merge logic outside Paimon. |
| 29 | + |
| 30 | +That approach becomes hard to keep correct once the writer spills data or prepares files. The |
| 31 | +application has to know which data is still in memory, which data has been spilled, which data has |
| 32 | +been prepared, and how to merge those records with committed snapshot files. This duplicates |
| 33 | +behavior that already belongs to Paimon. |
| 34 | + |
| 35 | +The proposed change exposes this state through a controlled writer-local read API, while preserving |
| 36 | +Paimon's normal snapshot isolation for committed readers. |
| 37 | + |
| 38 | +## 2. Scope |
| 39 | + |
| 40 | +This proposal targets **primary-key tables** in the first phase. |
| 41 | + |
| 42 | +`ReadLatest` depends on primary-key merge semantics: same-key conflict resolution, sequence |
| 43 | +ordering, and delete handling. Append-only tables have different semantics, where repeated records |
| 44 | +remain independent appended rows rather than versions of the same key. Append-only table serving |
| 45 | +is therefore out of scope for the first phase. |
| 46 | + |
| 47 | +The first phase focuses on correctness and API shape. Recovery from external write-ahead logs and |
| 48 | +serving-oriented performance optimization are left as follow-up work. |
| 49 | + |
| 50 | +## 3. Proposed Change |
| 51 | + |
| 52 | +Add a writer-local read path that can create a stable read view from: |
| 53 | + |
| 54 | +- committed snapshot files visible to the writer; |
| 55 | +- active in-memory buffer owned by the writer; |
| 56 | +- spilled sorted runs owned by the writer; |
| 57 | +- prepared files created by the writer before snapshot commit. |
| 58 | + |
| 59 | +The reader returns the latest primary-key result according to Paimon's existing primary-key merge |
| 60 | +semantics. |
| 61 | + |
| 62 | +```mermaid |
| 63 | +flowchart LR |
| 64 | + subgraph "Committed State" |
| 65 | + Snapshot[Committed Snapshot] |
| 66 | + Files[Committed Data Files] |
| 67 | + Snapshot --> Files |
| 68 | + end |
| 69 | +
|
| 70 | + subgraph "Writer-local State" |
| 71 | + Buffer[Active Buffer] |
| 72 | + Spill[Spilled Runs] |
| 73 | + Prepared[Prepared Files] |
| 74 | + end |
| 75 | +
|
| 76 | + Files --> View[ReadLatest View] |
| 77 | + Buffer --> View |
| 78 | + Spill --> View |
| 79 | + Prepared --> View |
| 80 | + View --> Merge[Primary-key Merge Reader] |
| 81 | + Merge --> Result[Latest Records] |
| 82 | +``` |
| 83 | + |
| 84 | +## 4. Public API |
| 85 | + |
| 86 | +The API should be explicit about the visibility model. The proposed naming is `ReadLatest`, not |
| 87 | +`ReadUncommitted`, because the feature does not introduce a table-wide isolation level. The names |
| 88 | +in the following example are proposed public API and are not present in the current public headers. |
| 89 | + |
| 90 | +```cpp |
| 91 | +ReadLatestOptions options; |
| 92 | +std::unique_ptr<RecordReader> reader = |
| 93 | + writer->CreateReadLatestReader(options); |
| 94 | +``` |
| 95 | +
|
| 96 | +Expected behavior: |
| 97 | +
|
| 98 | +- the API is called on a writer instance; |
| 99 | +- it includes data accepted by that writer; |
| 100 | +- it does not expose data from other writers; |
| 101 | +- it does not make uncommitted data globally visible; |
| 102 | +- it does not change existing snapshot readers. |
| 103 | +
|
| 104 | +The first implementation is intended to expose a full writer-local read view. Projection and |
| 105 | +predicate support should only be added where they can follow existing Paimon scan semantics safely. |
| 106 | +
|
| 107 | +## 5. Prototype |
| 108 | +
|
| 109 | +A local prototype has been implemented to validate the proposed direction in Paimon C++. |
| 110 | +
|
| 111 | +The prototype currently validates fixed-bucket primary-key tables with the deduplicate merge |
| 112 | +engine. Deletion vectors, predicates, and other merge engines are outside the validated prototype |
| 113 | +surface. |
| 114 | +
|
| 115 | +The prototype demonstrates: |
| 116 | +
|
| 117 | +- writer-local `ReadLatest` reader for primary-key tables; |
| 118 | +- reading records from the active write buffer before snapshot commit; |
| 119 | +- reading spilled writer-local sorted runs; |
| 120 | +- reading prepared files before snapshot commit; |
| 121 | +- merging committed snapshot files with writer-local records; |
| 122 | +- correct same-key insert, update, and delete resolution across snapshot files, memory buffer, |
| 123 | + spilled runs, and prepared files; |
| 124 | +- stable read view lifetime after later writer operations such as spill, prepare, commit, and close; |
| 125 | +- existing snapshot read and commit paths remain unchanged; |
| 126 | +- focused tests covering same-key mutations, multiple spills, prepared files, snapshot merge, delete |
| 127 | + handling, read-view lifetime, and failure cleanup. |
| 128 | +
|
| 129 | +The prototype is intended to validate API shape and correctness first. It does not claim to solve |
| 130 | +crash recovery from external write-ahead logs or serving-oriented performance optimization in the |
| 131 | +first phase. |
| 132 | +
|
| 133 | +## 6. Semantics |
| 134 | +
|
| 135 | +### 6.1 Visibility |
| 136 | +
|
| 137 | +`ReadLatest` is writer-local. It observes committed snapshot state plus uncommitted state owned by |
| 138 | +the same writer at the time the read view is created. |
| 139 | +
|
| 140 | +It is not a global read-uncommitted mode. Other readers continue to see committed snapshots only. |
| 141 | +
|
| 142 | +### 6.2 Read View Stability |
| 143 | +
|
| 144 | +Once `CreateReadLatestReader()` succeeds, the returned reader must remain readable until it is |
| 145 | +destroyed. |
| 146 | +
|
| 147 | +Later writer operations may continue: |
| 148 | +
|
| 149 | +- write more records; |
| 150 | +- spill buffered records; |
| 151 | +- prepare commit files; |
| 152 | +- commit a snapshot; |
| 153 | +- flush or close writer-owned resources. |
| 154 | +
|
| 155 | +Those later operations must not invalidate resources already pinned by the read view. |
| 156 | +
|
| 157 | +This proposal does not change the writer's existing thread-safety contract. It only requires that |
| 158 | +once a read view is created successfully, subsequent writer lifecycle operations must not make the |
| 159 | +returned reader dangling. |
| 160 | +
|
| 161 | +```mermaid |
| 162 | +sequenceDiagram |
| 163 | + participant App |
| 164 | + participant Writer |
| 165 | + participant View as ReadLatest View |
| 166 | + participant Reader |
| 167 | +
|
| 168 | + App->>Writer: Write records |
| 169 | + App->>Writer: CreateReadLatestReader() |
| 170 | + Writer->>View: Pin committed files and writer-local sources |
| 171 | + View->>Reader: Return stable reader |
| 172 | + App->>Writer: Continue write / spill / prepare / close |
| 173 | + App->>Reader: Consume pinned view safely |
| 174 | +``` |
| 175 | + |
| 176 | +### 6.3 Primary-key Result |
| 177 | + |
| 178 | +For primary-key tables, the reader returns the latest visible mutation for each key in the read |
| 179 | +view. |
| 180 | + |
| 181 | +- If the newest visible mutation is an insert or update, the latest row is returned. |
| 182 | +- If the newest visible mutation is a delete, the key is not returned. |
| 183 | +- If the same key appears in committed files and writer-local state, the writer-local newer mutation |
| 184 | + wins according to sequence ordering. |
| 185 | + |
| 186 | +All writer-local sources in a read view should use the same sequence ordering as the existing |
| 187 | +primary-key writer and merge path. The read path should not introduce an independent ordering rule. |
| 188 | + |
| 189 | +### 6.4 Resource Lifetime |
| 190 | + |
| 191 | +All resources captured by a read view must remain valid until the reader is released. This includes |
| 192 | +active buffer snapshots, spilled run readers, committed data files, and prepared files. |
| 193 | + |
| 194 | +Prepared files included in a read view must be retained until the view is released, regardless of |
| 195 | +later commit, close, or cleanup operations. |
| 196 | + |
| 197 | +## 7. Compatibility |
| 198 | + |
| 199 | +This proposal is additive. |
| 200 | + |
| 201 | +- Existing snapshot read behavior remains unchanged. |
| 202 | +- Existing write, prepare commit, and snapshot commit behavior remains unchanged. |
| 203 | +- Existing applications do not need to opt in. |
| 204 | +- Applications that need writer-local latest reads can use the new writer API. |
| 205 | + |
| 206 | +The implementation should not introduce additional overhead to the default snapshot read path. |
| 207 | + |
| 208 | +## 8. Implementation Outline |
| 209 | + |
| 210 | +If the community agrees with the direction, the implementation can be submitted incrementally: |
| 211 | + |
| 212 | +1. Expose writer-owned buffered records as an internal readable source. |
| 213 | +2. Add readable sources for spilled writer-local sorted runs. |
| 214 | +3. Include prepared files in the writer-local read view before snapshot commit. |
| 215 | +4. Build a merge reader over committed files and writer-local sources. |
| 216 | +5. Pin resources through the lifetime of the returned reader. |
| 217 | +6. Add the public `CreateReadLatestReader()` writer API for a full writer-local read view. |
| 218 | +7. Add focused tests for same-key mutations, multiple spills, prepared files, snapshot merge, delete |
| 219 | + handling, read-view lifetime, and failure cleanup. |
| 220 | + |
| 221 | +The implementation should reuse existing primary-key merge logic wherever possible and avoid |
| 222 | +introducing a second merge model outside the current table semantics. |
| 223 | + |
| 224 | +## 9. Generative AI tooling |
| 225 | + |
| 226 | +Generated-by: OpenAI Codex GPT-5 |
0 commit comments