Discord продолжает расти быстрее, чем мы ожидали, как и пользовательский контент. Чем больше пользователей — тем больше сообщений в чате. В июле мы объявили о 40 млн сообщений в день, в декабре объявили о 100 млн, а в середине января преодолели 120 млн. Мы сразу решили хранить историю чатов вечно, так что пользователи могут вернуться в любой момент и получить доступ к своим данным с любого устройства. Это много данных, поток и объём которых нарастает, и все они должны быть доступными. Как мы это делаем? Cassandra!

Что мы делали


Изначальную версию Discord написали быстрее чем за два месяца в начале 2015 года. Возможно, одной из лучших СУБД для быстрого выполнения итераций является MongoDB. Всё в Discord специально хранилось в едином реплисете (replica set) MongoDB, но мы также готовили всё для простой миграции в новую СУБД (мы знали, что не собираемся использовать шардинг MongoDB из-за его сложности и неизвестной стабильности). На самом деле это часть нашей корпоративной культуры: разрабатывай быстро, чтобы испытать новую функцию продукта, но всегда с курсом на более надёжное решение.

Сообщения хранились в коллекции MongoDB с единым составным индексом на channel_id и created_at. Примерно в ноябре 2015 года мы вышли на рубеж 100 млн сообщений в базе, и тогда начали понимать проблемы, которые нас ждут: данные и индекс больше не помещаются в ОЗУ, а задержки становятся непредсказуемыми. Пришло время мигрировать в более подходящую СУБД.

Выбор правильной СУБД


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

  • Быстро стало понятно, что операции чтения исключительно случайны, а соотношения чтение/запись примерно 50/50.
  • Тяжёлые серверы голосовых чатов Discord практически не присылали сообщений. То есть они присылали одно или два сообщения каждые несколько дней. За год сервер такого типа вряд ли достигнет рубежа в 1000 сообщений. Проблема в том, что даже несмотря на такое малое количество сообщений, эти данные труднее доставлять пользователям. Просто возвращение пользователю 50-ти сообщений может привести к многим случайным операциям поиска на диске, что приводит к вытеснению дискового кэша.
  • Тяжёлые серверы приватных текстовых чатов Discord отправляют приличное количество сообщений, легко попадая в диапазон между 100 тыс. и 1 млн сообщений в год. Запрашивают они обычно только самые последние данные. Проблема в том, что на этих серверах обычно менее 100 участников, так что скорость запроса данных низкая и вряд ли они будут в дисковом кэше.
  • Большие публичные серверы Discord отправляют очень много сообщений. Там тысячи участников, отправляющих тысячи сообщений в день. Легко набираются миллионы сообщений в год. Они почти всегда запрашивают сообщения, отправленные в последний час, и это происходит часто. Поэтому данные обычно находятся в дисковом кэше.
  • Мы знали, что в наступающем году у пользователей появится ещё больше способов генерировать случайные чтения: это возможность просматривать свои упоминания за последние 30 дней и затем перескакивать в тот момент истории, просмотр и переход к прикреплённым сообщениям и полнотекстовый поиск. Всё это означает ещё больше случайных чтений!

Затем мы определили наши требования:

  • Линейная масштабируемость — Мы не хотим пересматривать решение позже или вручную переносить данные в другой шард.
  • Автоматическая отказоустойчивость — Нам нравится спать по ночам и делать Discord настолько самоисцеляющимся, насколько это возможно.
  • Небольшая поддержка — Она должна работать сразу же, как мы её установим. От нас требуется только добавлять больше нод по мере увеличения данных.
  • Доказано в работе — Мы любим пробовать новые технологии, но не слишком новые.
  • Предсказуемая производительность — Нам отправляются сообщения, если время отклика API в 95% случаев превышает 80 мс. Мы также не хотим сталкиваться с необходимостью кэшировать сообщения в Redis или Memcached.
  • Не хранилище блобов — Запись тысяч сообщений в секунду не будет отлично работать, если нам придётся непрерывно десериализировать блобы и присоединять к ним данные.
  • Open source — Мы верим, что управляем собственной судьбой, и не хотим зависеть от сторонней компании.

