Аналитика FM
19 постов
Когда мы разрабатываем систему, чаще всего тестируем её на небольшом объёме данных: несколько десятков или сотен записей. Но в реальной эксплуатации база может содержать миллионы строк, а одновременно системой могут пользоваться тысячи пользователей.
Именно поэтому перед вводом системы в эксплуатацию проводят нагрузочное тестирование.
В канале Аналитика FM я публикую реальные запросы SQL с разбором логики. Обсуждаем продуктовые метрики и как правильно строить аналитику данных.
Канал веду с нуля подписчиков.
Подписывайся, если тоже хочешь вникнуть в прекрасный мир аналитики.
Нагрузочное тестирование - это проверка работы системы под нагрузкой, максимально приближенной к реальной.
Его цель - ответить на вопросы:
Сколько пользователей одновременно выдержит система?
Как быстро будут выполняться запросы?
Как изменится производительность при увеличении объёма данных?
Не начнёт ли база данных работать значительно медленнее?
Не возникнут ли ошибки из-за нехватки памяти, места во временном табличном пространстве или перегрузки процессора?
Например, запрос, который за 0,1 секунды обрабатывает 100 строк, может выполняться несколько минут при таблице в 100 миллионов записей.
Поэтому проверять производительность необходимо именно на объёмах, близких к реальным.
Проверяется работа системы при обычной ожидаемой нагрузке.
Например:
одновременно работают 500 пользователей;
таблица содержит 10 миллионов записей.
На систему специально подают нагрузку выше расчётной.
Цель - определить предел её возможностей и понять, как она поведёт себя при перегрузке.
Проверяется влияние количества данных на производительность.
Например:
запрос работает с таблицей из 100 тыс. строк;
затем с 10 млн;
затем со 100 млн.
Система работает под постоянной нагрузкой несколько часов или даже суток.
Проверяют, не возникает ли утечек памяти и деградации производительности.
Самый простой способ - сгенерировать тестовые записи прямо средствами Oracle.
Например:
SELECT 'ООО Ромашка ' || LEVEL AS name,
7700000000 + LEVEL AS inn,
1027700000000 + LEVEL AS ogrn
FROM dual
CONNECT BY LEVEL <= 1000000;
Этот запрос создаст 1 миллион строк без использования каких-либо таблиц.
И так вы сможете подготовить базу с наполненными данными для тестирования нагрузки.
Иногда такого подхода не хватает, и данные должны быть консистентными, иметь логическую зависимость от других параметров. Но это уже более глубокая семантика нагрузочного тестирования.
А мы с вами начинаем познавать увлекательный мир аналитики и данных.
Разбор скрипта проведем в канале Аналитика FM.
Подписывайся, чтобы узнавать техническую сторону работы аналитика.
Представьте дерево. У него есть корень, от него отходят ветки, от больших веток - более мелкие, а затем листья.
Примерно так же устроены иерархические справочники в информационных системах.
И как же можно понять: что есть ветка, а что есть лист в этом иерархическом справочнике?
В канале Аналитика FM я часто разбираю такие ситуации - когда задача вроде решаема, но без нормальной структуры превращается в кашу.
Например:
📁 Транспорт
├── Легковой транспорт
│ ├── Седаны
│ └── Кроссоверы
└── Грузовой транспорт
├── Малотоннажный
└── Тягачи
Или:
📁 Товары
├── Электроника
│ ├── Телефоны
│ └── Ноутбуки
└── Бытовая техника
├── Холодильники
└── Стиральные машины
На самом деле всё гораздо проще, чем кажется.
Обычно таблица справочника выглядит примерно так:
| id | name | parent_id |
| --- | ----------------------------------- | --------------- |
| 1 | Транспорт | NULL |
| 2 | Легковой транспорт | 1 |
| 3 | Грузовой транспорт | 1 |
| 4 | Седаны | 2 |
| 5 | Кроссоверы | 2 |
| 6 | Тягачи | 3 |
Структурно это выглядит так:
1 Транспорт
├── 2 Легковой транспорт
│ ├── 4 Седаны
│ └── 5 Кроссоверы
└── 3 Грузовой транспорт
└── 6 Тягачи
Каждая запись имеет:
id - собственный идентификатор;
parent_id - идентификатор родительского элемента.
Например:
id = 4
name = Седаны
parent_id = 2
Это означает:
Седаны → Легковой транспорт → Транспорт
Именно благодаря полю parent_id база понимает, что "Седаны" и "Кроссоверы" относятся к одной ветке дерева.
Если подниматься по родителям вверх, то рано или поздно мы придём к общему узлу - корню ветки.
Получается, что вся иерархия строится буквально на одном поле: parent_id
Они позволяют хранить данные не просто списком, а показывать связи между объектами.
Благодаря этому можно:
✅ группировать данные;
✅ строить отчёты по категориям;
✅ наследовать свойства от родительских узлов;
✅ задавать правила сразу для целой ветки;
✅ быстро находить все дочерние элементы.
Например, если правило применяется ко всей категории "Легковой транспорт", то оно автоматически действует и для седанов, и для кроссоверов, и для любых новых подкатегорий, которые появятся позже.
📌 MDM-системы (Master Data Management);
📌 каталоги товаров интернет-магазинов;
📌 банковские и страховые системы;
📌 ERP и CRM;
📌 классификаторы услуг и продуктов;
📌 организационная структура компании;
📌 государственные классификаторы и справочники.
✔ Гибкость. Можно добавлять новые ветки без изменения структуры данных.
✔ Удобная аналитика. Легко получить данные как по конкретному элементу, так и по всей категории.
✔ Наследование правил. Одно правило может применяться сразу к тысячам объектов.
✔ Масштабируемость. Структура может содержать десятки и сотни уровней вложенности.
❌ Сложность запросов. Иногда, чтобы найти всех потомков или родителей, приходится строить рекурсивные запросы.
❌ Производительность. Глубокие иерархии могут существенно замедлять выполнение запросов.
❌ Риск циклических ссылок. Если по ошибке сделать узел потомком самого себя, можно получить бесконечный цикл.
❌ Сложность сопровождения. Изменение структуры верхних уровней может затронуть большое количество дочерних элементов.
При работе с иерархическими справочниками чаще всего приходится решать четыре задачи:
🔹 найти всех потомков узла;
🔹 найти всех родителей элемента;
🔹 определить, принадлежит ли элемент определённой ветке;
🔹 определить, к какому верхнему узлу относится конкретный элемент.
В Oracle для этого используются специальные иерархические запросы:
START WITH ...
CONNECT BY ...
Именно они позволяют "обходить дерево" вверх или вниз по веткам.
В канале Аналитика FM (клик :-) ) уже готов пост про конструкцию START WITH и CONNECT BY.
Подписывайся, если интересно разбираться в особенностях работы аналитика.
Иерархический справочник - это не просто список значений. Это способ описать реальные взаимосвязи между объектами и сделать систему более гибкой и управляемой.
А одна маленькая колонка parent_id превращает обычную таблицу в целое дерево данных.
Если ты когда-нибудь писал длинный SQL-запрос и в какой-то момент ловил себя на мысли:
"Я уже сам не понимаю, что здесь происходит" - поздравляю, ты подошёл к моменту, где появляются временные таблицы.
В канале Аналитика FM я часто разбираю такие ситуации - когда задача вроде решаема, но без нормальной структуры превращается в кашу.
Это обычная таблица…
только с одним отличием:
👉 она живёт временно и потом исчезает
Ты создаёшь её:
чтобы сохранить промежуточный результат
поработать с ним
и не засорять основную базу
Тебе нужно:
взять заказы
отфильтровать только оплаченные
посчитать выручку
добавить сегментацию пользователей
ещё пару условий сверху
Можно написать один огромный запрос.
А можно сделать по-другому:
Сначала собрать "чистые заказы"
Потом на их основе считать метрики
Потом добавлять бизнес-логику
И вот тут временные таблицы начинают играть.
Как это выглядит
CREATE TEMP TABLE temp_orders AS
SELECT *
FROM orders
WHERE status = 'paid';
Создаем промежуточный слой данных.
А потом работаем уже с ним.
SELECT user_id, SUM(amount)
FROM temp_orders
GROUP BY user_id;
Вместо одного "монстра":
ты разбиваешь задачу на шаги
каждый шаг понятен
легче дебажить
Если один и тот же кусок данных нужен несколько раз:
не нужно каждый раз пересчитывать
можно сохранить и использовать
Иногда:
тяжёлый JOIN
сложная фильтрация
👉 выгодно посчитать один раз и сохранить результат
Если ты создашь обычную таблицу:
она останется
её надо потом удалять
она может мешать другим
Временная таблица:
живёт в рамках сессии
автоматически исчезает
сложные аналитические расчёты
многоступенчатые преобразования данных
работа с "грязными" данными
отладка логики
Временные таблицы - это не единственный инструмент.
Есть ещё:
CTE (WITH)
подзапросы
Но:
👉 CTE - это "логика в одном запросе"
👉 временные таблицы - это "разбивка на реальные шаги"
Иногда CTE читается тяжело.
А временные таблицы дают ощущение "пайплайна".
создают временные таблицы без необходимости
забывают, что они завязаны на сессию
используют их там, где проще CTE
То есть это инструмент - не серебряная пуля.
Самая простая мысль
Временная таблица - это способ остановиться посередине запроса и зафиксировать результат
И иногда именно это спасает:
читаемость
производительность
и твои нервы
В канале Аналитика FM (клик :-) )я рассказываю про продуктовые метрики в разных бизнесах. В чем особенности и нюансы. Серия постов про средний чек уже готова.
Подписывайся, если интересно интересно разбираться в особенностях работы аналитика.
Представь обычную ситуацию:
Есть клиент, сегодня он живет в Москве
| user_id | city |
| ----------- | ------------- |
| 1 | Москва |
Проходит время, он переезжает в Санкт-Петербург
Ты обновляешь данные
| user_id | city |
| ----------- | ---------------------------- |
| 1 | Санкт-Петербург|
И вроде всё ок.
Но потом приходит задача:
👉 А посчитай выручку по городам за прошлый год
И тут начинается самое интересное.
А в моем канале Аналитика FM выпуски про расчет Cohort Retention в разных бизнесах.
Канал я веду с нуля подписчиков, рассказываю про аналитику и разбираю различные кейсы на реальных примерах.
Подписывайся, если интересно как устроен мир аналитика!
Если ты просто возьмешь текущие данные, то все заказы пользователя "уедут " в Санкт-Петербург. Даже те, которые он делал, когда жил в Москве.
И аналитика начнёт врать.
Не потому что ты ошибся.
А потому что данные потеряли свою историю.
SCD (Slowly Changing Dimension) - это способ хранить изменения так,
чтобы ты мог ответить не только на вопрос:
👉 Как сейчас?
но и на более важный:
👉 Как было в момент события?
Как будет выглядеть: вместо одной строки в таблице будет:
| user_id | city | start_date | end_date |
| ------------ | --------------------------- | ----------------- | -------------------- |
| 1 | Москва | 2024-01-01 | 2025-01-01 |
| 1 | Санкт-Петербург| 2025-01-01 | NULL |
Теперь у нас есть не просто данные,
а контекст во времени.
Потому что почти всё в бизнесе меняется:
клиенты переходят между сегментами
продукты меняют категории
условия договоров обновляются
статусы живут своей жизнью
И если ты смотришь только на "сейчас" - ты теряешь половину смысла.
SCD - это не про таблицы.
Это про мышление.
Когда ты начинаешь задавать вопросы так:
А на момент события это было актуально?
А не поменялось ли это потом?
- ты переходишь на другой уровень понимания данных.
Берут текущие данные
и применяют их к прошлым событиям.
И получается:
красивые отчёты
аккуратные цифры
полностью неверные выводы
Данные без времени - это половина правды
SCD - это как раз про то, чтобы эту вторую половину не потерять.
И если тебе интересно разбираться в таких вещах глубже -
не просто "как написать SELECT", а как думать про данные,
в Аналитика FM я как раз про это и пишу.
Или превращение длинной таблицы в широкую
В канале Аналитика FM я часто про это пишу - потому что аналитика заканчивается не на запросе, а на том, как ты показал результат.
Подписывайся!
Представь, у тебя есть данные:
| user_id | metric | value |
| ----------- | ------------- | -------- |
| 1 | revenue | 100 |
| 1 | orders | 2 |
Это длинная таблица.
Она удобна для хранения:
гибкая
легко добавлять новые метрики
хорошо ложится в модели данных
Но попробуй быстро понять:
кто сколько потратил
у кого сколько заказов
и сравнить это между собой
Неудобно.
А теперь та же информация:
| user_id | revenue | orders |
| ------------ | ------------- | ---------- |
| 1 | 100 | 2 |
Это широкая таблица.
И вот тут уже:
метрики рядом
сравнение - в одну секунду
можно считать новые показатели
Это значит:
взять значения из строк и разложить их по колонкам
То есть:
metric → становится названиями колонок
value → становится значениями в этих колонках
Ты буквально "разворачиваешь" данные.
На самом деле, это и есть pivot.
Слово pivot - это "поворот".
Ты поворачиваешь таблицу так, чтобы:
строки стали колонками
данные стали наглядными
Если ты работал в Excel - ты это уже делал:
сводная таблица = pivot table.
В SQL это можно реализовать так:
SELECT
user_id,
SUM(CASE WHEN metric = 'revenue' THEN value END) AS revenue,
SUM(CASE WHEN metric = 'orders' THEN value END) AS orders
FROM table
GROUP BY user_id;
Потому что аналитика - это не про "достать данные".
Это про сделать их понятными.
Широкая таблица нужна, когда ты:
строишь отчёт
показываешь результат бизнесу
считаешь метрики между собой
Длинная таблица - когда:
хранишь данные
работаешь с событиями
строишь модели
Самая частая ошибка - пытаться всё делать в широком формате.
Это ломает гибкость:
добавилась новая метрика → переписывай запрос
данные перестают масштабироваться
Правильный подход:
храним - в длинном виде
показываем - в широком
Длинная таблица - это про хранение
Широкая таблица - это про понимание
И pivot - это просто инструмент, который помогает перейти от одного к другому.
И если ты хочешь не просто писать SQL, а реально чувствовать данные и понимать, как их правильно показывать,
в Аналитика FM я как раз разбираю такие вещи - простым языком, но с реальным смыслом.
Если ты только начинаешь разбираться в данных, то очень быстро сталкиваешься с кучей аббревиатур.
DDL, DML, ещё что-то… И кажется, что это какие-то сложные термины из учебника.
Но на самом деле всё гораздо проще.
И в канале Аналитика FM я как раз стараюсь разбирать такие вещи без заумных формулировок - через логику и реальные сценарии.
есть стены
есть комнаты
есть мебель
И есть вещи, которые ты туда кладёшь.
Так вот:
👉 DDL - это про квартиру
👉 DML - это про вещи внутри неё
DDL (Data Definition Language) - это команды, которые создают и изменяют структуру базы.
То есть всё, что связано с "построить" или "переделать".
CREATE TABLE users (...);
ALTER TABLE users ADD COLUMN age INT;
DROP TABLE users;
CREATE - ты создал новую "комнату"
ALTER - передвинул стену или добавил полку
DROP - снёс всё к чертям
📌 Важно:
DDL не трогает сами данные.
Он создаёт условия, где эти данные будут жить.
DML (Data Manipulation Language) - это уже про работу с данными.
SELECT * FROM users;
INSERT INTO users VALUES (...);
UPDATE users SET age = 30;
DELETE FROM users WHERE id = 1;
Это то, что делает аналитик каждый день:
посмотрел данные (SELECT)
добавил (INSERT)
изменил (UPDATE)
удалил (DELETE)
📌 Здесь ты уже работаешь не с "комнатой", а с тем, что внутри неё.
Не в терминах, а в сути:
DDL отвечает на вопрос: Как устроены данные?
DML отвечает на вопрос: Что происходит с данными?
Тебе нужно посчитать метрику.
Но:
нет нужной таблицы
не хватает колонок
данные лежат неудобно
👉 Тогда сначала приходит DDL:
создают таблицу, добавляют поля, настраивают структуру.
И только потом приходит DML:
ты пишешь SELECT, считаешь, анализируешь.
DDL и DML - это не просто разные команды.
Это разные уровни ответственности.
DDL чаще делают разработчики, инженеры, архитекторы
DML - это зона аналитиков
Но хороший аналитик всё равно должен понимать DDL.
Иначе он не понимает, почему данные лежат именно так.
DDL обычно:
применяется сразу
сложнее откатывается
DML:
можно обернуть в транзакцию
можно отменить
Это важно, когда ты начинаешь работать не только с SELECT, но и с изменением данных.
DDL - это "построить систему"
DML - это "жить внутри неё и работать с данными"
Если ты хочешь начать действительно понимать данные, а не просто писать запросы -
такие базовые вещи дают фундамент.В канале Аналитика FM я как раз разбираю такие темы:
без усложнений, но с пониманием того, как это работает в реальной работе аналитика.Разбор метрик Retention и Revenue в разных сферах бизнеса уже в канале.
Подписывайся!
Мы обычно пишем запросы: SELECT, INSERT, UPDATE…
Это отдельные команды, и каждая выполняется сама по себе. Но иногда нам хочется написать целую мини-программу, которая делает несколько действий сразу: например, берёт данные из одной таблицы, считает что-то, обновляет другую таблицу и выводит результат.
И при этом нам не нужно, чтобы эта программа сохранялась в базе как функция или процедура - мы просто хотим один раз выполнить код.
Вот для этого и нужен анонимный блок.
А в моем канале Аналитика FM выпуски про расчет Cohort Retention в разных бизнесах.
Канал я веду с нуля подписчиков, рассказываю про аналитику и разбираю различные кейсы на реальных примерах.
Подписывайся, если интересно как устроен мир аналитика!
Анонимный блок - это кусок кода, который:
Объединяет несколько SQL-команд в одну "программу".
Может содержать переменные, условия (IF), циклы (LOOP) и логику, как в обычной программе.
Не сохраняется в базе - после выполнения он исчезает. Его никто не будет видеть в списке функций или процедур.
То есть это как написать одноразовый скрипт: ты его запускаешь, видишь результат - и больше он никуда не сохраняется.
Пример в Oracle PL/SQL
BEGIN
-- объявляем переменную
DECLARE
v_count NUMBER;
BEGIN
-- считаем количество записей в таблице users
SELECT COUNT(*) INTO v_count FROM users;
-- выводим результат
DBMS_OUTPUT.PUT_LINE('Всего пользователей: ' || v_count);
END;
END;
/
Что здесь происходит:
BEGIN ... END; - тело блока, где выполняется логика.
DECLARE - можно объявлять переменные.
SQL-команда SELECT ... INTO считает количество пользователей и кладёт в переменную.
DBMS_OUTPUT.PUT_LINE выводит текст на экран.
/ - говорит Oracle, что блок закончился и его нужно выполнить.
После выполнения блока: переменные исчезают, код никуда не сохраняется, всё "одноразовое".
Пример в PostgreSQL
В PostgreSQL нет точно такого же синтаксиса, но есть DO-блок:
DO $$
DECLARE
v_count INT;
BEGIN
SELECT COUNT(*) INTO v_count FROM users;
RAISE NOTICE 'Всего пользователей: %', v_count;
END
$$;
DO $$ ... $$; - обёртка для анонимного блока. Мы говорим базе, что сейчас идет блок кода, который нужно выполнить целиком как отдельную программу.
Объявляем переменную v_count. Она нам нужна, чтобы сохранить результат вычислений и использовать его дальше внутри блока.
Все, что внутри BEGIN ... END, выполняется пошагово как мини-программа. Считаем количество строк в таблице users и записываем это значение в объявленную переменную
RAISE NOTICE выводит сообщение. % заменяется на значение переменной v_count.
Разовая операция. Хочешь разово обновить данные или проверить что-то - не нужно создавать отдельную функцию.
Тестирование логики. Пробуешь алгоритм, проверяешь, как работает SQL вместе с переменными и условиями.
Быстрое прототипирование. Хочешь понять, как соединить несколько шагов в одну последовательность действий.
Анонимный блок - это как одноразовый скрипт в SQL, который можно писать прямо в базе. Он может быть сложным, с переменными и логикой, но после выполнения исчезает.
Для аналитика это полезно, если нужно быстро посчитать что-то, проверить гипотезу или сделать массовую корректировку данных, не создавая постоянную функцию или процедуру.
В канале Аналитика FM разбираем реальные продуктовые метрики и их реализацию на SQL. Погружаемся в мира аналитики и аналитического мышления.
Если у тебя тоже есть интерес к аналитике,
Подписывайся!
Очень часто в аналитике смотрят на среднее:
средний чек
среднее время
средний доход
Но среднее - коварная штука.
Оно сглаживает реальность.
Если у 9 человек чек 100 ₽, а у одного - 10 000 ₽,
среднее будет 1 090 ₽.
И это вообще не похоже на реальность большинства.
Вот здесь и появляется перцентиль.
Подписывайся, если интересно как устроен мир аналитика!
В моем канале Аналитика FM выпуски про расчет Retention в разных бизнесах.Канал я веду с нуля подписчиков, рассказываю про аналитику и разбираю различные кейсы на реальных примерах.
Перцентиль это значение, ниже которого находится определённый процент данных.
Например:
50-й перцентиль (P50) - это медиана
90-й перцентиль (P90) - значение, ниже которого 90% наблюдений
95-й перцентиль (P95) - ещё более "правый хвост"
Если сказать по-человечески:
P90 - это "как живёт большинство, кроме самых крайних случаев".
Идея очень простая:
Берём все значения
Сортируем их по возрастанию
Берём нужную позицию
Например, у нас 100 значений:
P50 → 50-е значение
P90 → 90-е значение
В реальности всё чуть сложнее (интерполяции, разные методы расчёта),
но логика именно такая.
Во многих СУБД есть встроенные функции.
Например:
PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY amount)
Это P90 по полю amount.
Или:
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY duration)
Это медиана.
Среднее время ответа может быть нормальным,
но пользователи всё равно недовольны.
Почему?
Потому что:
90% запросов - быстрые
10% - очень медленные
И именно эти 10% формируют опыт.
Поэтому смотрят:
P90
P95
P99
Средний чек может быть завышен из-за крупных покупок.
Перцентили показывают:
как платит "обычный" клиент
где начинается премиум-сегмент
Средняя зарплата - почти всегда вводит в заблуждение.
Перцентили дают реальную картину:
P50 - "типичный доход"
P90 - "верх рынка"
В продуктовой аналитике:
сколько времени пользователь тратит на действие
сколько длится сессия
Среднее не показывает хвосты.
Перцентили - показывают.
Один аномально большой показатель не сломает картину.
Среднее - это одна точка.
Перцентили - это уже форма данных.
Пользователь - это не "среднее значение".
Он где-то внутри распределения.
Не только в аналитике:
Инженерия и DevOps - latency, SLA
Финансы - оценка рисков
Медицина - рост, вес, показатели анализов
Образование - результаты тестов
Логистика - время доставки
Везде, где важно понимать не "в среднем",
а "как распределены значения".
Важно помнить:
при маленьких выборках они нестабильны
разные системы могут считать их по-разному
P99 может сильно "скакать"
И ещё:
перцентили не заменяют среднее - они его дополняют
Перцентиль - это способ перестать думать "в среднем"
и начать видеть, как данные распределены на самом деле.
Потому что в аналитике
самые важные вещи часто происходят
не в центре,
а на краях.
В канале Аналитика FM разбираем реальные продуктовые метрики и их реализацию на SQL. Погружаемся в мира аналитики и аналитического мышления.
Если у тебя тоже есть интерес к аналитике,
Подписывайся!
