Как ускорить процесс записи в базу данных при высокой нагрузке на запись и низкой на чтение?
Что проверяют
Умеете ли вы найти, во что упирается запись, вместо того чтобы перечислять приёмы наугад. Следом почти всегда спрашивают, чем вы готовы заплатить за скорость: задержкой появления данных, риском потерять последние секунды записи или консистентностью.
Как отвечать
Идите от приложения к диску и называйте, что экономите на каждом шаге:
- пакетная вставка:
addBatch()иexecuteBatch()в JDBC, пачки по нескольку тысяч строк. Драйверу PostgreSQL стоит включитьreWriteBatchedInserts=true, драйверу MySQLrewriteBatchedStatements=true, тогда пачка уходит одним многострочнымINSERT; - Hibernate:
hibernate.jdbc.batch_size,hibernate.order_insertsи отказ отGenerationType.IDENTITY, который выключает батчинг вставок, в пользуSEQUENCEсallocationSize; - транзакции: коммит на каждую строку означает сброс журнала на диск для каждой строки. Одна транзакция на пачку дешевле в разы;
- массовая загрузка:
COPYчерезCopyManagerу PostgreSQL,LOAD DATAу MySQL; - индексы: каждый индекс это ещё одна запись на каждую вставку. При редком чтении лишние индексы убирают первыми;
- буфер в приложении: очередь (Kafka или ограниченная очередь) и отдельный писатель, который сбрасывает данные по размеру пачки или по таймеру;
- настройки базы:
synchronous_commit = offдля некритичных данных, интервал чекпоинтов,UNLOGGED-таблицы для промежуточных данных.
Когда одного узла мало, говорите о партиционировании, шардировании или хранилище под запись: ClickHouse для событий и аналитики, Cassandra с LSM-деревом для потока ключ-значение.
Где спотыкаются
Называют CompletableFuture ускорением записи. Асинхронность разгружает поток запроса, но в базу уходит тот же объём, а без ограничения очереди приложение просто копит задачи в памяти до OutOfMemoryError.
Раздувают пул соединений в надежде писать быстрее. Когда соединений заметно больше, чем ядер у сервера базы, они начинают конкурировать за блокировки и журнал, и пропускная способность падает.
Молчат о цене. synchronous_commit = off может потерять последние транзакции при падении сервера, буфер в памяти теряет всё несброшенное при рестарте приложения.