* refactor(operator): route shared requests through NodeManager Remove the concrete NodeClients requester API and use the existing node manager for owned and shared requests. Preserve the original write hint while the legacy error adapter remains in place. Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(meta): preserve retry policy through error hints Propagate client errors through External and let DDL read retry hints while preserving cleanup and terminal-child policies. Region transport failures retain the legacy Retryable classification, which now also participates in bounded write retries. Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(operator): restore sparse partition tags when rebuilding writes Decode retained primary keys when refreshed partition expressions need different tags. Keep the partitioner and multi-region sparse write regression tests with the fix. Signed-off-by: WenyXu <wenymedia@gmail.com> * fix(operator): stop detached write retries when callers cancel Signed-off-by: WenyXu <wenymedia@gmail.com> * feat(frontend): configure retries across distributed write paths Wire the shared dispatcher through frontend and logical batching, preserving partition columns and request headers. Keep standalone retries disabled and include configuration, batching, and storage regression coverage. Signed-off-by: WenyXu <wenymedia@gmail.com> --------- Signed-off-by: WenyXu <wenymedia@gmail.com>
6.9 KiB
frontend — Agent & Contributor Guide
Navigation aid for src/frontend. Keep it short and point to code. Paths are
relative to the repo root.
Repo-wide rules that apply here: .agents/architecture-invariants.md.
What this crate does
Frontend is the request entry point and orchestration layer. It accepts
multi-protocol requests (gRPC, HTTP, MySQL, PostgreSQL, InfluxDB, OTLP, Jaeger,
Prometheus, OpenTSDB), checks permissions, parses/plans SQL, and dispatches:
reads go to the query engine (query crate), writes go to the inserter/deleter
(operator crate).
Boundary with servers: the servers crate implements the wire protocols and
network I/O; frontend provides the business logic by implementing handler
traits (SqlQueryHandler, GrpcQueryHandler, InfluxdbLineProtocolHandler,
...). In standalone mode the standalone crate bridges the frontend to an
embedded datanode RegionServer; in distributed mode the frontend talks to
remote datanodes via operator/client.
Module map
| Module | Path | Purpose |
|---|---|---|
instance |
src/frontend/src/instance.rs |
Instance: the core handler; implements SqlQueryHandler, PrometheusHandler, etc. |
instance/builder |
src/frontend/src/instance/builder.rs |
FrontendBuilder assembles Instance from its dependencies |
instance/grpc |
src/frontend/src/instance/grpc.rs |
GrpcQueryHandler: insert/delete/query/promql over gRPC |
instance/region_query |
src/frontend/src/instance/region_query.rs |
Routes distributed region reads to datanodes |
instance/* |
src/frontend/src/instance/ |
Per-protocol handlers (influxdb.rs, promql.rs, otlp/, jaeger.rs, logs.rs, prom_store.rs, ...) |
frontend |
src/frontend/src/frontend.rs |
Frontend lifecycle wrapper (FrontendOptions, start/shutdown) |
server |
src/frontend/src/server.rs |
Services: builds and wires the protocol servers |
heartbeat |
src/frontend/src/heartbeat.rs |
Heartbeat to metasrv; handles suspend / cache invalidation |
service_config |
src/frontend/src/service_config/ |
Per-protocol option structs |
Shared frontend cache counters live in src/common/frontend/src/metrics.rs,
including caches owned by pipeline and protocol handlers.
Request lifecycles
-
SQL query (
instance.rs):do_query_innerhandles parsing, interceptors, permission checks, timeout/cancellation, and delegates planning/execution toStatementExecutor. Distributed scans enter throughregion_query.rs. -
Logical-table batch DDL (
instance.rs): dedicated SQL handler → bounded parse/interception → all-member permissions → operator CREATE preparation → one logical-table batch procedure. Keep pre-submission validation side-effect free. -
Insert (
instance/grpc.rs):handle_inserts/handle_row_inserts→check_permission→operator'sInserter(schema validation, optional auto-create, partition routing, meter admission) → localRegionServer(standalone) or RPC to datanodes (distributed).FrontendBuilderconfigures the operator write dispatcher with frontend retry options for distributed inserts, deletes, and bulk writes. Arrow bulk inserts pass the request channel toInserterand check meter admission for each nonempty batch. -
Finite ingestion requests split internally admit their total rows per database before dispatch (
operator::insert::admit_write/admit_row_insert_batches). The returned context covers chunks and derived writes while preserving WCU accounting and the original protocol channel. -
Internal gRPC listeners mark requests with
Channel::Internalin middleware (server.rs), including requests handled by Enterprise Flight wrappers. -
Flight bulk insert (
instance/grpc.rs): initializes on the first batch after the lazy schema handshake; checks permissions and reconciles missing columns throughInserteronce per stream, then reuses the refreshed table. -
OTLP trace lookup tables (
instance/otlp/trace_ingest.rs):Instanceowns a shared cache of confirmed service/operation writes, which bypass batching.otlp.trace_aux_cache_sizesets its estimated byte budget at startup; zero disables it. Async main writes retain completion results; a frontend task writes auxiliary rows only for confirmed chunks before populating the cache. Cold async trace requests reserve a shared auxiliary slot before main writes, bounded separately by the table batcher'smax_inflight_requests; confirmed cache hits bypass this admission. The slot lasts through auxiliary completion. These tables are ingestion-managed; manual mutation requires restarting serving frontends. -
Logical-table batching (
instance/logical_batcher.rs):Servicesinitializes one shared batcher for opted-in HTTP Prom and nonlegacy OTLP metric-engine writes. Downstream routers can initialize it when enabling replacement endpoints.Instance::handle_otlp_metric_row_insertsshares OTLP eligibility and dispatch between converters and falls back for incompatible tables. The schema adapter holds a weak instance reference to avoid an ownership cycle. -
Table batching (
instance/builder.rs): protocol entry points opt in throughQueryContext. The primary inserter prepares eligible ordinary-table writes forservers::batcher::table::TablePendingRowsBatcher. A separate execution-only inserter, with no batcher attached, sends the prepared bulk writes to datanodes, avoiding recursive batching. The batcher handles successful-write Flow notifications.
Public surface
Instance(instance.rs) — the business-logic container.Frontend(frontend.rs) — lifecycle wrapper aroundInstance+ servers + heartbeat.- Created from
cmd:src/cmd/src/frontend.rs(distributed) andsrc/cmd/src/standalone.rs(standalone, with embedded datanode).
When you change X, also touch Y
servershandler traits: a new/changed protocol handler requires the matchingimplhere.operatorInserter/Deleter orqueryQueryEngine API: update the call sites ininstance.rs/instance/grpc.rs.session::QueryContext: new context fields thread through most handlers.sqlstatements: new statement kinds need handling inquery_statement.
Testing
cargo nextest run -p frontend
Gotchas
- Keep the frontend/servers split straight: wire format and network live in
servers; permissions, planning, and routing live here. - Standalone vs distributed diverge in datanode access (the
standalonecrate's localRegionServeradapter vsNodeClientsRPC), MetaClient usage, and whether heartbeat matters. The builder defaults local invalidation to its layered cache registry; callers may supply a custom invalidator.
Maintenance contract
Update this file when you add a protocol handler, change the query/insert
lifecycle, or change how Instance is constructed or wired to servers.