FastComments.com Blog

Sun Mar 29 2026
...

FastComments готов к космосу!

! Эта статья содержит техническую терминологию

Что нового

Каждый point-of-presence FastComments теперь принимает записи локально и асинхронно реплицирует их во все остальные узлы. Это обеспечит повышенную надежность по сравнению с предыдущей системой, а также ускорит инструменты модерации для пользователей в некоторых регионах, хотя с определенными компромиссами.

"Готов к космосу" — это немного оптимистично, но идея заключается в том, что мы могли бы развернуть FastComments на разных планетах, и в конечном итоге система синхронизируется. Пользователи на Плутоне, однако, должны будут подождать около суток, чтобы изменения отразились на странице их предстоящего счета, так как только один мастер на регион в настоящее время может агрегировать информацию о выставлении счетов.

Немного истории, почему изменение

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

В конечном итоге мы переместили реплики базы данных во все зоны, где находятся большинство наших пользователей, и также развернули приложение там, а также создали свою собственную систему DNS и прокси (описанную в более позднем блоге), чтобы направлять запросы к ближайшему узлу приложения. Это делает чтение быстрым, но запись — медленной, так как теперь вместо того, чтобы ждать одного HTTP-обратного обращения к бэкенду, вы ждете HTTP-обратного обращения к близлежащему узлу, и этот узел может выполнять несколько записей в базу данных обратно к первичному. Не очень удобно!

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

Одна из проблем, с которой мы столкнулись, запуская Mongo глобально, — это сетевые разделения. Если достаточно много узлов оказывается отключенными, чтения останавливаются, так как каждый узел становится неуверенным в том, приемлемо ли обслуживать чтения. Есть несколько способов обойти это, но крайние случаи становятся запутанными. Это не теоретическая проблема — это происходило достаточно часто, вызывая сообщения в 3 часа ночи, от чего нам стало тошно, даже пытаясь настроить Mongo так, чтобы она справлялась с неопределенностью выборов репликации до одной минуты разницы. К сожалению, сети от Сан-Паулу до Фалькенштейна, например, просто не были очень хорошими у некоторых из наших провайдеров хостинга. Настройка контроля перегрузки помогла, но не решила проблему.

Священный Грааль решения, если вы готовы принять определенные компромиссы, — это возможность принимать записи локально на этом узле (у которого хорошее оборудование, RAID и т.д., что маловероятно, что выйдет из строя) и сообщать пользователю, что их данные сохранены. Вы также можете в этой точке присутствия иметь второй узел в качестве горячей реплики для надежности.

Вот к чему мы пришли. Орегон, Вирджиния, Фалькенштейн, Сан-Паулу, Сингапур — все это свои собственные наборы реплик и принимают записи. Развертывание в ЕС (хотя всего три PoP) ведет себя аналогично.

Как это работает

Некоторые из этих аспектов освещены в предыдущем разделе, но кратко: это CRDT-lite. Мы создали прокси (на Rust, потому что, конечно), который находится между приложением и Mongo и делает её мульти-мастером. Прокси знает о соседях, управляет контрольными точками, репликацией, мониторингом и начальной синхронизацией. Это много-мастерская замена системе репликации Mongo, включая некоторые команды DDL.

Разница с другими инструментами заключается в том, что это не отслеживает oplog. Отслеживание oplog или использование потоков изменений не сработает, потому что они показывают только конечное состояние объекта после записи, что делает невозможным обработку конфликтов. Вам нужно захватывать каждую операцию $set, $inc и реплицировать саму эту операцию.

Это решение специфично для домена. Оно не подойдет для всех продуктов. Можно сказать, что это обусловленный проектированием домен :). Это работает для нас, потому что изначально мы очень тщательно только $set поля, которые мы меняем в документах — мы никогда не используем replaceOne Mongo, например. То же самое касается счетчиков. Вы никогда не делаете SET VOTES = 5. Вместо этого вы бы написали INCREMENT VOTES BY 5, так как это позволяет обеспечить конечную согласованность. Распределенные блокировки обрабатываются полным их избеганием. Только один узел на кластеру имеет установленный флаг для выполнения cron-заданий. Хотя это может показаться ограниченным, мы можем покупать сервера с терабайтами оперативной памяти, поэтому мы можем принять этот компромисс для снижения риска и сложности.

Чтение своих собственных записей

Для разработчиков, использующих API, вы должны иметь возможность читать свои собственные записи так же, как и раньше (сделать API-запрос для создания комментария, затем перечислить комментарии и увидеть новый элемент в этом списке). Условие в том, что вы не можете делать это между регионами. Если ваш бэкенд работает только в одном регионе, например, us-west, то вы должны иметь возможность читать свои собственные записи, кроме случаев, когда между вашей записью и вашим чтением этот узел выходит из строя и ваш DNS-кэш обновляется, чтобы указывать на следующий ближайший узел. При условии, что это не произойдет, чтение своих собственных записей является надежным.

Тестирование и миграция

Около половины кода в системе составляют тестовый функционал, фреймворк и тесты. Тем не менее, выпуск прошел немного неспокойно, потребовав больше времени простоя (1 час для ЕС и 20 минут для us-global), чем ожидалось, но мы рады, что преодолели этот рубеж и благодарим вас за ваше терпение!

В заключение и что это значит для вас

Теперь FastComments должен быть быстрее и надежнее, чем когда-либо, и теперь мы можем вернуться к работе над функциями :)

Ура!