Software Engineering5. März 20268 min read

Muster zur Datenbankskalierung, bevor Sie shardieren

Sharding ist teuer, dauerhaft und schwer umkehrbar. Hier sind die Skalierungsmuster, die Sie zuerst ausschöpfen sollten, in der Reihenfolge, in der wir in echten Produktionssystemen darauf zurückgreifen.

Von Innovation T Team


Über Sharding wird gesprochen wie über einen Initiationsritus, den Moment, in dem eine Datenbank endlich erwachsen wird. In der Praxis ist es eine der teuersten und am wenigsten umkehrbaren Entscheidungen, die Sie treffen können, und die meisten Teams greifen Jahre zu früh darauf zurück. Dies ist die Reihenfolge, die wir durcharbeiten, wenn eine Datenbank zu schmerzen beginnt, und warum jede Sprosse Ihnen Zeit verschafft, bevor Sie sich auf die schwierige Lösung festlegen.

Warum Sharding das letzte Mittel ist

Sharding bedeutet, Ihre Daten auf mehrere unabhängige Datenbankinstanzen aufzuteilen, sodass keine einzelne Maschine den gesamten Bestand hält. Das klingt nach mehr Kapazität, und das ist es auch. Es ist zugleich eine Steuer auf fast alles, was Sie danach entwickeln.

Sobald Daten auf getrennten Shards liegen, sind Joins über Shards hinweg (cross-shard joins) nicht mehr kostenlos, Transaktionen über mehrere Shards benötigen eine Koordination auf Anwendungsebene, und jede Abfrage muss wissen, mit welchem Shard sie sprechen soll. Das Rebalancing eines überlasteten Shards ist ein Projekt, keine Konfigurationsänderung. Auswertungen, die früher eine einzige Abfrage waren, werden zu einem Scatter-Gather-Vorgang über die Knoten hinweg. Nach unserer Erfahrung verbringen Teams, die zu früh shardieren, mehr Entwicklungszeit damit, um ihre eigene Topologie herumzuarbeiten, als sie je für das Performance-Problem aufgewendet haben, das den Anstoß gab.

Das Ziel dieses Artikels ist deshalb nicht "wie man shardiert". Es ist "wie man es noch nicht nötig hat". Jedes der folgenden Muster ist günstiger einzuführen und günstiger rückgängig zu machen.

Messen Sie, bevor Sie handeln

Bevor Sie die Architektur anfassen, verschaffen Sie sich ehrliche Zahlen. Die falsche Schicht zu skalieren ist der Weg, auf dem Teams Lesereplikate hinzufügen, um ein Problem zu beheben, das in Wirklichkeit ein fehlender Index war.

Erfassen Sie mindestens:

  • Langsame Abfrageprotokolle mit tatsächlichen Ausführungsplänen, nicht Vermutungen darüber, welche Abfragen langsam sind.
  • p95- und p99-Latenz pro Endpunkt, keine Durchschnittswerte. Durchschnitte verbergen die Abfragen, die Nutzer zum Abwandern bringen.
  • Verbindungszahlen über die Zeit, einschließlich inaktiver und wartender Verbindungen.
  • Das Lese-/Schreib-Verhältnis. Eine Arbeitslast mit 95 % Lesevorgängen und eine mit 60 % Lesevorgängen erfordern unterschiedliche Sprossen.
  • Die Größe von Tabellen und Indizes sowie die Wachstumsrate der Zeilen pro Monat.

Wenn Sie diese Werte heute nicht sehen können, ist das Ihre erste Aufgabe. Sie können nicht skalieren, was Sie nicht messen können, und in der Hälfte der Fälle offenbaren die Daten eine Lösung, die einen Nachmittag statt eines Quartals kostet.

Die Skalierungsleiter

