Barrage schema annotation

Deephaven tables support Object-typed columns that can hold arbitrary Java objects. When exporting these tables over Flight using the Barrage format, Deephaven uses Apache Arrow schemas to describe the data. By default, if a column is typed as Object, the Arrow schema may not capture the intended structure of the data, which can lead to inefficient serialization or loss of type information. Use the Table.BARRAGE_SCHEMA_ATTRIBUTE to inject explicit Arrow schema information, which ensures that the Flight export uses the correct wire format.

Use this when your Deephaven column type is too generic for the intended wire type (for example, Object columns that should be exported as Union or Map), or when you want to opt into a wire-level compression such as Run-End Encoding. This guide includes examples of the Union, Map, and RunEndEncoded types, which are supported by Deephaven.

How it works

  1. Extract a base schema with BarrageUtil.schemaFromTable(...). This handles basic type mapping for primitive types and collections of primitives.
  2. Replace the target field with explicit Arrow types.
  3. Attach the schema using withAttributes(Map.of(Table.BARRAGE_SCHEMA_ATTRIBUTE, newSchema)).

Note

withAttributes(...) returns a new table. If you later transform the table (for example, with select, view, or update), attributes may not be preserved and you may need to re-apply the schema. Ideally, you would apply the schema as late as possible before export to minimize this risk.

Example: Annotate Union<String, Double> columns

The following example creates a table with a column of Objects (limited for this example to String and Double). The Arrow schema annotates the column as a dense union with String and Double branches. The final table can be exported over Flight / Barrage without error.

Example: Annotate Map<String, String> columns

The following example creates a table with a column of Map<String, String>. The Arrow schema annotates the column as an Arrow Map with the correct types for key and values. The final table can be exported over Flight / Barrage without error.

Example: Annotate Map<String, Integer> columns

The following example creates a table with a column of Map<String, Integer>. The Arrow schema annotates the column as an Arrow Map with String keys and Integer values. The final table can be exported over Flight / Barrage without error.

Example: Annotate Map<String, Union> columns

This example demonstrates using Union for values in a Map with String keys. The Union can contain a Double, String, Long, or Integer.

Example: Run-End Encoded (REE) columns

Run-End Encoding is a wire-level optimization for columns with many repeated values. Instead of sending every value, the column is serialized as two child arrays:

  • run_ends — a non-nullable integer array of cumulative 1-based end indices, one per run. The last value always equals the logical row count.
  • values — the values that will be repeated in the run.

A column of 1,000 rows where the same integer repeats 100 times in a row costs 10 run_end entries + 10 value entries instead of 1,000 integers. Deephaven stores the column flat (unchanged type); REE is a transport-only optimization. The run_ends integer width is determined by the Arrow field structure you supply via BARRAGE_SCHEMA_ATTRIBUTE. Use Int32 unless you have a specific reason to use Int16. Note that Int16 run_ends constrain the effective batch size to at most Short.MAX_VALUE / 32,767 rows per record batch.

To confirm that the column really is sent run-end encoded, see Verify the encoding from a subscriber below.

Example: Dictionary-Encoded columns

Dictionary Encoding is a wire-level optimization for low-cardinality columns. Instead of sending each value in full, Deephaven sends each unique value once (in a DictionaryBatch message) and replaces each row with a compact integer index.

A string column with 1,000 rows drawn from only 5 distinct values costs 5 full string entries (in the dictionary) + 1,000 integer indices, rather than 1,000 full strings. Deephaven stores the column flat (unchanged type); dictionary encoding is a transport-only optimization.

The DictionaryEncoding index width controls the integer type used for indices:

  • Int32 (32-bit signed) — handles up to about 1 billion distinct values; suitable for almost all use cases.
  • Int8 (8-bit signed) — the most compact option, but limits the dictionary to at most 128 distinct values.
  • Int16 (16-bit signed) — more compact than Int32, but limits the dictionary to at most 32,768 distinct values.
  • Int64 (64-bit signed) — rarely needed; use only when distinct values exceed 1 billion.

Caution

Dictionary updates are sent as deltas, so entries accumulate as new unique values appear. To prevent unbounded growth on the server and client, Deephaven resets the dictionary when its size exceeds the table or viewport size by flushing the current dictionary and accumulating only newly encountered values. Despite this safety net, if a single table (or viewport) contains more distinct values than the index type can represent (128 for Int8, 32,768 for Int16), Deephaven throws an error at serialization time. Prefer Int32 unless you are certain the column's active cardinality stays within the smaller limit.

Verify the encoding from a subscriber

Deephaven sends the export schema to every subscriber and stores it on the resulting client-side table under the same Table.BARRAGE_SCHEMA_ATTRIBUTE. Reading that attribute back tells you exactly which encoding each column was sent with.

Run the Run-End Encoded example above so that table_w_attributes exists, then subscribe to it — from a second Deephaven instance, or from the same instance over a URI:

This prints:

status arrived as RunEndEncoded with Int32 run ends, exactly as annotated, while value was sent unencoded. Running the same check against the dictionary-encoded example prints dictionary_encoded=true for status instead.

Subscribing to a table with no BARRAGE_SCHEMA_ATTRIBUTE prints false for both facets of every column, unless the server has encoding auto-detection enabled. The BarrageUtil.ree.autoDetectEnabled and BarrageUtil.dictionary.autoDetectEnabled properties are both off by default; when either is set, the server may choose an encoding on its own for a table you never annotated, and this check is how you see what it picked.

Use println wire_schema.toJson() to dump the entire negotiated schema, including each field's deephaven:type metadata.

Note

These encodings do not change the Deephaven column type — the subscriber's status column is still a String, and the subscriber's TableDefinition is identical either way. Both encodings are transport-only optimizations, so the schema attribute is the only thing that tells you how the bytes were sent.

Caution

Only a few operations propagate the attribute (where, firstBy, lastBy, partitionBy, reverse, sort, and flatten). Read it from the table returned by resolve rather than from a derived table.

From the producer

The server logs the same decision for every table it exports. Raise the level of the io.deephaven.extensions.barrage.util.BarrageUtil logger — in your logging configuration, or at runtime with ch.qos.logback.classic.Logger#setLevel — to DEBUG for a one-line summary per export, or to TRACE to also dump the complete Arrow schema:

The summary reports where the encodings came from — an explicit BARRAGE_SCHEMA_ATTRIBUTE, or auto-detection — which is how you confirm what the server chose for a table you did not annotate yourself. Tables are named by their Table.BARRAGE_PERFORMANCE_KEY_ATTRIBUTE when it is set, and by the table description otherwise.