Was kann bei Verwendung derselben Sequenz über mehrere Tabellen in Postgres hinweg schief gehen?

11

Wir erwägen, eine gemeinsame Sequenz zu verwenden, um Primärschlüsseln für alle Tabellen in unserer Datenbank IDs zuzuweisen. Es gibt ungefähr 100 von ihnen. Nur ein paar werden häufig und regelmäßig eingefügt. Wir möchten ausschließen, dass es "aus einem offensichtlichen Grund eine schreckliche Idee" ist, bevor wir uns der Phase zuwenden, in der wir es tatsächlich ausprobieren und unter Last testen.

Unsere Spitzenlast liegt in der Größenordnung von 1000 Einsätzen pro Sekunde über mehrere Tabellen hinweg.

Unsere bisherigen Untersuchungen zeigen, dass - Sequenzgenerierungsgeschwindigkeit kein Problem sein sollte - Sequenzfragmentierung (Lücken) auftreten wird, aber kein Problem sein sollte - ID-Erschöpfung kein Problem sein wird

Wir sind uns nicht sicher, ob wir andere große Dinge vermissen. Wir wären dankbar für die Meinungen der Menschen, insbesondere von Menschen, die es schon einmal versucht haben und entweder positive oder negative Erfahrungen gemacht haben.

Für den Kontext haben wir zwei Hauptmotive dafür.

Eine Motivation dafür ist, dass wir eine Reihe von Wörterbüchern definieren können (wir nennen sie Bereiche) und diesen IDs von Menschen lesbare Wörter zugewiesen bekommen. Daher möchten wir sicherstellen, dass sich IDs in verschiedenen Tabellen niemals überschneiden. In einem Bereich kann der ID 12345 der Wert "Grün" und in einem anderen Bereich "Verde" zugewiesen werden. (Eigentlich verwenden wir es nicht für die Internationalisierung, aber wir könnten eines Tages).

Die andere Motivation besteht darin, es einfach zu machen, mehrere Bereitstellungen vor Ort zu haben und zu wissen (indem Sie die Reihenfolge der wichtigsten Ziffern jeder Bereitstellung eindeutig festlegen), dass sich unsere Bereitstellungen nicht mit Primärschlüsseln überschneiden. (Wie ein GUID Lite).

Burleigh Bär
quelle
Wie sicher werden Sie sein, dass 12345 niemals in mehr als einer Tabelle verwendet wird (dh weil eine ID aus irgendeinem Grund in Zukunft manuell aktualisiert wird)? Ich hätte viel lieber die Sicherheit zu wissen, dass die Datenbank diese Einschränkung erzwingt, als die Last, sie selbst durchsetzen zu müssen.
Jack sagt, versuchen Sie topanswers.xyz

Antworten:

5

Drei mögliche Probleme, die mir in den Sinn kommen, sind:

  1. Mit jeder gemeinsam genutzten Ressource schaffen Sie einen potenziellen Engpass. Mein Bauch sagt, dass dies für Ihre Spitzenlast kein Problem sein sollte, aber ich empfehle dringend, eine solche Lösung in einer produktionsähnlichen Produktionsgröße zu vergleichen, um sicherzugehen.

  2. Sie weisen Ersatzschlüsseln im Wesentlichen eine Bedeutung zu, die einen Teil ihres Zwecks in der RDB-Theorie zunichte macht. Ein Ersatzschlüssel sollte von Natur aus keine Bedeutung haben, die über die Identifizierung von Tupeln in dieser Beziehung hinausgeht. Wenn die Entitäten zusammen eine Bedeutung haben und daher kollisionsfreie Schlüssel benötigen, ist es richtig, dass sie separat modelliert werden, oder wurde etwas in den Anforderungen und / oder im Datenmodelldesign übersehen?

  3. Sie führen einen potenziellen Fehlerpunkt ein. Was ist, wenn für eine Bereitstellung der Startpunkt für die anfängliche Sequenz nicht festgelegt wird? Sie haben dann entweder einen Fehler beim Blockieren der Bereitstellung oder die Bereitstellung beginnt an derselben Stelle, an der Ihre Funktion "beschädigt" wird. Was werden Sie auch tun, wenn irgendwo auf der ganzen Linie jemand der Meinung ist, dass es eine gute Idee ist, eine Bereitstellung zu verzweigen (in der Produktion veräußert möglicherweise ein Mandantenunternehmen einen Teil von sich selbst und muss die Daten trennen). Was ist, wenn der Startwert durch eine fehlerhafte Upgrade-Bereitstellung oder eine andere Migration zurückgesetzt wird? [0]

Wenn Sie keines dieser Probleme betrifft, wird die Idee IMO nicht zerstören. Natürlich kann es bessere Wege geben, auch wenn dieser an sich nicht falsch ist.


