Skip to main content

Signal flow

The ingester is stateless: auth key → {orgId, repositoryId} resolution, normalization, fingerprinting, ClickHouse writes, optional forward. Anything stateful (issue lifecycle, incidents, correlation, symbolication) belongs to the consumer of the sink webhook (Autter cloud, or your own backend).

Tenancy & key scopes

Every ClickHouse row is keyed by (org_id, repository_id) and every query must filter on both. One repository is one unit of analysis; the ingest key carries the mapping, so a key is scoped to exactly one repo. Two key scopes separate frontend and backend credentials:
  • Server keys (autter_rt_…, secret) — backends only, for OTLP endpoints and as the relay’s forwarding key. Sent as a bearer header.
  • Client keys (autter_rtc_…, publishable) — shipped in frontend bundles for direct browser ingest. Restricted to /v1/browser, enforced against a per-key origin allow-list, tighter rate limits, and carried as a ?key= query param because sendBeacon cannot set headers. A leaked client key can at worst submit fake browser events for one repo — it can never read data or send OTLP.

ClickHouse tables

runtime_metrics_1m is pre-aggregated per minute; readers must SUM(...) GROUP BY because SummingMergeTree collapses rows at merge time, eventually. Percentiles come from sampled spans at query time — the rollup table stores only counts and duration sums.

Occurrences are aggregation-ready at write time

runtime_error_occurrences holds errors and warnings/info (severity column: fatal | error | warning | info) — one dataset, sliceable by severity, rather than separate pipelines. Alongside the raw fields, the ingester stores derived columns computed by the same normalizers the fingerprint hashes, so aggregations never re-parse stacks or routes in SQL: Severity is deliberately not part of the fingerprint: the same defect reported as a warning in one code path and an error in another stays one group. Retention philosophy: raw signal is short-lived; anything worth keeping long-term (issue summaries, incident history, learnings) is derived and stored by the sink consumer.
Schema evolution: the ingester migrates the database itself at boot — baseline CREATE IF NOT EXISTS for fresh databases plus versioned, idempotent migrations tracked in <db>.schema_migrations for existing ones. Deploying a new ingester version is the schema deployment. Readers (dashboards, the Autter backend) should treat columns as additive-only within a major version.

Fingerprinting

sha256(source + service + error_type + normalised_message + top_5_frames + normalised_route), truncated to 32 hex chars.
  • Message normalization: quoted strings → <str>, UUIDs → <uuid>, long hex → <hex>, numbers → <n>.
  • Frame normalization: query strings and line/column offsets stripped — minified bundle offsets shift every deploy; file + function name are stable.
  • Route normalization: id-like path segments → :id (/orders/812/orders/:id).
The same algorithm runs in the Autter backend so browser-relay and OTLP occurrences group identically.

OTLP mapping (traces)

Resource attributes: Span-level:
  • Error occurrence emitted when span status is ERROR, or per exception event (exception.type, exception.message, exception.stacktrace).
  • route from http.route, falling back to url.path / http.target (query-stripped).
  • status_code from http.response.status_code / http.status_code.
  • Server spans aggregate into 1-minute usage rollups: request_count, error_count (status ≥ 500 or span error), duration_sum_ms.

Browser payload (v1)

Event types: exception, unhandled_rejection, session_start, and track_event (carries a name; counted into runtime_metrics_1m as request_count on the synthetic route event:<name> — coarse usage counters, not an analytics event store). Forbidden at the schema level (rejected/stripped): full URLs with query strings, cookies, DOM content, form values, request headers/bodies, emails.

Sink webhook (v1)

When AUTTER_SINK_URL is set, each ingest batch POSTs:
Delivery is best-effort fire-and-forget (the ingester is not a queue); the consumer should treat ClickHouse as the recovery source for missed batches.