Was ist der optimale Datentyp für ein MD5-Feld?

35

Wir entwerfen ein System, von dem bekannt ist, dass es schwer lesbar ist (in der Größenordnung von Zehntausenden von Lesevorgängen pro Minute).

  • Es gibt eine Tabelle names, die als eine Art zentrale Registrierung dient. Jede Zeile hat ein textFeld representationund ein eindeutiges key, das ein MD5-Hash davon ist representation. 1 Diese Tabelle enthält derzeit mehrere zehn Millionen Datensätze und wird voraussichtlich im Laufe der Lebensdauer der Anwendung Milliardenbeträge erreichen.
  • Es gibt Dutzende anderer Tabellen (mit sehr unterschiedlichen Schemata und Datensatzzahlen), die auf die namesTabelle verweisen . Für jeden Datensatz in einer dieser Tabellen ist garantiert, dass er name_keyeinen Fremdschlüssel für die namesTabelle enthält.

1: Übrigens sind Datensätze in dieser Tabelle, wie zu erwarten, nach dem Schreiben unveränderlich.

Bei jeder anderen Tabelle als der namesTabelle folgt die häufigste Abfrage diesem Muster:

SELECT list, of, fields 
FROM table 
WHERE name_key IN (md5a, md5b, md5c...);

Ich möchte die Leseleistung optimieren. Ich vermute, dass mein erster Stopp darin bestehen sollte, die Größe der Indizes zu minimieren (obwohl es mir nichts ausmacht, mich dort als falsch zu erweisen).

Die Frage:
Was ist / sind die optimalen Datentypen für die Spalten keyund name_key?
Gibt es einen Grund zu verwenden , hex(32)über bit(128)? BTREEoder GIN?

bobocopy
quelle

Antworten:

41

Der Datentyp uuidist perfekt für die Aufgabe geeignet. Es belegt nur 16 Bytes im Gegensatz zu 37 Bytes im RAM für die varcharoder text-Darstellung. (Oder 33 Bytes auf der Festplatte, aber die ungerade Anzahl würde in vielen Fällen ein Auffüllen erfordern, um 40 Bytes effektiv zu machen .) Und der uuidTyp hat einige weitere Vorteile.

Beispiel:

SELECT md5('Store hash for long string, maybe for index?')::uuid AS md5_hash

Details und weitere Erklärung:

Sie könnten andere (billigere) Hashing-Funktionen in Betracht ziehen, wenn Sie die kryptografische Komponente von md5 nicht benötigen, aber ich würde mich für Ihren Anwendungsfall für md5 entscheiden (meistens schreibgeschützt).

Ein Wort der Warnung : Für Ihren Fall ( immutable once written) ist eine funktional abhängige (pseudo-natürliche) PK in Ordnung. Aber das gleiche wäre ein Schmerz, bei dem Updates textmöglich sind. Denken Sie daran, einen Tippfehler zu korrigieren: Die PK und alle abhängigen Indizes, FK-Spalten in dozens of other tablesund andere Verweise müssten sich ebenfalls ändern. Tabelle und Index aufgebläht, Sperrprobleme, langsame Aktualisierungen, verlorene Referenzen, ...

Wenn textsich im Normalbetrieb etwas ändern kann, ist ein Ersatz-PK die bessere Wahl. Ich schlage eine bigserialSpalte (Bereich -9223372036854775808 to +9223372036854775807- das sind neun Billionen zweihundertdreiundzwanzig Billionen dreihundertzweiundsiebzig Billionen sechsunddreißig Milliarden ) mit unterschiedlichen Werten für vor billions of rows. Könnte auf jeden Fall eine gute Idee sein : 8 statt 16 Bytes für Dutzende von FK-Spalten und -Indizes!). Oder eine zufällige UUID für viel größere Kardinalitäten oder verteilte Systeme. Sie können immer der Speicher md5 (wie uuid) zusätzlich Reihen in der Haupttabelle schnell aus dem ursprünglichen Text zu finden. Verbunden:

Wie für Ihre Frage :


An @ Daniels Kommentar adressieren : Wenn Sie eine Darstellung ohne Bindestriche bevorzugen, entfernen Sie die Bindestriche für die Anzeige:

SELECT replace('90b7525e-84f6-4850-c2ef-b407fae3f271', '-', '')

Aber ich würde mich nicht darum kümmern. Die Standarddarstellung ist in Ordnung. Und das Problem ist wirklich nicht die Darstellung hier.

Wenn andere Parteien einen anderen Ansatz verfolgen und Strings ohne Bindestriche in die Mischung werfen sollten, ist dies ebenfalls kein Problem. Postgres akzeptiert mehrere sinnvolle Textdarstellungen als Eingabe für a uuid. Die Dokumentation :

PostgreSQL akzeptiert auch die folgenden alternativen Formen für die Eingabe: Verwendung von Großbuchstaben, dem von geschweiften Klammern umgebenen Standardformat, Weglassen einiger oder aller Bindestriche und Hinzufügen eines Bindestrichs nach einer Gruppe von vier Ziffern. Beispiele sind:

