Собесов

Сценарий: чанковая обработка CSV, который не помещается в память

PythonВекторизация и performanceСредняяMiddle

Условие

Есть CSV 40 ГБ — лог событий events.csv с колонками ts, user_id, event, value. Нужно посчитать DAU и суммарный value по дням, имея 8 ГБ RAM.

Решение

Подход

Читаем чанками через chunksize, инкрементально считаем агрегаты, в конце сводим.

Реализация

import pandas as pd
from collections import defaultdict
 
day_users = defaultdict(set)   # day -> set of users
day_value = defaultdict(float)
 
reader = pd.read_csv(
    'events.csv',
    chunksize=1_000_000,
    usecols=['ts', 'user_id', 'value'],
    parse_dates=['ts'],
    dtype={'user_id': 'int32', 'value': 'float32'},
)
 
for chunk in reader:
    chunk['day'] = chunk['ts'].dt.date
    for day, sub in chunk.groupby('day'):
        day_users[day].update(sub['user_id'].unique())
        day_value[day] += sub['value'].sum()
 
result = pd.DataFrame({
    'day': list(day_users.keys()),
    'dau': [len(u) for u in day_users.values()],
    'total_value': [day_value[d] for d in day_users.keys()],
}).sort_values('day')

Ускорение

# Альтернатива: один проход через polars (lazy)
import polars as pl
result = (
    pl.scan_csv('events.csv')
      .with_columns(pl.col('ts').str.to_datetime().dt.date().alias('day'))
      .group_by('day')
      .agg(pl.col('user_id').n_unique().alias('dau'), pl.col('value').sum())
      .collect()
)

Подводные камни

  1. set(user_id) всё ещё растёт по памяти — для миллиардов уникальных юзеров используйте HyperLogLog (datasketch) с погрешностью <1%.
  2. usecols= уменьшает память в разы — читать только нужные колонки.
  3. dtype= обязателен: int32 вместо дефолтного int64, float32 вместо float64 экономит в 2 раза.
  4. parse_dates в чанках работает, но медленный — иногда быстрее читать как строку и парсить вручную одной функцией.
  5. Не суммируйте через pd.concat все чанки — это вернёт всю память и съест преимущество чанков.

Эталонный ответ

pd.read_csv(chunksize=..., usecols=..., dtype=...), инкрементально агрегировать, не накапливать чанки в один df. Или сразу polars/Dask с lazy-планом.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти