Qual è il tipo di dati ottimale per un campo MD5?


35

Stiamo progettando un sistema che è noto per essere pesante (nell'ordine di decine di migliaia di letture al minuto).

  • C'è una tabella namesche funge da sorta di registro centrale. Ogni riga ha un textcampo representatione un unico keyche ne è un hash MD5 representation. 1 Questa tabella contiene attualmente decine di milioni di record e dovrebbe crescere fino a raggiungere i miliardi nel corso della vita dell'applicazione.
  • Esistono dozzine di altre tabelle (con schemi e conteggi record molto diversi) che fanno riferimento alla namestabella. Ogni dato record in una di queste tabelle è garantito per avere un name_key, che è funzionalmente una chiave esterna per la namestabella.

1: Per inciso, come ci si potrebbe aspettare, i record in questa tabella sono immutabili una volta scritti.

Per una determinata tabella diversa dalla namestabella, la query più comune seguirà questo modello:

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

Vorrei ottimizzare per le prestazioni di lettura. Sospetto che la mia prima fermata dovrebbe essere quella di ridurre al minimo la dimensione degli indici (anche se non mi dispiacerebbe essere smentito lì).

La domanda:
quali sono / sono i tipi di dati ottimali per le colonne keye name_key?
C'è una ragione per usare hex(32)sopra bit(128)? BTREEo GIN?

Risposte:


41

Il tipo di dati uuidè perfettamente adatto per l'attività. Occupa solo 16 byte anziché 37 byte nella RAM per la rappresentazione varcharo text. (O 33 byte sul disco, ma il numero dispari richiederebbe il riempimento in molti casi per renderlo efficacemente 40 byte.) E il uuidtipo ha alcuni vantaggi in più.

Esempio:

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

Dettagli e ulteriori spiegazioni:

Potresti considerare altre funzioni di hashing (più economiche) se non hai bisogno del componente crittografico di md5, ma andrei con md5 per il tuo caso d'uso (principalmente di sola lettura).

Un avvertimento : per il tuo caso ( immutable once written) un PK funzionalmente dipendente (pseudo-naturale) va bene. Lo stesso sarebbe un dolore in cui textsono possibili aggiornamenti . Pensa a correggere un refuso: anche il PK e tutti gli indici dipendenti, le colonne FK dozens of other tablese altri riferimenti dovrebbero cambiare. Gonfiore di tabelle e indici, problemi di blocco, aggiornamenti lenti, riferimenti persi, ...

Se textpuò cambiare durante il normale funzionamento, un PK surrogato sarebbe una scelta migliore. Suggerisco una bigserialcolonna (intervallo -9223372036854775808 to +9223372036854775807- che è nove quintilioni duecentoventitre tre trecento settantadue trilioni trentasei qualcosa miliardi ) valori distinti per billions of rows. Potrebbe essere una buona idea in ogni caso: 8 invece di 16 byte per dozzine di colonne e indici FK!). O un UUID casuale per cardinalità o sistemi distribuiti molto più grandi. Puoi sempre memorizzare detto md5 (as uuid) in aggiunta per trovare rapidamente le righe nella tabella principale dal testo originale. Relazionato:

Per quanto riguarda la tua query :


Per rispondere al commento di @ Daniel : se preferisci una rappresentazione senza trattini, rimuovi i trattini per la visualizzazione:

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

Ma non mi preoccuperei. La rappresentazione predefinita va bene. E il problema non è proprio la rappresentazione qui.

Se le altre parti dovrebbero avere un approccio diverso e lanciare stringhe senza trattini nel mix, anche questo non è un problema. Postgres accetta diverse rappresentazioni testuali ragionevoli come input per a uuid. La documentazione :

PostgreSQL accetta anche i seguenti moduli alternativi per l'input: uso di caratteri maiuscoli, il formato standard circondato da parentesi graffe, omettendo alcuni o tutti i trattini, aggiungendo un trattino dopo qualsiasi gruppo di quattro cifre. Esempi sono:

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