Cassandra оказалась единственной СУБД, которая удовлетворила всем нашим требованиям. Мы можем просто добавлять ноды при масштабировании, а она справляется с потерей нод без всякого влияния на приложение. В больших компаниях вроде Netflix и Apple — тысячи нод Cassandra. Связанные данные хранятся рядом на диске, обеспечивая минимум операций поиска и лёгкое распределение по кластеру. Она поддерживается компанией DataStax, но распространяется с открытым исходным кодом и силами сообщества.

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

Моделирование данных


Лучший способ описать новичку Cassandra — это аббревиатура KKV. Две буквы “K” содержат в себе первичный ключ. Первая “K” — это ключ раздела. Он помогает определить, в какой ноде живут данные и где их найти на диске. Внутри раздела множество строк, и конкретную строку внутри раздела определяет вторая “K” — ключ кластеризации. Он работает как первичный ключ внутри раздела и определяет способ сортировки строк. Можете представить раздел как упорядоченный словарь. Все эти качества вместе взятые позволяют очень мощное моделирование данных.

Помните, что сообщения в MongoDB индексировались с использованием channel_id и created_at? channel_id стал ключом раздела, поскольку все сообщения работают в канале, но created_at не даёт хорошего ключа кластеризации, потому что два сообщения могут быть созданы в одно время. К счастью, каждый ID в Discord на самом деле создан в Snowflake, то есть хронологически сортируется. Так что можно было использовать именно их. Первичный ключ превратился в (channel_id, message_id), где message_id — это Snowflake. Это значит, что при загрузке канала мы можем сказать Cassandra точный диапазон, где искать сообщения.

Вот упрощённая схема для нашей таблицы сообщений (она пропускает примерно 10 колонок).

CREATE TABLE messages (
  channel_id bigint,
  message_id bigint,
  author_id bigint,
  content text,
  PRIMARY KEY (channel_id, message_id)
) WITH CLUSTERING ORDER BY (message_id DESC);

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

Как только начался импорт существующих сообщений в Cassandra, мы сразу увидели в логах предупреждения, что найдены разделы размером более 100 МБ. Да ну?! Ведь Cassandra заявляет о поддержке разделов 2 ГБ! По всей видимости, сама возможность не означает, что так нужно делать. Большие разделы накладывают сильную нагрузку на сборщик мусора в Cassandra при уплотнении, расширении кластера и т.д. Наличие большого раздела также означает, что данные в нём нельзя распределить по кластеру. Стало ясно, что нам придётся как-то ограничить размеры разделов, потому что некоторые каналы Discord могут существовать годами и постоянно увеличиваться в размере.

Мы решили распределить наши сообщения блоками (buckets) по времени. Мы посмотрели на самые большие каналы в Discord и определили, что если хранить сообщения блоками примерно по 10 дней, то комфортно вложимся в лимит 100 МБ. Блоки нужно получать из message_id или метки времени.

DISCORD_EPOCH = 1420070400000
BUCKET_SIZE = 1000 * 60 * 60 * 24 * 10

def make_bucket(snowflake):
   if snowflake is None:
       timestamp = int(time.time() * 1000) - DISCORD_EPOCH
   else:
       # When a Snowflake is created it contains the number of
       # seconds since the DISCORD_EPOCH.
       timestamp = snowflake_id >> 22
   return int(timestamp / BUCKET_SIZE)
  
def make_buckets(start_id, end_id=None):
   return range(make_bucket(start_id), make_bucket(end_id) + 1)

Ключи разделов Cassandra могут быть составными, так что нашим новым первичным ключом стал ((channel_id, bucket), message_id).

CREATE TABLE messages (
   channel_id bigint,
   bucket int,
   message_id bigint,
   author_id bigint,
   content text,
   PRIMARY KEY ((channel_id, bucket), message_id)
) WITH CLUSTERING ORDER BY (message_id DESC);

