Сценарий
У меня есть следующие методы:
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 МБ.