Apa tipe data optimal untuk bidang MD5?

35

Kami sedang merancang sistem yang dikenal sebagai read-heavy (dengan urutan puluhan ribu bacaan per menit).

  • Ada tabel namesyang berfungsi sebagai semacam registry pusat. Setiap baris memiliki textbidang representationdan keunikan keyMD5 yang unik representation. 1 Tabel ini saat ini memiliki puluhan juta catatan dan diperkirakan akan tumbuh hingga miliaran sepanjang masa aplikasi.
  • Ada lusinan tabel lain (skema yang sangat bervariasi dan jumlah catatan) yang membuat referensi ke namestabel. Setiap catatan yang diberikan di salah satu tabel ini dijamin memiliki name_key, yang secara fungsional merupakan kunci asing ke namestabel.

1: Secara kebetulan, seperti yang Anda duga, catatan dalam tabel ini tidak dapat diubah begitu ditulis.

Untuk tabel tertentu selain namestabel, kueri paling umum akan mengikuti pola ini:

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

Saya ingin mengoptimalkan kinerja baca. Saya menduga bahwa pemberhentian pertama saya adalah meminimalkan ukuran indeks (meskipun saya tidak keberatan terbukti salah di sana).

Pertanyaan:
Apa tipe data optimal untuk keydan name_keykolom?
Apakah ada alasan untuk menggunakan hex(32)lebih bit(128)? BTREEatau GIN?

bobocopy
sumber

Jawaban:

41

Tipe data uuidyang sempurna cocok untuk tugas. Ini hanya menempati 16 byte sebagai lawan dari 37 byte di RAM untuk representasi varcharatau text. (Atau 33 byte pada disk, tetapi angka ganjil akan membutuhkan padding dalam banyak kasus untuk membuatnya 40 byte secara efektif.) Dan uuidtipe ini memiliki beberapa kelebihan.

Contoh:

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

Detail dan penjelasan lebih lanjut:

Anda mungkin mempertimbangkan fungsi hashing lainnya (lebih murah) jika Anda tidak memerlukan komponen kriptografi dari md5, tapi saya akan menggunakan md5 untuk use case Anda (kebanyakan hanya baca-saja).

Sebuah kata peringatan : Untuk kasus Anda ( immutable once written) a fungsional tergantung (pseudo-alam) PK baik-baik saja. Tetapi hal yang sama akan menyusahkan jika pembaruan textdimungkinkan. Pikirkan untuk memperbaiki kesalahan ketik: PK dan semua indeks tergantung, kolom FK di dozens of other tablesdan referensi lainnya harus berubah juga. Tabel dan indeks mengasapi, masalah penguncian, pembaruan lambat, referensi hilang, ...

Jika textdapat berubah dalam operasi normal, PK pengganti akan menjadi pilihan yang lebih baik. Saya menyarankan sebuah bigserialkolom (kisaran -9223372036854775808 to +9223372036854775807- itu sembilan trilyun dua ratus dua puluh tiga kuadriliun tiga ratus tujuh puluh dua triliun tiga puluh enam sesuatu miliar ) nilai yang berbeda untuk billions of rows. Mungkin ide yang baik dalam setiap kasus: 8 bukan 16 ! Byte untuk puluhan kolom FK dan indeks). Atau UUID acak untuk kardinalitas yang jauh lebih besar atau sistem terdistribusi. Anda selalu dapat menyimpan kata md5 (as uuid) tambahan untuk menemukan baris di tabel utama dari teks asli dengan cepat. Terkait:

Adapun permintaan Anda :


Untuk mengatasi komentar @ Daniel : Jika Anda lebih suka representasi tanpa tanda hubung, hapus tanda hubung untuk ditampilkan:

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

Tapi aku tidak mau repot. Representasi default baik-baik saja. Dan masalahnya bukan representasi di sini.

Jika pihak lain harus memiliki pendekatan yang berbeda dan melempar string tanpa tanda hubung ke dalam campuran, itu tidak masalah juga. Postgres menerima beberapa representasi teks yang masuk akal sebagai input untuk a uuid. Dokumentasi :

PostgreSQL juga menerima formulir alternatif berikut untuk input: penggunaan digit huruf besar, format standar yang dikelilingi oleh kawat gigi, menghilangkan beberapa atau semua tanda hubung, menambahkan tanda hubung setelah grup yang terdiri dari empat digit. Contohnya adalah:

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