Wenn Sie "UUID-lite" sagen, implizieren Sie, dass Sie UUIDs bereits berücksichtigt und abgezinst haben. Ist das der Fall und wenn ja, gibt es bestimmte Gründe für die Entscheidung, dass sie für dieses Projekt nicht geeignet sind?

Ein möglicher Grund für die Nichtverwendung von UUIDs ist die Indexfragmentierung, obwohl deren Bedeutung häufig stark überbewertet ist [1] . Die Antwort von SQL Server darauf ist die "sequentielle GUID", die ziemlich genau dem entspricht, was Sie vorschlagen, wenn wir die Zuweisung von Bedeutung zu Schlüsselwerten nicht berücksichtigen - vielleicht hat postgres eine Entsprechung dazu? Natürlich können immer größere Indizes ihre eigenen Leistungsprobleme haben (Konflikte auf der letzten Seite, Indexstatistiken werden immer veralteter), und zwar bei einigen sehr spezifischen Workloads mit hohem Volumen [2] .

Ein weiteres häufiges Argument gegen UUIDs ist die Schlüssellänge: Warum 16 Bytes pro Wert verwenden, wenn 4 oder 8 ausreichen? Wenn die Einzigartigkeit wirklich eine nützliche Eigenschaft ist, wird dies in der Regel die Bedenken hinsichtlich der Schlüsselgröße erheblich übertreffen. Wenn die Schlüsselgröße ein Problem darstellt, Sie jedoch gerne eine 64-Bit-INT verwenden, anstatt innerhalb von 32-Bit zu bleiben, können Sie Ihre Technik verwenden, ohne ein potenzielles Problem mit Konflikten mit gemeinsam genutzten Ressourcen hinzuzufügen, indem Sie Ihre Idee für einen gesetzten Ganzzahlschlüssel ausführen pro Tabelle [3] unter Verwendung einer normalen INT IDENTITY(<start>, 1)[4] Spaltendefinition, obwohl dies wiederum die Komplexität der Bereitstellung erhöht (eine kleine Menge, aber sicherlich nicht Null).

Die menschliche Lesbarkeit wird manchmal als Problem angeführt, aber das geht zurück auf die Zuweisung von Bedeutung zu Ersatzschlüsseln.

Komprimierbarkeit ist ein weniger verbreitetes Problem, auf das Sie jedoch möglicherweise stoßen. Für nahezu jeden Komprimierungsalgorithmus sehen UUIDs wahrscheinlich wie zufällige (daher nicht komprimierbare) Daten aus, es sei denn, Sie verwenden so etwas wie die sequentiellen UUIDs von SQL Server. Dies kann ein Problem für eine sehr große Anzahl von Links (oder anderen Datenblöcken) sein, die viele Entitäts-IDs enthalten, die einer Anwendung über ein langsames Netzwerk bereitgestellt werden, oder wenn Sie beispielsweise die Indexkomprimierungsfunktionen von SQL Server verwenden müssen, obwohl beides von Bedeutung ist Im Wesentlichen wird das Problem der Schlüsselgröße nur auf eine etwas andere Art und Weise neu formuliert, und auch hier können sequentielle UUIDs hilfreich sein.


[0] Dies könnte natürlich auch für normale Identitätsspalten passieren, aber da Sie eine weniger verbreitete Funktion verwenden, erhöhen Sie die Wahrscheinlichkeit eines weniger erfahrenen DBA, nachdem Sie das Problem verpasst haben, wenn es passiert, wenn Sie etwas Neues und Aufregendes tun anderswo!

[1] Ich bin ein SQL Server-Typ. Ich vermute, dass das potenzielle Problem bei Postgres dasselbe ist, aber soweit ich weiß, hat es möglicherweise ein anderes Indexlayout, das den Effekt abschwächen kann.

[2] Auch hier kann es sich um SQL Server-spezifisch handeln, insbesondere um das letztere der beiden von mir aufgelisteten Beispiele

[3] Die ersten beiden Bytes: variieren je nach Datenbank, die nächsten beiden: variieren je nach Tabelle, die restlichen vier: die inkrementierenden Bits

[4] Das ist die MS SQL Server-Syntax. Die Postgres-Syntax kann variieren, aber Sie sollten sehen, was ich meine, und in der Lage sein, zu übersetzen


tl; dr: Wenn Sie feststellen, dass Sie das Rad neu erfinden, stellen Sie sicher, dass alle vorhandenen Designs wirklich nicht geeignet sind, bevor Sie überlegen, warum ein neues möglicherweise vorhanden ist oder nicht.

