> Как пересчитывать и хранить агрегации для текущих периодов (час, день) для актуальных данных (Go)

Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: BrightPattern

Стек: Go

> Пример ответа

Для хранения и пересчёта агрегаций за текущие периоды (час, день) в Go я рекомендую использовать подход с материализованными представлениями в памяти с периодической синхронизацией в БД (например, ClickHouse или PostgreSQL с партиционированием).

Архитектура:

  1. In-memory счётчики - структуры с sync.RWMutex или атомарными операциями для быстрой записи.
  2. Буферизация - каждые N секунд (например, 10-60) сбрасываем накопленные данные в БД через батчи.
  3. Ключ агрегации - комбинация period_start (округлённый до часа/дня) + метка (например, endpoint).

Пример на Go:

GO
type HourlyAgg struct {
mu sync.RWMutex
counters map[string]int64 // ключ: "2025-03-20T14:00:00|/api/v1"
}
func (a *HourlyAgg) Increment(endpoint string) {
now := time.Now().Truncate(time.Hour)
key := fmt.Sprintf("%s|%s", now.Format(time.RFC3339), endpoint)
a.mu.Lock()
a.counters[key]++
a.mu.Unlock()
}
func (a *HourlyAgg) Flush(ctx context.Context, db *sql.DB) {
a.mu.Lock()
data := a.counters
a.counters = make(map[string]int64)
a.mu.Unlock()
// Батчевая вставка в БД
for key, count := range data {
parts := strings.SplitN(key, "|", 2)
_, err := db.ExecContext(ctx,
`INSERT INTO hourly_metrics (period, endpoint, count) VALUES ($1, $2, $3)
ON CONFLICT (period, endpoint) DO UPDATE SET count = count + $3`,
parts[0], parts[1], count)
if err != nil {
log.Printf("flush error: %v", err)
}
}
}

Ключевые моменты:

  • Для текущего часа/дня данные в памяти - это "горячие" агрегаты, которые могут быть неполными. При старте приложения загружаем из БД последние периоды и продолжаем накапливать.
  • Пересчёт - если нужна точность, раз в минуту запускаем фоновую задачу, которая пересчитывает агрегаты за текущий период из сырых событий (например, из Kafka или таблицы логов) и обновляет материализованное представление.
  • Для высокой нагрузки используйте шардирование по ключу (например, по endpoint) и пакетную обработку через каналы.

Такой подход балансирует между производительностью (быстрая запись в память) и надёжностью (периодическая персистентность).

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью