В каком порядке и синхронно или асинхронно выполнять запись данных
Что проверяют
Умеете ли вы выбирать между надёжностью и скоростью записи и понимаете ли, что асинхронность в Go дешёвая только до первой потерянной записи. Следом спрашивают, что будет с данными в канале при падении сервиса и как согласованно записать в базу и в Kafka.
Как отвечать
Критерий простой: синхронно пишется всё, подтверждение чего нужно клиенту. Если после ответа 200 запись не должна потеряться (платёж, заказ), сначала пишете и проверяете ошибку, потом отвечаете.
Асинхронно пишутся данные, которые можно потерять или догнать: логи, метрики, аналитика. В Go это обычно буферизованный канал и пул воркеров:
- ограниченный буфер: канал фиксированного размера и понятная политика на переполнение: ждать, отбросить или вернуть 503;
- батчинг: воркер копит записи и пишет пачкой по размеру или по таймеру, это главный выигрыш в пропускной способности;
- graceful shutdown: по сигналу закрыть канал, дочитать остаток и дождаться воркеров через
sync.WaitGroupс дедлайном изcontext; - ошибки: параллельные синхронные записи удобно запускать через
errgroup, он вернёт первую ошибку и отменит контекст остальным.
Порядок. Горутины его не гарантируют, поэтому горутина на каждую запись не годится там, где он важен. Записи одного ключа отправляют в один воркер по хешу ключа, в Kafka то же даёт ключ партиции.
Между хранилищами сначала пишется источник истины, база, затем сбрасывается кеш. Писать в базу и в брокер двумя независимыми вызовами нельзя, однажды одна из записей не пройдёт. Для этого есть transactional outbox: событие ложится в ту же транзакцию, а отдельный процесс доставляет его в брокер.
Где спотыкаются
Запускают go write(data) без ограничений. Под нагрузкой это тысячи горутин и открытых соединений, а порядок перемешан.
Считают, что Write без ошибки значит «данные на диске». Они в кеше ОС, долговечность даёт file.Sync(), а ошибку Close тоже нужно проверять.
Выбирают асинхронность ради скорости, не выяснив, какую потерю данных готов принять бизнес.