Query table configuration

This guide discusses how to control various QueryTable features that affect your Deephaven tables' latency and throughput.

QueryTable

QueryTable is Deephaven's primary implementation of our Table API.

The QueryTable has the following user-configurable properties:

CategoryPropertyDefault
MemoizationQueryTable.memoizeResultstrue
MemoizationQueryTable.redirectUpdatefalse
MemoizationQueryTable.redirectSelectfalse
MemoizationQueryTable.maximumStaticSelectMemoryOverhead1.1
DataIndexQueryTable.useDataIndexForWheretrue
DataIndexQueryTable.useDataIndexForAggregationtrue
DataIndexQueryTable.useDataIndexForJoinstrue
DataIndexQueryTable.dataIndexForWhereThreshold0.0625
Pushdown predicates with whereQueryTable.disableWherePushdownDataIndexfalse
Pushdown predicates with whereQueryTable.disableWherePushdownParquetRowGroupMetadatafalse
Pushdown predicates with whereQueryTable.disableWherePushdownMergedTablesfalse
Pushdown predicates with whereQueryTable.disableWherePushdownDictionaryfalse
Pushdown predicates with whereQueryTable.disableWherePushdownSortedColumnfalse
Pushdown predicates with whereQueryTable.dictionaryForWhereThreshold0.25
Parallel processing with whereQueryTable.disableParallelWherefalse
Parallel processing with whereQueryTable.parallelWhereRowsPerSegment1 << 16
Parallel processing with whereQueryTable.parallelWhereSegments-1
Parallel processing with whereQueryTable.forceParallelWhere (test-focused)false
Parallel processing with selectQueryTable.enableParallelSelectAndUpdatetrue
Parallel processing with selectQueryTable.minimumParallelSelectRows1L << 22
Parallel processing with selectQueryTable.forceParallelSelectAndUpdate (test-focused)false
Parallel snapshottingQueryTable.enableParallelSnapshottrue
Parallel snapshottingQueryTable.minimumParallelSnapshotRows1L << 20
Ungroup operationsQueryTable.minimumUngroupBase10
SoftRecycler configurationarray.recycler.capacity.*1024
SoftRecycler configurationsparsearray.recycler.capacity.*1024
SoftRecycler configurationarray.recycler.maximumCapacity.*unlimited
SoftRecycler configurationsparsearray.recycler.maximumCapacity.*unlimited
SoftRecycler configurationSoftRecycler.windowMillis1000
SoftRecycler configurationSoftRecycler.maximumWindowMillis60000
SoftRecycler configurationSoftRecycler.windowMargin1.5
SoftRecycler configurationSoftRecycler.shrinkFraction0.1
Stateless filters by defaultQueryTable.statelessFiltersByDefaultfalse
Stateless select by defaultQueryTable.statelessSelectByDefaultfalse

Each property is described below, roughly categorized by similarity.

Memoization

Deephaven utilizes memoization for many table operations to improve performance by eliminating duplicate work. See Query Memoization for more details.

It can be beneficial to disable memoization when benchmarking or testing, as memoized results can hide true operational costs and skew performance metrics.

Property NameDefault ValueDescription
QueryTable.memoizeResultstrueEnables memoizing table operations

Redirection

Deephaven Tables maintain a 63-bit keyspace that maps a logical row in row-key space to its data. Many of Deephaven's column sources use a multi-level data layout to avoid allocating more resources than necessary to fulfill operational requirements. See selection method properties for more details.

Redirection is a mapping between a parent column source and the resulting column source for a given operation. A sorted column, for example, is redirected from the original to present the rows in the targeted sort order. Redirection may also flatten from a sparse keyspace to a flat and dense keyspace.

