Оптимальное хранение структуры данных для быстрого поиска и постоянства - PullRequest
8 голосов
/ 30 марта 2010

Сценарий

У меня есть следующие методы:

public void AddItemSecurity(int itemId, int[] userIds)
public int[] GetValidItemIds(int userId)

Изначально я думаю о хранилище в форме:

itemId -> userId, userId, userId

и

userId -> itemId, itemId, itemId

AddItemSecurity основан на том, как я получаю данные от стороннего API, GetValidItemIds - это то, как я хочу использовать его во время выполнения.

Существует потенциально 2000 пользователей и 10 миллионов единиц. Идентификаторы товара указаны в форме: 2007123456, 2010001234 (10 цифр, где первые четыре представляют год).

AddItemSecurity не обязательно должен работать очень быстро, но GetValidIds должно быть меньше секунды. Кроме того, если имеется обновление для существующего itemId, мне нужно удалить этот itemId для пользователей, которых больше нет в списке.

Я пытаюсь думать о том, как я должен хранить это оптимальным образом. Желательно на диске (с кэшированием), но я хочу, чтобы код был исправным и чистым.

Если идентификатор элемента начинался с 0, я думал о создании байтового массива длиной MaxItemId / 8 для каждого пользователя и устанавливал бит истина / ложь, если элемент присутствовал или нет. Это ограничило бы длину массива чуть более 1 МБ на пользователя и обеспечило бы быстрый поиск, а также простой способ обновления списка для каждого пользователя. Сохраняя это как Файлы с отображением в памяти с помощью среды .Net 4, я думаю, что я бы также получил достойное кэширование (если на машине достаточно ОЗУ), не реализовав логику кэширования самостоятельно. Разбор идентификатора, выделение года и сохранение массива за год может быть решением.

Список ItemId -> UserId [] может быть сериализован непосредственно на диск и считан / записан с обычным FileStream, чтобы сохранить список и преобразовать его при появлении изменений.

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

Вопрос

Должен ли я продолжать опробовать этот подход, или есть другие пути, которые также следует изучить? Я думаю, что SQL-сервер не будет работать достаточно быстро, и это приведет к издержкам (по крайней мере, если он размещен на другом сервере), но мои предположения могут быть неверными. Любые мысли или идеи по этому вопросу приветствуются. И я хочу попытаться решить эту проблему, не добавляя слишком много оборудования:)

[Обновление 2010-03-31]

Я сейчас протестировал SQL Server 2008 при следующих условиях.

  • Таблица с двумя столбцами (ID пользователя, ItemID) оба Int
  • Кластерный индекс по двум столбцам
  • Добавлено ~ 800 000 элементов для 180 пользователей - Всего 144 миллиона строк
  • Выделенный 4 ГБ оперативной памяти для сервера SQL
  • Двухъядерный 2,66 ГГц ноутбук
  • SSD диск
  • Используйте SqlDataReader для чтения всех itemid в Список
  • Зацикливание на всех пользователях

Если я запускаю один поток, он в среднем занимает 0,2 секунды. Когда я добавляю второй поток, он идет до 0,4 секунд, что все еще в порядке Оттуда результаты уменьшаются. Добавление третьего потока приносит много запросов до 2-х секунд. Четвертый поток, до 4 секунд, пятый - некоторые запросы до 50 секунд.

Процессор работает, даже если он работает, даже в одном потоке. Мое тестовое приложение требует некоторых из-за быстрого цикла, а остальное sql.

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

[Обновление 2010-03-31 # 2]

Я провел быстрый тест с теми же данными, поместив их в виде битов в отображенных в память файлах. Это работает намного лучше. Шесть потоков дают время доступа от 0,02 до 0,06 с. Чисто память связана. Сопоставленные файлы были сопоставлены одним процессом, и к ним одновременно обращались шесть других. И поскольку база данных sql заняла 4 ГБ, файлы на диске заняли 23 МБ.

Ответы [ 3 ]

3 голосов
/ 15 июня 2010

После долгих испытаний я использовал файлы, отображенные в память, помечая их разреженным битом (NTFS), используя код из Разреженные файлы NTFS с помощью C # .

В Википедии есть объяснение, что такое разреженный файл .

Преимущества использования разреженного файла в том, что мне не нужно заботиться о том, в каком диапазоне находятся мои идентификаторы. Если я пишу только идентификаторы между 2006000000 и 2010999999, файл будет выделять только 625 000 байт из смещения 250 750 000 000 в файле. , Все пространство до этого смещения нераспределено в файловой системе. Каждый идентификатор хранится в файле в виде установленного бита. Вроде рассматривается как битовый массив. И если последовательность id внезапно изменится, она будет размещена в другой части файла.

Чтобы узнать, какие идентификаторы установлены, я могу выполнить вызов ОС, чтобы получить выделенные части разреженного файла, и затем я проверяю каждый бит в этих последовательностях. Также очень быстро проверяется, установлен ли конкретный идентификатор. Если он выходит за пределы выделенных блоков, то его там нет, если он попадает в него, это просто чтение одного байта и проверка битовой маски, чтобы увидеть, установлен ли правильный бит.

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

И хорошо то, что файлы, отображаемые в память, могут также использоваться совместно с Java (что оказалось чем-то необходимым). Java также поддерживает отображение файлов в Windows, и реализация логики чтения / записи довольно тривиальна.

1 голос
/ 30 марта 2010

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

0 голосов
/ 30 марта 2010

2000 пользователей не так уж и плохо, но с 10 миллионами связанных предметов вам действительно стоит подумать о том, чтобы поместить это в базу данных. БД выполняют все необходимое хранение, постоянство, индексацию, кэширование и т. Д., И они работают очень хорошо.

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

...