Не так давно, работая в SuperJob, в рамках распила монолита, вынесли в микросервис на Go поиск рекламных объявлений по таргетингам соискателя. Возникла задача логировать в Clickhouse все таргетинги, по которым идет поиск объявлений. Обработчик запросов принимает параматеры (таргетинги) и ищет по ним объявления. Средняя нагрузка на сервис ~ 2470 запросов в минуту. Таким образом создается ~2470 запросов на вставку в БД Clickhouse.
Задача заключалась в том, чтобы не исполнять каждую команду в базу данных по отдельности, а вместо этого сгруппировать все команды вместе, в одну большую операцию.
Go предлагает очень элегантный способ решить данную задачу.
1. Создаем канал
CommandsChannel = make(chan AdvertisingRequest)
2. При каждом поступающем запросе на поиск объявлений добавляем в канал параметры таргетингов
CommandsChannel <- AdvertisingRequest{ idRequest: uuid.New(), ip: uint32(100.64.2.2), bid: 500, eventId: "64393b4b03ca70.39019462", ..., createdAt: time.Now(), }
3. В горутине создаем карту и счетчик
func sync() { commands := make(map[int]AdvertisingRequest) ctr := 0 for { select { case cmd := <-CommandsChannel: ctr++ commands[ctr] = cmd } case <-time.After(10 * time.Second): if ctr > 0 { batch, _ := conn.PrepareBatch(context.Background(), "INSERT INTO EventServiceAdvertisingLoggerRequest") for id, command := range commands { batch.Append( cmd.idRequest, cmd.ip, cmd.bid, cmd.eventId, ..., cmd.createdAt, ) delete(v, id) } batch.Send() } ctr = 0 } }
В горутине CommandReady
происходит оптимизация. Вместо того, чтобы обрабатывать каждую команду по мере ее поступления, горутина использует оператор select
, либо для получения команды, либо для тайм-аута после десяти секунд бездействия.
Пока сообщения поступают, оператор select
будет выбирать случай получения сообщения и сохранять команду в карте readyCmd
.
Когда сообщения перестанут приходить на десять секунд, оператор select
перейдет во второй вариант: time.After(10 * time.Second)
.
Во втором случае карта readyCmd
очищается и все команды отправляются как одна операция. Далее в коде большой оператор INSERT
, включающий все команды, выполняется для базы данных Clickhouse.
По сути, этот алгоритм очень похож на японский shishi-odoshi (принцип работы показан на картинке статьи).
Текущая логика еще не оптимальна. В настоящее время он не устанавливает максимальный размер пакета, в основном потому, что в настоящее время в этом нет необходимости. В моей производственной среде максимальный размер запроса составляет ~ 220 байт, и этого достаточно, чтобы не беспокоиться об ограничении размера пакета.