David Spillett
quelle
1
Über [1]: Da (nach dem, was ich gehört habe) das Problem in SQL Server hauptsächlich in der Fragmentierung des CI liegt, wenn die UUID als CI verwendet wird, kann dies in Postgres nicht passieren, wo alle Tabellen Heaps sind. Nicht gruppierte btree-Indizes sollten sich auf beiden Plattformen gleich verhalten. Ich nehme an, Fragmentierung wird dort als weniger problematisch angesehen.
Ypercubeᵀᴹ
Postgres hat auch verschiedene andere Arten von Indizes (Hash, Trigramm, Gin, Gist, Brin, ...). Nicht sicher, wie sie betroffen sein könnten oder ob sie für eine UUID-Spalte nützlich sein könnten.
Ypercubeᵀᴹ
Nicht geclusterte Indizes (in SQL Server und normalerweise anderswo) basieren immer noch auf B-Bäumen, sodass sie aufgrund der Aufteilung der Seiten fragmentiert sind, wenn Daten in zufälliger Reihenfolge eintreffen. Die Verbindung zwischen UUIDs und CIs in vielen Diskussionen über SQL Server-Indizierungsstrategien beruht auf der Schlüsselauswahlfrage (reale Daten / immer inkrementierender Ersatz / effektiv zufälliger Ersatz) und der Tatsache, dass das Clustering durch PK häufig die Standardanordnung der Menschen ist. Wie Sie sagen, haben Indizes, die auf signifikant unterschiedlichen Strukturen wie Hashes basieren, völlig unterschiedliche Überlegungen.
David Spillett
Vielen Dank, sehr umfassende Antwort. Es hat uns beim Denken geholfen. Um Ihre obigen Fragen zu beantworten: Einer der Gründe, warum wir uns gegen eine UUID entschieden haben, war, dass eine natürliche Zuordnung zu einem Java, das lange von unseren vorhandenen ID-Spalten stammt, in unserer Codebasis sehr verbreitet ist. In unserem aktuellen Zeitrahmen wurde das Portieren des gesamten Codes als mühsamer bewertet, als es sich lohnte.
Burleigh Bear
1
Ah, das alte "bestehende externe Abhängigkeit, das über den Rahmen dieses Projekts hinausgeht, um sich zu ändern" -Problem! Dies ist sinnvoll, um sich gegen UUIDs zu entscheiden.
David Spillett
3

Wir erwägen, eine gemeinsame Sequenz zu verwenden, um Primärschlüsseln für alle Tabellen in unserer Datenbank IDs zuzuweisen. Es gibt ungefähr 100 von ihnen. Nur ein paar werden häufig und regelmäßig eingefügt. Wir möchten ausschließen, dass es "aus einem offensichtlichen Grund eine schreckliche Idee" ist, bevor wir uns der Phase zuwenden, in der wir es tatsächlich ausprobieren und unter Last testen.

Das ist eine schreckliche Idee: Ausschluss. Verwenden Sie einfach eine GUID / UUID. Warum haben Sie diese Idee ausgeschlossen? In PostgreSQL verwenden wir uuid-ossp:

uuid_generate_v4() Diese Funktion generiert eine UUID der Version 4, die vollständig aus Zufallszahlen abgeleitet wird.

So was,

CREATE EXTENSION uuid-ossp;
CREATE TABLE f ( f_id uuid DEFAULT uuid_generate_v4() );

Sie machen in Ihrer Antwort viele Annahmen, damit sie gültig ist.

  • Geschwindigkeit "sollte kein Problem sein"
  • Lücken "sollten kein Problem sein"
  • ID Erschöpfung wird nicht passieren

Sie müssen nichts davon annehmen. Was ist, wenn Sie ein DOS auf der ID erhalten, das eine massive Lücke erzeugt und einen Rollover auf einen Shard drückt? Warum nicht einfach die Branchenlösung für dieses Problem verwenden? Es ist nicht klar, dass es einen einzigen Nachteil gibt. Es ist wahrscheinlich alles zu gewinnen. Bis auf ein paar Bytes Speicher.

Evan Carroll
quelle
1
+1 für das Zitieren von "Es ist eine schreckliche Idee" zurück zu mir :)
Burleigh Bear
0

Eine Motivation dafür ist, dass wir eine Reihe von Wörterbüchern definieren können (wir nennen sie Bereiche) und diesen IDs von Menschen lesbare Wörter zugewiesen bekommen. Daher möchten wir sicherstellen, dass sich IDs in verschiedenen Tabellen niemals überschneiden. In einem Bereich kann der ID 12345 der Wert "Grün" und in einem anderen Bereich "Verde" zugewiesen werden. (Eigentlich verwenden wir es nicht für die Internationalisierung, aber wir könnten eines Tages).

