Users and roles control what a user can query. Row policies control which rows they see when they run that query, and quotas control how much of the cluster's resources they're allowed to consume doing it. Both exist to let one ClickHouse cluster safely serve multiple tenants or teams instead of needing a cluster each.
Row policies: filtering rows per user, transparently
A row policy attaches a filter condition to a table. Any query against that table from a matching user has the condition silently ANDed into its WHERE clause — the user never sees rows outside the filter, and doesn't need to remember to add the filter themselves.
CREATE ROW POLICY tenant_isolation ON analytics.events
FOR SELECT USING tenant_id = currentUser()
TO tenant_role;Here, a user's own name doubles as their tenant identifier via currentUser() — a common pattern when each tenant maps to a dedicated ClickHouse user. More typically the condition references a session setting or a mapping table rather than the username directly, but the mechanism is the same: the policy is enforced by ClickHouse itself, not by application code remembering to filter correctly on every query.
WHERE tenant_id = ? in the application" is that it can't be forgotten. One missed WHERE clause in one ad-hoc query or one new dashboard is a data leak; a row policy makes that class of bug structurally impossible for that user.Quotas: limiting how much a user can consume
A quota caps resource usage — queries, errors, rows read, execution time — over a rolling interval, per user or role. It doesn't make individual queries faster; it protects everyone else on a shared cluster from one runaway report, one misbehaving job, or one accidental SELECT * over a trillion-row table.
CREATE QUOTA dashboard_limits
FOR INTERVAL 1 HOUR
MAX QUERIES 1000,
MAX EXECUTION TIME 3600
TO analytics_reader;When a user tied to this quota exceeds it, further queries are rejected until the interval resets — a blunt but effective circuit breaker that requires no application-side rate limiting.
Network security, briefly
Two settings matter most day to day: listen_host controls which network interfaces the server accepts connections on (default configs often bind to all interfaces, which is fine inside a private Docker network but not on an open host), and tcp_port_secure / HTTPS enable TLS for the native and HTTP protocols respectively. In a replicated or sharded cluster, an interserver_http_credentials secret authenticates traffic between replicas and shards themselves, separate from any client-facing user — worth knowing exists, mostly a config-file concern rather than a deep concept.
listen_host stop being optional.