Skip to content

fix(server): bound request/statement time and raise ALB idle timeout - #87

Open
saturninoabril wants to merge 3 commits into
mainfrom
cursor/server-request-guardrails-a8f4
Open

fix(server): bound request/statement time and raise ALB idle timeout#87
saturninoabril wants to merge 3 commits into
mainfrom
cursor/server-request-guardrails-a8f4

Conversation

@saturninoabril

Copy link
Copy Markdown
Collaborator

Summary

First PR in the sequenced 504-mitigation work. Adds request-path guardrails so a slow query cannot hold a pgx pool connection until the load balancer 504s the request — the pool-exhaustion cascade behind the observed timeouts (heavy uncached reads competing with ingest/checkout for a 20-connection pool, no server-side time bound, ALB idle timeout at the AWS 60s default).

Changes:

  • DB pool (internal/db/pool.go): variadic PoolOptions — WithStatementTimeout sets a per-statement statement_timeout (default 30s via TSIO_DB_STATEMENT_TIMEOUT_MS) so a slow statement is aborted server-side and its connection returns to the pool; WithMaxConns makes pool size configurable (TSIO_DB_MAX_CONNS, default 20). Per-statement, so background multi-statement work (JSON extractor) is unaffected.
  • Public read routes (internal/server/server.go): report + orchestration-status GETs are grouped under a per-request chi timeout (default 30s via TSIO_READ_REQUEST_TIMEOUT), cancelling the request context so the in-flight query aborts. WebSocket (/ws) and write/upload routes are intentionally exempt.
  • ALB (infra/lib/constructs/networking.ts): explicit idleTimeout of 120s (above the 60s default) so a slow-but-completing request isn't guillotined while still holding a backend connection; the ~30s app-level bounds are the real limit.

No response-shape or API-contract changes. All knobs default to safe values (behaviour unchanged unless configured), except the new statement/read timeouts and ALB idle timeout, which only bound pathological cases.

Test Plan

  • Go: go build ./..., go vet, full non-e2e suite (15 packages) green; new unit tests for pool options and config defaults/overrides.
  • Live verification against dev Postgres 18.3: with a 500ms statement timeout, SELECT pg_sleep(3) aborted at ~500ms with SQLSTATE 57014 and the connection was reusable afterward; baseline (no timeout) pg_sleep(1) succeeded.
  • Read-timeout wiring verified against a real DB: a slow read route returned within ~1s of a 1s timeout (connection freed), while an ungrouped /ws-style route was unaffected.
  • Infra: tsc --noEmit, oxlint, and vitest (15 tests incl. new idle_timeout.timeout_seconds=120 assertion) all pass.

Release Note

NONE
Open in Web Open in Cursor 

cursoragent and others added 2 commits July 24, 2026 09:02
Add request-path guardrails so a slow query can't hold a pool connection
until the load balancer 504s the request (the observed pool-exhaustion
cascade under polling load):

- DB pool: per-statement timeout (TSIO_DB_STATEMENT_TIMEOUT_MS, default
  30s) and configurable MaxConns (TSIO_DB_MAX_CONNS) via variadic pool
  options; a slow statement is aborted server-side and the connection
  returns to the pool.
- Public read (GET) routes: per-request timeout (TSIO_READ_REQUEST_TIMEOUT,
  default 30s) via chi middleware, cancelling the request context so the
  in-flight query aborts. WebSocket and write/upload routes are exempt.
- ALB: explicit idleTimeout of 120s (above the AWS 60s default) so a
  slow-but-completing request isn't guillotined while holding a backend
  connection; the app-level ~30s bounds are the real limit.

Co-authored-by: saturnino <saturnino@mattermost.com>
Co-authored-by: saturnino <saturnino@mattermost.com>
@saturninoabril
saturninoabril marked this pull request as ready for review July 24, 2026 09:38
@cursor

cursor Bot commented Jul 24, 2026

Copy link
Copy Markdown

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 90bc6c58-333c-43cf-a926-9dae6fa83696

📥 Commits

Reviewing files that changed from the base of the PR and between f70cefc and 01ee479.

📒 Files selected for processing (3)
  • apps/server/internal/api/ws/events.go
  • apps/server/internal/db/pool.go
  • apps/server/internal/db/pool_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/server/internal/db/pool.go

📝 Walkthrough

Walkthrough

The change adds environment-configurable PostgreSQL pool and statement limits, public read request timeouts, WebSocket keepalive pings, and a 120-second ALB idle timeout. These settings are loaded, tested, wired into runtime components, and validated in infrastructure tests.

Changes

Timeout and database guardrails