Property NameDefault ValueDescription
QueryTable.redirectUpdatefalseForces non-flat refreshing QueryTable#update operations to redirect despite the increased performance costs
QueryTable.redirectSelectfalseForces non-flat refreshing QueryTable#select operations to redirect despite the increased performance costs
QueryTable.maximumStaticSelectMemoryOverhead1.1 (double)The maximum overhead as a fraction (e.g. 1.1 is 10% overhead; always sparse if < 0, never sparse if 0)

DataIndex

A Deephaven DataIndex is an index that can improve the speed of filtering operations.

Property NameDefault ValueDescription
QueryTable.useDataIndexForWheretrueEnables data index usage in QueryTable#where operations
QueryTable.useDataIndexForAggregationtrueEnables data index usage in QueryTable#aggBy, QueryTable#selectDistinct, within rollup-tables and tree-tables
QueryTable.useDataIndexForJoinstrueEnables data index usage in Deephaven Joins
QueryTable.disableWherePushdownDataIndexfalseDisables data index usage within where's pushdown predicates
QueryTable.dataIndexForWhereThreshold0.0625 (double)The maximum size of a data index table, as a fraction of the rows remaining to be filtered, for the index to be used by where

Pushdown predicates with where

Pushdown predicates refer to the mechanism whereby filtering conditions are applied as early as possible, ideally at the data source (e.g., Parquet or other columnar formats), before loading data into the system. By annotating source reads with predicates, the engine pulls in only the rows that satisfy the conditions, significantly reducing I/O and improving performance.

Property NameDefault ValueDescription
QueryTable.disableWherePushdownDataIndexfalseDisables the use of data index within where's pushdown predicates
QueryTable.disableWherePushdownParquetRowGroupMetadatafalseDisables the usage of Parquet row group metadata during pushdown filtering
QueryTable.disableWherePushdownMergedTablesfalseDisables predicate pushdown when filtering merged tables
QueryTable.disableWherePushdownDictionaryfalseDisables dictionary-encoding predicate pushdown operations
QueryTable.disableWherePushdownSortedColumnfalseDisables the use of sorted column binary search during pushdown filtering
QueryTable.dataIndexForWhereThreshold0.0625 (double)The maximum size of a data index table, as a fraction of the rows remaining to be filtered, for the index to be used by where
QueryTable.dictionaryForWhereThreshold0.25 (double)The dictionary size, as a fraction of the rows remaining to be filtered, that the dictionary must fall below for push-down filtering to use it

The two *ForWhereThreshold properties are cost heuristics rather than on/off switches. Scanning a data index table or a dictionary costs time proportional to its size, so the engine only does so when that structure is small relative to the rows it can eliminate. When the index table or dictionary exceeds its threshold, the engine does not use it, and the rows are filtered normally. Raise the value toward 1.0 to apply these optimizations to columns with more distinct values, or set it to 0 to effectively disable the technique.

For more details, see Predicate pushdown filtering.

Parallel processing with where

Parallelism for where operations is not enabled until the parent's size exceeds QueryTable.parallelWhereRowsPerSegment rows. This avoids the overhead of using threads for small operations. For tables larger than this threshold, the where operation uses a fixed number of parallel segments defined by QueryTable.parallelWhereSegments. These parameters can be tuned to avoid unnecessary parallelism when the overhead exceeds potential gains.

Property NameDefault ValueDescription
QueryTable.enableParallelWherefalseEnables parallelized optimizations for QueryTable#where operations
QueryTable.parallelWhereRowsPerSegment1 << 16The number of rows per segment when the number of segments is not fixed
QueryTable.parallelWhereSegments-1The number of segments to use when dividing all work equally into a fixed number of tasks; -1 implies one thread per core
QueryTable.forceParallelWhere (test-focused)falseForces Where operations to parallelize even when row requirements are not met

Parallel processing with select

The QueryTable operations select and update have performance enhancements that try to take advantage of parallelism during two separate phases of each table operation invocation. The first opportunity for parallelism is on the initial creation of the table. The engine will parallelize the initial computation of the resulting table state. The second opportunity for parallelism is when the operation's parent-table listener is notified that the parent was updated.

Parallelism for select operations is not enabled until the parent's size exceeds QueryTable.minimumParallelSelectRows rows. This can be tuned to avoid unnecessary parallelism (e.g., when the overhead exceeds potential gains).

Property NameDefault ValueDescription
QueryTable.enableParallelSelectAndUpdatetrueEnables parallelized optimizations for QueryTable#select and QueryTable#update operations
QueryTable.minimumParallelSelectRows1L << 22The minimum number of rows required to enable parallel select and update operations
QueryTable.forceParallelSelectAndUpdate (test-focused)falseForces Select and Update operations to parallelize even when row requirements are not met

Parallel snapshotting

Barrage clients, including our JavaScript implementation used on the web, fulfill subscription requests by snapshotting the required rows and columns in addition to listening for relevant changes when the table is refreshing. Parallel snapshotting is a feature that parallelizes this process across columns. If those columns are slow to access then parallel snapshotting will greatly reduce latency. However, parallel snapshotting may open many file handles to the same data source.

Parallel snapshotting is not enabled until the snapshot size exceeds QueryTable.minimumParallelSnapshotRows rows. This can be tuned to avoid unnecessary parallelism when the overhead exceeds potential gains.

Property NameDefault ValueDescription
QueryTable.enableParallelSnapshottrueEnables parallelized optimizations for snapshotting operations, such as Barrage subscription requests
QueryTable.minimumParallelSnapshotRows1L << 20The minimum number of rows required to enable parallel snapshotting operations

Ungroup operations

The ungroup table operation can expand one row into multiple rows. QueryTable.minimumUngroupBase controls the initial allocation used by ungroup.

Property NameDefault ValueDescription
QueryTable.minimumUngroupBase10The minimum base used for ungroup output row allocation (uses 2^base rows)

SoftRecycler configuration

Deephaven uses SoftRecycler objects to manage memory for array and sparse array column sources. These column sources must maintain previous values during an update graph cycle. Rather than allocating fresh memory on each cycle, when memory is needed to record previous values it is borrowed from the recycler and returned at the end of the update cycle. These pools can improve performance and reduce garbage collection pressure.

A recycler's capacity is the most arrays it holds at once. Each recycler adapts its capacity to its traffic: it starts at a configured capacity, grows when a cycle needs more arrays than it holds, and shrinks back toward the configured capacity when arrays go unused, but never below it and never above a configured maximum. Both values can be set per element type, and the growth and shrink rules are described below.

Array column source recyclers

Array-backed column sources (dense arrays) use SoftRecyclers to manage blocks of data for each primitive type. Each capacity property sets the capacity a recycler starts with and never shrinks below, and each maximumCapacity property sets the most it grows to.

Property NameDefault ValueDescription
array.recycler.capacity.default1024Default recycler capacity for all array types (used if type-specific property is not set)
array.recycler.capacity.boolean1024Recycler capacity for boolean array blocks
array.recycler.capacity.byte1024Recycler capacity for byte array blocks
array.recycler.capacity.char1024Recycler capacity for character array blocks
array.recycler.capacity.double1024Recycler capacity for double array blocks
array.recycler.capacity.float1024Recycler capacity for float array blocks
array.recycler.capacity.int1024Recycler capacity for integer array blocks
array.recycler.capacity.long1024Recycler capacity for long array blocks
array.recycler.capacity.short1024Recycler capacity for short array blocks
array.recycler.capacity.object1024Recycler capacity for object array blocks
array.recycler.maximumCapacity.defaultunlimitedDefault maximum recycler capacity for all array types (used if type-specific property is not set)
array.recycler.maximumCapacity.<type>unlimitedMaximum recycler capacity for blocks of <type> — one of boolean, byte, char, double, float, int, long, short, or object — never less than that type's array.recycler.capacity

The "in use" bitmaps, which record which entries of a block hold a previous value, have a recycler of their own that every type shares. Its capacity and maximum are the sums of the types' values, and it has no properties.

