Skip to content

ClickHouse Schema Reference

All tables live in the gateway database. Schema is initialized by infra/clickhouse/init.sql on first startup.

ClickHouse is accessible at http://localhost:8123 (HTTP interface) and clickhouse:9000 (native protocol, used by Grafana).

Tables

gateway.request_log_raw

L1 request log storage table. Receives data from the Buffer table. This is where queries should read from for historical analysis.

sql
CREATE TABLE IF NOT EXISTS gateway.request_log_raw (
  timestamp DateTime64(3),
  tenant_id String,
  route_id String,
  method String,
  path String,
  status_code UInt16,
  latency_ms Float32,
  request_size UInt32,
  response_size UInt32,
  client_ip String,
  user_agent String,
  trace_id String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY (tenant_id, timestamp)
TTL toDateTime(timestamp) + INTERVAL 90 DAY;
ColumnTypeDescription
timestampDateTime64(3)Request timestamp (millisecond precision)
tenant_idStringTenant identifier
route_idStringAPISIX route ID
methodStringHTTP method (GET, POST, etc.)
pathStringRequest URI path
status_codeUInt16HTTP response status code
latency_msFloat32End-to-end latency in milliseconds
request_sizeUInt32Request body size in bytes
response_sizeUInt32Response body size in bytes
client_ipStringClient IP address
user_agentStringUser-Agent header
trace_idStringDistributed trace ID (from X-Request-ID)
  • Engine: MergeTree
  • Partition: toYYYYMM(timestamp) (monthly)
  • Order: (tenant_id, timestamp) -- tenant-first for efficient filtered queries
  • TTL: 90 days

gateway.request_log

Buffer table in front of request_log_raw for high-throughput async writes. The clickhouse-logger plugin writes to this table. Buffers are flushed to the underlying MergeTree based on the configured thresholds.

sql
CREATE TABLE IF NOT EXISTS gateway.request_log (
  timestamp DateTime64(3),
  tenant_id String,
  route_id String,
  method String,
  path String,
  status_code UInt16,
  latency_ms Float32,
  request_size UInt32,
  response_size UInt32,
  client_ip String,
  user_agent String,
  trace_id String
) ENGINE = Buffer(gateway, request_log_raw, 16, 10, 100, 10000, 100000, 1000000, 10000000);
  • Engine: Buffer (16 buffers, flush thresholds: 10-100s time, 10K-100K rows, 1M-10M bytes)
  • Target: gateway.request_log_raw

:::note The Buffer engine is required to handle 50K TPS write throughput. Never write directly to request_log_raw from the hot path. :::

gateway.ai_request_log

L2 AI Gateway request log. Tracks LLM provider calls, token usage, cache performance, and PII detection events.

sql
CREATE TABLE IF NOT EXISTS gateway.ai_request_log (
  timestamp DateTime64(3),
  tenant_id String,
  model String,
  provider String,
  prompt_tokens UInt32,
  completion_tokens UInt32,
  total_tokens UInt32,
  latency_ms Float32,
  cache_hit Bool,
  pii_detected Bool,
  trace_id String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY (tenant_id, timestamp)
TTL toDateTime(timestamp) + INTERVAL 365 DAY;
ColumnTypeDescription
timestampDateTime64(3)Request timestamp
tenant_idStringTenant identifier
modelStringLLM model name (e.g., gpt-4o, claude-sonnet)
providerStringLLM provider (e.g., openai, anthropic)
prompt_tokensUInt32Input token count
completion_tokensUInt32Output token count
total_tokensUInt32Total tokens consumed
latency_msFloat32Provider response latency
cache_hitBoolWhether the response came from cache
pii_detectedBoolWhether PII was detected in the request
trace_idStringDistributed trace ID
  • Engine: MergeTree
  • Partition: toYYYYMM(timestamp) (monthly)
  • Order: (tenant_id, timestamp)
  • TTL: 365 days

gateway.agent_audit_log

L3 Agent Gateway audit log. Records all agent tool calls, A2A hops, and session actions. No TTL -- retained indefinitely for compliance.

sql
CREATE TABLE IF NOT EXISTS gateway.agent_audit_log (
  timestamp DateTime64(3),
  tenant_id String,
  session_id String,
  agent_id String,
  tool_name String,
  action String,
  status String,
  input_hash String,
  output_hash String,
  trace_id String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY (tenant_id, timestamp);
ColumnTypeDescription
timestampDateTime64(3)Event timestamp
tenant_idStringTenant identifier
session_idStringAgent session ID
agent_idStringAgent identifier
tool_nameStringMCP tool name invoked
actionStringAction type (e.g., tool_call, a2a_send)
statusStringOutcome (e.g., success, denied, error)
input_hashStringSHA-256 hash of tool input (content not stored)
output_hashStringSHA-256 hash of tool output (content not stored)
trace_idStringDistributed trace ID
  • Engine: MergeTree
  • Partition: toYYYYMM(timestamp) (monthly)
  • Order: (tenant_id, timestamp)
  • TTL: None (permanent retention for audit compliance)

DANGER

Never add a TTL to the agent audit log. Audit records must be retained indefinitely per security policy.

Materialized Views

gateway.request_rate_per_minute

Pre-aggregated request rates per tenant per minute. Used by dashboards to avoid expensive query-time aggregation.

sql
CREATE MATERIALIZED VIEW IF NOT EXISTS gateway.request_rate_per_minute
ENGINE = SummingMergeTree()
PARTITION BY toYYYYMM(minute)
ORDER BY (tenant_id, minute)
AS SELECT
  tenant_id,
  toStartOfMinute(timestamp) AS minute,
  count() AS request_count,
  countIf(status_code >= 400) AS error_count,
  avg(latency_ms) AS avg_latency_ms,
  quantile(0.99)(latency_ms) AS p99_latency_ms
FROM gateway.request_log_raw
GROUP BY tenant_id, minute;
ColumnTypeDescription
tenant_idStringTenant identifier
minuteDateTimeStart of the minute bucket
request_countUInt64Total requests in this minute
error_countUInt64Requests with status >= 400
avg_latency_msFloat64Average latency
p99_latency_msFloat6499th percentile latency
  • Engine: SummingMergeTree
  • Source: gateway.request_log_raw

gateway.ai_token_usage_hourly

Pre-aggregated AI token usage per tenant per hour, broken down by model and provider. Powers the AI usage dashboards.

sql
CREATE MATERIALIZED VIEW IF NOT EXISTS gateway.ai_token_usage_hourly
ENGINE = SummingMergeTree()
PARTITION BY toYYYYMM(hour)
ORDER BY (tenant_id, hour)
AS SELECT
  tenant_id,
  toStartOfHour(timestamp) AS hour,
  model,
  provider,
  sum(prompt_tokens) AS total_prompt_tokens,
  sum(completion_tokens) AS total_completion_tokens,
  sum(total_tokens) AS total_tokens,
  count() AS request_count,
  countIf(cache_hit = true) AS cache_hits
FROM gateway.ai_request_log
GROUP BY tenant_id, hour, model, provider;
ColumnTypeDescription
tenant_idStringTenant identifier
hourDateTimeStart of the hour bucket
modelStringLLM model name
providerStringLLM provider
total_prompt_tokensUInt64Sum of prompt tokens
total_completion_tokensUInt64Sum of completion tokens
total_tokensUInt64Sum of total tokens
request_countUInt64Number of requests
cache_hitsUInt64Number of cache hits
  • Engine: SummingMergeTree
  • Source: gateway.ai_request_log

Query Examples

Request volume per tenant (last 24h)

sql
SELECT
  tenant_id,
  sum(request_count) AS total_requests,
  sum(error_count) AS total_errors,
  avg(avg_latency_ms) AS avg_latency,
  max(p99_latency_ms) AS max_p99
FROM gateway.request_rate_per_minute
WHERE minute >= now() - INTERVAL 24 HOUR
GROUP BY tenant_id
ORDER BY total_requests DESC;

Token usage by model (last 7 days)

sql
SELECT
  model,
  provider,
  sum(total_tokens) AS tokens,
  sum(request_count) AS requests,
  sum(cache_hits) AS cache_hits,
  round(sum(cache_hits) / sum(request_count) * 100, 1) AS cache_hit_rate_pct
FROM gateway.ai_token_usage_hourly
WHERE hour >= now() - INTERVAL 7 DAY
GROUP BY model, provider
ORDER BY tokens DESC;

Error rate spike detection for a tenant

sql
SELECT
  minute,
  request_count,
  error_count,
  round(error_count / request_count * 100, 2) AS error_rate_pct,
  p99_latency_ms
FROM gateway.request_rate_per_minute
WHERE tenant_id = 'tenant-alpha'
  AND minute >= now() - INTERVAL 1 HOUR
ORDER BY minute;

Agent audit trail for a session

sql
SELECT
  timestamp,
  tool_name,
  action,
  status,
  agent_id
FROM gateway.agent_audit_log
WHERE session_id = 'your-session-id'
ORDER BY timestamp;

Top consumers by token usage (current month)

sql
SELECT
  tenant_id,
  sum(total_tokens) AS total_tokens,
  sum(total_prompt_tokens) AS prompt_tokens,
  sum(total_completion_tokens) AS completion_tokens,
  sum(request_count) AS requests
FROM gateway.ai_token_usage_hourly
WHERE hour >= toStartOfMonth(now())
GROUP BY tenant_id
ORDER BY total_tokens DESC
LIMIT 20;

Schema Rules

WARNING

  • Always use ALTER TABLE ... ADD COLUMN for schema evolution -- never recreate tables.
  • New columns must have defaults.
  • Never use SELECT * -- always name columns explicitly.
  • Tenant isolation is enforced via row-level WHERE tenant_id = ... filters, not separate tables.

Enterprise API + AI + Agent Gateway