Как мы перестроили аналитический движок Fathom и перенесли 65 миллиардов строк базы данных.
Я только что завершил самую сложную миграцию базы данных в своей жизни. Мы официально выпустили Fathom версии 4, перенесли более 65 миллиардов строк в совершенно новую конфигурацию базы данных и устранили весь технический долг, который у нас был. Проект занял сотни часов в течение нескольких месяцев и был полностью выполнен одним инженером-программистом (мной). Я собираюсь поделиться с вами всеми подробностями, так что устраивайтесь поудобнее, берите кофе, и позвольте мне взять вас в это путешествие.
История происхождения
Еще в 2019 году мы получали все больше вопросов от клиентов о новых законах, касающихся файлов cookie. Несмотря на то, что мы были простыми, уважали конфиденциальность и собирали действительно минимальный объем данных, мы все еще использовали файлы cookie. Эти клиенты были обеспокоены и хотели, чтобы мы прекратили их использование.
Проблема заключалась в том, что нам нужны были эти файлы cookie, чтобы знать, является ли посетитель уникальным для сайта и уникальным для страницы. Мы хранили данные следующим образом:
Поэтому мы не могли просто «отказаться от файлов cookie», поскольку они были основной частью нашей инфраструктуры сбора аналитики.
Мы оказались в тупике. Мы знали, что законы меняются, но вся наша модель данных опиралась на файлы cookie для определения того, является ли просмотр страницы уникальным. Аналитическое программное обеспечение использует файлы cookie для отслеживания уникальных посетителей с доисторических времен. Как отслеживать уникальных пользователей без файлов cookie? Вы не можете использовать журналы доступа. Это необработанные данные по миллионам веб-сайтов, которые включают персональные данные (IP-адреса). Вы также не можете просто использовать «отпечатки» (fingerprinting) пользователей, потому что тогда вы сможете уникально идентифицировать их, возможно, в течение многих лет, а отпечаток — это воссоздаваемые персональные данные. Нам некуда было идти.
Нам не нужны эти данные
Необработанные журналы доступа были неприемлемы. Владелец веб-сайта, конечно, может использовать необработанные журналы доступа на своем собственном сайте, но мы — аналитическая компания, отслеживающая миллиарды уникальных посетителей веб-сайтов на миллионах разных сайтов. Мы никогда не хотим отслеживать людей на разных сайтах и хранить их привычки просмотра.
Представьте себе рекламную компанию. Назовем их Google. У них интересная история защиты конфиденциальности своих пользователей. Вот разница между ведением собственных необработанных журналов доступа и тем, что происходит, когда вы устанавливаете Google Analytics на свой веб-сайт:
Вы способствуете способности рекламной компании наблюдать за тем, куда люди перемещаются по сети.
Это была наша отправная точка. Мы не могли решить проблему уникальности путем создания отпечатков пользователей или хранения IP-адресов, потому что это дало бы нам данные о том, как пользователь ведет себя в Интернете. Но потом мы поняли, что отпечаток может сработать.
Рождение аналитики, ориентированной на конфиденциальность
Типичные отпечатки не могли сработать, потому что они воссоздаваемы и могут идентифицировать отдельных посетителей веб-сайта. Если содержимое [ip]:[deviceTraits] всегда приводило к одному и тому же результату, мы бы хранили персональные данные. Это было запрещено спецификацией.
Может быть, мы могли бы хешировать обычный текст? Тогда, конечно, это было бы нормально, потому что мы не храним персональные данные; мы храним хешированные персональные данные. Нет, сказали юристы. Это не пройдет, потому что это можно легко воссоздать, как только у вас появятся IP-адрес и характеристики устройства. Если это можно легко связать с пользователем, вы обрабатываете персональные данные.
Мы просмотрели законы о конфиденциальности и поняли, что они правы. Это напомнило нам, что в свое время индустрия осознала, что простой MD5-хеш необработанного пароля пользователя небезопасен, потому что злоумышленники могут использовать радужные таблицы, чтобы сопоставить MD5-хеш с необработанным паролем. Это было до того, как менеджеры паролей стали такими популярными, как сейчас, поэтому, если кто-то взламывал ваш пароль, все ваши интернет-аккаунты часто оказывались взломаны. Решением индустрии стала «соль»: md5('$jlJhiuweh2sl40' + $rawUserPassword). И инженеры-программисты пошли дальше, используя уникальную соль для каждого пользователя. И тут нас осенило: соли — это решение проблемы Fathom с файлами cookie!
И не просто любая соль. Свежая, органическая соль, которая обновлялась ежедневно и загружалась вместе с идентификатором сайта, именем хоста и отпечатком пользователя. Соль представляла собой SHA-256 хеш случайной строки.
Окончательный идентификатор пользователя выглядел так:
И соль исчезала в течение 24 часов или меньше, что делало невозможным восстановление хеша. На самом деле, мой самый первый пост в блоге подробно освещал это.
Затем мы провели анализ рисков. Что самое худшее может случиться? Вы не можете перебрать 256-битный хеш методом грубой силы. В то время в блоге я написал:
«Перебор 256-битного хеша методом грубой силы стоил бы в 10^44 раз больше мирового валового продукта (МВП). МВП 2019 года составляет 88,08 триллиона долларов США (88 080 000 000 000 долларов), так что нам не хватает как минимум нескольких долларов для перебора 256-битного хеша».
И даже если предположить, что злоумышленник нацелился на конкретного человека, ему понадобились бы IP-адрес и агент пользователя этого человека; затем ему нужно было бы взломать нашу базу данных, чтобы получить доступ ко всем уникальным идентификаторам SHA-256, которые у нас были. Затем им пришлось бы создавать хеши из этого IP-адреса и агента пользователя, нашей соли на этот день и идентификатора сайта каждого отдельного идентификатора отслеживания Fathom. И тогда, как только все это будет завершено, они смогли бы увидеть сайты, которые этот человек посетил за один день. И в этот момент, если бы все это произошло, мы столкнулись бы с проблемами покрупнее.
Без ежедневной соли они смогли бы воссоздать многолетнюю активность одного пользователя. Это было бы катастрофой. Ежедневная соль — это то, что делает это решение прекрасным.
И это решение, которое наша небольшая компания разработала для аналитики еще в 2019 году, теперь повсюду. Многомиллиардные компании прочитали нашу методологию (и мои посты в блоге!) и используют ту же самую систему. Это потрясающе, потому что простая инновация в том, как делается аналитика, сделала Интернет лучше для всех.
Нарастающий технический долг
Мы с радостью использовали это решение в течение шести лет. Наша модель данных оставалась практически неизменной, с несколькими доработками по пути, включая переход с MySQL на SingleStore в 2021 году для устранения повторяющихся сбоев базы данных. На самом деле, с 2021 года у нас был столбец user_signature, хранящийся вместе с просмотрами страниц, но, и это большое «но», мы никогда его не использовали.
Вместо этого вот как мы обрабатывали входящие просмотры страниц и события:
Эти два хеша хранились в кэше между запросами. Я здесь упрощаю, потому что у нас также были такие вещи, как $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 дублировали строки из-за наших строк duration/departure, предназначенных только для добавления. Простой COUNT(*) в таблице просмотров страниц не сработал бы, потому что нам нужно было исключить строку выхода. Но строка выхода была нужна, так как в ней было exits=-1, чтобы отменить предполагаемое exits=1 при первом просмотре страницы. Получение количества выходов вместе со временем на странице/сайте стало запутанным. А если мы пытались сделать объединение счетчиков для данных, не относящихся к v1/ga, не относящихся к добавлению длительности и т. д., это просто становилось МЕДЛЕННЫМ.
- Мы не могли обрабатывать страницы входа/выхода в больших масштабах. У нас была структура запросов с использованием оконных функций, которая выдавала данные, но она «падала» даже на нашем мощном кластере баз данных.
- У нас не было понятия данных на уровне сессий для посетителей сайта. Поиск уникальных пользователей по каждому источнику перехода означал сканирование каждого просмотра страницы. Мы хотели иметь таблицу сессий и таблицу событий.
- Нам нужно было обновлять страницу выхода для исторических просмотров страниц. Каждая новая страница, которую посещал пользователь, означала, что страницу выхода в строке просмотра страницы нужно было обновлять. Это не масштабируется при OLAP-нагрузках, даже на такой эпической HTAP-базе данных, как SingleStore.
- Мы не могли обрабатывать просмотры страниц не по порядку (например, в неупорядоченной очереди), потому что наш кэш управлял уникальными значениями. Если обрабатывался старый просмотр страницы, это влияло на кэш различными способами. Это была огромная головная боль, и потребовалась бы тысяча слов, чтобы объяснить проблему, но кэш был фактически построен на предположении, что просмотры страниц и события будут обрабатываться по порядку. Поэтому мы были ограничены в наших возможностях обрабатывать вещи в фоновом режиме.
- Мы полагались на столбец goal_id в таблице событий, связанный через транзакции с той же базой данных, и хотели разделить OLTP и OLAP нагрузки. Каждый раз, когда новое событие попадало в Fathom, нам приходилось открывать блокировку, чтобы создать запись для события в таблице целей.
- Источники переходов не сохранялись для всех просмотров страниц. Если посетитель приходил из Google, а затем посещал пять других страниц, только целевая страница показывала Google. Остальные четыре имели бы +0 уникальных и +1 просмотр страницы для прямого захода.
- Показатель отказов рассчитывался за 30-минутную сессию, а не ежедневно. В некоторых продуктах это приемлемо, но мне это всегда казалось неправильным.
- Время на сайте — это время между обработанными нами просмотрами страниц. У нас никогда не было отдельного пинга об уходе. Поэтому последняя страница каждой сессии составляла 0 секунд, и у нас не было возможности получить длительность.
- Существовал разрыв между верхним полем итогов и метрикой людей в поле статистики страницы при фильтрации по пути, что приводило к множеству вопросов в службу поддержки.
У нас было так много проблем, что является реальностью для первопроходца на новом рынке, но это были самые крупные из них. Мы вложили значительные средства (деньги и время) в модель таблицы базы данных analytics_sessions и analytics_events, где analytics_sessions была бы одной строкой на посетителя сайта с апсертами, которые обновляли бы страницу входа/выхода, а затем мы использовали бы временные метки для условного обновления этих столбцов (например, если временная метка < 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-нагрузку (пользователи, цели, страны, сайты) от аналитической нагрузки, потому что мы выполняли слишком много JOIN-операций в SingleStore. Так что это стало первой областью для атаки.
Мы разделили каждую категорию объединений:
- Цели, которые ранее извлекались через JOIN между нашей таблицей аналитических событий и таблицей целей (для сопоставления goal_id с goals.name), стали отдельными запросами поиска с динамическим отображением массивов.
- Страны были преобразованы в кэшированную статическую карту (больше никаких таблиц в базе данных).
- Все связи, такие как Site::pageviews() и Event::goal(), были переписаны.
Первоначальное развертывание всего этого оставалось в SingleStore. Единственная разница заключалась в том, что данные теперь извлекались с помощью отдельных запросов. Мы распутали нашу OLTP-нагрузку от OLAP-нагрузки.
Это слишком дорого для кэша
Одним из преимуществ SingleStore было то, что мы получали много оперативной памяти с нашим масштабируемым кластером. Операции с rowstore в SingleStore были чрезвычайно быстрыми, и мы читали большие ключи, но операции удаления регулярно потребляли много ресурсов процессора. Я проанализировал данные и увидел, как часть нашей нагрузки на кэш доминирует в использовании CPU. Удаления происходили из Laravel, потому что именно так работает их драйвер базы данных для кэша: когда он находит просроченный ключ, он удаляет его ПРЯМО СЕЙЧАС. И самое забавное, что такой подход вполне нормален, потому что кто использует базу данных в качестве уровня кэширования в масштабе? Никто. Все наверняка используют Redis.
Что ж, в нашем случае это было не так. Поскольку у нас было много оперативной памяти, использование SingleStore для кэша казалось логичным. В то время я был полностью уверен, что это правильный шаг. Но реальность заключалась в том, что мы использовали HTAP-базу данных для нагрузки кэша. На каждый обработанный просмотр страницы приходилось 3-8 взаимодействий с базой данных. Мы использовали узкоспециализированную базу данных для нагрузки кэша типа «ключ-значение».
Переключатель
Когда мы начали работать над перестройкой аналитики, нас осенило — решение кажется очевидным, если смотреть назад. Мы поняли, что можем добавить кнопку-переключатель на дашборде, видимую только администраторам, для переключения между источниками данных. Это означало, что мне вообще не нужно менять аналитический модуль SingleStore. Мы могли дублировать наш код, преобразовать его в запросы, понятные ClickHouse, и никто бы этого не заметил, потому что они не видели бы переключатель.
Новый аналитический модуль ClickHouse был полностью написан с помощью Cursor и состоял из тысяч строк. Я прочитал каждую строку и остался очень доволен. Весь код был дублирован из нашего исходного класса, потому что я предполагал, что мы всё равно удалим версию для SingleStore.
В прошлом мы пытались использовать feature-флаги для одного класса, чтобы он вел себя по-разному в зависимости от пользователя. Но, как вы можете себе представить, это был огромный беспорядок. В этот раз я пошел гораздо выше по цепочке и создал переключатель на уровне контроллера. И вот так просто: данные о текущих посетителях и все блоки дашборда стали загружаться из 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» для миграции диапазона ID сайтов и чанки «mega site» для отдельной миграции крупных сайтов.
На самом деле я запускал это на своей локальной машине для максимального контроля. Мы создали чанки mega site и site bundle в локальной базе данных на основе производственных аналитических данных. На изображении ниже видно, что у нас были site bundles, позволяющие перенести site_id от 0 до 1946 за одно задание, поскольку объем данных составлял всего 800 миллионов строк. На скриншоте этого нет, но у нас также был chunk_type «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.
- страны