Sparse array column source recyclers

Sparse array column sources use a multi-level hierarchical structure and maintain separate recyclers at each level. Each level can be configured independently to optimize memory usage for your access patterns. As for array column sources, each capacity property sets the capacity a recycler starts with and never shrinks below, and each maximumCapacity property sets the most it grows to.

Property NameDefault ValueDescription
sparsearray.recycler.capacity.default1024Default recycler capacity for all sparse array types
sparsearray.recycler.capacity.boolean1024Base recycler capacity for boolean sparse arrays
sparsearray.recycler.capacity.byte1024Base recycler capacity for byte sparse arrays
sparsearray.recycler.capacity.char1024Base recycler capacity for character sparse arrays
sparsearray.recycler.capacity.double1024Base recycler capacity for double sparse arrays
sparsearray.recycler.capacity.float1024Base recycler capacity for float sparse arrays
sparsearray.recycler.capacity.int1024Base recycler capacity for integer sparse arrays
sparsearray.recycler.capacity.long1024Base recycler capacity for long sparse arrays
sparsearray.recycler.capacity.short1024Base recycler capacity for short sparse arrays
sparsearray.recycler.capacity.object1024Base recycler capacity for object sparse arrays
sparsearray.recycler.capacity.boolean.21024Level 2 recycler capacity for boolean sparse arrays
sparsearray.recycler.capacity.byte.21024Level 2 recycler capacity for byte sparse arrays
sparsearray.recycler.capacity.char.21024Level 2 recycler capacity for character sparse arrays
sparsearray.recycler.capacity.double.21024Level 2 recycler capacity for double sparse arrays
sparsearray.recycler.capacity.float.21024Level 2 recycler capacity for float sparse arrays
sparsearray.recycler.capacity.int.21024Level 2 recycler capacity for integer sparse arrays
sparsearray.recycler.capacity.long.21024Level 2 recycler capacity for long sparse arrays
sparsearray.recycler.capacity.short.21024Level 2 recycler capacity for short sparse arrays
sparsearray.recycler.capacity.object.21024Level 2 recycler capacity for object sparse arrays
sparsearray.recycler.capacity.boolean.11024Level 1 recycler capacity for boolean sparse arrays
sparsearray.recycler.capacity.byte.11024Level 1 recycler capacity for byte sparse arrays
sparsearray.recycler.capacity.char.11024Level 1 recycler capacity for character sparse arrays
sparsearray.recycler.capacity.double.11024Level 1 recycler capacity for double sparse arrays
sparsearray.recycler.capacity.float.11024Level 1 recycler capacity for float sparse arrays
sparsearray.recycler.capacity.int.11024Level 1 recycler capacity for integer sparse arrays
sparsearray.recycler.capacity.long.11024Level 1 recycler capacity for long sparse arrays
sparsearray.recycler.capacity.short.11024Level 1 recycler capacity for short sparse arrays
sparsearray.recycler.capacity.object.11024Level 1 recycler capacity for object sparse arrays
sparsearray.recycler.capacity.boolean.01024Level 0 (top) recycler capacity for boolean sparse arrays
sparsearray.recycler.capacity.byte.01024Level 0 (top) recycler capacity for byte sparse arrays
sparsearray.recycler.capacity.char.01024Level 0 (top) recycler capacity for character sparse arrays
sparsearray.recycler.capacity.double.01024Level 0 (top) recycler capacity for double sparse arrays
sparsearray.recycler.capacity.float.01024Level 0 (top) recycler capacity for float sparse arrays
sparsearray.recycler.capacity.int.01024Level 0 (top) recycler capacity for integer sparse arrays
sparsearray.recycler.capacity.long.01024Level 0 (top) recycler capacity for long sparse arrays
sparsearray.recycler.capacity.short.01024Level 0 (top) recycler capacity for short sparse arrays
sparsearray.recycler.capacity.object.01024Level 0 (top) recycler capacity for object sparse arrays
sparsearray.recycler.maximumCapacity.defaultunlimitedDefault maximum recycler capacity for all sparse array types
sparsearray.recycler.maximumCapacity.<type>unlimitedMaximum base recycler capacity for <type> sparse arrays, and the default maximum for its other levels; never less than the type's base capacity
sparsearray.recycler.maximumCapacity.<type>.2unlimitedMaximum level 2 recycler capacity for <type> sparse arrays; never less than its level 2 capacity
sparsearray.recycler.maximumCapacity.<type>.1unlimitedMaximum level 1 recycler capacity for <type> sparse arrays; never less than its level 1 capacity
sparsearray.recycler.maximumCapacity.<type>.0unlimitedMaximum level 0 (top) recycler capacity for <type> sparse arrays; never less than its level 0 capacity

