SteNicholas opened a new pull request, #227:
URL: https://github.com/apache/paimon-cpp/pull/227

   ### Purpose
   
   Linked issue: close #204
   
   Adds two independent capabilities to the write and compaction paths.
   
   **1. Managed BLOB storage for primary-key tables**
   
   A BLOB column of a primary-key table can now keep its payload out of the 
data file
   entirely. Payloads are externalized into rolling `.managed.blob` packs 
*before* they
   reach the merge-tree write buffer, so neither the buffer nor its spill ever 
carries
   payload bytes — only the fixed-size descriptor that replaces them.
   
   - `PrimaryKeyBlobExternalizer` sits in front of the merge-tree writer, seals 
a pack when
     it reaches `blob.target-file-size`, and hands the packs it opened to the 
commit message
     its writer produces. A retract row drops its payload rather than storing 
one, and a
     batch of nothing but retracts opens no pack at all. An input that already 
holds a
     descriptor is re-materialized so the value is stored under this table's 
own packs.
   - Each data file keeps a `.blobref` sidecar (`ManagedBlobReferenceFile`, a 
versioned
     binary format with a golden-bytes test against the Java writer) listing 
the packs its
     rows reference. `ManagedBlobReferenceCollector` writes it on close; it 
travels in the
     data file's `extra_files`, so snapshot expiration, orphan cleaning and 
abort all treat
     it as part of the data file.
   - Reads go through `ManagedBlobResolvingBatchReader`, which resolves each 
stored
     descriptor back to payload bytes with one ranged read per value.
   - Ownership is explicit: a pack belongs to the commit message whose writer 
created it, and
     a compaction that merely inherits a pack does not own it. 
`UncommittedFileCleaner` rolls
     back exactly the packs a failed commit — or a `PrepareCommit` that gives 
up partway —
     created, and leaves the inherited ones alone.
   - `SchemaValidation` enforces the merge-engine, primary-key, sequence-group 
ordering and
     option rules the layout relies on, so an unsupported combination 
(`pk-clustering-override`,
     `blob-descriptor.source-table`, a managed BLOB in a key or ordering field) 
is rejected at
     schema time rather than silently changing semantics.
   
   Only top-level scalar BLOB columns are managed; a BLOB nested in a 
ROW/MAP/ARRAY and the
   existing inline descriptor/view fields are untouched.
   
   **2. Compaction across the evolved field groups of a data-evolution table**
   
   `AppendCompactCoordinator` previously refused a data-evolution table. It now 
plans such a
   table with `DataEvolutionCompactPlanner`: files covering the exact same rows 
form one
   evolved field group, the groups of a contiguous row-id run are bin-packed, 
and each task
   rewrites one bin into a single normal file holding every non-dedicated 
column — preserving
   row ids and the merged file-level sequence-number range.
   
   - Because the rewrite keeps every input row, the deletions of the replaced 
groups are
     *re-keyed* onto the rewritten file and committed in the same snapshot 
rather than
     dropped. `DataEvolutionCompactDeletionVectorRewriter` performs the 
migration and
     `ConflictDetection` verifies it: a vector that went missing, shrank, or 
was left behind
     fails the commit instead of resurrecting deleted rows. Both bitmap32 and 
bitmap64 vectors
     are supported.
   - `MaterializeDeletionVectors` is the heavy alternative entry point: it 
applies the
     deletions physically, which reassigns the row ids of the surviving rows 
and therefore
     drops the global indexes over the touched partitions in the same commit. 
It is never done
     automatically, and a range covered by a dedicated blob or vector-store 
file is rejected
     rather than corrupted. Its commit uses the new
     `RowIdCheckConflictForMaterializeDeletionVectors` range rule, since a 
rewrite of whole
     row ranges cannot rely on the narrower column-overlap check.
   - Both `RunAndCommit` and `MaterializeDeletionVectors` split the row id 
space into bounded
     rounds and commit each round on its own, so only one round's file metadata 
is live at a
     time. The split is a soft target: a cut can only land where one data 
manifest's row id
     coverage ends before the next one's begins, and a snapshot without usable 
row id
     statistics falls back to a single round. Rounds are independent — a failed 
round leaves
     the committed ones committed and a later call re-plans the rest.
   - Options that decide the compaction path or the physical layout (row 
tracking, data
     evolution, deletion vectors, bucket, blob layout) are now rejected when an 
