Die Kunden erwarten Analytik innerhalb Ihres Produkts, nicht in einem separaten BI-Tool. Innerhalb Ihrer App, schnell ladend, und nur ihre Daten anzeigen. ClickHouse ist der Motor, auf den die meisten Teams zurückgreifen, wenn sie dies im großen Maßstab benötigen. PostHog, LaunchDarkly und Inigo betreiben alle kundenorientierte Analytik mit ClickHouse.
Was sie alle entdeckt haben: Der schwierige Teil ist nicht die Abfrageleistung, sondern die Mandantenisolierung. Viele Online-Ratschläge enthalten falsche Details.
Der richtige Ansatz
Für SaaS mit hunderten oder tausenden von Mandanten sollten Sie eine gemeinsame Ereignistabelle verwenden. Eine Tabelle, alle Mandanten, isoliert durch eine Zeilenrichtlinie. Dies ist das, was jedes Team im großen Maßstab verwendet.
Die wichtigste Entscheidung ist, wo die tenant_id in der Sortierungsregel steht:
CREATE TABLE events (
tenant_id UInt32,
event_id UUID,
event_time DateTime64(3),
user_id UInt64,
event_type LowCardinality(String),
properties Map(String, String)
) ENGINE = MergeTree() PARTITION BY toYYYYMM(event_time) ORDER BY (tenant_id, event_time, event_id);tenant_id sollte an erster Stelle in ORDER BY stehen. ClickHouses Sparse-Primärschlüssel basiert auf der Sortierungsregel. Wenn der Mandant zuerst kommt, überspringen Mandantenscans andere Mandantengranula完全.
Zeilensicherheit: Das Muster, das wirklich skaliert
Es gibt zwei Ansätze. Nur einer funktioniert für echte SaaS.
Der naive Ansatz erstellt einen ClickHouse-Benutzer pro Mandanten mit einer pro-Mandanten-Zeilenrichtlinie. Einfach zu verstehen. Funktioniert jedoch sofort in der Produktion, da es das Verbindungspooling zerstört – Sie benötigen einen separaten Pool pro Mandanten oder Sie verbinden sich bei jeder Anfrage neu.
Der Produktionsansatz verwendet einen gemeinsamen Benutzer mit einer benutzerdefinierten pro-Abfrage-Einstellung:
-- Erlauben Sie der readonly-Rolle, eine benutzerdefinierte Mandanteneinstellung zu setzen
CREATE ROLE analytics_readonly;
ALTER ROLE analytics_readonly ADD SETTINGS SQL_tenant_id CHANGEABLE_IN_READONLY;
GRANT SELECT ON events TO analytics_readonly;
-- Eine Richtlinie liest die pro-Abfrage-Einstellung
CREATE ROW POLICY tenant_isolation ON events FOR SELECT USING tenant_id = getSetting('SQL_tenant_id')::UInt32 TO analytics_readonly;
-- Ein gemeinsamer Anwendungsbenutzer
CREATE USER app_analytics IDENTIFIED BY 'strong_password' SETTINGS readonly = 1;
GRANT analytics_readonly TO app_analytics;
SET DEFAULT ROLE analytics_readonly TO app_analytics;Ihre Anwendung übermittelt die Mandantenidentität pro Abfrage:
await client.query({
query: `SELECT event_type, count() AS total FROM events WHERE tenant_id = {tenantId:UInt32} AND event_time >= now() - INTERVAL 7 DAY GROUP BY event_type`,
query_params: { tenantId },
clickhouse_settings: { SQL_tenant_id: tenantId.toString(), quota_key: tenantId.toString(), },
});Drei Gründe, warum dies das richtige Modell ist:
- Fail-closed: Wenn Ihre App
SQL_tenant_idweglässt, wirft ClickHouse einen Fehler aus. Es gibt keinen stillen Pfad, auf dem fehlende Mandantenkontext alle Mandantendaten zurückgibt.
- Verbindungspooling funktioniert: Ein Pool, ein Benutzer, alle Mandanten. Die Mandantenidentität befindet sich auf Abfrageebene, nicht auf Verbindungsebene.
- Null DDL zum Hinzufügen eines Mandanten: Mandant 10.001 benötigt kein ClickHouse
CREATE USERoderCREATE POLICY– nur eine Zeile in der Mandantentabelle Ihrer Anwendung.
Ein kritischer Detail, den fast jedes Tutorial verpasst: CHANGEABLE_IN_READONLY ist erforderlich für die Rolle. Ohne sie können schreibgeschützte Benutzer die benutzerdefinierte Einstellung nicht setzen und jede Abfrage schlägt mit einem Berechtigungsfehler fehl. Fügen Sie auch WHERE tenant_id = ? explizit in Ihre Abfragen ein, auch wenn die Zeilenrichtlinie die Korrektheit garantiert – die Zeilenrichtlinie treibt die Primärschlüsselbeschneidung nicht von selbst.
Verbindung herstellen über TypeScript
Der vollständige Anfragepfad: JWT → Mandantenextrahieren → ClickHouse-Abfrageeinstellung → Zeilenrichtlinienschutz.
Wenn Sie viele Analyseendpunkte definieren, zahlt sich die Zentralisierung dieser Injektion schnell aus. hypequery API extrahiert den Mandanten einmal in einem context-Callback und übermittelt ihn an jede Abfrage durch ein typisiertes ctx-Objekt:
import { createQueryBuilder } from '@hypequery/clickhouse';
import { initServe } from '@hypequery/serve';
import { z } from 'zod';
import type { IntrospectedSchema } from './generated-schema';
const db = createQueryBuilder<IntrospectedSchema>({
host: process.env.CLICKHOUSE_HOST!,
username: 'app_analytics',
password: process.env.CLICKHOUSE_PASSWORD!,
});
const { query, serve } = initServe({
context: ({ req }) => {
const payload = verifyJWT(req.headers.get('authorization'));
return { db, tenantId: payload.tenantId };
},
});
const eventCounts = query({
description: 'Ereigniszählungen nach Typ für die letzten N Tage',
input: z.object({
days: z.number().default(7)
}),
query: ({ ctx, input }) => ctx.db
.table('events')
.select(['event_type'])
.count('event_id', 'total_events')
.where('tenant_id', 'eq', ctx.tenantId)
.where('event_time', 'gte', `now() - INTERVAL ${input.days} DAY`)
.groupBy(['event_type'])
.orderBy('total_events', 'DESC')
.settings({ SQL_tenant_id: ctx.tenantId })
.execute(),
});
export const api = serve({ queries: { eventCounts } });
## Mounten in Next.js App Router:
// app/api/analytics/[[...slug]]/route.ts
import { createFetchHandler } from '@hypequery/serve';
import { api } from '@/analytics/api';
const handler = createFetchHandler(api.handler);
export { handler as GET, handler as POST };Schnell machen
Korrektes und isoliertes ist die Grundlage. Sub-100ms ist das, was die Funktion wie ein Produkt fühlen lässt.
Aggregierte Projektionen sind die höchste Hebeloptimierung. Sie berechnen Aggregationen im Voraus innerhalb der gleichen Teile wie die Grundtabelle und ClickHouse
Im Laufe der Zeit können Unternehmen ihre Datenmengen und die Anzahl der Mandanten nur noch weiter steigern. Es ist wichtig, dass Sie Ihre Analyseplattform entsprechend anpassen, um die zunehmenden Anforderungen zu bewältigen. Durch die Implementierung von Multi-Tenant-Dashboards mit ClickHouse können Sie Ihre Kunden zufriedenstellen und Ihre Konkurrenzfähigkeit steigern.
KI-Zusammenfassung
Müşteriler ürününüzde analitik bekliyorlar. ClickHouse ile çok kiracılı gösterge panelleri oluşturun ve müşteri verilerini güvenli bir şekilde izole edin.