Проектирование системы: стратегии борьбы с тяжелыми записями в БД - PullRequest
0 голосов
/ 29 октября 2018

С точки зрения проектирования / масштабируемости систем, каковы некоторые стандартные отраслевые стратегии при работе с системой, требующей интенсивной записи в конкретную таблицу в БД.

Для простоты, скажем, таблица представляет собой таблицу инвентаризации для продуктов и имеет столбец «Имя продукта» и столбец «Счет», и он просто увеличивается на +1 каждый раз, когда новый Продукт покупается в система. И миллионы пользователей покупают разные продукты каждый второй, и мы должны следить за последним счетом каждого продукта, но это не обязательно должно быть строго в реальном времени, возможно, допустимо отставание в 5 минут.

Мои варианты:

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

2) Разделение базы данных на основе диапазона названий продуктов или его хэшированного значения. Но что, если есть определенный продукт (например, Apple), который получает большое количество обновлений за короткое время, он все равно попадет в ту же БД.

3) Пакетные обновления? Использовать какое-то кэширование и записывать в таблицу каждые X секунд с накопленным подсчетом того, что мы получили за эти Х секунд? Это допустимая опция, и какой механизм кэширования я использую? А что, если между последним чтением и следующей записью произойдет сбой? Как восстановить потерянный счет?

4) Любые другие очевидные варианты, о которых я забыл?

Любое понимание приветствуется!

Ответы [ 2 ]

0 голосов
/ 08 ноября 2018

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

Чтобы добиться этого масштабируемым образом, вам необходимо подтвердить (т.е. принять ) запрос на увеличение и поставить его в очередь на обработку. И пусть чтения работают в режиме реального времени за запрос. Обработайте запросы в очереди с помощью обработчика команд background , который знает , как согласовать . то есть, если он терпит неудачу, он должен знать, как разрешить конфликт (например, если кто-то еще обновил строку, получить более новую версию и повторить попытку).

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

Вы могли бы использовать определенную технологию, такую ​​как кэш в памяти, как предлагали другие, но это опять-таки еще одна реализация парадигмы CQRS. Это может быть кеш или просто другая копия записи или базы данных. То же самое и тот же эффект.

0 голосов
/ 01 ноября 2018

Я бы сказал, что решение будет сильно зависеть от того, что именно вам нужно сделать. Решение для записи тысяч записей в секунду может сильно отличаться от увеличения счетчика в приведенном вами примере. Более того, не может быть tables, чтобы справиться с такой нагрузкой. Consistency / availability требования также отсутствуют в вашем вопросе, и в зависимости от них вся архитектура может сильно отличаться.

В любом случае, вернемся к вашему конкретному упрощенному случаю и вашим возможностям

Вариант 1 (репликация главного подчиненного)

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

Вариант 2 (Sharding the DB)

Ваше предположение верно, мало чем отличается от стр.1

Вариант 3 (пакетные обновления)

Очень близко. Кэширующий слой, обеспеченный легким хранилищем, обеспечивающий одновременное атомное увеличение / уменьшение с постоянство , чтобы не потерять ваши данные. Мы использовали redis для аналогичной цели, хотя любая другая база данных key-value также подойдет - буквально десятки таких баз данных существуют.

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

Решение будет выглядеть следующим образом:

incoming requests → your backend server -> kv_storage (atomic increment(product_id))

И у вас будет запущен «очищающий» скрипт, т.е. */5, который выполняет следующее (упрощенно):

  1. за каждые product_id в kv_storage читать его текущий value
  2. обновить свой счетчик дБ (+= value)
  3. уменьшить value в kv_storage

Дальнейшее масштабирование

  • если скрипт завершится неудачно, ничего плохого не произойдет - обновления появятся при следующем запуске
  • если ваши бэкэнд-боксы не справляются с нагрузкой - вы можете легко добавить больше ящиков
  • если один db со значением ключа не может справиться с нагрузкой - большинство из них поддерживают масштабирование по нескольким блокам, или простая стратегия шардинга в ваших скриптах на сервере будет работать нормально
  • если один «очищающий» сценарий не поспевает за приращениями - вы можете масштабировать их до нескольких блоков и решать, какие диапазоны ключей обрабатываются каждым
...