Leistungen

Backend Engineering, Infrastruktur

Branche

B2B-Marktplatz

Jahr

2021-2024

Ein Cache. Zweihundert Mandanten. Null veraltete Seiten.

Ein B2B-Marktplatz bediente 200+ Unternehmensprofilseiten aus einer einzigen Django-Instanz. Jedes Profil war öffentlich zugänglich und wurde bei jedem Besuch abgefragt - Unternehmensdaten, Bildergalerien, Karteneinbettungen, verwandte Listings. Ohne Caching traf jeder Seitenaufruf die Datenbank 8-12 mal. Mit naivem Caching lieferte die Bearbeitung eines Unternehmens veraltete Daten an Besucher völlig unzusammenhängender Profile. Das System brauchte mandantenspezifische Cache-Isolation mit sofortiger Invalidierung und null mandantenübergreifendem Datenleck.

01.
DIE HERAUSFORDERUNG

Das Problem veralteter Daten

Der erste Caching-Versuch nutzte Djangos Per-View-Cache mit der URL als Schlüssel. Es funktionierte, bis zwei Unternehmen ihre Profile innerhalb desselben Cache-TTL-Fensters bearbeiteten. Die Bearbeitung von Unternehmen A invalidierte den Cache, aber die Invalidierung war zu breit - sie löschte Seiten für Unternehmen B, C und jeden anderen Mandanten. Die Notlösung (längere TTLs mit manueller Invalidierung) führte ein schlimmeres Problem ein: Editoren speicherten Änderungen, luden die Seite neu und sahen alten Inhalt. Support-Tickets häuften sich. 'Ich habe meine Beschreibung vor 10 Minuten aktualisiert und es zeigt immer noch die alte an.'

Cache-Invalidierung ist nicht schwer. Cache-Invalidierung über 200 Mandanten, die ein Cache-Backend teilen, ist schwer.

02.
DIE LÖSUNG

Versions-Hash Cache-Schlüssel

Die Lösung verwendet einen zusammengesetzten Cache-Schlüssel: Unternehmens-Slug, Locale und einen gekürzten Hash des last_modified-Zeitstempels. Wenn ein Unternehmen eine Änderung speichert, aktualisiert sich der Zeitstempel automatisch. Die nächste Anfrage berechnet einen neuen Hash, der einen neuen Cache-Schlüssel ergibt. Die alte gecachte Seite wird nie explizit gelöscht - sie wird einfach unerreichbar, weil nichts diesen Schlüssel jemals wieder anfordern wird. Der eingebaute TTL des Caches entfernt verwaiste Einträge. Keine Invalidierungslogik. Keine cache.delete()-Aufrufe. Keine Race Conditions zwischen 'alten löschen' und 'neuen schreiben'.

Der zusammengesetzte Cache-Schlüssel-Generator:

Python
import hashlib

def company_cache_key(company, locale):
    """Composite cache key with version hash.

    The hash changes whenever the company saves any field,
    because Django auto-updates last_modified.
    No explicit invalidation needed.
    """
    ts = company.last_modified.isoformat()
    version = hashlib.md5(ts.encode()).hexdigest()[:8]
    return f"company:{company.slug}:{locale}:{version}"

Den Cache beobachten

Ein Live-Cache-Simulator. Jede Kachel ist eine Mandanten-Seite. Traffic-Anfragen treffen den Cache (grün) oder verfehlen ihn (amber). Klicken Sie auf eine Kachel, um eine Bearbeitung zu simulieren - der Versions-Hash ändert sich und die nächste Anfrage ist ein garantierter Miss. Beobachten Sie, wie die Hit Rate steigt, während sich der Cache aufwärmt.