override would
     contradict the persisted schema, and the legacy
     `data-evolution.compaction.rewrite-row-ids=true` mode fails instead of 
being ignored.
   
   ### Tests
   
   **Managed BLOB — unit**
   
   | Case group | Covers |
   | --- | --- |
   | `PrimaryKeyBlobExternalizerTest` (13) | descriptor replacement, per-field 
packs, pack rolling by target size, retract-only batches, `PrepareCommit` 
hand-over, seal failure still closing the pack stream, inline descriptor fields 
left alone, re-materializing descriptor input, IO failure |
   | `ManagedBlobReferenceFileTest` (14) | round trip with sort + dedup, empty 
list, non-ASCII, **Java golden bytes**, and rejection of corruption, trailing 
bytes, bad magic, unsupported version, negative/oversized count, malformed 
surrogate, nested relative path |
   | `ManagedBlobReferenceCollectorTest` (8) | collecting only declared 
columns, ignoring non-descriptor values, empty sidecar, abort deleting the 
sidecar, use-after-close and unknown-field rejection |
   | `ManagedBlobResolvingBatchReaderTest` (7) | descriptor resolution 
preserving nulls, declared-columns-only, pass-through without managed fields, 
missing pack / non-struct / wrong-type / non-descriptor rejection |
   | `UncommittedFileCleanerTest` (4) | rolling back data files, sidecars and 
*owned* packs; not touching an inherited pack; tolerating missing files; one 
unusable message not stranding the rest |
   | `SchemaValidationTest.TestPrimaryKeyManagedBlob{,SequenceGroups}` | 
merge-engine, key, ordering and unsupported-option rules |
   | `SingleFileWriterTest.TestAbortExecutorRemovesCompanionFiles` | an abort 
executor removes companion files and stays repeatable |
   
   **Managed BLOB — integration (`PkBlobTableInteTest`, 10 cases)**
   
   `TestWriteAndReadManagedBlob`, `TestReadWithPrefetchAndReadAheadCache`,
   `TestCompactionRebuildsExactBlobReferences`, 
`TestFirstRowManagedBlobKeepsFirstValue`,
   `TestPartialUpdateManagedBlob`, `TestDeleteDropsRow`,
   `TestSnapshotExpirationRemovesSidecar`, 
`TestAbortDeletesTheManagedBlobPacksItRollsBack`,
   `TestAbortOfACompactionKeepsTheHistoricalPacks`,
   `TestAbortOfAWriteAndCompactionKeepsOnlyTheHistoricalPacks`.
   Plus `CleanInteTest.TestOrphanFilesCleanKeepsCompanionFilesOfLiveDataFiles`.
   
   **Compaction — unit**
   
   | Case group | Covers |
   | --- | --- |
   | `DataEvolutionCompactPlannerTest` (19) | bin packing by target size and 
open-file cost, row-id gaps and large files cutting a bin, evolved groups 
packing together, mismatched group ranges / vector-store files rejected, blob 
files excluded, per-partition planning, `min-file-num`, and the round windowing 
(cut only at coverage gaps, file budget, fallback without row id statistics, 
delete-only manifests ignored) |
   | `DataEvolutionNormalCompactTaskTest` (3), 
`DataEvolutionMaterializeDeletionCompactTaskTest` (5) | input union, 
discontiguous / misaligned / dedicated-storage rejection |
   | `DataEvolutionCompactDeletionVectorRewriterTest` (6) | short-circuit 
without vectors, and rejection of a message that already changed the index, a 
bucketed message, a rewrite that moved its rows, several normal outputs |
   | `DataEvolutionCompactGlobalIndexDropperTest` (2) | nothing dropped for a 
normal compaction or without replaced normal files |
   | `DataEvolutionConflictDetectionTest` (10) | the migration check — a 
left-behind vector, lost deletions, a shrunken vector, two index files claiming 
one data file, an uncovered dropped group; accepting a materialized compaction 
and a fully deleted range |
   | `ConflictDetectionTest` (+2), `AppendCompactCoordinatorTest` (+2) | plain 
append table still refuses dropping data files; row-id existence on compaction; 
`RunAndCommit` / `MaterializeDeletionVectors` on a plain append table |
   | `RowIdRangeConflictCheckerTest` (2), 
