Observability

Distributed traces, N+1 detection, slow query alerts, and automatic index suggestions — all built in, zero configuration.

Overview

Every request flowing through Flux is automatically instrumented. You don't add libraries or configure exporters. Just deploy and observe.

Zero-config observability
All traces, spans, and diagnostics are captured automatically. No SDK calls, no OpenTelemetry setup.

Distributed traces

Every request gets a unique x-request-id that is propagated across the gateway, runtime, and data-engine. All spans for a request are correlated under this ID.

View a trace in your terminal:

$ flux trace <trace-id>

  Trace 4f9a3b2c   2026-03-10 14:22:01 UTC

  ▸ function:create_todo           312 ms
    ▸ db:query(todos)               28 ms  [MISS]
      table: todos
      op:    insert
    ▸ db:query(todos)               15 ms  [HIT]  ← cache

  ─── Slow Queries ────────────────────────────────
    todos.user_id   284 ms

  ─── Index Suggestions ───────────────────────────
    → todos.user_id   run: CREATE INDEX ON todos(user_id);

Trace IDs appear in:

  • x-request-id response header on every API call
  • flux logs output
  • The Flux dashboard under Logs → Traces

Span types

Span kindWhat it tracks
functionTotal function execution time
db:queryIndividual database queries — table, operation, duration, cache status
gatewayAuth, routing, rate-limit checks

N+1 query detection

Flux automatically detects N+1 query patterns — when the same table is queried inside a loop, turning one request into dozens of database round-trips.

Example warning in a trace response:

{
  "n1_warnings": [
    {
      "table": "users",
      "count": 47,
      "message": "N+1 detected: 'users' queried 47 times in one request"
    }
  ]
}

Fix example — batch with a single IN filter instead:

// ✗ N+1 — one query per item
for (const item of items) {
  const [user] = await ctx.db.query({
    table: "users", operation: "select",
    filters: [{ column: "id", op: "eq", value: item.user_id }],
  });
}

// ✔ Batched — one query for all items
const users = await ctx.db.query({
  table: "users", operation: "select",
  filters: [{ column: "id", op: "in", value: items.map(i => i.user_id) }],
});

Slow query detection

Any database query that takes longer than 100 ms is flagged as slow. The trace shows which table and operation was slow, helping you prioritize optimization.

Automatic index suggestions

When slow queries repeatedly filter on the same column, Flux generates a CREATE INDEX suggestion:

{
  "suggested_indexes": [
    {
      "table": "todos",
      "column": "user_id",
      "ddl": "CREATE INDEX ON todos(user_id);"
    }
  ]
}

Apply it:

-- Run against your project's database
CREATE INDEX ON todos(user_id);
How it works
The API aggregates all slow spans (>100 ms) for a trace. When the same (table, column) pair appears 2 or more times in slow filter positions, a CREATE INDEX DDL statement is generated and returned alongside the trace response.

Logs

All ctx.log() calls emit structured JSON logs correlated with the trace ID.

$ flux logs --tail
$ flux logs --function create_todo --since 1h --limit 100

Dashboard

All observability data is also visible in the Flux dashboard at /dashboard under Logs and Traces.


← Database Next: CLI Reference →