Cosa c'è di più, le md5()restituisce la funzione text, si può usare decode()per convertire in byteae la rappresentazione di default che è:

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

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

Dovresti di encode()nuovo ottenere la rappresentazione del testo originale:

SELECT encode(my_md5_as_bytea, 'hex');

Per finire, i valori memorizzati byteaoccuperebbero 20 byte nella RAM (e 17 byte sul disco, 24 con riempimento ) a causa del sovraccarico internovarlena , che è particolarmente sfavorevole per le dimensioni e le prestazioni di indici semplici.

Tutto funziona a favore di un uuidqui.


1
Questo è legittimo per "uuid"? Per favore, mi scusi se sono troppo pedante, ma penso che ciò che vedo sia che il tipo di dati "uuid" sia orientato alla memorizzazione di numeri di 16 ottetti di lunghezza in formato binario. Ma il termine "uuido" suggerisce un particolare algoritmo di generazione / hashing e la tradizionale rappresentazione testuale in 5 blocchi di caratteri esadecimali separati da trattini. Se questo nome di tipo suggerisce fortemente la generazione UUID / GUID, non è un po 'fuorviante, almeno per i programmatori, usare questo tipo per memorizzare un hash?
Andrew Wolfe,

2
@AndrewWolfe: Totalmente legittimo, IMO. Non lasciarti trasportare dal nome . È un'entità a 16 byte con un comodo set di cast di tipi forniti e logica di input / output. Il caso attuale richiede addirittura un "identificatore univoco". Puoi anche archiviare tutti i tipi di dati dei caratteri nelle textcolonne, anche se non è affatto un "testo".
Erwin Brandstetter,

cosa succede se l'hash MD5 viene convertito in base 64, come lo memorizzerai allora
PirateApp il

2
@PirateApp, decodificarlo prima: SELECT encode(decode('tZmffOd5Tbh8yXaVlZfRJQ==', 'base64'), 'hex')::uuid;.
nyov

1
@nyov: uuidè un tipo a 16 byte che non è in grado di memorizzare i risultati di alcun algoritmo SHA che produce tra 160 e 512 bit. Non esiste un tipo simile adatto alla distribuzione standard di Postgres. Potresti crearne uno ... In caso contrario , per impostazione predefinita bytea- come fa pg_crypto .
Erwin Brandstetter,

2

Vorrei archiviare MD5 in una texto varcharcolonna. Non vi è alcuna differenza di prestazioni tra i vari tipi di dati dei caratteri. Potresti voler limitare la lunghezza dei valori md5 usando varchar(xxx)per assicurarti che il valore md5 non superi mai una certa lunghezza.

Le grandi liste IN di solito non sono molto veloci, è meglio fare qualcosa del genere:

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

Un'altra opzione che a volte si dice sia più veloce è usare un array:

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

Dato che stai solo confrontando per l'uguaglianza, un normale indice BTree dovrebbe andare bene. Entrambe le query dovrebbero essere in grado di utilizzare un tale indice (soprattutto se stanno selezionando solo una piccola parte delle righe.


Qualche motivo particolare per non usare bit (128) o hex (32)? I valori sono garantiti per adattarsi perfettamente a un tale campo e mi piacerebbe proteggere da valori errati assegnati.
Bobocopy,

3
@bobocopy: in Postgres non esiste un tipo di dati "hex". Non ho mai usato il bittipo, quindi non posso commentarlo. Dato il numero atteso di righe, il suggerimento di Erwin sembra essere migliore a causa del risparmio di spazio che si ottiene con la memorizzazione come UUID
a_horse_with_no_name

-1

Un'altra opzione è quella di utilizzare 4 colonne INTEGER o 2 BIGINT.


2
In termini di dimensioni dello spazio di archiviazione, entrambe le opzioni si adatterebbero, ovviamente, ma quanto sarebbe conveniente lavorare con? Forse potresti espandere la tua risposta per mostrare un esempio o spiegarlo in altro modo.
Andriy M,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.