Для запроса недавних сообщений в канале мы сгенерировали диапазон блоков от текущего времени до channel_id (он тоже хронологически сортируется как Snowflake и должен быть старше, чем первое сообщение). Затем мы последовательно опрашиваем разделы до тех пор, пока не соберём достаточно сообщений. Обратная сторона такого метода в том, что изредка активным инстансам Discord придётся опрашивать много разных блоков, чтобы собрать достаточно сообщений со временем. На практике оказалось, что всё в порядке, потому что для активного инстанса Discord обычно находится достаточно сообщений в первом разделе, и таких большинство.

Импорт сообщений в Cassandra прошёл без помех, и мы были готовы опробовать её в производстве.

Тяжёлый запуск


Выводить новую систему в производство всегда страшно, так что хорошей идеей будет проверить её, не затрагивая пользователей. Мы настроили систему на дублирование операций чтения/записи в MongoDB и Cassandra.

Немедленно после запуска в баг-трекере появились ошибки, что author_id равен нулю. Как он может быть нулевым? Это обязательное поле!

Согласованность в конечном счёте


Cassandra — система типа AP, то есть гарантированная целостность здесь приносится в жертву доступности, что мы и хотели, в общем. В Cassandra противопоказано чтение перед записью (операции чтения более дорогие) и поэтому всё, что делает Cassandra, — это обновление и вставку (upsert), даже если предоставить только определённые колонки. Вы также можете писать в любую ноду, и она автоматически разрешит конфликты, используя семантику «последняя запись выигрывает» по каждой колонке. Так как это нас коснулось?


Пример состояния гонки редактирование/удаление

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

  1. Записывать обратно целое сообщение во время редактирования сообщения. Тогда есть возможность воскрешения удалённых сообщений и добавляются шансы конфликтов для одновременных записей в другие колонки.
  2. Выявить повреждённое сообщение и удалить его из базы.

Мы выбрали второй вариант, определив требуемую колонку (в этом случае author_id) и удаляя сообщение, если оно пустое.

Решая эту проблему, мы заметили, что были весьма неэффективны с операциями записи. Поскольку Cassandra согласована в конечном счёте, то она не может вот так взять и немедленно удалить данные. Ей нужно реплицировать удаления на другие ноды, и это следует сделать даже если ноды временно недоступны. Cassandra справляется с этим, приравнивая удаление к своеобразной форме записи под названием “tombstone” («надгробие»). Во время операции чтения она просто проскакивает через «надгробия», которые встречаются по пути. Время жизни «надгробий» настраивается (по умолчанию, 10 дней), и они навсегда удаляются во время уплотнения базы, если срок вышел.

Удаление колонки и запись нуля в колонку — это абсолютно одно и то же. В обоих случаях создаётся «надгробие». Поскольку все записи в Cassandra являются обновлениями и вставками, то вы создаёте «надгробие» даже если изначально записываете нуль. На практике, наша полная схема сообщения состояла из 16 колонок, но среднее сообщение имело только 4 установленных значения. Мы записывали 12 «надгробий» в Cassandra, обычно без всякой причины. Решение проблемы было простым: записывать в базу только ненулевые значения.

Производительность


Известно, что Cassandra быстрее выполняет операции записи, чем чтения, и мы наблюдали в точности это. Операции записи происходили в интервале менее миллисекунды, а операции чтения — менее 5 миллисекунд. Такие показатели наблюдались независимо от типа данных, к которым осуществлялся доступ. Производительность сохранялась неизменной в течение недели тестирования. Ничего удивительного, мы получили в точности то, чего ожидали.


Задержка чтения/записи, по данным из лога

В соответствии с быстрой, надёжной производительностью чтения, вот пример перехода к сообщению годичной давности в канале с миллионами сообщений:



Большой сюрприз


Всё прошло гладко, так что мы выкатили Cassandra как нашу основную базу данных и вывели из строя MongoDB в течение недели. Она продолжала безукоризненно работать… примерно 6 месяцев, пока однажды не перестала реагировать.

Мы заметили, что Cassandra непрерывно останавливается на 10 секунд во время сборки мусора, но совершенно не могли понять, почему. Начали копать — и нашли канал Discord, который требовал 20 секунд для загрузки. Виновником был публичный Discord-сервер подреддита Puzzles & Dragons. Поскольку он публичный, мы присоединились посмотреть. К нашему удивлению, на канале было только одно сообщение. В тот момент стало очевидно, что они удалили миллионы сообщений через наши API, оставив только одно сообщение на канале.

Если вы внимательно читали, то помните, как Cassandra обрабатывает удаления при помощи «надгробий» (упомянуто в главе «Согласованность в конечном счёте»). Когда пользователь загружает этот канал, хоть там одно сообщение, Cassandra приходится эффективно сканировать миллионы «надгробий» сообщений. Тогда она генерирует мусор быстрее, чем JVM может собрать его.

Мы решили эту проблему следующим образом:

  • Уменьшили время жизни надгробий с 10 дней до 2 дней, потому что мы каждый вечер запускаем починку Cassandra (противоэнтропийный процесс) на нашем кластере сообщений.
  • Изменили код запросов, чтобы отслеживать пустые блоки на канале и избегать их в будущем. Это значит, что если пользователь снова инициировал этот запрос, то в худшем случае Cassandra будет сканировать только самый последний блок.

Будущее


В данный момент у нас работает кластер из 12 нодов с коэффициентом репликации 3, и мы продолжим добавлять новые ноды Cassandra по мере надобности. Мы верим, что этот подход работоспособен в долговременной перспективе, но по мере роста Discord просматривается отдалённое будущее, когда придётся сохранять миллиарды сообщений в день. У Netflix и Apple работают кластеры с сотнями нодов, поэтому пока что нам не о чем волноваться. Однако хочется иметь пару идей про запас.

Ближайшее будущее


  • Обновить наш кластер сообщений с Cassandra 2 на Cassandra 3. Новый формат хранения в Cassandra 3 может сократить объём хранения более чем на 50%.
  • Более новые версии Cassandra лучше справляются с обработкой большего количества данных в каждом ноде. Мы сейчас храним примерно 1 ТБ сжатых данных в каждом из них. Думаем, что можно безопасно сократить количество нодов в кластере, увеличив этот лимит до 2 ТБ.


Отдалённое будущее


  • Изучить Scylla — это СУБД, совместимая с Cassandra и написанная на C++. В нормальной работе наши ноды Cassandra в реальности потребляют немного ресурсов CPU, однако в непиковые часы во время починки Cassandra (противоэнтропийный процесс) они довольно сильно зависят от CPU, а время починки возрастает в зависимости от количества данных, записанных с момента прошлой починки. Scylla обещает значительно увеличить скорость починки.
  • Создать систему для архивации неиспользуемых каналов в Google Cloud Storage и загрузки их обратно по требованию. Мы хотим избежать этого и не думаем, что такое придётся делать.

Заключение


Прошло уже больше года с момента перехода на Cassandra, и несмотря на «большой сюрприз», это было спокойное плавание. Мы вышли с более 100 миллионов общего количества сообщений на более чем 120 миллионов сообщений в день, сохранив производительность и стабильность.

Благодаря успеху этого проекта, с тех пор мы перенесли все остальные наши данные в производстве на Cassandra, и тоже успешно.

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

У нас до сих пор нет специализированных инженеров DevOps (только четыре инженера бэкенда), так что очень классно иметь систему, о которой не приходится волноваться. Мы набираем сотрудников, так что обращайтесь, если подобные задачки щекочут ваше воображение.
Поделиться с друзьями
-->

Комментарии (55)


  1. jacob1237
    11.03.2017 17:19
    +11

    Изначальную версию Discord написали быстрее чем за два месяца в начале 2015 года

    Интересно как много человек участвовало в этом процессе. Production-ready приложение быстрее чем за два месяца — звучит вызывающе.


    1. phoenixweiss
      11.03.2017 23:58
      +1

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


    1. Nakosika
      13.03.2017 01:08
      +1

      Когда нет легаси и все на энтузиазме еще и не такое возможно.


    1. Kesantielu
      21.03.2017 10:51

      Павел Дуров: «Первую версию ВКонтакте в 2006 году я собрал за месяц».


  1. ZOXEXIVO
    11.03.2017 22:36

    не собираемся использовать шардинг MongoDB из-за его сложности и неизвестной стабильности

    в статье не хватает практических подтверждений таких заявлений


    1. QtRoS
      11.03.2017 22:55
      +5

      Про монгу много слухов ходит (про потерю данных, нестабильность, etc), и вот в чем проблема — им нет опровержений. Вам есть что сказать на эту тему? Я думаю, не одному мне было бы интересно послушать контраргументы.


      1. leventov
        12.03.2017 03:52
        +2

        Вроды бы Афир наконец-то починил Монгу: https://jepsen.io/analyses/mongodb-3-4-0-rc3


        1. leventov
          12.03.2017 04:03
          +1

          Хотя вообще, полностью поддерживаю этот FUD по отношению к Монге, мое мнение — изначально настолько плохо написанные проекты баз данных не вытягиваются на новый технический уровень никакими заплатками и рефакторингами, кроме полного переписывания с нуля. Даже если сейчас они что-то починили, очень большая вероятность, что опять сломают в следующей версии. Если не баг в распределенной системе, так какую-нибудь регрессию по скорости схватят.


      1. ZOXEXIVO
        15.03.2017 21:55
        +1

        В интернете кричат, в основном пользователи с негативным опытом, хотя стоит признать, что до DocumentLevel lock все было очень печально, но и тут люди умело обходили проблемы.
        Проблемы под нагрузкой есть у всех, но у MongoDB в свое время были слишком сильные фейлы, которые хорошо отложились в памяти (суточный downtime у Foursquare)
        1) http://www.serverdensity.com в 2015 назад обрабатывал 350ТБ данных в месяц на шардированной MongoDB, причем время отклика было в среднем 40ms (на MMAPv1), чего уже говорить о WiredTiger.
        2) CraigList, Disqus, но это все и так известно. Есть даже целый список из 4000 компаний по всему миру.
        Сейчас, кстати, есть тенденция, что к Mongo возвращаются разочарованные и идет большой поток новичков, потому как детский ошибки пройдены и сейчас она выглядит очень неплохо


    1. phoenixweiss
      12.03.2017 00:04
      +4

      Мне кажется ребята просто не хотели лишний раз рисковать превозмогая в случае чего. Это как если вы услышите статистику «самолеты авиакомпании N разбиваются значительно чаще чем у компании M», подсознательно будете при возможности стараться выбирать авиакомпанию с учетом этой информации, потому что цена ошибки может быть крайне высока даже если в реальности это не так или выбор стоит между одним к ста тысячам или одним к двухста тысячам случаев.
      Возможно не самый красивый и правильный пример, но он помогает посмотреть немного под другим углом.


  1. vanburg
    12.03.2017 00:03
    +3

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


  1. Falseclock
    12.03.2017 08:45
    -4

    Мне кажется чат надо хранить в JSON либо в другом сериализаторе. Или хотя бы XML.
    1. Чат это текст, соответственно хранить как текст
    2. Отправитель и получатель никогда не изменяются
    3. Данные о дате сообщения никогда не изменяются.

    так почему бы не хранить это статически?

    В случае чата каждый с каждым — это не куча записей в базе по каждому сообщению, а только одна запись диалога… при чем даже не надо на стороне сервера что-то парсить, пусть делает JS и рисует на основе данных интерфейс.

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


    1. Carburn
      12.03.2017 09:22

      Вы предлагаете работать на уровне локальной файловой системы без распределенности?


      1. Falseclock
        12.03.2017 09:34

        я предлагаю хранить текст чата в БД в виде JSON строки. Это логически правильно. Тем более базы умеет делать выборку по JSON, а некоторые не то чтобы обновлять отдельные поля, но и даже строить индексы по полям в JSON.

        Если хранить каждое сообщение отдельно, как делают в статье, то если есть 10 чатов по 10 человек и каждый напишет в чат по одному сообщению, то это получится будет 100 записей. Если хранить диалог, то это так и останется 10 строк в базе, что на порядок меньше. А если количество пользователей и их сообщений будет увеличиваться арифметически, то количество строк будет увеличиваться геометрически. Поэтому и пришли к тупику как хранить миллиарды сообщений. Завтра им придется хранить 10 или 100 миллиардов сообщений и они опять придут к тупику как жить дальше.


        1. Carburn
          12.03.2017 09:49

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


          1. Falseclock
            12.03.2017 09:56

            и какие ограничения? 1 гигабайт? Как у текстового поля? json тип и есть текстовое поле, только с валидацией.

            записать пару мегабайт текста проще чем обновить таблицу с миллиардами записей из-за перестроения индексов. Да и я уже ниже написал, что чат размером в 2 мегабайта — это предел мечтаний.

            кстати вот статья про постгрес и бенчмарки работы с JSON https://hashrocket.com/blog/posts/faster-json-generation-with-postgresql


            1. Carburn
              12.03.2017 10:24

              Какие миллиарды записей, скорее миллион? В разделе хранятся данные из чата за 10 дней ? 100 МБ.
              Сообщения удаляются не так часто чтобы перестроение индексов было критичным.
              Вы какую СУБД предлагаете использовать?


              1. Falseclock
                12.03.2017 10:30

                Какие миллиарды записей, скорее миллион? В разделе хранятся данные из чата за 10 дней ? 100 МБ.
                а заголовок о чем говорит? Говорит о том что успешные пацаны научились без тормозов хранить 1кк записей. У меня сразу вопрос: а что будет если их будет 10кк или 100кк. Опять будут думать как хранить данные?

                Сообщения удаляются не так часто чтобы перестроение индексов было критичным.
                индексы перестраиваются при UPDATE, DELETE, INSERT.

                Вы какую СУБД предлагаете использовать?
                я не предлагаю какую-либо БД конкретно, я взываю к логике: хранить не отдельные реплики, а хранить диалоги в одном месте. Как уже приводил пример с диктофоном ниже.


                1. Carburn
                  12.03.2017 10:59
                  +1

                  Будут использовать горизонтальное масштабирование и всё, просто кол-во чатов (разделов) увеличится.


            1. Carburn
              12.03.2017 11:12
              +1

              2МБ это за какое время чат. У них чат за 10 дней весит 50-90 МБ.
              Я считаю, что в Cassandra при одинаковом кол-ве сообщений добавление строки в таблицу сообщений это быстрее, чем считывание json строки из ячейки базы данных, добавление объекта сообщения и обратная запись в базу данных.


              1. Falseclock
                12.03.2017 11:33

                Можете так дальше считать.

                Но предлагаю задуматься и ответить что быстрее:

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


                1. Carburn
                  12.03.2017 12:09
                  +1

                  Поле message_id все время увеличивается, так что индексы перестаиваться будут быстро.
                  В первом случае нет необходимости хранить миллиард сообщений в одном разделе Cassandra, можно хранить сотни тысяч сообщений.


                  1. Falseclock
                    12.03.2017 12:21

                    так что индексы перестаиваться будут быстро.

                    а при чем тут быстро или не быстро обновляется индекс? Вопрос что из двух вариантов быстрее и жрет меньше процессорного времени?

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

                    можно хранить сотни тысяч сообщений

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


                    1. Carburn
                      12.03.2017 12:33
                      +2

                      Это не просто обновление поля, а


                      считывание json строки из ячейки базы данных, добавление объекта сообщения и обратная запись в базу данных.

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


                      вот к этому и приходят, когда нет нормальной архитектуры, начинают партиционировать таблицы

                      Вообще-то это называется распределенная архитектура. Не хватает ресурсов и мощностей одного сервера для работы с единой базой данных.


              1. Falseclock
                12.03.2017 11:38

                При этом вы должны понимать, что не надо весь текст вытаскивать из ячейки, чтобы его обновить, достаточно туда просто добавить текст, а ля update table set text = text + new_text.


                1. Carburn
                  12.03.2017 12:14

                  Cassndra этого не поддерживает.


          1. Falseclock
            12.03.2017 10:11

            Вот у вас есть на телефон диктофон. Вы записываете разговор 10-рых людей. Вы же не пишете слова или реплики каждого человека в отдельный файл. Вы записываете всех и сразу, в единую запись. Чем чат отличается от диалога на диктофон? Да по сути ничем. Вы собираетесь менять слова говорящих, их интонацию, последовательность разговора? Нет конечно. Так и с чатом. Все что было сказано — это единое целое и зачем это разбивать на отдельный записи каждой реплики, если там ничего не изменится? Ни автор, ни дата, ни последовательность? Так хранить это как единый диалог всех участников.


            1. 4umak
              12.03.2017 10:36
              +5

              Вы собираетесь менять слова говорящих, их интонацию, последовательность разговора?

              Конечно собираемся. Именно для этого есть возможности редактировать и удалять сообщения. А также перемещаться к конкретным репликам. Мне кажется, диктофонная запись в данном случае — не самая подходящая аналогия, увы:)


              1. MMik
                21.03.2017 10:51

                А нельзя позволять редактирование и удаление. Всё введённое должно храниться для спецслужб. Удаление и редактирование – это слишком дорогие по IO и особенно по seek операции, и они редко выполняются пользователями. Ведите рядом ещё один файл, в который пишите своеобразный diff от основного. И ещё один индексный файл. Перемещаться по репликам можно на клиенте.


                1. 4umak
                  21.03.2017 11:00

                  Вот бы сейчас IO начало бы определять бизнес задачи:)


        1. napa3um
          12.03.2017 10:08

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


          1. Carburn
            12.03.2017 10:30
            +1

            Они же написали, что не доверяют шардингу в MongoDB. Структура была такая же.


            1. napa3um
              12.03.2017 10:31
              +1

              Вот структуру и нужно было менять.


              1. Carburn
                12.03.2017 11:21

                Я хотел сказать обратное, хранить данные в массиве внутри объекта в MongoDB плохо, потому что существует ограничение на размер объекта в 16 МБ, ограничение же на размер коллекции – 4,2 млн документов и 32 терабайта.


                1. napa3um
                  12.03.2017 11:22

                  Да, программировать не так просто, как может показаться, всюду куча ограничений. Так и живём.


                  1. Carburn
                    12.03.2017 11:26

                    Так вы теперь за или против массива сообщений в одном документе?


                    1. napa3um
                      12.03.2017 11:32

                      Конечно я за «несколькоуровневую» денормализацию данных в информационных системах, упирающихся в производительность при эксплуатации и масштабировании. Решать все свои проблемы только лишь выбором ПО (пафосно выписывая в столбик эпические требования своего грандиозного бизнеса к этому ПО) не получится, рано или поздно придётся проектировать и программировать.


                1. lega
                  12.03.2017 12:44

                  потому что существует ограничение на размер объекта в 16 МБ
                  Это совсем не причина. Не хватает одного документа — возьмите два, GridFS так и работает, хранит гигабайтные файлы в монге разбиавая на чанки. Считать 2 документа будет в сотни (тысячи) раз быстрее чем выкачать миллионы документов, не говоря об гигантской экономии памяти и диска.
                  Кроме того если сжать текст (xz/bzip) то в 16Мб можно уместить до 50-200Мб текста.

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


                  1. Carburn
                    12.03.2017 12:46

                    При чем тут MongoDB, когда разговор о Cassandra?


                    1. Carburn
                      12.03.2017 12:55

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


                1. grossws
                  12.03.2017 19:46

                  ограничение же на размер коллекции – 4,2 млн

                  скорее 4.2 млрд, т. е. int32. У меня в коллекция малые сотни миллионов прекрасно живут без шардинга.


                  1. ZOXEXIVO
                    13.03.2017 00:31

                    Нет в ней никаких ограничений на количество элементов, только на размер коллекции


                    1. Carburn
                      13.03.2017 01:47

                      a single MMAPv1 database has a maximum size of 32TB.

                      https://docs.mongodb.com/manual/reference/limits/#Database-Size


                  1. Carburn
                    13.03.2017 01:14

                    Да, конечно 4.2 млрд.


                    1. rbatoon89
                      14.03.2017 11:18
                      +1

                      Пруф есть? Это же только для capped collection


    1. 4umak
      12.03.2017 09:31
      +1

      > при чем даже не надо на стороне сервера что-то парсить, пусть делает JS и рисует на основе данных интерфейс.

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

      Ну и как отметил товарищ выше, не совсем понятно, как резервировать такие данные и масштабировать такую систему.


      1. Falseclock
        12.03.2017 09:42
        -2

        два мегабайта — это 2 097 000 байт. Длина одного символа UTF8 составляет от 1 до 6 байт. Возьмем среднее значение 3. То есть в 2 097 000 уместится 699000 тысяч символов. Средняя длина слова в английском языке составляет около 5 символов, соответственно это около 139800 слов. Средняя длина предложения в чате — 6 слов. То есть 23 300 предложений или сообщений в одном чате. Где вы видели чаты с таким количеством сообщений?

        И потом никто не предлагает весь чат передавать на сторону клиента. Подгружать только последние 30-40 допустим, а если человек хочет прошлые посмотреть, загружай нужный период через AJAX и отдавай пользователю кусками.


        1. Carburn
          12.03.2017 09:55
          +1

          В статье же написано


          Тяжёлые серверы приватных текстовых чатов Discord отправляют приличное количество сообщений, легко попадая в диапазон между 100 тыс. и 1 млн сообщений в год.


          1. Falseclock
            12.03.2017 09:58
            -1

            так это сумма всех приватных сообщений, а не в одном чате.


            1. 4umak
              12.03.2017 10:32

              «Приватных» в данном случае, как я понял, противопоставлено публичным, т.е. общедоступным чатам. Т.е. под приватными чатами подразумеваются закрытые групповые чаты, а не чья-либо личка. Особенно если учесть фразу:

              Виновником был публичный Discord-сервер подреддита Puzzles & Dragons. Поскольку он публичный, мы присоединились посмотреть.


              Но, т.к. дополнительных пояснений нет, то трактоваться может двояко, да


            1. 4umak
              12.03.2017 10:33
              +2

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


      1. Falseclock
        12.03.2017 09:48
        -1

        да даже если пользователю через websocket или AJAX отгрузить 2 мегабайта данных, то на стороне JS можно также делать выборку из цикла. На крайний случай разбить на несколько массивов, например помесячно или недельно. Так работают Whatsapp и Skype. Они отдают кусками, при подгрузке.


  1. Krey
    14.03.2017 19:53

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

    Разве что клавишей PgUp? Никаких других средств для этого в дискорде я не нашел. Ни перехода к дате, ни поиска по истории… По факту в высоконагруженных чатах указанным функционалом пользователь воспользоваться не может.


    1. zartarn
      16.03.2017 16:49

      Вы сильно отстали от жизни, уже несколько месяцев как есть. Всё прекрасно ищется, релевантный поиск в т.ч. хорошо работает
      image


      1. Krey
        16.03.2017 20:25

        А, да, спасибо. Надо было обновиться.