feat: DH-22441: allow Dictionary encoding for Barrage and Arrow clients#8187
feat: DH-22441: allow Dictionary encoding for Barrage and Arrow clients#8187lbooker42 wants to merge 10 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Enables Apache Arrow Dictionary Encoding support in Deephaven’s Barrage / Arrow Flight wire paths, adding both writer-side emission of DictionaryBatch messages (plus dict-index columns in RecordBatch) and reader-side consumption of DictionaryBatch messages to decode dictionary-encoded columns.
Changes:
- Add dictionary-encoded column support end-to-end: schema generation (
DictionaryEncoding), writer emission (DictionaryBatch+ index buffers), and reader decoding (dictionary registry + index expansion). - Extend Barrage/Arrow readers to process
DictionaryBatchIPC messages and wire dictionary registries into chunk readers. - Add coverage across unit / integration tests and update Barrage schema documentation with a dictionary-encoding example and guidance.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| server/jetty/src/test/java/io/deephaven/server/jetty/BarrageChunkFactoryTest.java | Adds Arrow Flight interop test verifying dict-encoded download behavior. |
| extensions/barrage/src/test/java/io/deephaven/extensions/barrage/util/BarrageUtilTest.java | Adds schema-level tests for dictionary encodings and id assignment. |
| extensions/barrage/src/test/java/io/deephaven/extensions/barrage/chunk/DictionaryWriterStateTest.java | Unit tests for per-stream dictionary state (delta + stable indices). |
| extensions/barrage/src/test/java/io/deephaven/extensions/barrage/chunk/DictionaryWriterRegistryTest.java | Tests writer registry reuse and delta tracking. |
| extensions/barrage/src/test/java/io/deephaven/extensions/barrage/chunk/DictionaryReaderRegistryTest.java | Tests reader registry update semantics (replace/delta/ids/nulls). |
| extensions/barrage/src/test/java/io/deephaven/extensions/barrage/chunk/DictionaryChunkWriterTest.java | Tests boxing semantics (notably NaN canonicalization) for dictionary keys. |
| extensions/barrage/src/test/java/io/deephaven/extensions/barrage/chunk/BarrageColumnRoundTripTest.java | Adds dictionary encoding round-trip + overflow tests. |
| extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/BarrageUtil.java | Adds dict encoding inference from schema + dict-id assignment in schema creation; removes prior hard rejection of dictionary fields. |
| extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/BarrageMessageReaderImpl.java | Adds DictionaryBatch handling and per-stream dictionary registry wiring for decoding. |
| extensions/barrage/src/main/java/io/deephaven/extensions/barrage/util/ArrowToTableConverter.java | Adds DictionaryBatch IPC handling for Arrow-to-table conversion. |
| extensions/barrage/src/main/java/io/deephaven/extensions/barrage/ColumnEncoding.java | Expands dictionary encodings to explicit index-width variants + helper predicate. |
| extensions/barrage/src/main/java/io/deephaven/extensions/barrage/chunk/DictionaryWriterState.java | Implements per-dictionary-id state tracking (value→index + deltas). |
| extensions/barrage/src/main/java/io/deephaven/extensions/barrage/chunk/DictionaryWriterRegistry.java | Tracks per-stream dictionary states and associated value writers. |
| extensions/barrage/src/main/java/io/deephaven/extensions/barrage/chunk/DictionaryReaderRegistry.java | Stores decoded per-id dictionary values for expansion during reads. |
| extensions/barrage/src/main/java/io/deephaven/extensions/barrage/chunk/DictionaryChunkWriter.java | Implements dict index serialization and delta-values chunk construction. |
| extensions/barrage/src/main/java/io/deephaven/extensions/barrage/chunk/DictionaryChunkReader.java | Implements dict index expansion into Deephaven chunks using registry contents. |
| extensions/barrage/src/main/java/io/deephaven/extensions/barrage/chunk/DefaultChunkWriterFactory.java | Creates DictionaryChunkWriter when Field has DictionaryEncoding. |
| extensions/barrage/src/main/java/io/deephaven/extensions/barrage/chunk/DefaultChunkReaderFactory.java | Creates DictionaryChunkReader when Field has DictionaryEncoding and wires dict registries/readers. |
| extensions/barrage/src/main/java/io/deephaven/extensions/barrage/BarrageMessageWriterImpl.java | Emits DictionaryBatch messages before each RecordBatch and wires writer registry/state. |
| docs/groovy/how-to-guides/data-import-export/barrage-schema.md | Documents how to apply dictionary encoding via BARRAGE_SCHEMA_ATTRIBUTE with cautions. |
| // Dictionary encoding is identified by the presence of a DictionaryEncoding on the field, not by typeId. | ||
| final org.apache.arrow.vector.types.pojo.DictionaryEncoding dictEncoding = field.getDictionary(); | ||
| if (dictEncoding != null) { | ||
| if (registry == null) { |
There was a problem hiding this comment.
maybe this is long enough to delegate to a function for just dictionaries.
| final ChunkReader<? extends WritableChunk<Values>> valuesReader = | ||
| newReaderPojo(valuesTypeInfo, options, false, null, null); | ||
|
|
||
| if (dictValuesReadersOut != null) { |
There was a problem hiding this comment.
should we be checking for a dictValuesReader already; and using that instead?
| * skips unknown dictionary ids (no reader registered for that id). Callable by subclasses that receive | ||
| * {@code DictionaryBatch} messages through a different transport path (e.g. gRPC doPut streaming). | ||
| */ | ||
| protected void applyDictionaryBatch(@NotNull final BarrageProtoUtil.MessageInfo mi) { |
There was a problem hiding this comment.
If we skip an unknown dictionary ID what happens? Do we end up with bogus values in those columns; or will we properly fail?
There was a problem hiding this comment.
Code has changed since review.
On a mismatch, we throw a descriptive error: "IOException: No DictionaryBatch received for dictionary id X before RecordBatch that references it"
| if (subset == null) { | ||
| for (int srcPos = 0; srcPos < logicalSize; ++srcPos) { | ||
| final Object v = boxValue(source, srcPos); | ||
| final Object v = options.useDeephavenNulls() |
There was a problem hiding this comment.
This is doing a dispatch/lookup per position. We cannot have that kind of per-cell virtual call.
| * true, callers pass the boxed null sentinel (e.g. {@code Integer.valueOf(QueryConstants.NULL_INT)}) as a real | ||
| * dictionary entry instead. | ||
| */ | ||
| int indexFor(@NotNull Object value); |
There was a problem hiding this comment.
I would make this operate on a chunk; and then we might not actually need to expose getDeltaValues (or if we do we could expose it as a chunk to write).
| encodings.put(field.getName(), encoding); | ||
| } else if (field.getDictionary() != null) { | ||
| final org.apache.arrow.vector.types.pojo.DictionaryEncoding dict = field.getDictionary(); | ||
| ColumnEncoding encoding = ColumnEncoding.DICTIONARY_ENCODED_INT32; |
There was a problem hiding this comment.
Maybe make this final and don't overwrite it sometimes.
| clientRemovedRows = null; // we'll send full subscriptions the full removed set | ||
|
|
||
| if (keyspaceViewport != null) { | ||
| // Growing-to-full subscriptions are still materializing rows on the client side. Restrict |
There was a problem hiding this comment.
Is this a bug we previously had? Should we test/fix it separately so we have the option of cherry-picking it?
There was a problem hiding this comment.
Yes, existing bug identified through the new tests. Will make a separate fix for this.
| final List<DefensiveDrainable> messages = getInputStream(this, 0, 0, actualBatchSize, metadata, | ||
| BarrageMessageWriterImpl.this::appendAddColumns); | ||
| for (final DefensiveDrainable msg : messages) { | ||
| bytesWritten.add(msg.available()); | ||
| visitor.accept(msg); | ||
| } | ||
| final DictionaryWriterRegistry dictRegistry = dictionaryRegistry(); | ||
| if (dictRegistry != null) { | ||
| dictRegistry.resetDeltas(); | ||
| } |
There was a problem hiding this comment.
Helper for the duplicated code also around 416?
|
|
||
| final ChunkWriter.DrainableColumn drainableColumn; | ||
| if (numElements == 0) { | ||
| if (mcd.chunkListWriter.writer() instanceof DictionaryChunkWriter && dictionaryRegistry != null) { |
There was a problem hiding this comment.
If you have a DictionaryChunkWriter and no registry that seems like it should be an error to me.
| } | ||
|
|
||
|
|
||
| final class ObjectDictionaryIndexKernel implements DictionaryIndexKernel { |
There was a problem hiding this comment.
I do really think this should be replicated; and am not a fan of this pattern where we have the package private classes jammed into one file.
| } | ||
|
|
||
| private static WritableColumnSource<?> newSource(@NotNull final ChunkType chunkType) { | ||
| switch (chunkType) { |
There was a problem hiding this comment.
This seems like a hole in the io.deephaven.engine.table.impl.sources.ArrayBackedColumnSource#getMemoryColumnSource(long, java.lang.Class, java.lang.Class<?>) constructors; and that we should probably add this method there.
No description provided.