Initialization and updates

Deephaven code often looks like it runs sequentially, line by line, just like standard Python. But with ticking tables, that's not quite what happens — and this surprises many developers.

This guide explains:

  • Why ticking table code behaves differently than standard Python.
  • The two phases of execution: initialization and updates.
  • How to safely mix Python code with Deephaven ticking tables.
  • Clear "do this, don't do that" guidelines.

The surprise: code isn't always sequential

Consider this code:

In standard Python, this would work fine — create the data source, then read from it. But with a Kafka consumer (or any external data source), the table might be empty when you try to read from it. The data hasn't arrived yet.

This is the core issue: Deephaven table operations define relationships, not sequential data transformations.

Two phases: initialization and updates

Deephaven execution has two distinct phases:

Phase 1: Initialization

When your script runs:

  1. Table operations execute and build the DAG (dependency graph).
  2. Initial calculations run on any existing data.
  3. External data sources may not have data yet.

Phase 2: Real-time updates

After initialization completes:

  1. Data flows in from sources (Kafka, time tables, etc.).
  2. Changes propagate through the DAG automatically.
  3. Derived tables update incrementally.

The problem occurs when you try to extract data to Python during initialization, before the real-time data has arrived.

Prerequisites

Before reading this guide, you should understand:

For details on how Deephaven processes updates internally, see Incremental update model.

Do this, don't do that

Here's the quick reference. Details follow in the sections below.

DODON'T
Use table operations — let the engine handle consistency.Extract data to Python immediately after creating a ticking table.
Wait for data with await_update before extracting.Assume data exists just because you created the table.
Use snapshot to get a static copy for Python processing.Call to_pandas directly on a ticking table.
Use listeners for reacting to updates.Access other tables inside listeners.
Initialize state with do_replay=True.Mix data from different update cycles.

Waiting for data: the await_update pattern

The most common initialization problem is trying to read data before it arrives. Use await_update to pause until the table receives an update:

Note

await_update blocks until the table receives at least one update. In app mode startup scripts, this ensures your initialization code waits for data to flow in before proceeding.

You can also specify a timeout in milliseconds: trades.await_update(timeout=5000) returns False if the timeout is reached before an update occurs.

When to use await_update

  • App mode startup scripts — When you need to extract initial values from external data sources.
  • Sequential initialization — When later code depends on data from earlier tables.
  • One-time setup — When you need a value once at startup, not continuously.

When NOT to use await_update

  • In listeners — Listeners already receive data when it's ready.
  • For continuous processing — Use table operations or listeners instead.
  • In the console — Console code runs interactively; just wait and re-run.

Safe patterns for initialization

Pattern 1: Let the engine do the work

The safest approach is to express your logic using Deephaven table operations rather than extracting data to Python. The engine guarantees consistency for all table operations.

If you need custom Python logic, consider using it in a formula via Python functions:

Pattern 2: Use snapshot for point-in-time extraction

When you need a static copy of ticking data for Python processing, use snapshot:

The snapshot creates a static table that won't change, making it safe to extract and process in Python.

Pattern 3: Use snapshot_when for controlled timing

When you need to extract data at specific intervals or synchronized with other events, use snapshot_when:

This is useful for:

  • Reducing update frequency for downstream processing.
  • Synchronizing data extraction with external systems.
  • Batching updates for efficiency.

Safe patterns for reacting to updates

Pattern 4: Use listeners with TableUpdate

When using table listeners, the TableUpdate object provides safe, consistent access to changed data:

Caution

Keep listener execution fast — listeners block further Update Graph processing while running.

The .added, .modified, .removed, and .modified_prev methods return dictionaries with column names as keys and NumPy arrays as values. This data is a consistent snapshot from the update cycle.

Pattern 5: Initialize state with do_replay

When setting up a listener, use do_replay=True to receive the table's existing data as the first update:

Pattern 6: Initialize with a snapshot before listening

For complex initialization, take a snapshot first, then attach a listener for ongoing updates:

Pattern 7: Use locking for direct table access (advanced)

For advanced use cases where you need to read directly from a ticking table, use the update graph lock:

Caution

Keep lock duration minimal — it blocks update processing while held.

Unsafe patterns

DON'T read ticking data without synchronization

What can go wrong:

  • The table might be mid-update when you read.
  • Some columns could have new values, others old values.
  • Row keys might have shifted.

Do instead: Use snapshot first, or use locking.

DON'T access other tables inside listeners

What can go wrong:

  • Other tables are not guaranteed to have been consistently updated.
  • You may see partial or stale data from other tables.

Do instead: Only access the table you are listening to within a listener. If you need data from multiple tables, add them as dependencies:

DON'T mix data from different update cycles

What can go wrong:

  • Row keys from previous cycles may no longer be valid.
  • Comparisons assume consistency that doesn't exist.

Do instead: Use modified_prev (which reads pre-shift values) for before/after comparisons within the same update, or store only computed results (not raw data) between cycles.

DON'T hold locks longer than necessary

What can go wrong:

  • The entire update graph is blocked.
  • All ticking tables freeze.
  • User interfaces become unresponsive.

Do instead: Extract data quickly, release the lock, then process:

Common scenarios

ScenarioRecommended approach
Wait for Kafka/external data before extractingtable.await_update before extraction.
Export ticking data to pandas periodicallysnapshot_when with a trigger table, then to_pandas.
React to updates and call external APIListener with added data access.
Aggregate ticking data with custom Python logicUse table operations (agg_by, update_by), or Python functions in formulas.
Initialize ML model from table statedo_replay=True in listener, or snapshot before listening.
Join ticking data with external databaseSnapshot the ticking table, then join in Python.
Debug why data looks inconsistentCheck if you're reading without synchronization; add logging to listener.

Understanding the update cycle

For debugging and advanced use cases, it helps to understand the update cycle in more detail:

  1. Idle phase: Tables are consistent. Console commands run here (under exclusive lock automatically).
  2. Update phase: Changes propagate through the DAG. Listeners fire. Data is in flux.

The exclusive lock ensures you're in the idle phase. Listeners fire during the update phase but receive consistent data through TableUpdate.