`MaterializedIndexChangesProviderTest` (3) | range overlap rule; global indexes 
rescanned every attempt, other buckets kept |
   
   **Compaction — integration (`DataEvolutionTableTest`, 33 new cases)**
   
   Field-group compaction (`TestCompactAcrossEvolvedFieldGroups`, with 
partitions, with a
   partition filter, across schema evolution, after a dropped column), 
deletion-vector
   migration (`TestCompactKeepsDeletionVectorsOfOneRowRangeGroup`, bitmap64, 
shifts across
   groups, only the touched index file rewritten, sibling vectors carried, 
out-of-group
   deletion rejected), materialization 
(`TestMaterializeDeletionVectorsReassignsRowIds`,
   no-op without deletions, untouched ranges left alone, global indexes 
dropped, other
   partitions untouched, fully deleted range, option guards), bounded rounds
   (`TestRunAndCommitSplitsRowIdSpaceIntoRounds`, with a partition filter, 
moving deletion
   vectors every round for both bitmap kinds), and concurrency
   (`TestStaleCompactMessagePreservesConcurrentPartialUpdate`,
   `TestSmallFileCompactConflictsWithConcurrentPartialUpdate`,
   `TestCompactKeepsConcurrentAppendForNextSmallFileMerge`).
   Plus `PkCompactionInteTest.DeduplicateWith{,Bitmap64}DeletionVectors`.
   
   ### API and Format
   
   Yes — additive public API, plus one new on-disk file kind.
   
   - `include/paimon/append/append_compact_coordinator.h`: two new static entry 
points,
     `RunAndCommit` and `MaterializeDeletionVectors`, and 
`kDefaultCandidateFilesPerRound`.
     `Run` keeps its signature; its contract is documented more tightly — on a 
data-evolution
     table with deletion vectors the returned vector also holds index-only 
messages and the
     caller must commit the whole vector in one commit.
   - `include/paimon/file_store_commit.h`: new pure virtual
     `RowIdCheckConflictForMaterializeDeletionVectors`. This is a 
source-breaking change for
     an out-of-tree implementer of the interface; there is none in this 
repository.
   - `include/paimon/format/format_writer.h`: new `LastPayloadRange()` with a 
`std::nullopt`
     default, so the externalizer can point a descriptor at the bytes it just 
wrote without
     downcasting the writer the format factory handed it.
   - `include/paimon/defs.h`: adds `BLOB_COPY_BUFFER_SIZE`, 
`BLOB_DESCRIPTOR_SOURCE_TABLE`,
     `PK_CLUSTERING_OVERRIDE` and `DATA_EVOLUTION_COMPACTION_REWRITE_ROW_IDS` 
(the last three
     documenting rejected configurations), and updates the 
`DELETION_VECTORS_ENABLED` and
     `DELETION_VECTOR_BITMAP64` notes now that a data-evolution table is 
compactable and
     bitmap64 is supported.
   - **Format**: the `.managed.blob` pack is the existing blob file format. New 
is the
     `.blobref` sidecar, a versioned binary file with magic, version and a 
length-prefixed
     reference list; `ManagedBlobReferenceFileTest.TestJavaGoldenBytes` pins it 
against the
     Java bytes. The sidecar is referenced from `DataFileMeta::extra_files`, an 
existing field,
     so the manifest format is unchanged. Deletion vector index files, 
snapshots and manifests
     are unchanged.
   
   ### Documentation
   
   Yes.
   
   - `docs/source/user_guide/primary_key_table.rst` gains *Managed BLOB 
Storage*: how the
     payload is externalized and resolved, the `.blobref` sidecar, pack 
ownership versus
     reference, and the supported surface (top-level scalar BLOB columns only) 
with the option
     and merge-engine rules.
   - `docs/source/user_guide/compaction.rst` gains *Data-Evolution Table 
Compaction*, covering
     evolved field groups, the deletion-vector migration and the commit check 
that verifies it,
     *Materializing Deletions* and why it is a separate opt-in, and *Bounded 
Rounds and
     Committing*. The previous note that such a table is never compacted is 
removed.
   - `docs/source/user_guide/read.rst` is updated where it stated the same 
limitation.
   
   ### Generative AI tooling
   
   Generated-by: Claude Opus 5 (1M context)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to