Який оптимальний тип даних для поля MD5?


35

Ми розробляємо систему, яка, як відомо, є важкою для читання (порядку десятків тисяч читань в хвилину).

  • Є таблиця, namesяка служить своєрідним центральним реєстром. Кожен рядок має textполе representationта унікальний keyхеш MD5 цього representation. 1 Ця таблиця наразі містить десятки мільйонів записів і, як очікується, виросте в мільярди протягом життя програми.
  • Є десятки інших таблиць (із сильно різними схемами та кількістю записів), які посилаються на namesтаблицю. Будь-який заданий запис в одній із цих таблиць гарантовано містить "a" name_key, який функціонально є іноземним ключем до namesтаблиці.

1: Між іншим, як ви могли очікувати, записи в цій таблиці незмінні після написання.

Для будь-якої таблиці, окрім namesтаблиці, найпоширеніший запит буде відповідати цій схемі:

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

Я хотів би оптимізувати ефективність читання. Я підозрюю, що моєю першою зупинкою має стати мінімізація розміру індексів (хоча я не заперечую, щоб там було доведено неправильно).

Питання:
Які / оптимальні типи даних для стовпців keyта name_keyстовпців?
Чи є причина використовувати hex(32)більше bit(128)? BTREEабо GIN?

Відповіді:


41

Тип даних uuidбуде відмінно підходять для виконання цього завдання. Він займає лише 16 байт на відміну від 37 байт в оперативній пам'яті для varcharабо textпредставлення. (Або 33 байти на диску, але непарне число зажадає в багатьох випадках прокладки для ефективного використання 40 байт.) І uuidтип має ще деякі переваги.

Приклад:

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

Деталі та додаткові пояснення:

Ви можете розглянути інші (дешевші) функції хешування, якщо вам не потрібен криптографічний компонент md5, але я б пішов з md5 для вашого випадку використання (здебільшого лише для читання).

Слово попередження : для вашого випадку ( immutable once written) функціонально залежний (псевдо-природний) ПК є нормальним. Але те саме було б болем, коли textможливі оновлення . Подумайте, щоб виправити помилку помилки: PK та всі залежні індекси, стовпці FK у dozens of other tablesінших та інших посиланнях повинні були також змінитися. Таблиця і покажчик індексу, проблеми з блокуванням, повільні оновлення, втрачені посилання, ...

Якщо textможна змінитись у звичайній роботі, кращим вибором буде сурогатний ПК . Я пропоную bigserialстовпцю (діапазон -9223372036854775808 to +9223372036854775807- це дев'ять квінтільйонів двісті двадцять три квадрильйони триста сімдесят два трильйони тридцять шість щось мільярд ) різних значень billions of rows. У будь-якому випадку це може бути хорошою ідеєю : 8 замість 16 байт для десятків стовпців та індексів FK!). Або випадковий UUID для значно більших кардинальностей або розподілених систем. Ви завжди можете зберігати вказаний md5 (as uuid) додатково, щоб швидко знаходити рядки в головній таблиці з оригінального тексту. Пов'язані:

Щодо вашого запиту :


Щоб звернутися до коментаря @ Daniel : Якщо ви віддаєте перевагу представництву без дефісів, видаліть дефіси для відображення:

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

Але я б не переймався. Представлення за замовчуванням просто чудово. І проблема насправді не в представництві.

Якщо інші сторони повинні мати інший підхід і кидати струни без дефісів в суміш, це теж не проблема. Postgres приймає кілька розумних подань тексту як вхідні дані для uuid. Документація :

PostgreSQL також приймає такі альтернативні форми для введення: використання малих цифр, стандартний формат, оточений дужками, опущення деяких або всіх дефісів, додавання дефісу після будь-якої групи з чотирьох цифр. Приклади:

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

Більше того, md5()функція повертається text, яку ви б використовували decode()для перетворення byteaта представлення за замовчуванням цього :

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

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

Вам доведеться encode()знову отримати оригінальне подання тексту:

SELECT encode(my_md5_as_bytea, 'hex');

На додаток до цього, значення, що зберігаються як byteaби займають 20 байт в оперативній пам’яті (і 17 байт на диску, 24 з прокладкою ) через внутрішні varlenaнакладні витрати , що особливо несприятливо для розміру та продуктивності простих індексів.

Тут все працює на користь uuid.


1
Це легітим для "uuid"? Вибачте, будь ласка, якщо я занадто педантичний, але я думаю, що я бачу, що тип даних "uuid" орієнтований на збереження чисел, що мають довжину 16 октетів у двійковому форматі. Але термін "uuid" передбачає певний алгоритм покоління / хешування, а також звичайне текстове подання в 5 блоках шістнадцяткових символів, розділених тире. Якщо назва цього типу настійно підказує покоління UUID / GUID, хіба це трохи не вводить в оману, принаймні, для програмістів використовувати цей тип для зберігання хешу?
Ендрю Вулф

2
@AndrewWolfe: Повністю легітимно, IMO. Не захоплюйтесь іменем . Це 16-байтний об'єкт із зручним набором заданих типів кастингу та логікою введення / виводу Справа в цьому випадку навіть насправді вимагає "унікального ідентифікатора". Ви також можете зберігати всі види символьних даних у textстовпцях - навіть якщо це зовсім не "текст".
Ервін Брандштетер

Що робити, якщо хеш MD5 перетворений на базу 64, як ви його
збережете

2
@PirateApp, декодувати його перший: SELECT encode(decode('tZmffOd5Tbh8yXaVlZfRJQ==', 'base64'), 'hex')::uuid;.
nyov

1
@nyov: uuidце 16-байтовий тип, який не може зберігати результати будь-якого алгоритму SHA, що створює між 160 і 512 бітами. Не існує подібного типу, який би вписувався в стандартний розподіл Postgres. Ви можете створити його ... Якщо цього не зробити, це за замовчуванням bytea- як pg_crypto .
Ервін Брандстеттер

2

Я б зберігав MD5 у колонці textчи varcharколонці. Немає різниці в продуктивності між різними типами даних символів. Ви можете обмежити довжину значень md5, використовуючи, varchar(xxx)щоб переконатися, що значення md5 ніколи не перевищує певну довжину.

Великі списки IN зазвичай не дуже швидкі, краще зробити щось подібне:

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

Інший варіант, про який іноді кажуть, що швидше - це використовувати масив:

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

Оскільки ви просто порівнюєте рівність, звичайний індекс BTree повинен бути нормальним. Обидва запити повинні мати можливість використовувати такий індекс (особливо якщо вибирають лише невелику частину рядків.


Якась конкретна причина не використовувати біт (128) або шістнадцятковий (32)? Значення гарантовано чітко вписуються в таке поле, і я хотів би захистити від присвоєння поганих значень.
бобокопія

3
@bobocopy: у Postgres відсутній тип «шестигранних» даних. Я ніколи не використовував цей bitтип, тому не можу це коментувати. З огляду на очікувану кількість рядків, пропозиція Ервіна, здається, краща через економію місця, яку ви отримуєте, зберігаючи це як UUID
a_horse_with_no_name

-1

Іншим варіантом є використання 4 колонок INTEGER або 2 BIGINT.


2
З точки зору обсягу пам’яті, звичайно, підійде будь-який варіант, але як зручно було б працювати? Можливо, ви могли б розширити свою відповідь, щоб показати приклад чи іншим чином пояснити це.
Андрій М
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.