Dies ist die Reihenfolge, in der wir aufsteigen, vom Günstigsten und Sichersten bis zum Eingreifendsten. Arbeiten Sie sich von oben nach unten vor und hören Sie auf, sobald Ihre Zahlen wieder gesund sind.

  1. Beheben Sie Abfragen und Indizes.
  2. Dimensionieren und poolen Sie Ihre Verbindungen richtig.
  3. Fügen Sie Caching dort hinzu, wo das Lesemuster es zulässt.
  4. Lagern Sie Lesevorgänge auf Replikate aus.
  5. Skalieren Sie den Primärknoten vertikal.
  6. Partitionieren Sie große Tabellen innerhalb einer Datenbank.
  7. Trennen Sie nach Domäne (getrennte Datenspeicher oder CQRS).
  8. Erst dann shardieren Sie.

1. Zuerst Abfragen und Indizes

Die Arbeit mit dem höchsten Ertrag ist fast nie architektonisch. Es ist ein fehlender Index auf einer WHERE- oder JOIN-Spalte, eine N+1-Abfrage, die hunderte Male pro Anfrage ausgelöst wird, oder ein SELECT *, das breite Zeilen zieht, obwohl drei Spalten genügen würden.

Führen Sie EXPLAIN ANALYZE (Postgres) oder das Äquivalent auf Ihren langsamsten Abfragen aus. Achten Sie auf sequenzielle Scans (sequential scans) über große Tabellen, Sortiervorgänge, die auf die Festplatte auslagern, und verschachtelte Schleifen über große Zeilenmengen. Fügen Sie abdeckende Indizes (covering indexes) hinzu, damit die Datenbank direkt aus dem Index antwortet. Hüten Sie sich jedoch vor Überindexierung: Jeder Index, den Sie hinzufügen, verlangsamt Schreibvorgänge und verbraucht Speicher, also indexieren Sie für die Abfragen, die Sie tatsächlich ausführen, und entfernen Sie danach die, die nie genutzt werden.

Diese Sprosse senkt regelmäßig die p99-Latenz um eine große Spanne bei nahezu null Infrastrukturkosten. Tun Sie es vor allem anderen.

2. Connection-Pooling und Limits

Datenbanken verkraften weit weniger gleichzeitige Verbindungen gut, als die meisten annehmen. Eine Postgres-Maschine, die bei 100 aktiven Verbindungen zufrieden ist, kann bei 500 zusammenbrechen, denn jede Verbindung bringt realen Speicher- und Scheduling-Aufwand mit sich. Serverless- und automatisch skalierende Anwendungsschichten verschärfen das: Jede neue Instanz öffnet ihren eigenen Pool, und die Verbindungszahlen explodieren.

Setzen Sie einen Pooler davor (PgBouncer für Postgres, ProxySQL für MySQL oder das verwaltete Äquivalent Ihres Cloud-Anbieters). Verwenden Sie Pooling auf Transaktionsebene, damit Verbindungen aggressiv geteilt werden. Begrenzen Sie die Poolgröße bewusst und dimensionieren Sie die Pools der Anwendungsschicht mit Blick auf das reale Limit der Datenbank, nicht auf die Zahl, die Ihr Anwendungs-Framework voreinstellt.

3. Caching dort, wo sich Lesevorgänge wiederholen

Wenn dieselben Daten weit häufiger gelesen werden, als sie sich ändern, fängt ein Cache vor der Datenbank die Last ab. Redis oder Memcached für häufige Key-Value-Abfragen, HTTP- und CDN-Caching für alles, was leicht veraltet ausgeliefert werden darf, und materialisierte Sichten (materialized views) für teure Aggregationen, die nicht in Echtzeit sein müssen.

Der schwierige Teil des Cachings ist die Invalidierung, seien Sie also explizit bei Ihrer Strategie: zeitbasierter Ablauf für Daten, die etwas veraltet sein dürfen, und ereignisbasierte Invalidierung für Daten, die beim Schreiben aktuell sein müssen. Cachen Sie die teuren Lesevorgänge, nicht alles, und messen Sie stets Ihre Trefferquote (hit rate). Ein Cache, der mit einer Trefferquote von 40 % läuft, fügt oft Latenz hinzu, statt sie zu entfernen.

4. Lesereplikate