Tenant Pages
Hit Miss Invalidated
acme-gmbh
v1
berg-ag
v1
delta-io
v1
echo-sys
v1
flux-tech
v1
grip-co
v1
hive-lab
v1
ion-dev
v1
kite-ops
v1
lens-ai
v1
meta-hub
v1
nova-biz
v1
orb-net
v1
peak-it
v1
quad-fx
v1
rim-sol
v1
sync-up
v1
trak-io
v1
unit-cx
v1
vex-api
v1
wave-ly
v1
xen-ops
v1
yoke-io
v1
zeta-hq
v1
Click a tile to trigger an edit
0
Requests
0
Cache Hits
0
Cache Misses
0%
Hit Rate

Template-Fragment-Caching für aufwändige Komponenten:

HTML
{% load cache %}

{# Gallery fragment — cached independently #}
{# Uses gallery_modified, not company.last_modified #}
{% cache 3600 gallery company.slug gallery_hash %}
  {% for image in company.gallery_images.all %}
    <img src="{{ image.url }}" alt="{{ image.alt }}" loading="lazy">
  {% endfor %}
{% endcache %}

{# Map embed — rarely changes, long TTL #}
{% cache 86400 map company.slug company.address_hash %}
  {% include "company/_map_embed.html" %}
{% endcache %}

Produktions-Absicherungen

Fragment-Level Caching

Aufwändige Seitenabschnitte - Bildergalerien, Karteneinbettungen, verwandte Listings - werden unabhängig mit eigenen mandantenspezifischen Schlüsseln gecacht. Wenn ein Unternehmen seine Beschreibung aktualisiert, bleibt das Galerie-Fragment gecacht, weil es sein eigenes last_modified-Tracking hat. Das reduziert die Datenbanklast um 60% im Vergleich zu reinem Full-Page-Caching, weil die meisten Bearbeitungen Textfelder betreffen, nicht Bilder.

Staff Preview Bypass

Staff-User sehen immer die ungecachte Version. Die Cache-Middleware prüft request.user.is_staff bevor sie aus dem Cache ausliefert. Das eliminiert das häufigste Support-Problem: Editoren speichern Änderungen und sehen veralteten Inhalt. Es bedeutet auch, dass Staff die Live-Rendering-Performance sehen, was die Template-Optimierung ehrlich hält.

Locale-aware Schlüssel-Isolation

Der Cache-Schlüssel enthält das Locale-Segment. Ein deutscher Besucher und ein englischer Besucher, die dieselbe Unternehmensseite aufrufen, erzeugen unterschiedliche Cache-Schlüssel. Ohne dies würde ein Besucher, der die Sprache wechselt, die gecachte Version des vorherigen Locale sehen. Die erste Implementierung hatte das übersehen und lieferte deutschsprachigen Inhalt an englischsprachige Besucher für ein Unternehmen, dessen deutsches Profil populärer war.

03.
DAS ERGEBNIS

Sub-Millisekunden Seitenladezeiten

Die durchschnittliche Seitenladezeit sank von 340ms (ungecacht) auf 12ms (gecacht). Die Cache Hit Rate stabilisierte sich bei 94% nach der ersten Stunde Traffic. Datenbankabfragen pro Seite sanken von 11 auf 1 (die Zeitstempel-Prüfung). Null Vorfälle mit veralteten Daten in 18 Monaten Produktion. Null mandantenübergreifendes Cache-Leaking. Der Cache speichert etwa 800 Einträge (200 Unternehmen × 2 Locales × 2 Fragment-Typen) mit unter 40MB Arbeitsspeicher.

WICHTIGE KENNZAHLEN

0msØ Ladezeit
0%Cache Hit Rate
0DB-Queries/Seite
WAS DER KUNDE SAGT

"Seiten laden sofort. Editoren sehen ihre Änderungen unmittelbar. Wir gingen von 3-4 Support-Tickets pro Woche wegen veraltetem Inhalt auf null. Die beste Infrastruktur ist die, über die niemand nachdenkt."

Technical Lead

B2B-Marktplatz · Platform Team

FAQ

Warum nicht Redis Tags oder Cache-Versionierung?

Was passiert, wenn der Cache voll wird?

Fügt die Zeitstempel-Prüfung Latenz hinzu?

TECHNOLOGIE-STACK