Apa lagi, md5()kembali fungsi text, Anda akan menggunakan decode()untuk mengkonversi ke byteadan representasi default yang adalah:

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

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

Anda harus encode()kembali untuk mendapatkan representasi teks asli:

SELECT encode(my_md5_as_bytea, 'hex');

Sebagai tambahan, nilai yang disimpan byteaakan menempati 20 byte dalam RAM (dan 17 byte pada disk, 24 dengan padding ) karena overhead internalvarlena , yang sangat tidak menguntungkan untuk ukuran dan kinerja indeks sederhana.

Semuanya berfungsi mendukung di uuidsini.

Erwin Brandstetter
sumber
1
Apakah ini sah untuk "uuid"? Maaf jika saya terlalu bertele-tele, tapi saya pikir yang saya lihat adalah tipe data "uuid" berorientasi pada penyimpanan angka yang panjangnya 16 oktet dalam format biner. Tetapi istilah "uuid" menunjukkan algoritma pembangkitan / hashing tertentu serta representasi tekstual konvensional dalam 5 blok karakter heksadesimal yang dipisahkan oleh tanda hubung. Jika nama jenis ini sangat menyarankan generasi UUID / GUID, bukankah itu agak menyesatkan, setidaknya untuk programmer, untuk menggunakan jenis ini untuk menyimpan hash?
Andrew Wolfe
2
@AndrewWolfe: Benar-benar sah, IMO. Jangan terbawa oleh namanya . Ini adalah entitas 16-byte dengan seperangkat gips tipe dan logika input / output yang disediakan. Kasus yang ada bahkan sebenarnya membutuhkan "pengidentifikasi unik". Anda dapat menyimpan semua jenis data karakter dalam textkolom juga - bahkan jika itu bukan "teks" sama sekali.
Erwin Brandstetter
bagaimana jika hash MD5 dikonversi ke basis 64, bagaimana Anda menyimpannya kemudian
PirateApp
2
@PirateApp, decode terlebih dahulu: SELECT encode(decode('tZmffOd5Tbh8yXaVlZfRJQ==', 'base64'), 'hex')::uuid;.
nyov
1
@nyov: uuidadalah tipe 16-byte yang tidak dapat menyimpan hasil dari algoritma SHA yang menghasilkan antara 160 dan 512 bit. Tidak ada tipe serupa yang sesuai dengan distribusi standar Postgres. Anda dapat membuat satu ... Gagal itu, default ke bytea- seperti pg_crypto tidak.
Erwin Brandstetter
2

Saya akan menyimpan MD5 dalam kolom textatau a varchar. Tidak ada perbedaan kinerja antara berbagai tipe data karakter. Anda mungkin ingin membatasi panjang nilai md5 dengan menggunakan varchar(xxx)untuk memastikan nilai md5 tidak pernah melebihi panjang tertentu.

Daftar IN besar biasanya tidak terlalu cepat, lebih baik melakukan sesuatu seperti ini:

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

Opsi lain yang kadang-kadang dikatakan lebih cepat adalah menggunakan array:

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

Karena Anda hanya membandingkan untuk kesetaraan, indeks BTree biasa harus baik-baik saja. Kedua pertanyaan harus dapat menggunakan indeks seperti itu (terutama jika mereka hanya memilih sebagian kecil dari baris.

seekor kuda tanpa nama
sumber
Ada alasan khusus untuk tidak menggunakan bit (128) atau hex (32)? Nilai dijamin akan cocok dengan bidang seperti itu, dan saya ingin melindungi dari nilai-nilai buruk yang ditugaskan.
bobocopy
3
@bobocopy: tidak ada tipe data "hex" di Postgres. Saya tidak pernah menggunakan bittipe itu jadi saya tidak bisa mengomentari itu. Mengingat jumlah baris yang Anda harapkan, saran Erwin tampaknya lebih baik karena penghematan ruang yang Anda dapatkan dengan menyimpan ini sebagai UUID
a_horse_with_no_name
-1

Opsi lain adalah menggunakan 4 INTEGER atau 2 kolom BIGINT.

happy_marmoset
sumber
2
Dalam hal ukuran penyimpanan, tentu saja salah satu opsi akan cocok, tetapi seberapa nyaman untuk digunakan? Mungkin Anda bisa memperluas jawaban Anda untuk menunjukkan contoh atau menjelaskannya.
Andriy M