Layer / File(s) Summary
Configuration and runtime wiring
.env.example, apps/server/internal/config/config.go, apps/server/internal/config/config_test.go, apps/server/cmd/tsio/main.go
Adds database pool, statement, and public read timeout settings with defaults and override tests, then passes them into pool and server construction.
Database pool option application
apps/server/internal/db/pool.go, apps/server/internal/db/pool_test.go
Adds configurable pool options for maximum connections and PostgreSQL statement timeout, including handling for non-positive values and unit tests.
Public read request timeout
apps/server/internal/server/server.go
Applies an optional timeout to public report and orchestration-read routes while keeping WebSocket, write, and upload routes outside the timed group.
WebSocket keepalive
apps/server/internal/api/ws/events.go
Sends periodic WebSocket pings and cancels the connection context when a ping fails.
ALB idle timeout configuration
infra/lib/constructs/networking.ts, infra/test/app_stack.test.ts
Sets the ALB idle timeout to 120 seconds and asserts the generated load-balancer attribute.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

Public read routing

sequenceDiagram
  participant Client
  participant HTTPServer
  participant ReadTimeout
  participant PublicReadHandlers
  Client->>HTTPServer: request public API route
  HTTPServer->>ReadTimeout: apply configured timeout
  ReadTimeout->>PublicReadHandlers: dispatch report or orchestration read
  PublicReadHandlers-->>Client: return read response
Loading

WebSocket keepalive

sequenceDiagram
  participant WebSocketClient
  participant serve
  participant WebSocketConnection
  participant ConnectionContext
  serve->>WebSocketConnection: start periodic Ping calls
  WebSocketConnection-->>WebSocketClient: send ping
  WebSocketConnection->>ConnectionContext: cancel on ping failure
  ConnectionContext-->>serve: stop forwarding and close connection
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: request/statement timeouts and a higher ALB idle timeout.
Description check ✅ Passed The description is clearly related and matches the database, request, WebSocket, and ALB timeout changes in the diff.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/server-request-guardrails-a8f4

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/internal/db/pool.go (1)

50-57: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Keep MinConns at or below MaxConns. NewPool hard-codes MinConns = 2, but WithMaxConns(1) is still allowed, which leaves the pool configured with a floor above its cap. Clamp MinConns after applying options or reject smaller max values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/server/internal/db/pool.go` around lines 50 - 57, Update NewPool so the
final configuration always keeps MinConns at or below MaxConns after all opts,
including WithMaxConns, are applied. Clamp MinConns to MaxConns or reject
invalid smaller max values, while preserving the existing defaults for valid
configurations.

Source: MCP tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/server/internal/db/pool.go`:
- Around line 35-40: Update WithStatementTimeout so a zero ms value removes the
existing "statement_timeout" entry from cfg.ConnConfig.RuntimeParams, while
positive values continue setting it via strconv.Itoa. Preserve the current
behavior for positive timeouts.

In `@infra/lib/constructs/networking.ts`:
- Around line 133-136: Update the WebSocket handling associated with the
networking construct’s idleTimeout configuration to keep quiet sessions alive
beyond 120 seconds by adding periodic ping/pong heartbeat traffic, or raise
idleTimeout to the required session duration if heartbeats are not supported.
Preserve the existing request timeout behavior for regular backend connections.

---

Outside diff comments:
In `@apps/server/internal/db/pool.go`:
- Around line 50-57: Update NewPool so the final configuration always keeps
MinConns at or below MaxConns after all opts, including WithMaxConns, are
applied. Clamp MinConns to MaxConns or reject invalid smaller max values, while
preserving the existing defaults for valid configurations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9886953a-aec0-4526-b6b1-9aee1a84911e

📥 Commits

Reviewing files that changed from the base of the PR and between cc3234f and f70cefc.

📒 Files selected for processing (9)
  • .env.example
  • apps/server/cmd/tsio/main.go
  • apps/server/internal/config/config.go
  • apps/server/internal/config/config_test.go
  • apps/server/internal/db/pool.go
  • apps/server/internal/db/pool_test.go
  • apps/server/internal/server/server.go
  • infra/lib/constructs/networking.ts
  • infra/test/app_stack.test.ts

Comment thread apps/server/internal/db/pool.go
Comment thread infra/lib/constructs/networking.ts
… keepalive

- WithStatementTimeout(0) now sets statement_timeout=0 (explicit disable),
  overriding any value inherited from the connection string, so the
  documented '0 disables' contract holds; negative leaves the default.
- Add a 30s WebSocket server ping so quiet subscriptions aren't dropped by
  the 120s ALB idle timeout before the next event arrives (coder/websocket
  Ping is concurrency-safe; a failed ping tears the connection down).

Co-authored-by: saturnino <saturnino@mattermost.com>
@saturninoabril
saturninoabril requested review from yasserfaraazkhan and removed request for yasserfaraazkhan July 24, 2026 13:42
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.

2 participants