A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11
{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}
a0eebc999c0b4ef8bb6d6bb9bd380a11
a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11
{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}

Was mehr ist, gibt die md5()Funktion zurück text, die Sie decode()zum Konvertieren verwenden würden, byteaund die Standarddarstellung davon ist:

SELECT decode(md5('Store hash for long string, maybe for index?'), 'hex')

\220\267R^\204\366HP\302\357\264\007\372\343\362q

Sie müßten encode()wieder die ursprüngliche Textdarstellung zu erhalten:

SELECT encode(my_md5_as_bytea, 'hex');

Um das Ganze abzurunden, byteabelegen Werte, die gespeichert werden , 20 Bytes im RAM (und 17 Bytes auf der Festplatte, 24 mit Auffüllung ), aufgrund des internen varlenaOverheads , der für die Größe und Leistung einfacher Indizes besonders ungünstig ist.

Alles funktioniert zugunsten eines uuidhier.

Erwin Brandstetter
quelle
1
Ist das echt für "uuid"? Bitte entschuldigen Sie, wenn ich zu umständlich bin, aber ich denke, ich sehe, dass der Datentyp "uuid" darauf ausgerichtet ist, Zahlen mit einer Länge von 16 Oktetten im Binärformat zu speichern. Der Begriff "uuid" legt jedoch einen bestimmten Generierungs- / Hashing-Algorithmus sowie die herkömmliche Textdarstellung in 5 Blöcken mit strichgetrennten hexadezimalen Zeichen nahe. Wenn dieser Typenname stark auf die UUID / GUID-Generierung hindeutet, ist es für Programmierer nicht irreführend, diesen Typ zum Speichern eines Hash zu verwenden?
Andrew Wolfe
2
@ AndrewWolfe: Absolut legitim, IMO. Lassen Sie sich nicht vom Namen mitreißen . Es handelt sich um eine 16-Byte-Entität mit einem praktischen Satz bereitgestellter Typumwandlungen und Eingabe- / Ausgabelogik. Der vorliegende Fall erfordert sogar eine "eindeutige Kennung". Sie können auch alle Arten von Zeichendaten in textSpalten speichern - auch wenn es sich überhaupt nicht um einen "Text" handelt.
Erwin Brandstetter
Was
passiert,
2
@PirateApp, dekodieren es zuerst: SELECT encode(decode('tZmffOd5Tbh8yXaVlZfRJQ==', 'base64'), 'hex')::uuid;.
Nyov
1
@nyov: uuidist ein 16-Byte-Typ, der die Ergebnisse eines SHA-Algorithmus, der zwischen 160 und 512 Bit erzeugt, nicht speichern kann. Es gibt keinen ähnlichen Typ, der in die Standardverteilung von Postgres passt. Sie könnten eine erstellen ... Andernfalls wird standardmäßig bytea- wie bei pg_crypto - verwendet.
Erwin Brandstetter,
2

Ich würde das MD5 in einer textoder varcharSpalte speichern . Es gibt keinen Leistungsunterschied zwischen den verschiedenen Zeichendatentypen. Möglicherweise möchten Sie die Länge der md5-Werte einschränken, indem Sie varchar(xxx)sicherstellen, dass der md5-Wert niemals eine bestimmte Länge überschreitet.

Große IN-Listen sind normalerweise nicht sehr schnell, es ist besser, so etwas zu tun:

with md5vals (md5) as (
  values ('one'), ('two'), ('three')
)
select t.*
from the_table t
  join md5vals m on t.name_key  = m.md5;

Eine andere Option, die manchmal als schneller bezeichnet wird, ist die Verwendung eines Arrays:

select t.*
from the_table t
where name_key = ANY (array['one', 'two', 'three']);

Da Sie nur die Gleichheit vergleichen, sollte ein regulärer BTree-Index in Ordnung sein. Beide Abfragen sollten in der Lage sein, einen solchen Index zu verwenden (insbesondere wenn nur ein kleiner Bruchteil der Zeilen ausgewählt wird).

ein Pferd ohne Name
quelle
Gibt es einen bestimmten Grund, Bit (128) oder Hex (32) nicht zu verwenden? Es ist garantiert, dass die Werte genau in ein solches Feld passen, und ich möchte mich davor schützen, dass schlechte Werte zugewiesen werden.
Bobocopy
3
@bobocopy: In Postgres gibt es keinen "hex" -Datentyp. Ich habe den bitTyp noch nie verwendet, daher kann ich dazu keinen Kommentar abgeben. Angesichts der erwarteten Anzahl von Zeilen scheint Erwins Vorschlag besser zu sein, da Sie Platz sparen, wenn Sie diese als UUID speichern
a_horse_with_no_name
-1

Eine andere Option ist die Verwendung von 4 INTEGER- oder 2 BIGINT-Spalten.

happy_marmoset
quelle
2
In Bezug auf die Speichergröße würde natürlich jede Option passen, aber wie bequem wäre es, damit zu arbeiten? Vielleicht könnten Sie Ihre Antwort erweitern, um ein Beispiel zu zeigen oder dies auf andere Weise zu erklären.
Andriy M