Allein würde ich nicht zulassen, dass dies der Grund für die Wahl eines skurrilen und fragilen Designs ist. Wenn Sie den Weg gehen, gibt es keine Möglichkeit, die Datenbankfunktionen zu nutzen, um beispielsweise die referenzielle Integrität sicherzustellen. Ein traditioneller normalisierter Weg, um dasselbe zu erreichen, hätte Vorteile, die über RI hinausgehen:

create table tab1(tab1_id serial primary key);
create table tab2(tab2_id serial primary key);
create table scope(scope_id serial primary key, scope_name text);
create table scope_tab1(scope_id integer references scope, tab1_id integer references tab1, val text, primary key(scope_id,tab1_id));
insert into scope(scope_name) values ('English'),('French');
insert into tab1(tab1_id) select generate_series(1,5);
insert into tab2(tab2_id) select generate_series(1,5);
insert into scope_tab1(scope_id,tab1_id,val) values (1,1,'Green'),(2,1,'Verde');
select tab1_id
     , (select val from scope_tab1 where scope_id=1 and tab1_id=tab1.tab1_id) val_s1
     , (select val from scope_tab1 where scope_id=2 and tab1_id=tab1.tab1_id) val_s2
from tab1;
tab1_id | val_s1 | val_s2
------: | : ----- | : -----
      1 | Grün | Verde
      2 | null    | null   
      3 | null    | null   
      4 | null    | null   
      5 | null    | Null  

dbfiddle hier

Die andere Motivation besteht darin, es einfach zu machen, mehrere Bereitstellungen vor Ort zu haben und zu wissen (indem Sie die Reihenfolge der wichtigsten Ziffern jeder Bereitstellung eindeutig festlegen), dass sich unsere Bereitstellungen nicht mit Primärschlüsseln überschneiden. (Wie ein GUID Lite).

Ich würde vorschlagen, wie andere es getan haben, dass die Verwendung von UUID viel besser (dh viel weniger fehleranfällig) ist als die Erfindung einer neuen UUID-Lite.

Ich denke immer noch nicht, dass dies die beste Wahl ist - Sie sind nicht am Splittern, sodass zwischen den Bereitstellungen keine nicht überlappenden IDs erforderlich sind, die ich anhand der von Ihnen bereitgestellten Informationen sehen kann. Vermutlich haben Sie andere Möglichkeiten, eine Bereitstellung in einer Datenbank zu identifizieren, als die IDs in diesen Tabellen zu betrachten.

Jack sagt, versuchen Sie es mit topanswers.xyz
quelle
0

Ich habe das von Ihnen vorgeschlagene Muster mit einer zusätzlichen zentralen ID-Tabelle verwendet, für die alle anderen IDs Fremdschlüssel sind. Es funktionierte in einem großen Produktionssystem völlig in Ordnung.

Ich denke, der wahre Grund dafür ist, dass Ihre IDs einen Bereich haben, der über Ihre Datenbank hinausgeht. In meinem Beispiel wurden in diesen IDs beispielsweise eindeutige finanzielle Wertpapiere und Unternehmen aufgeführt. Sie könnten sich fragen, warum Sie nicht einen Satz if-IDs für Unternehmen und einen zweiten Satz für Wertpapiere als Primärschlüssel für die automatische Zuordnung für jede Tabelle erstellen sollten. Weil wir wollten, dass sich andere Zeitreihenaufzeichnungen entweder auf Wertpapiere oder auf Unternehmen beziehen. Die Fremdreihenfolge der Zeitreihentabelle ist also mit der zentralen ID-Tabelle verknüpft.

In Anbetracht dessen würde eine GUID / UUID auch gut funktionieren. Diese Formate haben jedoch häufig eine Größe von 128 Bit, was sich auswirken kann, da sie in fast allen Indizes, Primärschlüsseln und Fremdschlüsseln der Datenbank verwendet werden zu suboptimaler Auswahlleistung. Unsere Datenbank war sehr darauf ausgerichtet, die Leistung auszuwählen.

GUIDs / UUIDs haben einen Vorteil: Sie lassen sich viel einfacher mit Verbundgenerierungsprozessen erstellen. Das heißt, Sie können mehrere ID-Generierungs- / Zuweisungsprozesse in Ihrem Unternehmen ohne Koordination durchführen, indem Sie einfach davon ausgehen, dass sie niemals in Konflikt geraten. Wenn sich Ihre einzigen ID-Generierungsprozesse in Ihrer Datenbank befinden, ist dies weniger bedenklich, aber erwähnenswert.

Beachten Sie, dass die UUID-Generierung davon abhängt, dass Ihre MAC-Adressen eindeutig sind. Daher müssen Sie dies in einer virtuellen / Container-Umgebung berücksichtigen.

ThatDataGuy
quelle