Skip to content

fix(deserializer): heatmap routing drops all actions for unassigned contracts when ds_pool_size = 1#169

Merged
igorls merged 1 commit into
devfrom
fix/ds-pool-heatmap-routing-single-worker
May 29, 2026
Merged

fix(deserializer): heatmap routing drops all actions for unassigned contracts when ds_pool_size = 1#169
igorls merged 1 commit into
devfrom
fix/ds-pool-heatmap-routing-single-worker

Conversation

@igorls
Copy link
Copy Markdown
Member

@igorls igorls commented May 29, 2026

Problem

With scaling.routing_mode: "heatmap" and scaling.ds_pool_size: 1, the indexer ingests blocks and deltas correctly but get_actions returns 0 and /v2/health reports Elasticsearch "Error" because the {chain}-action-* index is never created. All action traces are silently dropped.

Root cause

In MainDSWorker.routeToPool (src/indexer/workers/deserializer.ts), the heatmap path uses 0-indexed worker ids from dsPoolMap[code][2] and converts them to a 1-indexed ds_pool:N queue with selected_q += 1.

selected_q was initialized to 1. When a contract has no heatmap assignment yet — empty dsPoolMap on a fresh/low-traffic chain, or any contract not seen since the last update_pool_map IPC update — the assignment block is skipped, selected_q stays 1, and += 1 makes it 2. Traces are published to {chain}:ds_pool:2.

But ds_pool workers are created in master.ts setupDSPool() with local_id = i + 1 for i in 0..ds_pool_size-1, consuming queues ds_pool:1..ds_pool_size. With ds_pool_size: 1 only ds_pool:1 exists, so a default-exchange publish to the unrouted ds_pool:2 is a no-op and the traces are lost. Deployments with ds_pool_size >= 2 happen to have a ds_pool:2 consumer, so they never observe this.

The heatmap worker ids in dsPoolMap[code][2] are 0-indexed (master builds them with for (let i = 0; i < pool_size; i++) proposedWorkers.push(i)), and selected_q += 1 is exactly the 0→1-index conversion. The init value should therefore represent 0-indexed worker 0, i.e. the first pool worker.

Fix

Initialize selected_q to 0 instead of 1, so an unassigned contract falls back to the first pool worker (queue ds_pool:1). round_robin reassigns selected_q and default exits, so neither relies on the initial value.

Workarounds (for affected users on older builds)

  • Set scaling.routing_mode: "round_robin", or
  • Set scaling.ds_pool_size: 2 or higher.

Verification

tsc compiles cleanly with no errors. Reproduced and fixed against a local Antelope Spring 1.2.2 node: before the fix, blocks/deltas indexed but get_actions = 0 and ES health = Error; after, a clean re-index produced the chain's full action history (transfer/create/issue/setup/setfeesmap/setcode/newaccount/…) with all four /v2/health services OK.

Optional follow-up (not in this PR)

A startup warning when routing_mode === "heatmap" && ds_pool_size < 2 would surface the (now-functional but pointless) single-worker-heatmap config explicitly. Happy to add it here or separately if you'd like.

In heatmap routing mode, `routeToPool` initialized `selected_q` to 1 and
then applied `selected_q += 1` to convert the 0-indexed heatmap worker id
(dsPoolMap[code][2], built by master with `for (let i = 0; i < pool_size; i++)`)
into a 1-indexed `ds_pool:N` queue name.

When a contract has no heatmap assignment yet (empty dsPoolMap, e.g. fresh or
low-traffic chains, or any contract not seen since the last update_pool_map),
the assignment block is skipped, `selected_q` keeps its init value 1, and the
`+= 1` makes it 2. Traces are then published to `ds_pool:2`. But ds_pool
workers are created with `local_id = i + 1` (i in 0..ds_pool_size-1), consuming
`ds_pool:1..ds_pool_size`. With `scaling.ds_pool_size: 1` only `ds_pool:1`
exists, so everything published to `ds_pool:2` is silently dropped (publishing
to the default exchange with an unrouted queue name is a no-op). Result: blocks
and deltas index fine, but `get_actions` returns 0 and `/v2/health` reports
Elasticsearch Error because the `{chain}-action-*` index is never created.
Public deployments use `ds_pool_size >= 2`, so they never hit this.

Initialize `selected_q` to 0 so an unassigned contract falls back to the first
pool worker (queue `ds_pool:1`), matching the 0-indexed heatmap ids that the
`+= 1` conversion already expects. round_robin and the default case do not rely
on the initial value.
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the deserializer worker to initialize selected_q to 0 instead of 1. This ensures that contracts without a heatmap assignment correctly fall back to the first pool worker (queue ds_pool:1) instead of routing to a non-existent ds_pool:2 queue, which previously caused action traces to be silently dropped when ds_pool_size is 1. There are no review comments, so I have no feedback to provide.

@igorls igorls changed the base branch from main to dev May 29, 2026 06:02
@igorls igorls merged commit 3aba7c8 into dev May 29, 2026
2 checks passed
@igorls igorls mentioned this pull request Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant