o1-labs / o1-labs/Archive-Node-API

Measure the heaviest mainnet queries against the new 15s PG_STATEMENT_TIMEOUT (15-20s band now hard-errors)

Open
#216 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

P2 production-readiness
Dominant language
TypeScript
Stars
19
Forks
9
Avg merge
14h 20m
Merged PRs (30d)
8

Description

Context

#182 sets PG_STATEMENT_TIMEOUT to a default of 15000 ms. Both known downstream consumers use a 20 s client timeout:

  • mina-explorersrc/services/api/http.ts:9, DEFAULT_TIMEOUT_MS = 20_000
  • mina-explorer-apiapp/config.py:91, upstream_timeout_seconds = 20.0

The 15 s server-side cap therefore sits below the 20 s client-side cap. That ordering is deliberate and correct — the server should give up before the client does, so the failure is a clean cancellation rather than an abandoned query still consuming a connection.

But it creates a band that did not exist before: any query that previously completed in 15–20 s now hard-errors instead of succeeding slowly. Before #182 there was no server-side statement timeout at all, so such a query returned data.

This is not a blocker — #182 was approved on this basis — but it is an unverified assumption, and the only honest way to close it is measurement against real data.

What to measure

The most plausible candidate is mina-explorer's 2000-block analytics query on mainnet. It is the heaviest known consumer query (measured at depth 7, 89 tokens, graphql-armor cost ~161) and it runs against the largest dataset.

Others worth timing while you are set up:

  • blocksFullPaginatedBestChain at its maximum limit
  • The account-transaction queries at a large block range
  • Anything in mina-explorer-api's backfill path that requests a wide range

How to measure

Against a mainnet archive database — devnet and mesa are far too small to be representative:

-- capture the plan and the real execution time
EXPLAIN (ANALYZE, BUFFERS) <the query the resolver emits>;

Or end-to-end through the API, which is more faithful because it includes serialisation:

for i in $(seq 1 20); do
  curl -s -o /dev/null -w '%{time_total}\n' \
    -X POST https://<mainnet-archive-endpoint>/ \
    -H 'content-type: application/json' \
    -d '{"query":"<the 2000-block analytics query>"}'
done | sort -n

Look at p95 and max, not the mean — the tail is what trips a timeout.

Interpreting the result

Observed p99 Action
well under 15 s No action. Close this issue and record the number in docs/runbook.md's capacity section.
10–15 s Uncomfortably close. Either raise PG_STATEMENT_TIMEOUT (and correspondingly the consumers' client timeouts, keeping server < client), or optimise the query / add an index. Record the decision.
over 15 s The query is already broken by this default. Raise PG_STATEMENT_TIMEOUT for that deployment and open a follow-up to optimise, because a >15 s interactive query is a problem independent of the timeout.

PG_STATEMENT_TIMEOUT is env-tunable per deployment, so the mitigation is immediate if the measurement comes back bad — no code change or redeploy of the image required.

Note on how the failure surfaces

A cancelled statement returns SQLSTATE 57014, which yoga masks into a generic error. Confirmed that the resulting message contains none of Cannot query field, Unknown argument, or Unknown type — the three literal strings mina-explorer-api matches at app/upstream/graphql.py:33-42 to classify a permanent schema error.

That matters: a schema-error classification would poison that consumer's capability cache and cause a sticky tier downgrade. Instead a statement timeout classifies as a generic upstream error, which is transient and retried appropriately. So the failure mode is safe — it is only a question of whether it fires at all.

Acceptance criteria

  • p95 and p99 of the 2000-block mainnet analytics query recorded
  • Confirmed no known consumer query falls in the 15–20 s band, or PG_STATEMENT_TIMEOUT adjusted with the rationale recorded
  • The measured figure added to docs/runbook.md's capacity section, so the next person does not have to re-derive it

Related

#182, #197 (runbook capacity section)

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the consumer timeout references at src/services/api/http.ts:9, app/config.py:91, and the query classification at app/upstream/graphql.py:33-42. Run the 2000-block analytics query and other listed candidates against a mainnet archive database with EXPLAIN ANALYZE or the provided API loop, then record p95 and p99. Done means the 15–20 s band is ruled out or a timeout decision is documented in docs/runbook.md's capacity section.

Written by the indexing model from the issue text.

Assessment

Tech stack
graphql, postgresql, typescript
Domain
api, databases, documentation, performance
Issue type
Documentation
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.