Из нескольких каналов вперемешку приходят события разных проектов. Писать
каждое событие в хранилище по одному дорого, поэтому их нужно собирать в
батчи по проекту и отдавать дальше одним каналом.
type Event struct {
ProjectID string
Data []byte
}
type Batch struct {
ProjectID string
Events []Event
}
type AggregatorCfg struct {
FlushEvery time.Duration
MaxPerProject int
}
type Aggregator struct {
cfg AggregatorCfg
}
func NewAggregator(cfg AggregatorCfg) *Aggregator
func (a *Aggregator) Run(ctx context.Context, ins []<-chan Event) <-chan Batch
Требования
Run сразу возвращает выходной канал и читает все ins одновременно:
пустой открытый вход не мешает остальным.
- Каждый батч содержит события одного проекта, не пустой.
- Как только по проекту накопилось
MaxPerProject событий, батч уходит сразу,
не дожидаясь таймера. MaxPerProject < 1 считается как 1.
- Раз в
FlushEvery все накопленные непустые буферы отправляются (по батчу на
проект). FlushEvery <= 0 — сброса по таймеру нет.
- Когда все входы закрыты, остатки отправляются и выходной канал
закрывается. Нет входов — сразу закрытый канал.
- Отмена
ctx — аварийная остановка: выход закрывается, неотправленные
остатки отбрасываются, все горутины завершаются, даже если потребитель
перестал читать выход.
- Внутри проекта события одного входа идут в порядке поступления. Каждое
событие попадает ровно в один батч.
- Отправленный батч больше не меняется: его
Events не переиспользуется
под следующие события.
- Код проходит
go test -race.
Пример
agg := NewAggregator(AggregatorCfg{FlushEvery: time.Second, MaxPerProject: 3})
out := agg.Run(ctx, []<-chan Event{in1, in2})
// in1: A1 A2 B1, in2: A3 — сразу батч A[A1 A2 A3] (или в другом порядке
// между входами); B[B1] — через секунду по таймеру
Устно: что делать, если потребитель батчей медленный и выход забит: копить
дальше, блокировать входы, терять события? Как сделать graceful shutdown, чтобы
при остановке сервиса остатки не терялись?
Разбор и эталонное решение — после своей попытки: на собеседовании подсказок не будет.
Сначала попробуй сам — подсказки не дают готовый ответ.