Каков оптимальный тип данных для поля 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?

bobocopy
источник

Ответы:

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) а функционально зависит (псевдо-натуральный) PK прекрасно. Но то же самое было бы болью, где возможны обновления 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 принимает несколько разумных текстовых представлений в качестве входных данных для a 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: полностью законно, ИМО. Не увлекайся именем . Это 16-байтовая сущность с удобным набором приведенных типов и логики ввода / вывода. Рассматриваемый случай даже фактически требует «уникального идентификатора». Вы также можете хранить всевозможные символьные данные в textстолбцах - даже если они вообще не являются «текстовыми».
Эрвин Брандштеттер,
Что, если MD5-хеш преобразуется в базу 64, как вы будете хранить ее тогда
PirateApp
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 должен быть в порядке. Оба запроса должны иметь возможность использовать такой индекс (особенно если они выбирают только небольшую часть строк).

a_horse_with_no_name
источник
Любая конкретная причина не использовать бит (128) или шестнадцатеричный (32)? Значения гарантированно вписываются в такое поле, и я хотел бы защитить от присвоения неверных значений.
Бобокопия
3
@bobocopy: в Postgres нет «шестнадцатеричного» типа данных. Я никогда не использовал этот bitтип, поэтому я не могу комментировать это. Учитывая ожидаемое количество строк, предложение Эрвина кажется лучше из-за экономии места, которую вы получаете, сохраняя это как UUID
a_horse_with_no_name
-1

Другой вариант - использовать 4 столбца INTEGER или 2 BIGINT.

happy_marmoset
источник
2
С точки зрения размера хранилища, конечно, подойдет любой вариант, но насколько удобно будет с ним работать? Возможно, вы могли бы расширить свой ответ, чтобы показать пример или иначе объяснить это.
Андрей М