The "in use" bitmaps have a recycler of their own at each level, which every type shares. Each one's capacity and maximum are the sums of the types' values at that level, and none has properties.

How recyclers grow and shrink

A recycler grows during the cycle that needs more arrays than it holds. When a cycle returns arrays to a full recycler, the recycler turns them away and counts them. Each later borrow that finds the recycler empty is an array it could have kept, so the recycler grows by one for each such borrow until the count is spent. Growth therefore never exceeds what some cycle used, and it never exceeds the configured maximum.

A recycler shrinks at the end of each window. A window is sized to span a whole cycle of every update graph in the process: it lasts SoftRecycler.windowMargin times the longest interval between consecutive cycle starts among the update graphs, but no less than SoftRecycler.windowMillis and no more than SoftRecycler.maximumWindowMillis. If the recycler never emptied during the window, the arrays it never lent out were not needed, and it discards the fraction of them set by SoftRecycler.shrinkFraction, never shrinking below its configured capacity.

Windows end only when an update graph starts a cycle. In a process whose update graphs never cycle, recyclers never shrink. The recyclers hold their arrays through soft references, so the garbage collector reclaims them under memory pressure in any case.

Property NameDefault ValueDescription
SoftRecycler.windowMillis1000The shortest window, in milliseconds, and the window when no update graph has a longer interval between cycles
SoftRecycler.maximumWindowMillis60000The longest window, in milliseconds, between possibly shrinking the recycler. Even if one of the update graphs has a longer interval between cycles, the next update graph to start a cycle will shrink the recycler after the window exceeds this value
SoftRecycler.windowMargin1.5The multiple of the longest interval between update graph cycle starts that a window lasts
SoftRecycler.shrinkFraction0.1The fraction, from 0 to 1, of the arrays a window never needed that the recycler discards when the window ends; 0 keeps the capacity once grown, and 1 shrinks to what the window needed

Tuning recyclers

Because recyclers adapt, most workloads need no tuning. The configured capacity sets how many arrays a recycler keeps through quiet periods, and the maximum bounds how many it keeps through busy ones.

  • Workloads that alternate busy and quiet cycles: Raise SoftRecycler.windowMillis, or set SoftRecycler.shrinkFraction to 0, so that a recycler keeps what a busy cycle needed through the quiet cycles between.
  • Memory-constrained environments: Set a maximumCapacity to bound what a recycler holds, or raise SoftRecycler.shrinkFraction toward 1 to discard unneeded arrays sooner.
  • Type-specific tuning: If certain types dominate, raise their capacity or maximum while reducing others.

Stateless by default

These flags enable the engine to assume more often that a given Filter or Selectable can be executed in parallel (unless the Filter or Selectable is marked serial or has barriers interface).

Property NameDefault ValueDescription
QueryTable.statelessFiltersByDefaulttrueEnables the engine to assume that filters are stateless by default, allowing for more optimizations
QueryTable.statelessSelectByDefaulttrueEnables the engine to assume that Selectables are stateless by default, allowing for more optimizations