Как мы пересоздали аналитический движок Fathom и перенесли 65 миллиардов строк базы данных.
Я только что завершил самую сложную миграцию базы данных в своей жизни. Мы официально выпустили Fathom версии 4, перенесли более 65 миллиардов строк в совершенно новую инфраструктуру баз данных и избавились от каждого накопленного технического долга. На этот проект ушли сотни часов за несколько месяцев, и им полностью занимался один единственный разработчик (то есть я). Я собираюсь поделиться со всеми вами всеми деталями, так что устраивайтесь поудобнее, берите чашку кофе, и позвольте мне взять вас с собой в это путешествие.
Предыстория
Еще в 2019 году мы получали все больше вопросов от клиентов по поводу новых законов о файлах cookie. Несмотря на простоту, уважение к конфиденциальности и сбор поистине минимального объема данных, мы все равно использовали файлы cookie. Эти клиенты беспокоились и хотели, чтобы мы прекратили это.
Проблема заключалась в том, что эти файлы cookie были нужны нам, чтобы понимать, является ли посетитель уникальным для сайта и уникальным для конкретной страницы. Мы хранили данные примерно так:
Так что мы не могли просто «отказаться от файлов cookie», поскольку они были ключевой частью нашей инфраструктуры сбора аналитики.
Мы оказались в тупике. Мы знали, что законы меняются, но вся наша модель данных опиралась на файлы cookie для определения уникальности просмотров страниц. Аналитическое ПО использовало файлы cookie для подсчета уникальных посетителей со времен динозавров. Как отслеживать уникальных пользователей без файлов cookie? Нельзя использовать логи доступа. Это «сырые» данные по миллионам веб-сайтов, содержащие персональные данные (IP-адреса). И нельзя просто создавать цифровые отпечатки (fingerprint), потому что тогда вы сможете однозначно идентифицировать пользователей, потенциально в течение многих лет, а отпечаток — это воссоздаваемые персональные данные. У нас не было выхода.
Нам не нужны данные
«Сырые» логи доступа даже не рассматривались. Владелец сайта, конечно, может использовать логи на собственном сайте, но мы — аналитическая компания, которая отслеживает миллиарды уникальных посетителей на миллионах различных сайтов. Мы никоим образом не хотим отслеживать людей на разных сайтах и сохранять их историю просмотров.
Представьте себе рекламную компанию. Назовем ее, например, Google. У них интересная история в отношении защиты конфиденциальности пользователей. Вот в чем разница между ведением собственных логов доступа и тем, что происходит, когда вы устанавливаете Google Analytics на свой сайт:
Вы способствуете тому, что рекламная компания получает возможность следить за перемещениями людей в интернете.
Таким было наше исходное положение. Мы не могли решить проблему уникальных посетителей с помощью цифровых отпечатков или сохранения IP-адресов, так как это дало бы нам данные о поведении пользователя в интернете. Но затем мы поняли, что отпечаток на самом деле может сработать.
Рождение аналитики, ориентированной на конфиденциальность
Обычные цифровые отпечатки не подходили, поскольку они воссоздаваемы и могут идентифицировать отдельных посетителей сайта. Если содержимое выражения [ip]:[deviceTraits] всегда давало бы один и тот же результат, мы бы хранили персональные данные. Согласно спецификации, это было запрещено.
Может быть, хешировать простой текст? Тогда точно все будет в порядке, ведь мы храним не персональные данные, а их хеш. Нет, сказали юристы. Это не пройдет, так как его легко воссоздать в ту же минуту, когда у вас есть IP-адрес и характеристики устройства. Если данные можно легко связать с пользователем, вы обрабатываете персональные данные.
Мы изучили законы о конфиденциальности и поняли, что они правы. Это напомнило нам, что в былые времена индустрия осознала: простой MD5-хеш исходного пароля пользователя небезопасен, поскольку злоумышленники могут использовать раддужные таблицы, чтобы сопоставить MD5-хеш с исходным паролем. Это было до того, как менеджеры паролей стали популярными, поэтому, если кто-то взламывал ваш пароль, часто оказывались взломаны все ваши интернет-аккаунты. Решением индустрии стала «соль»: md5('$jlJhiuweh2sl40' + $rawUserPassword). И разработчики пошли дальше, используя уникальную соль для каждого пользователя. И тут нас осенило: соль — это решение нашей проблемы с файлами cookie в Fathom!
И не просто любая соль. Свежая, органическая соль выращенная на лугах, которая обновляется ежедневно и подгружается вместе с идентификатором сайта, именем хоста и цифровым отпечатком пользователя. Солью должен стать SHA-256 хеш случайной строки.
Итоговый идентификатор пользователя выглядел так:
А соль исчезала в течение 24 часов или быстрее, что делало невозможным восстановление хеша. На самом деле в моей самой первой записи в блоге это было подробно описано.
Затем мы провели анализ рисков. Что самое худшее может случиться? Невозможно брутфорсить 256-битный хеш. В той статье в блоге я писал:
«Брутфорс 256-битного хеша обойдется в 10^44 раз дороже мирового валового продукта (ГВП). ГВП за 2019 год составил 88,08 трлн долларов США ($88,080,000,000,000), так что до брутфорса 256-битного хеша нам не хватает хотя бы пары долларов».
И даже если предположить, что злоумышленник нацелился на конкретного человека, ему понадобился бы IP-адрес и User-Agent этого человека; затем ему нужно было бы взломать нашу базу данных, чтобы получить все уникальные идентификаторы SHA-256, которые у нас есть. После этого ему пришлось бы создавать хеши из этого IP-адреса и User-Agent, нашей соли за этот день и ID каждого отдельного трекера Fathom. И тогда, когда все это будет завершено, он смог бы увидеть сайты, которые этот человек посетил за один день. И если бы все это произошло, у нас были бы проблемы посерьезнее.
Без ежедневной соли они могли бы воссоздать многолетнюю активность отдельного пользователя. Это было бы катастрофой. Именно ежедневная соль делает это решение прекрасным.
И это решение, которое наша небольшая компания разработала для аналитики еще в 2019 году, теперь применяется повсеместно. Многомиллиардные компании изучают нашу методологию (и мои посты в блоге!) и используют точно такую же систему. Это потрясающе, потому что простая инновация в методах аналитики сделала интернет лучше для всех.
Накопление технического долга
Мы счастливо пользовались этим решением в течение шести лет. Наша модель данных оставалась в основном неизменной, с некоторыми доработками, включая переход с MySQL на SingleStore в 2021 году для решения проблемы повторяющихся сбоев базы данных. На самом деле, столбец user_signature сохранялся вместе с просмотрами страниц с 2021 года, но — и это большое «но» — мы никогда им не пользовались.
Вместо этого мы обрабатывали входящие просмотры страниц и события следующим образом:
Эти два хеша сохранялись в кэше между запросами. Здесь я все сильно упрощаю, потому что у нас также были такие вещи, как $uniqueIdentifier + '_source' для привязки UTM-параметров и источников перехода к событиям, но ключевым техническим долгом было то, что мы полагались на внешний кэш для установления состояния наших аналитических данных при их поступлении. А это значит, что мы полагались на is_unique и page_unique для определения уникальных посетителей сайта во всем нашем приложении.
По мере развития сферы конфиденциальной аналитики пользователи начали запрашивать страницы входа, страницы выхода, данные UTM на уровне сессий и многое другое. Но поскольку вся наша модель — от приема данных до дашборда — опиралась на SUM() по уникальным колонкам, а уникальные пользователи определялись при приеме, внедрить это простым способом не представлялось возможным. Мы рассматривали добавление колонок entry_page и exit_page. Страница входа в теории давалась легко, ее можно было кэшировать между просмотрами страниц. Страница выхода так не работала бы. Нам пришлось бы обновлять все предыдущие просмотры страниц каждый раз, когда пользователь открывал новую страницу (поскольку его страница выхода менялась бы). И я совсем не планировал запускать обновления аналитики на уровне приема данных в большом масштабе, так как уже обжигался на таком подходе.
Мы боролись с этим долгое время, пытаясь совмещать управление бизнесом силами команды из трех человек, разработку новых функций и поиск способов изменить нашу архитектуру под отчаянные потребности ПО. Мне больно об этом говорить, но мы годами жили без этого функционала. Мы зашли слишком далеко, и у нас не было четкого выхода. Каждый раз, когда мы приближались к прорыву в работе с данными, гигантский блокер сводил на нет месяцы работы. поймите меня правильно: мы все равно выпускали функции, развивали бизнес и имели тысячи довольных клиентов, но со стороны данных царил полный штиль. Сказать, что мы выгорели на работе с данными — это ничего не сказать.
Слишком много проблем, так почему же я здесь?
Причина, по которой мы ходили по кругу, заключалась в том, что я хотел разбить проект на мелкие, выполнимые задачи и не хотел признавать, что мы имеем дело с гигантским и запутанным клубом технического долга. А поскольку простые и управляемые шаги не находились, я оставался в состоянии паралича анализа.
Список наших проблем с данными разросся до такой степени, что превратился в сплошной клубок, с которым казалось невозможным справиться:
- Мы не могли запустить COUNT(DISTINCT()) по нашим данным, потому что у нас были данные V1 и Google Analytics (все они были агрегированы, помечены как type=site_stats или type=page_stats) вперемешку с данными Fathom V2. Нахождение этих данных в одной таблице давало прирост скорости для определенных запросов, но было кошмаром для всего остального. О, и count distinct не масштабируется для больших объемов данных, так как ему пришлось бы обрабатывать SHA-256 и удерживать результаты в памяти. Даже с HyperLogLog, который использует гораздо меньше памяти, это все равно МЕДЛЕННО.
- Данные Fathom V2 дублировали строки из-за наших строк длительности/убытия, добавленных только через append. Простой COUNT(*) по таблице просмотров страниц не работал, потому что нам нужно было исключить строку убытия. Но строка убытия была нужна, так как в ней содержалось exits=-1 для отмены предполагаемого значения exits=1 на первом просмотре страницы. Получение количества выходов вместе со временем на странице / сайте превращалось в хаос. А если мы пытались сделать union подсчетов для не-v1/ga, не-duration вставок и т.д., это просто начинает работать МЕДЛЕННО.
- Мы не могли масштабировано обрабатывать страницы входа/выхода. У нас была структура запросов с оконными функциями, которая выдавала данные, но она падала даже на нашем мощном кластере баз данных.
- У нас не было концепции данных на уровне сессий для посетителей сайта. Поиск уникальных пользователей по рефереру означал сканирование каждого отдельного просмотра страницы. Мы хотели иметь таблицу сессий и таблицу событий.
- Нам нужно было обновлять страницу выхода для исторических просмотров страниц. Каждая новая страница, которую посещал пользователь, означала, что его страницу выхода в строке просмотра страницы нужно было обновить. Это не масштабируется при OLAP-нагрузках, даже на такой эпической HTAP-базе данных, как SingleStore.
- Мы не могли обрабатывать просмотры страниц не по порядку (например, в неупорядоченной очереди), поскольку наш кэш управлял уникальными пользователями. Если обрабатывался старый просмотр страницы, это тем или иным образом влияло на кэш. Это была огромная головная боль, и потребовалось бы тысяча слов, чтобы объяснить проблему, но кэш фактически строился на допущении, что просмотры страниц и события будут обрабатываться по порядку. Таким образом, наши возможности фоновой обработки были ограничены.
- Мы полагались на колонку goal_id в таблице событий, связанную через транзакции с той же базой данных, и мы хотели разделить OLTP- и OLAP-нагрузки. Каждый раз, когда новое событие поступало в Fathom, нам приходилось открывать блокировку, чтобы создать запись для этого события в таблице целей.
- Рефереры не сохранялись во всех просмотрах страниц. Если посетитель приходил из Google, а затем посещал еще пять страниц, только целевая страница показывала Google. Остальные четыре давали +0 уникальных и +1 просмотр страниц для прямого трафика (direct).
- Показатель отказов рассчитывался по 30-минутной сессии, а не за день. Это приемлемо в некоторых продуктах, но мне всегда казалось неправильным.
- Время на сайте — это время между обработанными нами просмотрами страниц. У нас никогда не было отдельного пинга об уходе. Поэтому последняя страница каждой сессии давала 0 секунд, и мы не имели возможности узнать длительность.
- Существовал разрыв между верхним блоком тоталов и метрикой людей в блоке статистики страниц при фильтрации по пути (pathname), что вызывало множество вопросов в поддержке.
У нас было столько проблем — такова реальность первопроходца на новом рынке, но это были самые крупные из них. Мы вложили много сил (денег и времени) в разработку модели таблиц базы данных analytics_sessions и analytics_events, где analytics_sessions представляла бы одну строку на посетителя сайта с upsert-операциями, которые обновляли бы страницу входа/выхода, а затем мы использовали бы метки времени для условного обновления этих колонок (например, если метка времени < session.entry_timestamp, то этот просмотр страницы получал бы приоритет над тем, что хранилось в сессиях). Это позволило бы нам обрабатывать просмотры страниц не по порядку. В конце концов, поговорив с парой друзей, я понял, что это не масштабируется без различных компромиссов, и проект свернули (в американском переводе: выбросили на свалку).
Все было переплетено в сложную паутину проблем. Я не могу передать словами всю сложность имевшегося у нас технического долга, но если вы инженер-программист, то поймете. Дополнительной проблемой было то, что, несмотря на все эти трудности, наши клиенты все равно оставались довольны. Поэтому было проще оставить все как есть, чем раскачивать лодку. Но ваши клиенты полагаются на вас, принимая правильные решения для ПО, за которое они платят. И я четко осознал, что эти изменения нельзя провести маленькими порциями. Требовался рефакторинг всего нашего аналитического модуля.
Ломка привычек
В конце 2025 года я приостановил свою философию «маленьких изменений». С этим проектом она просто не работала, а за нашими плечами осталось много неудач. Я поставил на паузу наш роадмап и пообещал сосредоточиться на проекте данных. Я привлек потрясающего подрядчика по базам данных, мы рассмотрели варианты, и начал формироваться план. Реализация обещала быть абсолютно жестокой, и я понятия не имел, сколько времени уйдет на ее завершение.
Затем 24 ноября 2025 года произошло чудо. Появилась новая модель ИИ — Opus 4.5. Раньше я пробовал программировать с помощью ИИ, и мне это не понравилось. Все вокруг расхваливали его, но по сути это был автозаполнитель, который только мешал. Opus 4.5 изменил всё. Внезапно я стал отвечать за структуру и направление, но мне больше не нужно было писать код. Я по-прежнему читал каждую строку, но теперь мог переключаться между различными реализациями за минуты, а не за недели. Поработав с Cursor над несколькими небольшими проектами, я полностью «подсел», проводя с ним по 8+ часов в день, и теперь был готов принять самый серьезный вызов, с которым бизнес сталкивался со времен великой DDoS-атаки 2020 года.
Развод
За последние несколько лет я понял, что у нас нет полноценной HTAP-нагрузки. У нас есть OLTP и OLAP, но по большей части они существуют отдельно друг от друга. В конце 2025 года мы начали присматриваться к ClickHouse и PlanetScale. ClickHouse — это популярная OLAP-база данных с открытым исходным кодом, которой пользуются все. PlanetScale — лидер индустрии в сфере OLTP-нагрузок. Но самое интересное для меня заключалось в том, что во время общения со мной никто из них не требовал подписывать годовой контракт. Это застало меня врасплох, но с самого начала вызвало доверие.
Бен Пол (ClickHouse) помог мне доработать нашу новую схему ClickHouse. Он также активно помогал нам ТРАТИТЬ МЕНЬШЕ. Я до сих пор в шоке от их подхода к проектной инженерии. Он сказал мне: «Слушай, ты можешь снизить тариф, я не думаю, что тебе это нужно». Такое поведение компании формирует долгосрочное доверие, поскольку показывает, что они заботятся о наших отношениях. Бен и Луиш Невиш даже созвонились со мной, чтобы подробнее разобраться в том, как работает ClickHouse.
Что касается PlanetScale, Крис Маннс помог мне разобраться в платформе и поддержал миграцию нашей OLTP-нагрузки (которая была ничтожно мала по сравнению с тем, к чему они привыкли). Бен Дикен также любезно объяснил мне, как работают их реплики баз данных.
2 декабря 2025 года я официально уведомил SingleStore о том, что мы не будем продлевать контракт. Когда я отправил это письмо, начался обратный отсчет. Я еще ничего не построил, но знал, что мне нужно временное давление, чтобы заставить себя двигаться быстро. У нас был жесткий дедлайн — 28 февраля, дата окончания контракта, к которой нужно было полностью пересобрать аналитический движок и перенести все данные.
Оглядываясь назад, эта затея была безумием. У меня было чуть меньше трех месяцев на то, чтобы в одиночку переписать всю нашу аналитическую систему, дашборд и процесс сбора данных, а затем мигрировать всё остальное, одновременно лавируя между различными бизнес-задачами и оставаясь вовлеченным отцом. Но я уже ввязался в это, и пути назад не было.
Разъединение джойнов
Как только мы начали, я понял, что мы увязли в болоте. Было невозможно отделить нашу OLTP-нагрузку (пользователи, цели, страны, сайты) от аналитической нагрузки, потому что в SingleStore мы выполняли слишком много джойнов. Так что это стало нашей первой целью для атаки.
Мы разделили каждую категорию джойнов:
- Цели, которые раньше получались через JOIN между таблицей аналитических событий и таблицей целей (для сопоставления goal_id с goals.name), превратились в отдельные запросы поиска с динамическим сопоставлением массивов.
- Страны были преобразованы в кэшируемую статические карту (никаких больше таблиц в базе данных).
- Все связи, такие как Site::pageviews() и Event::goal(), были переписаны.
Первоначальный деплой всего этого по-прежнему выполнялся в SingleStore. Единственная разница заключалась в том, что теперь данные извлекались с помощью раздельных запросов. Мы распутали нашу OLTP-нагрузку и OLAP-нагрузку.
Слишком дорогая цена за кэш
Одним из преимуществ SingleStore было то, что наш масштабируемый кластер обладал огромным объемом оперативной памяти. Операции с хранилищем строк SingleStore выполнялись чрезвычайно быстро, и мы читали большие ключи, но операции удаления регулярно потребляли много процессора. Я выгрузил некоторые данные и увидел, как часть нашей нагрузки на кэш доминирует в использовании ЦП. Удаления происходили из Laravel, потому что так устроен их драйвер базы данных для кэша: когда он получает просроченный ключ, он удаляет его НЕМЕДЛЕННО. И самое забавное, что такой подход абсолютно нормален, ведь кто использует базу данных в качестве слоя кэширования в масштабе? Никто. Они наверняка используют Redis.
Что ж, в нашем случае это было не так. Поскольку у нас было столько оперативки, казалось логичным использовать SingleStore для кэша. Тогда я искренне верил, что это правильный шаг. Но на самом деле мы использовали HTAP-базу данных для работы с кэшем. На каждый обработанный просмотр страницы приходилось от 3 до 8 обращений к базе данных. Мы использовали специализированную базу данных под нагрузку ключ-значение для кэша.
Переключатель
Когда мы начали работу над переработкой аналитики, нас осенило — мы поняли это, оглядываясь назад. Мы осознали, что можем добавить на дашборд кнопку-переключатель, видимую только администраторам, для переключения между источниками данных. Это означало, что мне вообще не придется модифицировать аналитический модуль SingleStore. Мы могли дублировать наш код, переписать его под запросы для ClickHouse, и никто бы ничего не заметил, потому что они не видели переключатель.
Новый аналитический модуль ClickHouse был полностью написан Cursor и насчитывал тысячи строк. Я прочитал каждую строку и остался очень доволен. Весь код был продублирован из нашего оригинального класса, потому что я предполагал, что мы все равно удалим версию для SingleStore.
В прошлом мы пытались настроить feature flag для отдельного класса, чтобы он вел себя по-разному в зависимости от пользователя. Но, как вы можете догадаться, это был огромный бардак. На этот раз я пошел гораздо выше по цепочке и создал переключатель на уровне контроллера. И вот так просто мы получили текущих посетителей и все информационные блоки дашборда, которые загружались из ClickHouse всякий раз, когда мы этого хотели.
Планирование эвакуации
После завершения разъединения мы смогли разделить OLTP- и OLAP-нагрузки. И когда наша схема была скопирована в ClickHouse, мы были готовы перенести данные для сравнительного тестирования (с помощью переключателя!).
Но теперь перед нами встала новая задача: как перенести десятки миллиардов строк из SingleStore в ClickHouse? Если бы мы не смогли этого сделать, игра была бы окончена.
Это был самый большой объем данных, который я когда-либо переносил в любом проекте. И поскольку миграция происходила между разными СУБД, я знал, что это будет головная боль.
Я прошел через восемь итераций скрипта миграции. Я попытался схитрить, создав отдельный файл parquet для каждого календарного дня. Первой проблемой было то, что я не использовал ключ сортировки (site_id, timestamp) для выборки данных, поэтому мой запрос WHERE timestamp BETWEEN работал МЕДЛЕННО.
Вторая итерация скрипта миграции продолжала пытаться разбивать данные на чанки по времени, но объединяла несколько дней в один чанк. Пустая трата времени, не намного лучше первой итерации.
Для третьей итерации мы перешли на извлечение данных по сайтам и датам. Это позволило нам использовать ключ сортировки. Это было здорово, так как запросы на чтение теперь выполнялись в 75 раз быстрее, но разброс в размерах файлов составлял 33 раза. Никуда не годится.
Когда мы подошли к четвертой-восьмой итерациям, меня не устраивал подход SingleStore -> S3 -> ClickPipes -> ClickHouse. Я потратил уйму времени на эксперименты, и в итоге мы поняли, что ClickHouse может использовать протокол MySQL для чтения данных из нашей базы данных SingleStore (которая была совместима с протоколом MySQL). Это был прорыв, который все изменил.
К моменту создания восьмой версии я построил сложную систему миграции с учетом контекста. Я также одержимо профилировал нашу миграцию, так как знал, что нам придется запускать ее несколько раз по мере обнаружения ошибок, поэтому пропускная способность имела значение. Вывод заключался в том, что нам нужны чанки "site bundle" для миграции диапазона Site ID и чанки "mega site" для миграции крупных сайтов по отдельности.
На самом деле я запускал это на своей локальной машине для максимального понимания. Мы создали чанки mega site и site bundle в локальной базе данных на основе производственных аналитических данных. На изображении ниже вы можете видеть, что у нас были бандлы сайтов, позволяющие мигрировать site_id от 0 до 1946 в рамках одной задачи, так как объем данных составлял всего 800 миллионов строк. На скриншоте этого нет, но у нас также был тип чанка "mega site", где start_site_id и end_site_id представляли собой один ID сайта, и мы разбивали его на чанки по метке времени, поскольку нужно было перенести миллиарды строк.
У нас также была встроенная верификация. Сначала определялось исходное количество строк, а затем мы отслеживали rows_exported, чтобы убедиться, что целевое хранилище ClickHouse соответствует ожидаемому количеству строк. Это решение потребовало огромной работы, но оно было невероятным.
В конце концов, нам удалось достичь пропускной способности в 700 000–1 000 000 строк в секунду при миграции из SingleStore в ClickHouse. Я запускал оркестровку миграции локально, используя задачи Laravel, но фактическая передача данных по сети происходила между ClickHouse и SingleStore. Я отправлял запросы в ClickHouse, указывая ему читать данные из SingleStore, но все это происходило в облаке.
Вы будете очень шокированы, узнав, что я не запускал миграцию из своей рабочей базы данных. Вместо этого я сделал резервную копию базы данных, восстановил ее в гигантский (серьезно, он был огромным) кластер SingleStore, чтобы мы могли нагружать его запросами. Мы также переносили данные до определенной фиксированной метки времени, а исторические данные не меняются. Именно это позволило нам использовать такую модель миграции. Мы также масштабировали ClickHouse для обработки нагрузки.
И вот так мы создали основу нашей миграции, и я знал, что все у нас получится.
Нужно проиграть, чтобы научиться побеждать
Наша первая архитектура ClickHouse включала выделенную таблицу сессий, три материализованных представления и AggregatingMergeTree. Все выглядело великолепно. Затем мы загрузили дашборд для сайта Fathom среднего размера (~65 миллионов просмотров страниц), и его загрузка заняла 6–8 секунд. Столько же, сколько и в SingleStore. Конечно, у нас был гораздо более скромный тарифный план ClickHouse, но я все равно ожидал, что он будет работать намного быстрее.
Я пообщался с Беном (команда ClickHouse) и провел собственную отладку, и мы поняли, что вся эта структура не будет работать с той скоростью (и стоимостью), которую мы хотели. Поскольку нам все равно нужно было бы объединять сессии с событиями в определенных запросах, и хотя COUNT(*) по сессиям выполнялся БЫСТРО, так как это была одна строка на уникального посетителя, все остальное работало медленно.
Именно в этот момент мы поняли, что должны применить технику оптимизации, которой избегали много лет.
Дорого один раз, дешево навсегда
Хотя подход с одной таблицей работал быстро для малых и средних сайтов, он начинался "захлебываться" на более крупных сайтах при фильтрации по источникам переходов (реферерам) или выполнении любых операций на уровне сессий посетителей веб-сайта. Так что настало время создать сводные таблицы (rollup tables).
Сводная таблица — это таблица, в которой данные предварительно агрегируются и сгруппированы по различным измерениям, чтобы вам не приходилось выполнять динамический подсчет (count/count distinct) на лету для миллионов (и миллиардов) строк в одном запросе. Сводные таблицы означают, что один миллиард строк теоретически может быть сжат до нескольких миллионов или десятков миллионов строк.
Да, и этот запрос, выполнявшийся 6–8 секунд, стал выполняться менее чем за секунду, а каждый дашборд продемонстрировал колоссальный прирост скорости.
Аудит без ошибок
Мы были всем довольны, и настало время миграции.
Пусть начнется битва
К этому моменту в моем списке оставались следующие задачи:
- Удалить старые таблицы в SingleStore.
- задачи










