Как организовать хранение и получение локализованных текстов в разных дата-центрах
Что проверяют
Понимаете ли вы, что локализованные тексты это данные с огромным перекосом в чтение и редкими изменениями, и умеете ли спроектировать под это распределённое хранение без лишней сложности. Следом спрашивают, что увидит пользователь, если дата-центры временно разошлись по версиям, и что будет при отказе одного из них.
Как отвечать
Ключевая идея: не ходить в базу за каждой строкой, а раздавать тексты версионированными сборками.
- единый источник правды: система управления переводами (Weblate, Crowdin) или своя таблица
key, locale, textв одной первичной базе. Пишут туда редко, поэтому конфликтов записи между дата-центрами нет; - публикация сборок: CI выгружает тексты в неизменяемый бандл с номером версии, например
.mo-файлы послеpybabel compileили JSON по локалям, и кладёт в объектное хранилище с репликацией во все дата-центры; - чтение в сервисе: приложение загружает бандл в память при старте, дальше перевод это поиск в словаре. В Django это
gettextизdjango.utils.translation, вне фреймворковbabel.support.Translations; - обновление: указатель на актуальную версию лежит в etcd или Consul, либо сервис опрашивает хранилище и атомарно подменяет словарь;
- цепочка fallback:
ru-RU, затемru, затемen, и только потом сам ключ. Отсутствующий перевод не должен ронять ответ; - множественные формы: для русского CLDR задаёт формы one, few, many и other, поэтому
ngettextили ICU MessageFormat, а не склейка строк.
Фронтенду те же бандлы отдаются через CDN с версией в имени файла, их можно кэшировать надолго. При отказе одного дата-центра остальные работают на своей копии: тексты нужны только на чтение, и загруженная версия остаётся в памяти.
Где спотыкаются
Предлагают мастер-мастер репликацию базы с переводами. Для данных, которые меняются раз в день, это сложность без выигрыша.
Выкатывают код с новым ключом раньше, чем бандл дошёл до всех дата-центров. Пользователь видит сырой ключ, поэтому тексты публикуют до кода, а старые ключи удаляют только после выкатки везде.
Делают сетевой запрос за каждой строкой, и страница из сотни подписей превращается в сотню обращений к кэшу.