Eigene Analytics in 200 Zeilen Rust
Warum ich PostHog ersetzt habe
- analytics
- gdpr
- privacy
- rust
Beim Umbau meiner Website habe ich PostHog rausgeworfen und eigene Analytics gebaut. Nicht weil PostHog schlecht ist — es ist exzellent — sondern weil manchmal die einfache Lösung die richtige ist.
Warum PostHog ersetzen?
Datenhoheit: Selbst mit PostHog EU lagen meine Daten auf Drittanbieter-Servern. Alles auf dem eigenen deutschen VPS macht DSGVO-Compliance deutlich einfacher. Keine Auftragsverarbeitungsverträge, keine Sub-Prozessoren.
Kosten: PostHog’s Free-Tier ist großzügig, kann aber schnell kippen. Meine Lösung kostet exakt 0 EUR zusätzlich zum bestehenden VPS.
Integration: Ich wollte enge Verzahnung zwischen Analytics und meinem Signatur-Feature. Wer hat was gezeichnet, wie lange, was war die Journey? Mit eigenen Analytics sind solche Joins trivial.
Overkill: PostHog hat Feature Flags, Session Replay, A/B Testing, Funnels und dutzende weitere Features. Für eine persönliche Website brauche ich Page Views und Custom Events. Mehr nicht.
Was ich tracke
Das Tracking ist bewusst minimal:
Page Views
- URL und Referrer (nur extern — keine interne Navigation)
- Scroll-Tiefe
- Timestamp
Custom Events
- Signatur-Editor: Öffnen, Speichern, Undo, Redo, Radierer
- Karussell: Navigation, Autoplay-Status
- Blog: Tag-Klicks, Verweildauer
- Kontaktformular: Absendungen
Sessions
- Anonyme Session-ID (clientseitig generiert)
- Land und Region (GeoIP-Lookup)
- Erste/letzte Aktivität
Die Implementierung
Das gesamte Backend-Analytics besteht aus ca. 200 Zeilen Rust:
// Event-Ingestion-Endpoint
pub async fn track_event(
State(state): State<Arc<AppState>>,
Json(req): Json<TrackEventRequest>,
) -> Result<Json<()>, AppError> {
let now = chrono::Utc::now().timestamp();
sqlx::query(
"INSERT INTO events (id, session_id, event, properties, ts)
VALUES (?, ?, ?, ?, ?)"
)
.bind(uuid::Uuid::new_v4().to_string())
.bind(&req.session_id)
.bind(&req.event)
.bind(&req.properties)
.bind(now)
.execute(&state.db)
.await?;
Ok(Json(()))
}
Client-seitig sind es ca. 50 Zeilen TypeScript, die Events an /api/tracking/events mit Session-ID und Event-Daten senden.
Keine Cookies. Die Session-ID wird einmalig mit crypto.randomUUID() erzeugt und in sessionStorage gespeichert. Tab schließen, neue Session. Fertig.
DSGVO-Vorteile
Dieses Setup macht DSGVO-Compliance trivial:
- Alle Daten auf dem eigenen deutschen VPS — keine Drittanbieter-Transfers
- Keine Datenweitergabe — ich bin gleichzeitig Verantwortlicher und Auftragsverarbeiter
- Anonym by Design — keine personenbezogenen Daten, nur Session-IDs
- Einfache Löschung —
DELETE FROM events WHERE session_id = ? - Keine Einwilligung nötig — anonymes Analytics braucht keine Consent-Abfrage
Was ich nicht tracke:
- IP-Adressen (nur für GeoIP-Lookup, nicht gespeichert)
- User Agents
- Device Fingerprints
- Cross-Site-Tracking
- Personenbezogene Daten jeglicher Art
Analytics + Signaturen
Die eigentliche Stärke: Joins zwischen Analytics und Signatur-Daten. Jede Signatur hat eine session_id, damit kann ich fragen:
Zeichendauer: Wie lange zwischen Editor-Öffnen und Speichern?
SELECT
s.id,
s.name,
(save_event.ts - open_event.ts) as duration_seconds
FROM signatures s
JOIN events open_event ON open_event.session_id = s.session_id
AND open_event.event = 'signature_editor_open'
JOIN events save_event ON save_event.session_id = s.session_id
AND save_event.event = 'signature_saved'
WHERE save_event.ts > open_event.ts
ORDER BY duration_seconds DESC
Conversion Funnel: Wie viel Prozent der Nutzer, die den Editor öffnen, speichern tatsächlich?
SELECT
COUNT(DISTINCT CASE WHEN event = 'signature_editor_open' THEN session_id END) as opened,
COUNT(DISTINCT CASE WHEN event = 'signature_saved' THEN session_id END) as saved
FROM events
Radierer-Nutzung: Wie oft wurde korrigiert?
SELECT
s.id,
COUNT(*) as eraser_toggles
FROM signatures s
JOIN events e ON e.session_id = s.session_id
AND e.event = 'signature_eraser_toggled'
WHERE e.ts BETWEEN s.ts_created - 3600 AND s.ts_created + 60
GROUP BY s.id
Nachteile und Tradeoffs
Ehrlich gesagt:
Eigenimplementierung: Alles selbst bauen. PostHog liefert Dashboards, Funnels, Retention Charts out of the box. Ich habe SQL-Queries.
Keine Dashboards: Mein „Dashboard” sind Queries im SQLite-CLI. Für eine persönliche Website reicht das. Für ein Unternehmen nicht.
Kein Echtzeit: Ich frage Daten ab, wenn ich sie sehen will. Kein Live-Dashboard mit aktuellen Besuchern.
Keine horizontale Skalierung: Aber für eine Website mit ein paar hundert Besuchen am Tag ist SQLite mühelos ausreichend.
Implementierungszeit: Ein Tag Arbeit. PostHog einrichten dauert 10 Minuten. Der ROI stimmt nur bei spezifischen Anforderungen wie meinen.
SQLite reicht
„SQLite für Analytics? Wirklich?”
Ja.
Wenig Traffic: 100-500 Page Views am Tag. SQLite schafft tausende Writes pro Sekunde.
WAL-Modus: Write-Ahead Logging — Reads blockieren nie Writes. Parallele Requests kein Problem.
Single-File-Backup: cp app.db backup.db — fertig. Kein Datenbankserver, kein Connection Pooling.
Query-Performance: Mit vernünftigen Indizes laufen selbst Queries über Millionen Zeilen in Millisekunden.
Die Tech-Branche hat Datenhaltung überkompliziert. Für die meisten Anwendungen ist SQLite nicht nur ausreichend — es ist optimal.
Ergebnisse
Nach einigen Wochen Betrieb, echte Zahlen:
- 16,7% Editor Conversion Rate — Nutzer die den Editor öffnen und eine Signatur speichern
- Durchschnittliche Zeichenzeit: ~13 Minuten — die Leute nehmen sich Zeit
- Längste Session: 30 Minuten — jemand war sehr engagiert
- 5,3 durchschnittliche Radierer-Nutzungen pro Zeichnung — Fehler passieren
Diese Insights wären in PostHog’s Interface vergraben. Mit eigenen Analytics sind sie eine SQL-Query entfernt.
Wann eigene Analytics sinnvoll sind
Eigene Analytics lohnen sich wenn:
- Volle Datenhoheit gewünscht
- Spezifische Integrationsanforderungen bestehen
- SQL kein Problem ist
- Traffic moderat ist (unter 10k täglich)
- Keine Echtzeit-Dashboards nötig sind
PostHog/Plausible/etc. sind besser wenn:
- Ein Team Dashboards braucht
- Plug-and-Play gewünscht ist
- Features wie Session Replay oder A/B Testing nötig sind
- Millionen Events anfallen
- Kein Code gewartet werden soll
Für meine persönliche Website ist der eigene Ansatz perfekt. Volle Kontrolle, null Zusatzkosten, genau die Features die ich brauche.