risingwavelabs/risingwave · error
query_epoch not set in distributed lookup join
Error message
query_epoch not set in distributed lookup join
What it means
The distributed lookup join executor requires an epoch (query_epoch) from the protobuf plan node to snapshot the inner side table at a consistent MVCC version. If the proto field is None (unset), new_boxed_executor cannot build the inner-side executor and fails with this anyhow error.
Source
Thrown at src/batch/executors/src/executor/join/distributed_lookup_join.rs:196
.map(ColumnId::from)
.collect();
// Use a full vnode bitmap so that the lookup can be performed on any worker.
// For a lookup table with hash distribution, the lookup keys always contain the
// distribution key; for a lookup table with singleton distribution, all lookups
// are gathered into a single task. In both cases the lookup is correct regardless
// of which worker the task is scheduled to.
let vnodes = Some(Bitmap::ones(table_desc.vnode_count()).into());
dispatch_state_store!(source.context().state_store(), state_store, {
let table = BatchTable::new_partial(state_store, column_ids, vnodes, table_desc);
let inner_side_builder = InnerSideExecutorBuilder::new(
outer_side_key_types,
inner_side_key_types.clone(),
lookup_prefix_len,
distributed_lookup_join_node
.query_epoch
.ok_or_else(|| anyhow!("query_epoch not set in distributed lookup join"))?,
vec![],
table,
chunk_size,
);
let identity = source.plan_node().get_identity().clone();
Ok(DistributedLookupJoinExecutorArgs {
join_type,
condition,
outer_side_input,
outer_side_data_types,
outer_side_key_idxs,
inner_side_builder,
inner_side_key_types,
inner_side_key_idxs,
null_safe,
lookup_prefix_len,View on GitHub (pinned to 6469eb736d)
Solutions
- Set query_epoch when constructing DistributedLookupJoinNode in the batch planner/frontend
- Rebuild/redeploy frontend and compute nodes to the same version so the field is always populated
- Check for stale plan fragments/caches from an older cluster version and invalidate them
Example fix
// before
DistributedLookupJoinNode { lookup_prefix_len, ..Default::default() }
// after
DistributedLookupJoinNode { lookup_prefix_len, query_epoch: Some(epoch), ..Default::default() } Defensive patterns
Strategy: validation
Validate before calling
fn validate_lookup_join_plan(node: &DistributedLookupJoinNode) -> anyhow::Result<()> {
anyhow::ensure!(node.query_epoch.is_some(), "distributed lookup join plan missing query_epoch");
Ok(())
} Type guard
fn has_query_epoch(node: &DistributedLookupJoinNode) -> bool {
node.query_epoch.is_some()
} Try / catch
let epoch = distributed_lookup_join_node.query_epoch.ok_or_else(|| {
anyhow::anyhow!("query_epoch not set in distributed lookup join")
})?; Prevention
- Always populate query_epoch when constructing DistributedLookupJoinNode in the planner
- Add a round-trip proto serialization test asserting query_epoch survives encode/decode
- Keep frontend and compute node versions in lockstep to avoid proto field skew
When it happens
Trigger: Executing a distributed lookup join plan whose DistributedLookupJoinNode protobuf was constructed without setting query_epoch — typically a plan serialization/deserialization gap or a frontend/meta node running older code that does not populate the field.
Common situations: Version-skew clusters where the frontend generates plans without query_epoch but the compute node requires it; hand-crafted or replayed protobuf plans; plan cache/format changes after an upgrade.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- empty sink metadata
- empty commit metadata
- Join key types are not aligned: LHS: {outer_type:?}, RHS: {i
- Join key types are not aligned: LHS: {outer_type:?}, RHS: {i
- Expr error: {0}
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/b24f538fe11813a6.
Report an issue: GitHub.