Skip to content

GET /v1/pre-aggregations/jobs throws getQueueDriver of undefined on a replica that did not build the pre-aggregation #11615

Description

@dochernyshov

Describe the bug
I used AI to go through this bug.

Polling pre-aggregation build jobs via POST /cubejs-api/v1/pre-aggregations/jobs { "action": "get", "tokens": [...] } throws 500:

TypeError: Cannot read properties of undefined (reading 'getQueueDriver')
    at PreAggregations.isPartitionExist (…/PreAggregations.ts:471:47)
    at ApiGateway.getPreAggJobResultStatus (…/gateway.ts:1276:9)
    at async Promise.all (index N)
    at ApiGateway.preAggregationsJobsGET (…/gateway.ts:1138:45)

isPartitionExist uses this.queue[dataSource] without creating the queue. this.queue is an in-process map.
Cube Store can already hold the job. The replica that handles the poll may never have called getQueue() for that data source.

This shows up with the Orchestration API: POST jobs on one API process, then GET the tokens from another. Jobs stay posted until isPartitionExist runs. If they have already left this replica’s queue, the poll hits the missing this.queue[dataSource] and throws.

To Reproduce
Two Cube API processes, same model, shared Cube Store (not in-memory queue). Official image cubejs/cube:latest (1.7.24).

  1. Run Postgres + Cube Store + two Cube replicas (:4000 builder, :4001 poller) with CUBEJS_CACHE_AND_QUEUE_DRIVER=cubestore, a named data source, and CUBEJS_DEFAULT_API_SCOPES including jobs.
  2. Use the schema below (orders extends a parent that owns data_source; partitioned rollup so POST returns several tokens).
  3. Trigger builds on replica A:
curl -sS -X POST http://localhost:4000/cubejs-api/v1/pre-aggregations/jobs \
  -H "Authorization: $CUBE_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "action": "post",
    "selector": {
      "contexts": [{"securityContext": {}}],
      "timezones": ["UTC"],
      "preAggregations": ["orders.main"]
    }
  }'
  1. Wait until partitions leave the queue (scheduled / processing is too early). Then poll replica B with all tokens:
curl -sS -X POST http://localhost:4001/cubejs-api/v1/pre-aggregations/jobs \
  -H "Authorization: $CUBE_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"action":"get","resType":"object","tokens":["paste", "tokens", "here"]}'
  1. Replica B returns 500 with getQueueDriver of undefined.

Same crash without HTTP, calling isPartitionExist(..., 'named_data_source', ...) on a PreAggregations instance that never called getQueue for that source.

Expected behavior
The poller replica should lazily create the local queue client (getQueue(dataSource)), read the job result from shared Cube Store, and return 200 with per-token statuses (done / missing_partition / failure), not 500.

Screenshots
Only response body:

{
  "error": "TypeError: Cannot read properties of undefined (reading 'getQueueDriver')"
}

Minimally reproducible Cube Schema

cubes:
  - name: orders_base
    data_source: named_data_source
    sql: |
      SELECT 1 AS id, 100 AS amount, 'new' AS status, TIMESTAMP '2024-01-01' AS created_at
      UNION ALL
      SELECT 2, 200, 'new', TIMESTAMP '2024-01-02'
      UNION ALL
      SELECT 3, 300, 'processed', TIMESTAMP '2024-01-03'
      UNION ALL
      SELECT 4, 500, 'processed', TIMESTAMP '2024-01-04'
      UNION ALL
      SELECT 5, 600, 'shipped', TIMESTAMP '2024-01-05'
    measures:
      - name: count
        type: count
      - name: total_amount
        sql: amount
        type: sum
    dimensions:
      - name: id
        sql: id
        type: number
        primary_key: true
      - name: status
        sql: status
        type: string
      - name: created_at
        sql: created_at
        type: time

  - name: orders
    extends: orders_base
    pre_aggregations:
      - name: main
        measures: [count, total_amount]
        dimensions: [status]
        time_dimension: created_at
        granularity: day
        partition_granularity: day

Version:
1.7.24

Additional context
Related: #11313 (getPreAggJobQueueStatus inspected only the default data source queue, so named-source builds reported missing_partition while still running). Passing job.dataSource into the queue lookup does not cover this crash: after the job has left the queue, the poll still calls isPartitionExist, which indexes this.queue[dataSource] without getQueue().

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions