Skip to content

feat(AF-49): result byte cap + global concurrency budget in QueryExecutor#609

Merged
babltiga merged 1 commit into
mainfrom
feature/AF-49-byte-cap-concurrency-budget
Jul 17, 2026
Merged

feat(AF-49): result byte cap + global concurrency budget in QueryExecutor#609
babltiga merged 1 commit into
mainfrom
feature/AF-49-byte-cap-concurrency-budget

Conversation

@babltiga

Copy link
Copy Markdown
Contributor

Closes #49

What

Two heap-protection guards on top of the existing row cap in the proxy executor:

  • Per-result byte capJdbcResultRowMapper accumulates a rough per-row size estimate (new ResultByteEstimator) during materialization; when the running total would exceed accessflow.proxy.execution.max-result-bytes (default 50 MiB) it stops, marks the result truncated=true with truncated_reason="BYTE_LIMIT" (row-cap truncation now sets "ROW_LIMIT"). The first row is always kept. Relational JDBC path only — engine-managed (NoSQL) datasources keep their own row caps but are not byte-capped.
  • Global concurrency budgetConcurrencyLimitingQueryExecutor, a @Primary decorator over QueryExecutor, bounds in-flight executions across all datasources with a fair semaphore (max-concurrent default 32). Overflow callers block up to acquire-timeout (default 5s), then get QueryConcurrencyLimitExceededException503 QUERY_CONCURRENCY_LIMIT. execute() and sampleTable() are guarded; dryRun() passes through (EXPLAIN-only, zero rows). Per-datasource concurrency remains HikariCP's job — no double-bounding.

truncated_reason is threaded end-to-end: SelectExecutionResult (new nullable component, old constructors kept — all 7 engine plugins remain source/binary compatible, no re-pins), Redis result cache, query_request_results.truncated_reason (Flyway V116), GET /queries/{id}/results, GET /datasources/{id}/sample-rows, and a byte-limit-specific truncation message in the frontend results table / sample preview (all 7 locales).

New env vars: ACCESSFLOW_PROXY_EXECUTION_MAX_RESULT_BYTES, ACCESSFLOW_PROXY_EXECUTION_MAX_CONCURRENT, ACCESSFLOW_PROXY_EXECUTION_ACQUIRE_TIMEOUT.

Load test

backend/load-tests/concurrency-budget.js (k6, manual — see backend/load-tests/README.md): saturates a MAX_CONCURRENT=4 backend via break-glass SELECT pg_sleep(2) with 20 VUs; thresholds assert 503s carry QUERY_CONCURRENCY_LIMIT, no other error statuses, and completed executions stay within the cap-implied throughput.

Docs & website

  • docs/05-backend.md — new "Result byte cap & concurrency budget (Add byte-size cap and concurrency budget to QueryExecutor #49)" section, config + exception tables; removed the stale "byte-size caps and concurrency budgets out of scope" note.
  • docs/04-api-spec.mdtruncated_reason on results-page + sample-rows payloads; QUERY_CONCURRENCY_LIMIT in the error-code table.
  • docs/03-data-model.md — documented query_request_results (was missing) incl. the new column.
  • docs/09-deployment.md + CLAUDE.md — 3 new env-var rows.
  • website/docs/index.html — new "Heap protection" env-var group.
  • README.md / website/index.html — unchanged (operator-internal tuning; no pitch/feature/quick-start impact).

e2e

No spec changes: the new field is additive and nullable, no route/selector/flow changed, and the default (ROW_LIMIT) truncation message text is untouched. No new user-facing flow (a 503 under saturation isn't user-drivable in e2e).

Verification

  • mvn verify -Pcoverage green (incl. ApplicationModulesTest, ApiPackageDependencyTest, MessagesParityTest).
  • New tests: ResultByteEstimatorTest, ConcurrencyLimitingQueryExecutorTest, byte/row-cap cases in JdbcResultRowMapperTest, 503 mapping in GlobalExceptionHandlerTest, reason round-trip in SelectResultCacheTest, plus record-evolution coverage in SelectExecutionResultTest / ProxyPoolPropertiesTest / DefaultQueryResultPersistenceServiceTest.
  • Frontend: lint / typecheck / test:coverage (91.8% stmts, 83.8% branches) / build green; new QueryResultsTable.test.tsx + BYTE_LIMIT case in SampleDataPreview.test.tsx.

@github-actions

Copy link
Copy Markdown
Contributor

Frontend Test Results

1 246 tests   1 246 ✅  3m 16s ⏱️
  165 suites      0 💤
    1 files        0 ❌

Results for commit 8046ff8.

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Frontend Coverage (frontend)

Status Category Percentage Covered / Total
🟢 Lines 93.83% (🎯 90%) 2055 / 2190
🟢 Statements 91.79% (🎯 90%) 2293 / 2498
🟢 Functions 90.88% (🎯 90%) 628 / 691
🟢 Branches 83.82% (🎯 80%) 1296 / 1546
File CoverageNo changed files found.
Generated in workflow #783 for commit 8046ff8 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Backend Test Results

4 969 tests   4 969 ✅  16m 59s ⏱️
  646 suites      0 💤
  646 files        0 ❌

Results for commit 8046ff8.

@github-actions

Copy link
Copy Markdown
Contributor

@babltiga
babltiga merged commit fc3b455 into main Jul 17, 2026
28 checks passed
@babltiga
babltiga deleted the feature/AF-49-byte-cap-concurrency-budget branch July 17, 2026 06:47
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.

Add byte-size cap and concurrency budget to QueryExecutor

1 participant