Wenn Lesevorgänge überwiegen und Sie die offensichtlichen Gewinne bereits gecacht haben, replizieren Sie. Ein Primärknoten verarbeitet die Schreibvorgänge, ein oder mehrere Lesereplikate verarbeiten die Abfragen, und die Anwendung leitet entsprechend weiter. Verwaltete Cloud-Datenbanken machen das nahezu zu einem Häkchen.

Der Kompromiss ist der Replikationsverzug (replication lag). Ein Replikat ist letztlich konsistent (eventually consistent), sodass ein Nutzer, der gerade eine Änderung gespeichert hat, ein Replikat lesen könnte, das noch nicht nachgezogen hat, und veraltete Daten sieht. Leiten Sie Lesevorgänge, die sofort konsistent sein müssen (ein Nutzer, der sein soeben abgeschicktes Formular liest), an den Primärknoten und senden Sie alles, was eine kleine Verzögerung verträgt (Dashboards, Listen, Suche), an die Replikate. Insbesondere Reporting- und Analyseabfragen gehören auf ein Replikat, damit sie nie mit den Produktions-Schreibvorgängen konkurrieren.

5. Vertikale Skalierung

Manchmal ist die günstigste Lösung eine größere Maschine. Moderne Hardware ist gewaltig: Instanzen mit Hunderten Gigabyte RAM und Dutzenden Kernen sind Alltag, und ein einzelner gut abgestimmter Knoten kann Arbeitslasten tragen, von denen Teams annehmen, sie erforderten einen Cluster. RAM hinzuzufügen, damit der Arbeitsdatensatz in den Speicher passt, bewirkt oft mehr als jede Topologieänderung.

Vertikale Skalierung hat eine Obergrenze und eine Rechnung, sie ist also nicht der Endzustand. Doch als Mittel, sich sechs bis zwölf Monate zu erkaufen, während Sie die nächste Sprosse in Ruhe aufbauen, ist sie häufig die richtige Wahl. Stellen Sie nur sicher, dass Ihr Anbieter die Größenänderung mit minimaler Ausfallzeit unterstützt, bevor Sie sich darauf verlassen, und behalten Sie die Kostenkurve im Auge. Unser Praxisleitfaden zur Optimierung von Cloud-Kosten erläutert, wie Sie verhindern, dass Ihnen diese Ausgaben still davonlaufen.

6. Partitionierung innerhalb einer Datenbank

Die Partitionierung teilt eine große Tabelle in kleinere physische Teile auf, während sie eine einzige logische Tabelle bleibt und innerhalb einer Datenbank verbleibt. Das ist kein Sharding: Es gibt keine verteilte Koordination, und Ihre Abfragen ändern sich kaum.

Der klassische Gewinn ist die zeitbasierte Partitionierung auf einer Ereignis-, Log- oder Bestelltabelle: Partitionieren Sie nach Monat, und Abfragen, die auf einen Datumsbereich gefiltert sind, scannen nur die relevanten Partitionen (Partitionsbeschneidung, partition pruning). Das Löschen alter Daten wird zu einem sofortigen Verwerfen einer Partition anstelle eines langsamen, aufblähenden DELETE. Sie können auch nach einem Schlüssel wie Mandant (tenant) oder Region partitionieren. Die Partitionierung liefert einen Großteil des Nutzens, den man sich vom Sharding erhofft, und bewahrt zugleich die betriebliche Einfachheit einer einzigen Datenbank.

7. Trennung nach Domäne

Bevor Sie eine einzelne Tabelle über viele Maschinen aufteilen, erwägen Sie, Ihr Schema über einige wenige aufzuteilen. Wenn Abrechnung, Analytik und das Kernprodukt alle in einer Datenbank leben und um dieselben Ressourcen konkurrieren, gibt ihnen die Trennung nach abgegrenztem Kontext (bounded context) jeweils Raum zum Atmen und zum Skalieren nach eigenen Maßstäben.

Das passt naturgemäß zu Servicegrenzen. Wenn Sie sich bereits in diese Richtung bewegen, zeigt unser Leitfaden zum Weg vom Monolithen zu Microservices, wie Sie diese Nahtstellen ziehen, ohne ein verteiltes Durcheinander zu schaffen. Eine CQRS-Trennung (ein normalisierter Schreibspeicher plus ein leseoptimierter Speicher, oft eine Suchmaschine oder ein denormalisiertes Replikat) ist dieselbe Idee, angewandt auf eine einzelne Domäne mit hohem Datenverkehr.

Wann Sharding wirklich sinnvoll ist

Manchmal brauchen Sie es tatsächlich. Die ehrlichen Signale:

  • Eine einzelne Tabelle ist über das hinausgewachsen, was ein Knoten effizient halten oder indexieren kann, selbst nach der Partitionierung.
  • Der Schreibdurchsatz auf dem Primärknoten ist der Engpass, und Replikate helfen nicht, weil sie nur Lesevorgänge skalieren.
  • Sie haben einen natürlichen, stabilen Shard-Schlüssel (Mandanten-ID, Nutzer-ID, Region), der nahezu alle Abfragen innerhalb eines Shards hält.
  • Sie sind bereits jede obige Sprosse hinaufgestiegen und haben immer noch eine Wand vor sich.

Wenn das auf Sie zutrifft, shardieren Sie bewusst: Wählen Sie einen Shard-Schlüssel, der zu Ihren Zugriffsmustern passt, planen Sie das Rebalancing vom ersten Tag an ein, und stützen Sie sich auf ein System, das die Verteilung für Sie übernimmt (Citus, Vitess oder eine nativ verteilte Datenbank), statt das Routing im Anwendungscode von Hand zu bauen.

Eine Checkliste vor dem Sharding

Gehen Sie diese durch, bevor irgendjemand ein Sharding-Designdokument öffnet:

  1. Sind die 20 langsamsten Abfragen indexiert und frei von sequenziellen Scans über große Tabellen?
  2. Wurden N+1-Muster in den am stärksten frequentierten Endpunkten beseitigt?
  3. Ist ein Connection-Pooler mit bewusst gewählten Poolgrößen vorhanden?
  4. Sind wiederholte Lesevorgänge gecacht, mit einer Trefferquote, die Sie tatsächlich gemessen haben?
  5. Wurden leselastige und Reporting-Abfragen auf Replikate verlagert?
  6. Wurde vertikale Skalierung versucht oder zumindest kalkuliert?
  7. Sind große Zeitreihen- oder anfügelastige Tabellen partitioniert?
  8. Haben Sie nicht zusammenhängende Domänen in eigene Datenspeicher getrennt?

Wenn Sie die meisten dieser Kästchen nicht ankreuzen können, wird Sharding Ihr Problem nicht lösen. Es fügt ein Problem verteilter Systeme zu dem hinzu, das Sie bereits haben.

Wie Innovation T helfen kann

Der Großteil des Datenbankschmerzes, zu dessen Behebung man uns ruft, ist kein Kapazitätsproblem, sondern ein Instrumentierungs- und Abfrageproblem im Kapazitätskostüm. Bei Innovation T setzen unsere Software- und Cloud-Engineering-Teams dort an, wohin die Zahlen zeigen: Wir profilieren die reale Arbeitslast, beheben die Abfragen und Indizes, die tatsächlich schmerzen, und arbeiten uns dann die Leiter hinauf (Pooling, Caching, Replikate, Partitionierung), sodass Sie Komplexität nur dort hinzufügen, wo sie sich lohnt. Wenn Sharding oder eine verteilte Datenbank wirklich die Antwort ist, entwerfen wir gemeinsam mit Ihnen den Shard-Schlüssel und den Migrationspfad, damit sie Jahre halten, nicht Monate.

Wenn sich Ihre Datenbank allmählich wie eine Decke anfühlt, werfen Sie einen Blick auf unsere Leistungen oder nehmen Sie Kontakt auf. Wir helfen Ihnen lieber, einen Shard zu vermeiden, als nach einem überstürzten aufzuräumen.

#Datenbank#Skalierung#Performance#Architektur

Bereit, mit Innovation T zu bauen?

Ob Sicherheit, Wachstum oder Engineering, unser Team hilft Ihnen, es gut umzusetzen.