← Назад к вопросам

С каким объемом данных работаешь

1.6 Junior🔥 151 комментариев
#Soft Skills и карьера

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Масштаб работы с данными

В своей практике я работал с довольно крупными объёмами данных. Расскажу о конкретных числах.

Текущий проект

В текущей компании работаю с системой, где:

Размер БД:

  • Основная таблица users: 50+ миллионов записей
  • Таблица transactions: 500+ миллионов записей
  • Таблица analytics_events: 2+ миллиарда записей (partitioned)
  • Общий размер БД: ~300 GB

Ежедневно:

  • 10-15 миллионов новых записей в transaction логи
  • 50 миллионов analytics событий
  • Обработка 1 TB+ данных в месяц

Как я работаю с такими объёмами

1. Индексирование — правильные индексы критичны

-- Без индекса
SELECT * FROM transactions WHERE user_id = 123 AND created_at > '2024-01-01';
-- Seq Scan: 500M rows (5-10 секунд) ❌

-- С индексом
CREATE INDEX idx_transactions_user_created 
ON transactions(user_id, created_at DESC);
-- Index Scan: несколько миллисекунд ✅

2. Partitioning — разделение больших таблиц

-- Таблица analytics_events распределена по месяцам
CREATE TABLE analytics_events (
    id BIGINT,
    user_id BIGINT,
    event_type VARCHAR(50),
    created_at TIMESTAMP,
    data JSONB
) PARTITION BY RANGE (created_at);

CREATE TABLE analytics_events_2024_01 PARTITION OF analytics_events
    FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
    
CREATE TABLE analytics_events_2024_02 PARTITION OF analytics_events
    FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');

Преимущества:

  • Запросы автоматически ищут нужную партицию
  • Быстрее удаляем старые данные (DROP PARTITION)
  • Параллельное сканирование нескольких партиций

3. Оптимизация запросов — EXPLAIN ANALYZE

-- Анализируем плохой запрос
EXPLAIN ANALYZE
SELECT u.id, u.email, COUNT(t.id) as transaction_count
FROM users u
LEFT JOIN transactions t ON u.id = t.user_id
WHERE u.created_at > '2023-01-01'
GROUP BY u.id, u.email
ORDER BY transaction_count DESC
LIMIT 100;

-- Результат: 8 секунд (плохо)
-- Seq Scan on users: 50M rows
-- Nested Loop Left Join: N+1 problem

-- Оптимизация: используем aggregate функцию
SELECT u.id, u.email, COALESCE(t.cnt, 0) as transaction_count
FROM users u
LEFT JOIN (
    SELECT user_id, COUNT(*) as cnt
    FROM transactions
    WHERE created_at > '2023-01-01'
    GROUP BY user_id
) t ON u.id = t.user_id
WHERE u.created_at > '2023-01-01'
ORDER BY transaction_count DESC
LIMIT 100;

-- Результат: 200ms (в 40 раз быстрее!)

4. Кеширование — не запрашиваем БД каждый раз

@Service
public class UserService {
    private final UserRepository repository;
    private final RedisTemplate<String, User> cache;
    
    // Кешируем на 1 час
    @Cacheable(
        value = "users",
        key = "#id",
        unless = "#result == null",
        cacheManager = "cacheManager"
    )
    public User getUser(Long id) {
        // Этот запрос выполнится только первый раз
        // Остальные 1000 запросов возьмут из Redis
        return repository.findById(id);
    }
}

// Результаты:
// Cache hit (< 1ms): 95% запросов
// DB query (50-100ms): 5% запросов
// Средний ответ: 2-5ms вместо 50-100ms

5. Асинхронная обработка — не блокируем основной поток

@Service
public class ReportService {
    // Обработка 1 миллиона строк асинхронно
    @Async
    @Scheduled(cron = "0 2 * * *") // 2 AM
    public void generateDailyReport() {
        // Читаем чанками, не загружаем всё в память
        Iterable<Transaction> transactions = 
            repository.findAll(PageRequest.of(0, 10000));
        
        List<ReportItem> reportItems = new ArrayList<>();
        for (Transaction t : transactions) {
            reportItems.add(processTransaction(t));
            
            // Когда накопилось 10K строк, пишем в файл
            if (reportItems.size() >= 10000) {
                fileService.append(reportItems);
                reportItems.clear();
            }
        }
    }
}

6. Batch processing — обновляем данные партиями

@Service
@Transactional
public class BatchUpdateService {
    public void updateUserStatistics() {
        // Обновляем 50 миллионов пользователей по 10K за раз
        List<User> batch = new ArrayList<>(10000);
        
        try (Iterator<User> iterator = repository.findAll().iterator()) {
            while (iterator.hasNext()) {
                User user = iterator.next();
                user.setLastActivityAt(LocalDateTime.now(UTC));
                batch.add(user);
                
                if (batch.size() >= 10000) {
                    repository.saveAll(batch);
                    entityManager.clear(); // Освобождаем память
                    batch.clear();
                }
            }
        }
    }
}

7. Логирование аналитики в JSON — структурированная информация

@Service
public class AnalyticsLogger {
    public void logEvent(AnalyticsEvent event) {
        // Вместо 10 отдельных полей — 1 JSONB поле
        // SELECT * FROM analytics_events WHERE data->>'user_type' = 'premium';
        
        analyticsRepository.save(new AnalyticsEventEntity(
            userId = event.getUserId(),
            eventType = event.getType(),
            createdAt = LocalDateTime.now(UTC),
            data = objectMapper.writeValueAsString(new {
                version = "1.0",
                userType = event.getUserType(),
                deviceType = event.getDeviceType(),
                location = event.getLocation(),
                // ... ещё 50 полей динамически
            })
        ));
    }
}

Самые большие таблицы, с которыми я работал

1. Analytics Events (2+ миллиарда записей)

  • Каждый клик, каждый скролл логируется
  • В час добавляется ~2-3 миллиона событий
  • Партиционирована по месяцам
  • После 3 месяцев отправляется в архив (S3)

2. Transactions (500+ миллионов)

  • Финансовые транзакции за 5 лет
  • Индекс по user_id и created_at
  • Регулярно статистика переносится в отдельную аналитическую таблицу
  • 99% запросов за последние 30 дней

3. User Sessions (100+ миллионов)

  • Сессия = одно посещение приложения
  • Быстро растёт, но нам нужна только горячая часть (30 дней)
  • Старые сессии автоматически удаляются

Инструменты, которые помогают

// 1. Prometheus + Grafana для мониторинга
metrics.timer("db.query.time")
    .tag("table", "transactions")
    .record(duration);

// 2. Slow query log
-- PostgreSQL config
log_min_duration_statement = 1000; -- логируем запросы > 1 сек

// 3. Query profiling
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM transactions WHERE user_id = 123;

// 4. Connection pooling (HikariCP)
// Без pooling: 1000 запросов = 1000 соединений
// С pooling (20 connections): переиспользуем соединения
hikari:
  maximum-pool-size: 20
  minimum-idle: 5

Основной результат

До оптимизации:

  • 500M transactions таблица: 10-30 секунд на запрос
  • 50M users: 5-10 секунд
  • Используется 80% CPU

После оптимизации:

  • Те же таблицы: 100-500ms
  • Cache hit: 1-5ms
  • Используется 20% CPU
  • Улучшение в 50-100 раз

Ключевое правило: С большими данными работаешь не мощностью железа, а умностью алгоритмов.