Skip to content

Cold nested subquery shape can time out subscribing to an intermediate dependency materializer #4715

Description

@crcorbett

Summary

Electric 1.7.7 deterministically returns an initial HTTP 500 for a cold nested dependency shape. Retrying the same shape succeeds after the dependency snapshots finish.

Minimal topology

assets
WHERE id NOT IN (
  SELECT asset_id
  FROM container_rels
  WHERE asset_id IS NOT NULL
    AND asset_id NOT IN (
      SELECT asset_id
      FROM container_rels
      WHERE asset_id IS NOT NULL
        AND container_id IN (
          SELECT id FROM containers WHERE <per-user access predicate>
        )
    )
)

The equivalent historical positive-IN topology did not fail in the matched run.

Controlled results on 1.7.7

  • Concurrency 1: one current nested shape returned 500 after 5.191 seconds; retry returned 200 after 2.966 seconds and reached 0_inf.
  • Bounded concurrency 4: four current nested shapes produced exactly four first-request 500s; all recovered on retry.
  • All five recovered clients had valid handles, reached up-to-date, returned the expected accessible row set, and excluded the inaccessible probe row.
  • Five matched historical positive-IN shapes returned 200 on their first request.
  • Service logs contained five shape removals caused by GenServer.call(intermediate_materializer, :subscribe, 5000) timing out and five outer await_snapshot_start(..., 45000) request failures.
  • No restart, OOM, missing-materializer, Stack-not-ready, snapshot-failure, or iodata signature occurred in the bounded window.

Expected

A single cold nested shape waits for dependency materializers and completes without an externally visible 500.

Actual

The outer consumer times out after five seconds while subscribing to the intermediate dependency materializer. Electric removes the outer shape and returns 500. A later client retry succeeds.

Relevant source

In 1.7.7:

  • packages/sync-service/lib/electric/shapes/consumer/materializer.ex: subscribe/1 uses the default five-second GenServer.call timeout.
  • packages/sync-service/lib/electric/shapes/consumer.ex: all_materializers_alive?/1 calls Materializer.subscribe/1 during outer consumer initialization.

A minimal read-only client harness can be provided. It creates distinct cold shape keys with a no-op predicate nonce and records status, latency, handle, offset, up-to-date state, and row-set correctness. It does not require schema or data mutation.

Retry is a client mitigation, not a resolution: every cold current-topology request in the matched run still exposed an initial 500.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions