Архитектура проекта
Для создания собственного B2B‑каталога было выбрано решение, способное обрабатывать десятки миллионов записей и обеспечивать быстрый отклик даже при сложных фильтрах. Основные компоненты стека:
- ClickHouse – колонко‑ориентированная СУБД, ядро всей системы.
- FastAPI – Python‑backend, работающий в шести процессах
uvicorn. - Next.js – клиентская часть, отвечающая за UI и взаимодействие с API.
- Supabase – сервис аутентификации и учёта пользовательских кредитов.
- AWS EC2 m7i‑flex.large – виртуальная машина с 2 vCPU и 8 ГБ ОЗУ, где размещены все сервисы.
Такой набор позволяет держать нагрузку на уровне одного небольшого сервера, экономя на инфраструктуре без потери производительности.
Почему ClickHouse вместо традиционных реляционных СУБД
База данных содержит около 37 млн строк, каждая из которых хранит примерно 15 полей: имя, e‑mail, должность, компания, география, уровень seniority и т.д. Типичный запрос выглядит так:
SELECT first_name, last_name, email_domain, job_title, company,
location_city, location_country, seniority
FROM leads_clean
WHERE location_country = 'United States' AND seniority = 'manager'
LIMIT 100;
При использовании PostgreSQL с полностью продуманными индексами такой запрос занимает 8–15 секунд. В ClickHouse время падает до 80–200 мс. Причина – колонко‑ориентированная модель хранения: система читает только те столбцы, которые указаны в запросе, игнорируя остальные. Для аналитических запросов с множественными фильтрами это даёт порядок величины в скорости.
Кроме того, ClickHouse эффективно использует сжатие данных и распараллеливание на уровне столбцов, что позволяет быстро сканировать большие объёмы информации без полного перебора строк.
Оптимизация запросов и индексация
В ClickHouse нет традиционных B‑tree индексов, но есть skip‑indexes (пропускающие индексы), которые позволяют исключать блоки данных, не удовлетворяющие условиям фильтрации. Для полей location_country и seniority были созданы bloom_filter‑индексы, что сократило количество читаемых блоков почти до нуля при типичных запросах.
Также использовалась материализованная колонка email_domain, получаемая из полного e‑mail‑адреса. Это позволило выполнять поиск по домену без необходимости парсинга строки в момент запроса.
Ошибка фронтенда при массовом раскрытии данных
Первоначальная реализация функции «выводить e‑mail для списка лидов» выглядела так:
// Плохой подход – запросы в цикле на клиенте
for (const lead of leads) {
const email = await fetch(`/api/reveal/${lead.domain}`);
results.push(email);
}
Для небольших наборов (до 10 записей) такой способ работал, но уже при 60 запросах браузер начинал «зависать»: тайм‑ауты, подвисание промисов и коррумпированный стейт. Причина – одновременное открытие большого количества HTTP‑соединений из браузера, что приводит к превышению лимитов и блокировке UI‑потока.
Перенос логики на сервер
Было решено вынести всю операцию в бекенд:
- API‑endpoint принимает массив доменов.
- На сервере FastAPI формирует один запрос к ClickHouse, извлекает нужные e‑mail‑адреса и формирует CSV‑файл.
- Ответ передаётся клиенту как потоковый
Content‑Disposition: attachment, позволяя скачать файл без задержек.
Результат: 200 записей экспортируются в CSV за 0.2 секунды, а браузер получает единственный запрос, который не приводит к тайм‑аутам.
Кеширование и масштабирование
Для дальнейшего ускорения часто используемых запросов был введён ин‑мемори кеш на уровне FastAPI с помощью aiocache. Ключом служит строка фильтра (country=US&seniority=manager), а значением – готовый набор записей. При повторных запросах время отклика падает до 30‑40 мс, так как данные берутся из оперативной памяти без обращения к ClickHouse.
Кроме того, использовалась стартовая загрузка (warm‑up): после развертывания сервера скрипт прогоняет типовые запросы, заполняя кеш и «прогревая» сегменты ClickHouse. Это устраняет скачки в латентности при первом обращении к системе.
Итоги реализации
- ClickHouse обеспечивает аналитический уровень производительности на больших наборах данных без необходимости масштабировать инфраструктуру.
- FastAPI и Next.js позволяют быстро построить API и UI, оставаясь в рамках одного сервера.
- Перенос тяжёлых операций с клиентской части на сервер избавляет от проблем с тайм‑аутами и повышает пользовательский опыт.
- Кеширование на уровне приложения и предзагрузка часто используемых запросов значительно сокращают латентность после первого обращения.
В результате получилась полностью самостоятельная B2B‑платформа, способная обслуживать десятки миллионов записей и возвращать результаты в пределах 200 мс, при этом расходы на инфраструктуру оставляются в рамках небольшого облачного инстанса.