Алгоритм хэширования электронной почты с низким уровнем коллизий? - PullRequest
1 голос
/ 05 сентября 2011

Справочная информация:

Мы создаем почтовый инструмент и в настоящее время выделим emailaddresses в отдельную таблицу, так что один emailaddress сохраняется только один раз и вместо него ссылаетсяего id.Мы считаем, что это хорошая идея, поскольку число получателей на одно электронное письмо может быть огромным, и вполне вероятно, что большинство адресов электронной почты получат значительно больше 100 писем.

Однако, когда пользователь импортирует emailaddresses вlist или аналогичные операции, нам сначала нужно выполнить массовую вставку, чтобы убедиться, что все адреса электронной почты имеют ids, мы просто игнорируем коллизии, это работает.Однако, когда мы затем хотим вставить их в list, мы должны получить emailaddresses один за другим или с огромным IN-запросом с адресами электронной почты (поскольку list ссылается на emailaddress на id), не очень заманчиво!

РЕДАКТИРОВАТЬ: пользователи могут импортировать более 100 000 адресов электронной почты, для 1000 или более адресов электронной почты, это не реальная проблема, чтобы запросить один за другим, конечно.

Вопрос:

Таким образом, одна идея состоит в том, чтобы хэшировать каждый emailaddress и использовать его вместо id.Это означает, что мы можем предсказать id для всех emailaddresses, не запрашивая их.Но есть ли какие-нибудь хорошие алгоритмы для хранения 16-байт / 128-битных + поражений цели ... 64-битных должно хватить нет?Что было бы оценено, если учесть, что все это тоже должно быть проиндексировано.

Есть какие-нибудь рекомендации?Что если бы мы просто взяли первые 8 байт из MD5?8 байтов от SHA1 лучше?Возможно, есть более специализированные алгоритмы?Я не все читал о вероятности столкновения, но мне любопытно, насколько хорошо работают существующие алгоритмы, когда сокращены, и как электронные письма строчные и в основном буквы или цифры.(Обратите внимание, что набор данных потенциально может быть огромным)

PS.Мы используем PHP, поэтому это несколько ограничивает нашу способность реализовывать специальные алгоритмы.

Ответы [ 3 ]

1 голос
/ 05 сентября 2011

Не уверен, что я понимаю ваш вариант использования, но на столбец адреса электронной почты наложено уникальное ключевое ограничение ...

0 голосов
/ 05 сентября 2011

Прежде чем делать что-либо радикальное, проверьте свой план запросов (как это зависит от используемого вами сервера базы данных, проверьте его документацию).

Проверьте, не можете ли вы получить индекс для работы с адресами электронной почты. Это должно немного ускорить процесс, хотя планировщик может их пропустить, потому что вы вставляете огромные объемы данных.

Когда (и только когда) вы попробовали это, вы можете посмотреть на проблему хеширования.

Я не знаю никаких алгоритмов, специально разработанных для хэширования адресов электронной почты, и, хотя вы могли бы использовать MD5, он предназначен для использования, когда вероятность коллизий должна быть настолько мала, что в принципе никогда не происходит (я не думаю, что кто-то обнаружил столкновение MD5 в дикой природе). Это можно сделать, но это дорого в вычислительном отношении. Это еще хуже, если вы используете SHA.

В вашем случае я бы предложил что-то попроще: во-первых, мы можем предположить, что все электронные письма находятся в форме

<someName>@<someServer>

Я хотел бы разделить письмо на две части, убрать все не буквенные, нечисловые символы из каждой.

Затем мы можем вычислить числовое значение для каждой из двух частей, которое мы получим, суммируя значение ascii каждой отдельной буквы (вы удалили все остальное, поэтому с многобайтовыми символами проблем не будет) .

На данный момент все, что остается сделать, - это объединить две суммы, и, поскольку мы можем ожидать, что будет гораздо меньше возможных отправителей, мы можем потратить только два байта для хранения имени сервера.

В псевдокоде:

function emailHash(namePart, serverPart){
  $someName = asciiStrip(namePart)
  $someServer = asciiStrip(serverPart)
  $someNameSum = 0 
  $someServerSum = 0 
  foreach($letter in $someName){
    $someNameSum += asciiValue($letter)
  }
  foreach($letter in $someServer){
    $someServerSum += asciiValue($letter)
  }
  return ($someNameSum % 2^6)*2^2 + $someServerSum % 2^2
}

Редактировать на основе комментариев

Вы правы, этот действительно беден. Однако есть еще одна интересная вещь, которую вы можете сделать, хотя ее будет немного сложнее реализовать.

После того, как мы удалим иностранные символы, у нас есть только 36 возможных символов, поэтому нам нужно только 6 бит для хранения каждого значения. С 48 битами памяти для части имени пользователя можно хранить 8 символов с адреса электронной почты. Как низко будет столкновение для этого?

Можно было бы улучшить, как-то отменяя числа (скажем, сохраняя их после деления их на два), так что в итоге мы имеем дело только с 32 числами. Затем можно хранить каждую цифру всего в 5 битах, что в сумме составляет 9 символов.

Если это не дает достаточно низкую частоту столкновений, вам, возможно, придется использовать MD5, который должен (при условии, что алгоритм дает идеальное распределение) только с вероятностью столкновения 1 из нескольких миллиардов миллиардов.

0 голосов
/ 05 сентября 2011

Существует огромный список проблем с вашим текущим подходом и его ограничениями.

Большинство из них просто решаются путем сохранения таблицы адресов электронной почты с идентификатором в качестве первичного ключа (автоинкремент на MySQL илиSQLite, последовательность в другом месте) и уникальный индекс по адресу электронной почты.

Почему ваши "пользователи" манипулируют большими списками адресов электронной почты, далеко не ясно.Похоже, большая часть ваших данных (т.е. получателей в определенном списке) не поддерживается в вашей базе данных.Вы никогда не должны «извлекать адреса электронной почты по одному или с огромным IN-запросом с адресами электронной почты».

Сокращение вывода md5 или sha подрывает уникальность хэша и делает коллизии намного более вероятными.

...