Compare commits
14
Commits
7a002f4d5a
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
453a1d6ddf | ||
|
|
9394b789e5 | ||
|
|
4693bc5af9 | ||
|
|
3c3eb825e1 | ||
|
|
d6b172e511 | ||
|
|
b839a671a1 | ||
|
|
c1d3a6db9b | ||
|
|
bb8605eed7 | ||
|
|
3e9249933b | ||
|
|
bb087f4477 | ||
|
|
515f386e5f | ||
|
|
2502277af1 | ||
|
|
ddb186f841 | ||
|
|
090df8a715 |
No files matched your search
@@ -10,6 +10,7 @@ CLICKHOUSE_ETL_PASSWORD=etl-26-writer
|
|||||||
CLICKHOUSE_LIFECYCLE_PASSWORD=lifecycle-26-owner
|
CLICKHOUSE_LIFECYCLE_PASSWORD=lifecycle-26-owner
|
||||||
CLICKHOUSE_BI_PASSWORD=bi-26-reader
|
CLICKHOUSE_BI_PASSWORD=bi-26-reader
|
||||||
CLICKHOUSE_ANALYST_PASSWORD=analyst-26-reader
|
CLICKHOUSE_ANALYST_PASSWORD=analyst-26-reader
|
||||||
|
CLICKHOUSE_STUDENT_PASSWORD=student-26-lab
|
||||||
CLICKHOUSE_GRAFANA_PASSWORD=grafana-26-reader
|
CLICKHOUSE_GRAFANA_PASSWORD=grafana-26-reader
|
||||||
KAFKA_EXTERNAL_PORT=29092
|
KAFKA_EXTERNAL_PORT=29092
|
||||||
PROMETHEUS_PORT=29090
|
PROMETHEUS_PORT=29090
|
||||||
|
|||||||
@@ -46,15 +46,10 @@
|
|||||||
|
|
||||||
## Кому что поручать
|
## Кому что поручать
|
||||||
|
|
||||||
Читатель здесь не побочный потребитель, а тот, ради кого стенд существует:
|
Автор задачи сам пишет и редактирует код, комментарии и документы, если
|
||||||
код, комментарии и документы и есть продукт. Поэтому всё, что человек будет
|
владелец не задал другое распределение работы.
|
||||||
читать и разбирать, пишет модель с чувством меры — сейчас это Opus. Codex
|
|
||||||
берёт техническую работу без вкусовых решений: миграции, разбор журналов и
|
|
||||||
объёмного вывода, обход репозитория, повторяющиеся правки. Там его запас
|
|
||||||
лимитов работает на нас, а экономить их на читаемом коде — экономия ложная:
|
|
||||||
переписывать выйдет дороже, чем сразу написать хорошо.
|
|
||||||
|
|
||||||
Делить работу между ними стоит не целыми задачами, а подзадачами, и решать
|
Делить работу стоит не целыми задачами, а подзадачами, и решать
|
||||||
это при планировании. Вкусовая часть идёт первой: она задаёт форму, имена и
|
это при планировании. Вкусовая часть идёт первой: она задаёт форму, имена и
|
||||||
границы, под которые механическая потом подстраивается.
|
границы, под которые механическая потом подстраивается.
|
||||||
|
|
||||||
|
|||||||
@@ -121,8 +121,11 @@ Kafka, затем заново создаст схему хранилища и
|
|||||||
Для подключения ко второму узлу укажите порт `28124`.
|
Для подключения ко второму узлу укажите порт `28124`.
|
||||||
|
|
||||||
Пользователь `analyst` может читать данные во всех слоях и справочниках.
|
Пользователь `analyst` может читать данные во всех слоях и справочниках.
|
||||||
|
Для заданий с собственными таблицами наблюдений используйте `student` и пароль
|
||||||
|
из `CLICKHOUSE_STUDENT_PASSWORD` в `.env`. Он читает те же источники, а создавать,
|
||||||
|
заполнять и удалять таблицы может только в базе `default`.
|
||||||
Для создания учебных витрин используйте подключение, описанное
|
Для создания учебных витрин используйте подключение, описанное
|
||||||
в [инструкции к заданиям](docs/labs/README.md#как-применить-свой-ddl).
|
в [инструкции к заданиям](docs/labs/README.md#выполнение-sql-в-dbeaver).
|
||||||
Нативные порты `29000` и `29001` нужны `clickhouse-client`, но не DBeaver.
|
Нативные порты `29000` и `29001` нужны `clickhouse-client`, но не DBeaver.
|
||||||
Остальные пользователи и роли описаны в
|
Остальные пользователи и роли описаны в
|
||||||
[справочнике стенда](docs/architecture/stand.md#пользователи-и-роли-clickhouse).
|
[справочнике стенда](docs/architecture/stand.md#пользователи-и-роли-clickhouse).
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ x-clickhouse-common: &clickhouse-common
|
|||||||
CLICKHOUSE_LIFECYCLE_PASSWORD: ${CLICKHOUSE_LIFECYCLE_PASSWORD:?Скопируйте .env.example в .env}
|
CLICKHOUSE_LIFECYCLE_PASSWORD: ${CLICKHOUSE_LIFECYCLE_PASSWORD:?Скопируйте .env.example в .env}
|
||||||
CLICKHOUSE_BI_PASSWORD: ${CLICKHOUSE_BI_PASSWORD:?Скопируйте .env.example в .env}
|
CLICKHOUSE_BI_PASSWORD: ${CLICKHOUSE_BI_PASSWORD:?Скопируйте .env.example в .env}
|
||||||
CLICKHOUSE_ANALYST_PASSWORD: ${CLICKHOUSE_ANALYST_PASSWORD:?Скопируйте .env.example в .env}
|
CLICKHOUSE_ANALYST_PASSWORD: ${CLICKHOUSE_ANALYST_PASSWORD:?Скопируйте .env.example в .env}
|
||||||
|
CLICKHOUSE_STUDENT_PASSWORD: ${CLICKHOUSE_STUDENT_PASSWORD:?Добавьте CLICKHOUSE_STUDENT_PASSWORD из .env.example в .env}
|
||||||
CLICKHOUSE_GRAFANA_PASSWORD: ${CLICKHOUSE_GRAFANA_PASSWORD:?Скопируйте .env.example в .env}
|
CLICKHOUSE_GRAFANA_PASSWORD: ${CLICKHOUSE_GRAFANA_PASSWORD:?Скопируйте .env.example в .env}
|
||||||
depends_on:
|
depends_on:
|
||||||
clickhouse-keeper:
|
clickhouse-keeper:
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
Дата: 9 августа 2026 года. Статус: принято; роль `monitoring_reader`
|
Дата: 9 августа 2026 года. Статус: принято; роль `monitoring_reader`
|
||||||
и пользователь `grafana` добавлены 21 августа 2026 года (#114), права
|
и пользователь `grafana` добавлены 21 августа 2026 года (#114), права
|
||||||
`bi_reader` расширены до ODS, DDS и DM 25 августа 2026 года (#150).
|
`bi_reader` расширены до ODS, DDS и DM 25 августа 2026 года (#150).
|
||||||
|
Пользователь `student` и роль `student_writer` добавлены 22 сентября 2026 года.
|
||||||
|
|
||||||
## Решение
|
## Решение
|
||||||
|
|
||||||
У кластера семь пользователей и шесть ролей.
|
У кластера восемь пользователей и семь ролей.
|
||||||
|
|
||||||
- `default` — с паролем, только служебный: проверки здоровья нод и работа
|
- `default` — с паролем, только служебный: проверки здоровья нод и работа
|
||||||
изнутри контейнеров. Приложения им не ходят.
|
изнутри контейнеров. Приложения им не ходят.
|
||||||
@@ -20,6 +21,10 @@
|
|||||||
шире.
|
шире.
|
||||||
- `analyst` с ролью `analyst_reader` — чтение всех слоёв и справочников. Им
|
- `analyst` с ролью `analyst_reader` — чтение всех слоёв и справочников. Им
|
||||||
человек подключается снаружи, из своего клиента.
|
человек подключается снаружи, из своего клиента.
|
||||||
|
- `student` с ролями `analyst_reader` и `student_writer` — то же чтение и
|
||||||
|
работа со своими таблицами в `default`: создание, вставка и удаление.
|
||||||
|
Сохранение учебных наблюдений не требует прав технической учётной записи
|
||||||
|
`etl` на изменение слоёв. Таблицы локальные; работа ведётся на одной ноде.
|
||||||
- `grafana` с ролью `monitoring_reader` — чтение слоёв, таблиц базы `dic` и
|
- `grafana` с ролью `monitoring_reader` — чтение слоёв, таблиц базы `dic` и
|
||||||
системных таблиц, `CLUSTER` и `REMOTE`. Им ходит Grafana, и больше никто.
|
системных таблиц, `CLUSTER` и `REMOTE`. Им ходит Grafana, и больше никто.
|
||||||
- `dict` с ролью `dict_reader` — чтение одной базы `dic`, пароля нет. Им
|
- `dict` с ролью `dict_reader` — чтение одной базы `dic`, пароля нет. Им
|
||||||
@@ -148,6 +153,11 @@ DDL для матвью приёма выполняется от имени `lif
|
|||||||
|
|
||||||
## Что проверено
|
## Что проверено
|
||||||
|
|
||||||
|
Права `student` сверены 22 сентября 2026 года через MCP Context7 и на стенде
|
||||||
|
26.3.17.56: создание локальных `MergeTree`, `INSERT SELECT` из DDS и DM,
|
||||||
|
чтение и удаление своих таблиц работают. На обеих нодах вставка в STG, ODS,
|
||||||
|
DDS, DM и `dic` завершается `ACCESS_DENIED`.
|
||||||
|
|
||||||
Сверено 9 августа 2026 года по документации ClickHouse через MCP Context7 и
|
Сверено 9 августа 2026 года по документации ClickHouse через MCP Context7 и
|
||||||
на живом стенде 26.3.17.56.
|
на живом стенде 26.3.17.56.
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
`first_matched_at` нет. Запланированные витрины его не читают, а два возможных
|
`first_matched_at` нет. Запланированные витрины его не читают, а два возможных
|
||||||
смысла — «первая покупка вообще» и «момент, с которого связь была известна
|
смысла — «первая покупка вообще» и «момент, с которого связь была известна
|
||||||
хранилищу» — требуют разных исторических моделей. Этот вопрос оставлен
|
хранилищу» — требуют разных исторических моделей. Этот вопрос оставлен
|
||||||
[самостоятельной работе](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/145),
|
[самостоятельной работе](../../labs/optional/identity-over-time.md),
|
||||||
а текущий стенд не выдаёт один смысл за другой.
|
а текущий стенд не выдаёт один смысл за другой.
|
||||||
|
|
||||||
Статус заказа связь не меняет: любой заказ, сопоставленный с событием покупки,
|
Статус заказа связь не меняет: любой заказ, сопоставленный с событием покупки,
|
||||||
|
|||||||
@@ -281,7 +281,10 @@ business_key`.
|
|||||||
- **Инкрементальный пересчёт затронутых дней по `first_matched_at`.**
|
- **Инкрементальный пересчёт затронутых дней по `first_matched_at`.**
|
||||||
Производственный ход, но на объёмах стенда его экономия неизмерима, а
|
Производственный ход, но на объёмах стенда его экономия неизмерима, а
|
||||||
след новых связок требует вернуть колонку, выкинутую в #133. Стал лабой
|
след новых связок требует вернуть колонку, выкинутую в #133. Стал лабой
|
||||||
повышенной сложности — issue #149.
|
повышенной сложности —
|
||||||
|
[«Как обновлять витрину без полной пересборки»](../labs/optional/incremental-daily-traffic.md).
|
||||||
|
В ней новые связи находятся сравнением наблюдений: время покупки
|
||||||
|
не заменяет момент обнаружения связи.
|
||||||
- **Superset поверх DDS без слоя DM.** Терялся бы урок «витрина — контракт
|
- **Superset поверх DDS без слоя DM.** Терялся бы урок «витрина — контракт
|
||||||
под потребителя», против раздела 7 спеки.
|
под потребителя», против раздела 7 спеки.
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
## Пользователи и роли ClickHouse
|
## Пользователи и роли ClickHouse
|
||||||
|
|
||||||
В ClickHouse семь пользователей и шесть ролей:
|
В ClickHouse восемь пользователей и семь ролей:
|
||||||
|
|
||||||
- `etl` — учётная запись рабочих дагов Airflow; роль `etl_writer` читает и
|
- `etl` — учётная запись рабочих дагов Airflow; роль `etl_writer` читает и
|
||||||
пишет слои хранилища;
|
пишет слои хранилища;
|
||||||
@@ -33,6 +33,9 @@
|
|||||||
справочники `dic`, а запросы Superset обращаются к публичным `_v`;
|
справочники `dic`, а запросы Superset обращаются к публичным `_v`;
|
||||||
- `analyst` — учётная запись аналитика с правом чтения всех слоёв и
|
- `analyst` — учётная запись аналитика с правом чтения всех слоёв и
|
||||||
справочников;
|
справочников;
|
||||||
|
- `student` — учебная учётная запись: роль `analyst_reader` дает чтение
|
||||||
|
источников, а `student_writer` — создание, заполнение и удаление своих
|
||||||
|
таблиц в `default`. Записи в слои хранилища у нее нет;
|
||||||
- `grafana` — учётная запись Grafana; роль `monitoring_reader` читает все слои,
|
- `grafana` — учётная запись Grafana; роль `monitoring_reader` читает все слои,
|
||||||
системные таблицы и данные со всех реплик кластера;
|
системные таблицы и данные со всех реплик кластера;
|
||||||
- `dict` — беспарольная учётная запись словаря товаров с правом чтения базы
|
- `dict` — беспарольная учётная запись словаря товаров с правом чтения базы
|
||||||
@@ -49,6 +52,9 @@ Superset получает пароль `bi`, а Grafana — пароль `grafan
|
|||||||
|
|
||||||
## Применение настроек
|
## Применение настроек
|
||||||
|
|
||||||
|
Для существующего стенда сначала добавьте `CLICKHOUSE_STUDENT_PASSWORD`
|
||||||
|
из `.env.example` в свой `.env`.
|
||||||
|
|
||||||
Чтобы применить новые значения ClickHouse из `.env` или изменения файлов в
|
Чтобы применить новые значения ClickHouse из `.env` или изменения файлов в
|
||||||
`infra/clickhouse/config.d/` и `infra/clickhouse/users.d/`, пересоздайте ноды:
|
`infra/clickhouse/config.d/` и `infra/clickhouse/users.d/`, пересоздайте ноды:
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ DDS складывает свою модель и называет атрибу
|
|||||||
| 41 | `productID` | `Array(String)` | `object` | `product_id` | id товаров события |
|
| 41 | `productID` | `Array(String)` | `object` | `product_id` | id товаров события |
|
||||||
| 42 | `productName` | `Array(String)` | `object` | `product_name` | названия тех же товаров |
|
| 42 | `productName` | `Array(String)` | `object` | `product_name` | названия тех же товаров |
|
||||||
| 43 | `productCategory` | `Array(String)` | `object` | `product_category` | категории тех же товаров |
|
| 43 | `productCategory` | `Array(String)` | `object` | `product_category` | категории тех же товаров |
|
||||||
| 44 | `productPrice` | `Array(Int64)` | `int64` | `product_price` | цена за штуку целым числом: деньги генератор считает целыми |
|
| 44 | `productPrice` | `Array(Int64)` | `int64` | `product_price` | цена за штуку, округленная до целых рублей |
|
||||||
| 45 | `productQuantity` | `Array(UInt64)` | `uint64` | `product_quantity` | количество штук каждого товара |
|
| 45 | `productQuantity` | `Array(UInt64)` | `uint64` | `product_quantity` | количество штук каждого товара |
|
||||||
| 46 | `productEventType` | `Array(String)` | `object` | `product_event_type` | действие с товаром: стенд шлёт add и purchase, полный словарь Метрики (detail, remove, impressions) не берём |
|
| 46 | `productEventType` | `Array(String)` | `object` | `product_event_type` | действие с товаром: стенд шлёт add и purchase, полный словарь Метрики (detail, remove, impressions) не берём |
|
||||||
| 47 | `ecommerce` | `String` | `object` | `ecommerce` | сырой JSON события, как отдаёт Метрика — материал лабы про разбор JSON внутри колонки |
|
| 47 | `ecommerce` | `String` | `object` | `ecommerce` | сырой JSON события, как отдаёт Метрика — материал лабы про разбор JSON внутри колонки |
|
||||||
+12
-10
@@ -4,9 +4,11 @@
|
|||||||
и попадают в отчеты. В главах есть короткие примеры, которые можно выполнить
|
и попадают в отчеты. В главах есть короткие примеры, которые можно выполнить
|
||||||
на запущенном стенде перед переходом к учебным заданиям.
|
на запущенном стенде перед переходом к учебным заданиям.
|
||||||
|
|
||||||
## Как читать
|
## Порядок чтения
|
||||||
|
|
||||||
Читайте главы в порядке ниже. Незнакомые термины можно найти в
|
Сначала запустите стенд по [инструкции](../../README.md#быстрый-старт).
|
||||||
|
Затем читайте главы по порядку и выполняйте примеры из них.
|
||||||
|
Незнакомые термины можно найти в
|
||||||
[словаре понятий](../../CONTEXT.md), а подробности устройства —
|
[словаре понятий](../../CONTEXT.md), а подробности устройства —
|
||||||
в [технических справочниках](../architecture/). К ним достаточно обращаться
|
в [технических справочниках](../architecture/). К ним достаточно обращаться
|
||||||
по мере необходимости. После путеводителя переходите к
|
по мере необходимости. После путеводителя переходите к
|
||||||
@@ -19,27 +21,27 @@ Gitea показывает HTML как текст. Откройте файл и
|
|||||||
или скачайте его кнопкой «Raw» и откройте в браузере.
|
или скачайте его кнопкой «Raw» и откройте в браузере.
|
||||||
Сверяйтесь с картой при чтении главы «Хранилище и его слои».
|
Сверяйтесь с картой при чтении главы «Хранилище и его слои».
|
||||||
|
|
||||||
## Главы
|
## Содержание
|
||||||
|
|
||||||
### [Хранилище и его слои](storage-layers.md)
|
### [Хранилище и его слои](storage-layers.md)
|
||||||
|
|
||||||
Как меняются данные при переходе из Kafka в слои STG, ODS, DDS и DM.
|
Как меняются данные при переходе из Kafka в слои STG, ODS, DDS и DM.
|
||||||
В конце главы вы посмотрите события, сессии и выручку с помощью SQL.
|
В конце главы вы посмотрите события, сессии и выручку с помощью SQL.
|
||||||
|
|
||||||
### [Потоки Airflow и даги модельного времени](airflow-dags.md)
|
### [Генерация и обработка данных в Airflow](airflow-dags.md)
|
||||||
|
|
||||||
Какие даги создают новые данные, а какие обрабатывают их в хранилище.
|
Какие даги создают новые данные, а какие обрабатывают их в хранилище.
|
||||||
Вы запустите генерацию и обработку за день и посмотрите результат в витринах.
|
Вы сравните содержимое витрин до и после обработки нового дня.
|
||||||
|
|
||||||
### [Мониторинг: куда смотреть](monitoring.md)
|
### [Мониторинг в Grafana](monitoring.md)
|
||||||
|
|
||||||
Что показывают панели Grafana и как меняются их показания, пока в стенд
|
Что показывают панели Grafana и как меняются их показания, пока в стенд
|
||||||
поступают события и заказы.
|
поступают события и заказы. Вы увидите, почему заказы обновляются реже событий.
|
||||||
|
|
||||||
### [Немного Superset](superset.md)
|
### [Отчеты в Superset](superset.md)
|
||||||
|
|
||||||
Как Superset строит графики по витринам и что происходит с вашими отчетами
|
Как Superset строит графики по витринам. Вы сравните числа на готовом графике
|
||||||
при перезапуске стенда.
|
с результатом SQL и узнаете, какие отчеты сохраняются при перезапуске стенда.
|
||||||
|
|
||||||
### [Генератор: откуда берутся данные](generator.md)
|
### [Генератор: откуда берутся данные](generator.md)
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
# Потоки Airflow и даги модельного времени
|
# Генерация и обработка данных в Airflow
|
||||||
|
|
||||||
Airflow управляет генерацией данных и их обработкой в хранилище. Это разные
|
Airflow управляет генерацией данных и их обработкой в хранилище. Это разные
|
||||||
этапы: сначала в Kafka поступают события и заказы за день работы магазина,
|
этапы: сначала в Kafka поступают события и заказы за день работы магазина,
|
||||||
затем по ним рассчитываются данные DDS и витрины DM.
|
затем по ним рассчитываются данные DDS и витрины DM.
|
||||||
|
Даг описывает задачи Airflow и порядок их выполнения.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
world_initialize --> etl_pipeline
|
world_initialize --> etl_pipeline
|
||||||
world_live_day --> etl_pipeline
|
world_live_day --> etl_pipeline
|
||||||
manual[запуск рукой] --> etl_pipeline
|
manual[ручной запуск] --> etl_pipeline
|
||||||
etl_pipeline --> dds_transform --> dm_transform --> dq_check
|
etl_pipeline --> dds_transform --> dm_transform --> dq_check
|
||||||
world_next_day --> sources[источники без ETL]
|
world_next_day --> sources[источники без ETL]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Два потока входят по-разному
|
## Загрузка событий и заказов
|
||||||
|
|
||||||
События поступают в топик `hits` и обрабатываются по мере поступления:
|
События поступают в топик `hits` и обрабатываются по мере поступления:
|
||||||
материализованные представления переносят строки из STG в ODS без участия
|
материализованные представления переносят строки из STG в ODS без участия
|
||||||
@@ -24,7 +25,7 @@ Airflow.
|
|||||||
Даг `orders_ingest` загружает ее целиком. Такой способ приема называется
|
Даг `orders_ingest` загружает ее целиком. Такой способ приема называется
|
||||||
[пакетным](../../CONTEXT.md).
|
[пакетным](../../CONTEXT.md).
|
||||||
|
|
||||||
## Даги мира
|
## Генерация модельных дней
|
||||||
|
|
||||||
В документации миром называется учебный магазин с его посетителями,
|
В документации миром называется учебный магазин с его посетителями,
|
||||||
товарами и заказами. Время в нем идет отдельно от календарного.
|
товарами и заказами. Время в нем идет отдельно от календарного.
|
||||||
@@ -54,7 +55,7 @@ Airflow.
|
|||||||
номер дня остается прежним. Подробности — в [справочнике генератора
|
номер дня остается прежним. Подробности — в [справочнике генератора
|
||||||
и модельного времени](../architecture/world.md#даги-модельного-времени).
|
и модельного времени](../architecture/world.md#даги-модельного-времени).
|
||||||
|
|
||||||
## Дневной конвейер
|
## Обработка данных в хранилище
|
||||||
|
|
||||||
Даг `etl_pipeline` по очереди запускает `dds_transform`, `dm_transform`
|
Даг `etl_pipeline` по очереди запускает `dds_transform`, `dm_transform`
|
||||||
и `dq_check`, дожидаясь завершения каждого. Они рассчитывают данные DDS,
|
и `dq_check`, дожидаясь завершения каждого. Они рассчитывают данные DDS,
|
||||||
@@ -84,20 +85,21 @@ Airflow.
|
|||||||
проверок описан в [разделе «Звено DQ
|
проверок описан в [разделе «Звено DQ
|
||||||
конвейера»](../architecture/dm.md#звено-dq-конвейера).
|
конвейера»](../architecture/dm.md#звено-dq-конвейера).
|
||||||
|
|
||||||
## Как найти в интерфейсе
|
## Поиск дагов в Airflow
|
||||||
|
|
||||||
Даги мира ищите по тегам «модельное время» и «жизненный цикл мира». Остальные
|
Даги мира ищите по тегам «модельное время» и «жизненный цикл мира». Остальные
|
||||||
даги ищите по тегам «заказы», `etl`, `dds`, `dm` и `dq`. `homework_check` с
|
даги ищите по тегам «заказы», `etl`, `dds`, `dm` и `dq`. `homework_check` с
|
||||||
тегами `dm` и `labs` проверяет домашние работы; о нем рассказано в учебных
|
тегами `dm` и `labs` проверяет домашние работы; о нем рассказано в учебных
|
||||||
заданиях.
|
заданиях.
|
||||||
|
|
||||||
## Посмотрите сами
|
## Один день от генерации до витрин
|
||||||
|
|
||||||
Откройте Airflow по адресу из раздела [«Адреса и
|
Откройте Airflow по адресу из раздела [«Адреса и
|
||||||
пароли»](../../README.md#адреса-и-пароли). В Admin → Variables найдите
|
пароли»](../../README.md#адреса-и-пароли). В Admin → Variables найдите
|
||||||
`world_position`. Затем запустите `world_next_day` на один день по [инструкции
|
`world_position` и запомните его значение. Затем запустите `world_next_day`
|
||||||
из README](../../README.md#добавить-данные-за-следующие-дни). Убедитесь, что
|
на один день по [инструкции из README](../../README.md#добавить-данные-за-следующие-дни). Убедитесь, что
|
||||||
`orders_ingest` завершился успешно, а в витринах нового дня еще нет.
|
`orders_ingest` завершился успешно, а в витринах нового дня еще нет.
|
||||||
|
Значение `world_position` должно увеличиться на один.
|
||||||
Запустите `etl_pipeline` вручную и дождитесь его завершения.
|
Запустите `etl_pipeline` вручную и дождитесь его завершения.
|
||||||
|
|
||||||
В DBeaver, подключенном по
|
В DBeaver, подключенном по
|
||||||
@@ -121,8 +123,8 @@ LIMIT 3;
|
|||||||
приходит слепок за день D−1. Выручка дня D появится после генерации
|
приходит слепок за день D−1. Выручка дня D появится после генерации
|
||||||
дня D+1 и запуска конвейера.
|
дня D+1 и запуска конвейера.
|
||||||
|
|
||||||
## Куда дальше
|
## Следующая глава и справочники
|
||||||
|
|
||||||
Детали — в справочниках [конвейера](../architecture/etl.md) и
|
Детали — в справочниках [конвейера](../architecture/etl.md) и
|
||||||
[мира](../architecture/world.md). Следующая глава — [«Мониторинг: куда
|
[мира](../architecture/world.md). Следующая глава —
|
||||||
смотреть»](monitoring.md).
|
[«Мониторинг в Grafana»](monitoring.md).
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
магазина раз в модельные сутки выгружает состояние заказов в топик `orders`.
|
магазина раз в модельные сутки выгружает состояние заказов в топик `orders`.
|
||||||
Эта дневная выгрузка называется слепком.
|
Эта дневная выгрузка называется слепком.
|
||||||
|
|
||||||
## Что он производит
|
## События сайта и заказы
|
||||||
|
|
||||||
Событие содержит сведения о действии посетителя: например, время,
|
Событие содержит сведения о действии посетителя: например, время,
|
||||||
адрес страницы и идентификатор посетителя. Формат соответствует образцу
|
адрес страницы и идентификатор посетителя. Формат соответствует образцу
|
||||||
@@ -20,11 +20,11 @@
|
|||||||
разобраны в [отдельном
|
разобраны в [отдельном
|
||||||
справочнике](../architecture/orders/snapshot.md).
|
справочнике](../architecture/orders/snapshot.md).
|
||||||
|
|
||||||
## Почему данные всегда одни и те же
|
## Почему начальные данные повторяются
|
||||||
|
|
||||||
Случайные решения генератора зависят от заданного числа —
|
Случайные решения генератора зависят от заданного числа —
|
||||||
[зерна генерации](../architecture/generator/seeds.md) — и номера дня.
|
[зерна генерации](../architecture/generator/seeds.md) — и номера дня.
|
||||||
При неизменных настройках и версии генератора одинаковые зерно и день
|
При неизменных коде, каталоге товаров и настройках одинаковые зерно и день
|
||||||
дают одинаковые события. Поэтому при создании стенда вы каждый раз
|
дают одинаковые события. Поэтому при создании стенда вы каждый раз
|
||||||
получаете те же начальные данные. Повторный `make up` на работающем стенде
|
получаете те же начальные данные. Повторный `make up` на работающем стенде
|
||||||
сохраняет уже загруженные данные.
|
сохраняет уже загруженные данные.
|
||||||
@@ -34,10 +34,10 @@
|
|||||||
В документации этот файл называется паспортом мира. С ним можно сверить
|
В документации этот файл называется паспортом мира. С ним можно сверить
|
||||||
результат генерации и проверить, все ли события загрузились в ODS.
|
результат генерации и проверить, все ли события загрузились в ODS.
|
||||||
|
|
||||||
## Кто его запускает
|
## Запуск генератора
|
||||||
|
|
||||||
На стенде генератором управляют [даги модельного
|
На стенде генератором управляют [даги модельного
|
||||||
времени](airflow-dags.md#даги-мира). Его можно запустить и напрямую. Команды
|
времени](airflow-dags.md#генерация-модельных-дней). Его можно запустить и напрямую. Команды
|
||||||
собраны в [справочнике генератора и модельного
|
собраны в [справочнике генератора и модельного
|
||||||
времени](../architecture/world.md#как-запустить-генератор).
|
времени](../architecture/world.md#как-запустить-генератор).
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
В живом режиме события поступают постепенно, как в работающем магазине,
|
В живом режиме события поступают постепенно, как в работающем магазине,
|
||||||
но время идет ускоренно: модельные сутки занимают около 24 минут.
|
но время идет ускоренно: модельные сутки занимают около 24 минут.
|
||||||
|
|
||||||
## Посмотрите сами
|
## Сверка с паспортом мира
|
||||||
|
|
||||||
Подключитесь из DBeaver по [инструкции](../../README.md#подключиться-из-dbeaver)
|
Подключитесь из DBeaver по [инструкции](../../README.md#подключиться-из-dbeaver)
|
||||||
и выполните запрос:
|
и выполните запрос:
|
||||||
@@ -62,7 +62,7 @@ ORDER BY EventDate;
|
|||||||
Числа должны совпасть. Если вы уже генерировали следующие дни,
|
Числа должны совпасть. Если вы уже генерировали следующие дни,
|
||||||
в результате запроса будут дополнительные даты; в паспорте их нет.
|
в результате запроса будут дополнительные даты; в паспорте их нет.
|
||||||
|
|
||||||
## Куда дальше
|
## Учебные задания и справочники
|
||||||
|
|
||||||
Подробнее генератор разобран в [README папки
|
Подробнее генератор разобран в [README папки
|
||||||
`generator/`](../../generator/README.md) и в [справочнике его
|
`generator/`](../../generator/README.md) и в [справочнике его
|
||||||
|
|||||||
+18
-15
@@ -1,20 +1,19 @@
|
|||||||
# Мониторинг: куда смотреть
|
# Мониторинг в Grafana
|
||||||
|
|
||||||
В Grafana настроены три дашборда. «Данные» показывает загрузку и обработку
|
В Grafana настроены три дашборда — экрана с показателями и графиками.
|
||||||
событий и заказов, «Кластер» — состояние узлов и самого ClickHouse,
|
«Данные» показывает загрузку и обработку событий и заказов,
|
||||||
а «Запросы» — выполненные запросы и затраченные на них ресурсы.
|
«Кластер» — состояние узлов и самого ClickHouse, а «Запросы» — выполненные
|
||||||
|
запросы и затраченные на них ресурсы.
|
||||||
|
|
||||||
Prometheus собирает метрики с двух узлов ClickHouse, ClickHouse Keeper
|
|
||||||
и экспортера Kafka — службы, которая предоставляет метрики Kafka.
|
|
||||||
Grafana получает данные из Prometheus и напрямую из ClickHouse.
|
|
||||||
На панелях не заданы пороги для цветовых предупреждений: оценивать показания
|
На панелях не заданы пороги для цветовых предупреждений: оценивать показания
|
||||||
вы будете самостоятельно. Качество данных автоматически проверяет
|
вы будете самостоятельно. Качество данных автоматически проверяет
|
||||||
даг `dq_check`.
|
даг `dq_check`.
|
||||||
|
|
||||||
## Дашборд «Данные»
|
## Дашборд «Данные»
|
||||||
|
|
||||||
«Свежесть» показывает, сколько времени прошло с последнего события и с
|
«Свежесть» показывает, сколько реального времени прошло с поступления
|
||||||
последнего заказа в ODS. Показатель для заказов растет весь модельный день
|
последних успешно разобранных событий и заказов. Время поступления хранится
|
||||||
|
в колонке `_load_ts` слоя ODS. Показатель для заказов растет весь модельный день
|
||||||
и снижается, когда поступает очередная дневная выгрузка — слепок.
|
и снижается, когда поступает очередная дневная выгрузка — слепок.
|
||||||
Это ожидаемо: заказы загружаются раз в день.
|
Это ожидаемо: заказы загружаются раз в день.
|
||||||
|
|
||||||
@@ -33,7 +32,11 @@ STG: по нему видно, поступила ли очередная выг
|
|||||||
Источники и правила чтения всех панелей разобраны в [справочнике дашборда
|
Источники и правила чтения всех панелей разобраны в [справочнике дашборда
|
||||||
«Данные»](../architecture/monitoring/data.md).
|
«Данные»](../architecture/monitoring/data.md).
|
||||||
|
|
||||||
## Почему у панелей разные источники
|
## Источники показателей
|
||||||
|
|
||||||
|
Grafana получает данные напрямую из ClickHouse и через Prometheus.
|
||||||
|
Prometheus собирает метрики с двух узлов ClickHouse, ClickHouse Keeper
|
||||||
|
и экспортера Kafka — службы, которая предоставляет метрики Kafka.
|
||||||
|
|
||||||
Свежесть считается по ODS: если новые сообщения перестали разбираться,
|
Свежесть считается по ODS: если новые сообщения перестали разбираться,
|
||||||
показатель будет расти. Число поступивших сообщений берется из STG,
|
показатель будет расти. Число поступивших сообщений берется из STG,
|
||||||
@@ -45,7 +48,7 @@ STG: по нему видно, поступила ли очередная выг
|
|||||||
всех доставленных сообщений. Подробности — в [разделе о слоях
|
всех доставленных сообщений. Подробности — в [разделе о слоях
|
||||||
панелей](../architecture/monitoring/data.md#почему-слои-у-панелей-разные).
|
панелей](../architecture/monitoring/data.md#почему-слои-у-панелей-разные).
|
||||||
|
|
||||||
## Два других дашборда
|
## Состояние кластера и запросы
|
||||||
|
|
||||||
Открывайте «Кластер», чтобы проверить нагрузку на процессор, использование
|
Открывайте «Кластер», чтобы проверить нагрузку на процессор, использование
|
||||||
памяти и дисков, число частей таблиц и ход их слияний в ClickHouse. Здесь же
|
памяти и дисков, число частей таблиц и ход их слияний в ClickHouse. Здесь же
|
||||||
@@ -58,11 +61,11 @@ STG: по нему видно, поступила ли очередная выг
|
|||||||
[справочник дашборда
|
[справочник дашборда
|
||||||
«Запросы»](../architecture/monitoring/queries.md).
|
«Запросы»](../architecture/monitoring/queries.md).
|
||||||
|
|
||||||
## Посмотрите сами
|
## Показатели за один модельный день
|
||||||
|
|
||||||
Откройте Grafana по адресу из раздела [«Адреса и
|
Откройте Grafana по адресу из раздела [«Адреса и
|
||||||
пароли»](../../README.md#адреса-и-пароли), затем дашборд «Данные». Запустите в
|
пароли»](../../README.md#адреса-и-пароли), затем дашборд «Данные». Запустите в
|
||||||
Airflow [`world_live_day`](airflow-dags.md#даги-мира): модельный день займет
|
Airflow [`world_live_day`](airflow-dags.md#генерация-модельных-дней): модельный день займет
|
||||||
около двадцати четырех минут.
|
около двадцати четырех минут.
|
||||||
|
|
||||||
Смотрите свежесть событий и свежесть заказов по отдельности. Пока день идет,
|
Смотрите свежесть событий и свежесть заказов по отдельности. Пока день идет,
|
||||||
@@ -73,11 +76,11 @@ Airflow [`world_live_day`](airflow-dags.md#даги-мира): модельны
|
|||||||
при генерации следующего дня. Даг пока продолжает работу, ожидая завершения
|
при генерации следующего дня. Даг пока продолжает работу, ожидая завершения
|
||||||
обработки данных в дневном конвейере.
|
обработки данных в дневном конвейере.
|
||||||
|
|
||||||
## Куда дальше
|
## Следующая глава и справочники
|
||||||
|
|
||||||
Устройство мониторинга описано в [техническом
|
Устройство мониторинга описано в [техническом
|
||||||
справочнике](../architecture/monitoring/README.md), панели дашборда «Данные» —
|
справочнике](../architecture/monitoring/README.md), панели дашборда «Данные» —
|
||||||
в [отдельном разделе](../architecture/monitoring/data.md).
|
в [отдельном разделе](../architecture/monitoring/data.md).
|
||||||
Почему выбраны именно эти показатели, объясняет
|
Почему выбраны именно эти показатели, объясняет
|
||||||
[ADR 0002](../adr/0002-monitoring-scope.md). Следующая глава —
|
[ADR 0002](../adr/0002-monitoring-scope.md). Следующая глава —
|
||||||
[«Немного Superset»](superset.md).
|
[«Отчеты в Superset»](superset.md).
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
# Хранилище и его слои
|
# Хранилище и его слои
|
||||||
|
|
||||||
В хранилище приходят два потока из Kafka: в топике `hits` — события
|
В Kafka поступают события сайта, или кликстрим, и заказы магазина.
|
||||||
кликстрима, в `orders` — заказы магазина. Оба проходят один путь: STG → ODS →
|
Для событий выделен топик `hits`, для заказов — `orders`.
|
||||||
DDS → DM. В STG сообщения сохраняются как есть, в ODS разбираются по полям,
|
Из Kafka данные проходят четыре слоя хранилища. В STG сообщения сохраняются
|
||||||
в DDS из них собираются события, заказы и сессии, а в DM — витрины для отчетов.
|
как есть, в ODS разбираются по полям, в DDS из них собираются события,
|
||||||
|
заказы и сессии, а в DM — витрины для отчетов.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
Kafka --> STG --> ODS --> DDS --> DM
|
Kafka --> STG --> ODS --> DDS --> DM
|
||||||
```
|
```
|
||||||
|
|
||||||
## Как читать имена
|
## Имена таблиц и представлений
|
||||||
|
|
||||||
Для обычного чтения выбирайте представления с суффиксом `_v`. Они позволяют
|
Для обычного чтения выбирайте представления с суффиксом `_v`. Они позволяют
|
||||||
менять устройство таблиц, сохраняя привычные запросы к данным.
|
менять устройство таблиц, сохраняя привычные запросы к данным.
|
||||||
@@ -20,7 +21,7 @@ flowchart LR
|
|||||||
Подробный разбор имен есть в [справочнике
|
Подробный разбор имен есть в [справочнике
|
||||||
хранилища](../architecture/storage.md#имена-объектов).
|
хранилища](../architecture/storage.md#имена-объектов).
|
||||||
|
|
||||||
## STG: как поступило
|
## STG: исходные сообщения
|
||||||
|
|
||||||
STG — сырой слой. Он сохраняет сообщение целой строкой, без разбора. Вместе
|
STG — сырой слой. Он сохраняет сообщение целой строкой, без разбора. Вместе
|
||||||
с ним записываются сведения о доставке: топик, партиция, смещение и время
|
с ним записываются сведения о доставке: топик, партиция, смещение и время
|
||||||
@@ -31,7 +32,7 @@ STG — сырой слой. Он сохраняет сообщение цело
|
|||||||
удаляются по правилу из [справочника
|
удаляются по правилу из [справочника
|
||||||
хранилища](../architecture/storage.md#срок-жизни-сырых-данных).
|
хранилища](../architecture/storage.md#срок-жизни-сырых-данных).
|
||||||
|
|
||||||
## ODS: язык источника
|
## ODS: разбор сообщений
|
||||||
|
|
||||||
В ODS содержимое сообщения разбирается на колонки с нужными типами данных:
|
В ODS содержимое сообщения разбирается на колонки с нужными типами данных:
|
||||||
датами, числами, строками. Колонки сохраняют имена из источника. Например,
|
датами, числами, строками. Колонки сохраняют имена из источника. Например,
|
||||||
@@ -41,7 +42,7 @@ STG — сырой слой. Он сохраняет сообщение цело
|
|||||||
`*_errors` вместе с исходной строкой и классом ошибки. Правильно записанные
|
`*_errors` вместе с исходной строкой и классом ошибки. Правильно записанные
|
||||||
сообщения обрабатываются дальше.
|
сообщения обрабатываются дальше.
|
||||||
|
|
||||||
## DDS: язык бизнеса
|
## DDS: события, заказы и сессии
|
||||||
|
|
||||||
DDS — детальный слой: в нем данные подготовлены для анализа событий,
|
DDS — детальный слой: в нем данные подготовлены для анализа событий,
|
||||||
заказов и сессий. Здесь же хранится карта идентичностей — таблица связей
|
заказов и сессий. Здесь же хранится карта идентичностей — таблица связей
|
||||||
@@ -51,6 +52,8 @@ DDS — детальный слой: в нем данные подготовле
|
|||||||
|
|
||||||
В стенде визитом, или сессией, считают подряд идущие события одного
|
В стенде визитом, или сессией, считают подряд идущие события одного
|
||||||
`ClientID` без пауз дольше 30 минут. Визит не пересекает границу модельных суток.
|
`ClientID` без пауз дольше 30 минут. Визит не пересекает границу модельных суток.
|
||||||
|
Модельные сутки — день в календаре учебного магазина. Этот календарь
|
||||||
|
не зависит от текущей даты на вашем компьютере.
|
||||||
|
|
||||||
Источник передает готовый идентификатор визита `VisitID` по образцу
|
Источник передает готовый идентификатор визита `VisitID` по образцу
|
||||||
Яндекс Метрики. В учебном хранилище сессии рассчитываются заново по времени
|
Яндекс Метрики. В учебном хранилище сессии рассчитываются заново по времени
|
||||||
@@ -61,7 +64,7 @@ DDS — детальный слой: в нем данные подготовле
|
|||||||
Готовые сессии читайте через `dds.session_v`. События для самостоятельного
|
Готовые сессии читайте через `dds.session_v`. События для самостоятельного
|
||||||
расчета доступны в `dds.event_v`: в этом представлении `VisitID` скрыт.
|
расчета доступны в `dds.event_v`: в этом представлении `VisitID` скрыт.
|
||||||
|
|
||||||
## DM: данные для дашбордов
|
## DM: витрины для отчетов
|
||||||
|
|
||||||
DM — слой витрин. Витрина — готовый набор показателей для отчета или
|
DM — слой витрин. Витрина — готовый набор показателей для отчета или
|
||||||
дашборда. Здесь уже посчитаны, например, дневная выручка по категориям и
|
дашборда. Здесь уже посчитаны, например, дневная выручка по категориям и
|
||||||
@@ -71,7 +74,7 @@ DM — слой витрин. Витрина — готовый набор по
|
|||||||
путь расчета. Состав готовых витрин смотрите в [карте
|
путь расчета. Состав готовых витрин смотрите в [карте
|
||||||
таблиц](../architecture/storage.md#карта-таблиц).
|
таблиц](../architecture/storage.md#карта-таблиц).
|
||||||
|
|
||||||
## Справочные данные: зона `dic`
|
## Справочники в базе `dic`
|
||||||
|
|
||||||
В базе `dic` хранятся готовые справочные данные. Они загружаются отдельно
|
В базе `dic` хранятся готовые справочные данные. Они загружаются отдельно
|
||||||
от событий и заказов и могут использоваться в расчетах любого слоя.
|
от событий и заказов и могут использоваться в расчетах любого слоя.
|
||||||
@@ -81,7 +84,7 @@ DM — слой витрин. Витрина — готовый набор по
|
|||||||
устроена зона, смотрите в [разделе о
|
устроена зона, смотрите в [разделе о
|
||||||
справочниках](../architecture/storage.md#справочники).
|
справочниках](../architecture/storage.md#справочники).
|
||||||
|
|
||||||
## Сквозные ключи
|
## Связь данных между слоями
|
||||||
|
|
||||||
Идентификаторы позволяют связать данные из разных слоев. При переходе в DDS
|
Идентификаторы позволяют связать данные из разных слоев. При переходе в DDS
|
||||||
часть колонок переименовывается: например, `WatchID` становится `event_id`.
|
часть колонок переименовывается: например, `WatchID` становится `event_id`.
|
||||||
@@ -99,7 +102,7 @@ DM — слой витрин. Витрина — готовый набор по
|
|||||||
[словарь понятий](../../CONTEXT.md#язык). Точные имена объектов и полей сверяйте с
|
[словарь понятий](../../CONTEXT.md#язык). Точные имена объектов и полей сверяйте с
|
||||||
[картой таблиц](../architecture/storage.md#карта-таблиц).
|
[картой таблиц](../architecture/storage.md#карта-таблиц).
|
||||||
|
|
||||||
## Посмотрите сами
|
## Примеры запросов
|
||||||
|
|
||||||
Подключитесь к ClickHouse по инструкции из разделов [«Адреса и
|
Подключитесь к ClickHouse по инструкции из разделов [«Адреса и
|
||||||
пароли»](../../README.md#адреса-и-пароли) и [«Подключиться из
|
пароли»](../../README.md#адреса-и-пароли) и [«Подключиться из
|
||||||
@@ -122,6 +125,8 @@ ORDER BY UTCEventTime
|
|||||||
LIMIT 20;
|
LIMIT 20;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Каждая строка здесь — отдельное действие посетителя.
|
||||||
|
|
||||||
Второй запрос покажет 20 сессий того же дня с наибольшим числом событий.
|
Второй запрос покажет 20 сессий того же дня с наибольшим числом событий.
|
||||||
Сессии рассчитаны по всем событиям дня:
|
Сессии рассчитаны по всем событиям дня:
|
||||||
|
|
||||||
@@ -140,7 +145,12 @@ ORDER BY events_total DESC
|
|||||||
LIMIT 20;
|
LIMIT 20;
|
||||||
```
|
```
|
||||||
|
|
||||||
Третий запрос покажет готовую выручку по категориям за тот же день:
|
Теперь одна строка представляет целый визит. `events_total` показывает,
|
||||||
|
сколько событий в него вошло; начало и конец визита записаны в
|
||||||
|
`started_at` и `finished_at`.
|
||||||
|
|
||||||
|
Третий запрос покажет выручку магазина по категориям за тот же день.
|
||||||
|
Эта витрина рассчитана по заказам:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT
|
SELECT
|
||||||
@@ -153,13 +163,13 @@ WHERE report_date = '2026-06-01'
|
|||||||
ORDER BY revenue DESC;
|
ORDER BY revenue DESC;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Здесь одна строка содержит итоги дня для категории товаров.
|
||||||
Все три запроса читают представления `_v`.
|
Все три запроса читают представления `_v`.
|
||||||
|
|
||||||
## Куда дальше
|
## Следующая глава и справочники
|
||||||
|
|
||||||
Откройте [интерактивную карту](architecture-map.html) из своей копии
|
Откройте [интерактивную карту](architecture-map.html) из своей копии
|
||||||
репозитория и проследите путь данных.
|
репозитория и проследите путь данных.
|
||||||
Список объектов, сроки хранения и устройство таблиц — в [справочнике
|
Список объектов, сроки хранения и устройство таблиц — в [справочнике
|
||||||
хранилища](../architecture/storage.md). Как данные обрабатываются между слоями — в
|
хранилища](../architecture/storage.md). Как данные обрабатываются между слоями — в
|
||||||
следующей главе: [потоки Airflow и даги модельного
|
следующей главе: [«Генерация и обработка данных в Airflow»](airflow-dags.md).
|
||||||
времени](airflow-dags.md).
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Немного Superset
|
# Отчеты в Superset
|
||||||
|
|
||||||
Superset показывает витрины слоя DM в виде графиков. К ClickHouse он
|
Superset показывает витрины слоя DM в виде графиков. К ClickHouse он
|
||||||
подключается пользователем `bi`. Дашборд стенда «Бизнес-сюжеты» собран из трех
|
подключается пользователем `bi`. Дашборд стенда «Бизнес-сюжеты» собран из трех
|
||||||
@@ -24,7 +24,7 @@ Superset показывает витрины слоя DM в виде графи
|
|||||||
Затем на основе датасета создается график: выбираются показатели и способ
|
Затем на основе датасета создается график: выбираются показатели и способ
|
||||||
их отображения. Несколько графиков можно разместить на одном дашборде.
|
их отображения. Несколько графиков можно разместить на одном дашборде.
|
||||||
|
|
||||||
## Стендовые объекты и ваши
|
## Готовые и собственные отчеты
|
||||||
|
|
||||||
Готовые датасеты, графики и дашборд сохранены в файлах репозитория
|
Готовые датасеты, графики и дашборд сохранены в файлах репозитория
|
||||||
и импортируются при каждом `make up`. Если изменить их через интерфейс,
|
и импортируются при каждом `make up`. Если изменить их через интерфейс,
|
||||||
@@ -43,13 +43,14 @@ Superset](../architecture/superset.md#импорт-при-каждой-иниц
|
|||||||
Последняя дата с выручкой на день раньше последней даты с событиями
|
Последняя дата с выручкой на день раньше последней даты с событиями
|
||||||
кликстрима. Выгрузка заказов за день D поступает при генерации следующего
|
кликстрима. Выгрузка заказов за день D поступает при генерации следующего
|
||||||
дня D+1, а затем обрабатывается конвейером. Вы уже видели эту разницу
|
дня D+1, а затем обрабатывается конвейером. Вы уже видели эту разницу
|
||||||
в [главе об Airflow](airflow-dags.md#посмотрите-сами). Начальное состояние
|
в [главе об Airflow](airflow-dags.md#один-день-от-генерации-до-витрин). Начальное состояние
|
||||||
показано в разделе [«Стартовый мир»](../architecture/world.md#стартовый-мир).
|
показано в разделе [«Стартовый мир»](../architecture/world.md#стартовый-мир).
|
||||||
|
|
||||||
## Посмотрите сами
|
## Сверка графика с витриной
|
||||||
|
|
||||||
Откройте Superset по адресу из раздела [«Адреса и
|
Откройте Superset по адресу из раздела [«Адреса и
|
||||||
пароли»](../../README.md#адреса-и-пароли) и выберите дашборд «Бизнес-сюжеты».
|
пароли»](../../README.md#адреса-и-пароли) и выберите дашборд «Бизнес-сюжеты».
|
||||||
|
Найдите график «Посетители и известные пользователи».
|
||||||
Затем откройте SQL Lab и выполните запрос:
|
Затем откройте SQL Lab и выполните запрос:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
@@ -59,9 +60,14 @@ ORDER BY report_date DESC
|
|||||||
LIMIT 7;
|
LIMIT 7;
|
||||||
```
|
```
|
||||||
|
|
||||||
Как построить свой отчет, рассказывают [учебные задания](../labs/README.md).
|
Выберите одну дату из результата. На дашборде задайте «Период», в который
|
||||||
|
она входит, и обновите график. Наведите указатель на точку этой даты:
|
||||||
|
числа посетителей и известных пользователей должны совпасть с `visitors`
|
||||||
|
и `known_users` из SQL.
|
||||||
|
|
||||||
## Куда дальше
|
Как построить собственный отчет, рассказывают [учебные задания](../labs/README.md).
|
||||||
|
|
||||||
|
## Следующая глава и справочники
|
||||||
|
|
||||||
Подключение и настройка Superset описаны в [справочнике
|
Подключение и настройка Superset описаны в [справочнике
|
||||||
Superset](../architecture/superset.md), а состав витрин — в [справочнике
|
Superset](../architecture/superset.md), а состав витрин — в [справочнике
|
||||||
|
|||||||
@@ -1,37 +1,38 @@
|
|||||||
# Задание 1: `dm.events_enriched_v`
|
# Задание 1: события для анализа, `dm.events_enriched_v`
|
||||||
|
|
||||||
**Чему учит:** подготовить удобный для анализа набор полей события
|
Вы подготовите представление с полями для анализа событий. Оно будет
|
||||||
и обойтись представлением, сохранив одну строку на событие.
|
добавлять вычисляемые поля при чтении, сохраняя одну строку на событие.
|
||||||
|
|
||||||
## Сначала прочитать
|
## Подготовка
|
||||||
|
|
||||||
- [«Хранилище и его слои»](../handbook/storage-layers.md).
|
- [«Хранилище и его слои»](../handbook/storage-layers.md).
|
||||||
- [Как применить свой DDL](README.md#как-применить-свой-ddl): файл для решения
|
- [Выполнение SQL в DBeaver](README.md#выполнение-sql-в-dbeaver): файл для решения
|
||||||
и подключение с правами на создание витрины.
|
и подключение с правами на создание витрины.
|
||||||
|
|
||||||
## Для чего нужна витрина
|
## Данные для анализа событий
|
||||||
|
|
||||||
В `dds.event_v` 44 колонки, включая технические поля источника и массивы
|
В `dds.event_v` 44 колонки, включая технические поля источника и массивы
|
||||||
товаров. Для анализа событий в Superset нужны деловые поля и три готовых
|
товаров. Для анализа событий в Superset нужны сведения о посетителе и его
|
||||||
значения: час события, путь страницы и домен перехода. Тогда вам не придется
|
действиях, а также три вычисляемых поля: час события, путь страницы и домен,
|
||||||
|
с которого пришел посетитель. Тогда вам не придется
|
||||||
повторять эти выражения в каждом запросе.
|
повторять эти выражения в каждом запросе.
|
||||||
|
|
||||||
Витрины следующих заданий читают DDS напрямую. Использовать
|
Витрины следующих заданий читают DDS напрямую. Использовать
|
||||||
`dm.events_enriched_v` как источник для них не нужно: задания независимы, их
|
`dm.events_enriched_v` как источник для них не нужно: задания независимы, их
|
||||||
можно выполнять в любом порядке.
|
можно выполнять в любом порядке.
|
||||||
|
|
||||||
## Что сделать
|
## Создание представления
|
||||||
|
|
||||||
Создайте обычное представление `CREATE VIEW dm.events_enriched_v`.
|
Создайте обычное представление `CREATE VIEW dm.events_enriched_v`.
|
||||||
Единственный источник — `dds.event_v`. Зерно — **событие**: каждой строке
|
Единственный источник: `dds.event_v`. Каждой строке источника должна
|
||||||
источника соответствует одна строка витрины.
|
соответствовать ровно одна строка витрины: одно событие.
|
||||||
|
|
||||||
Физическая таблица, таблица-донор и задача в `dm_transform` не нужны.
|
Представление выполняет запрос при чтении и не хранит отдельную копию данных.
|
||||||
Представление не хранит строки, поэтому ключ шардирования и пересборка ему
|
Для него не нужно создавать таблицы или добавлять задачу в `dm_transform`.
|
||||||
не требуются.
|
|
||||||
|
|
||||||
Укажите колонки явно. Иначе новая колонка источника попадет в витрину
|
Укажите колонки явно. Иначе новая колонка источника попадет в витрину
|
||||||
автоматически, и договор с потребителем изменится без вашего участия.
|
автоматически. Отчет получит ее, даже если вы не собирались менять его набор
|
||||||
|
полей.
|
||||||
|
|
||||||
| Группа | Колонки |
|
| Группа | Колонки |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -57,9 +58,7 @@ DDS читают из ODS без отдельной загрузки. Эти с
|
|||||||
## Типичная ошибка
|
## Типичная ошибка
|
||||||
|
|
||||||
Не разворачивайте массивы товаров через `ARRAY JOIN`. У события с тремя
|
Не разворачивайте массивы товаров через `ARRAY JOIN`. У события с тремя
|
||||||
позициями получится три строки. Зерно изменится. Витрину с новым зерном на
|
позициями получится три строки вместо одной.
|
||||||
стенде хранят готовым агрегатом — причины описаны в разделе [«Как принято на
|
|
||||||
ступени 1»](README.md#как-принято-на-ступени-1).
|
|
||||||
|
|
||||||
Главная опасность — потеря событий с пустым массивом товаров: `ARRAY JOIN`
|
Главная опасность — потеря событий с пустым массивом товаров: `ARRAY JOIN`
|
||||||
исключает их из результата. Товары есть только в событиях корзины и покупки.
|
исключает их из результата. Товары есть только в событиях корзины и покупки.
|
||||||
@@ -68,9 +67,9 @@ DDS читают из ODS без отдельной загрузки. Эти с
|
|||||||
по-прежнему будет выглядеть таблицей событий.
|
по-прежнему будет выглядеть таблицей событий.
|
||||||
|
|
||||||
Сохраните массивы как в источнике. Для анализа отдельных товарных позиций
|
Сохраните массивы как в источнике. Для анализа отдельных товарных позиций
|
||||||
нужна другая витрина с другим зерном и именем.
|
нужна отдельная витрина, где одна строка соответствует товарной позиции.
|
||||||
|
|
||||||
## Как проверить себя
|
## Проверка результата
|
||||||
|
|
||||||
После применения DDL откройте Airflow по [адресу из README](../../README.md#адреса-и-пароли),
|
После применения DDL откройте Airflow по [адресу из README](../../README.md#адреса-и-пароли),
|
||||||
найдите даг `homework_check` и запустите его вручную. В новом запуске откройте
|
найдите даг `homework_check` и запустите его вручную. В новом запуске откройте
|
||||||
@@ -79,16 +78,15 @@ DDS читают из ODS без отдельной загрузки. Эти с
|
|||||||
расхождениях с источником — дни и примеры несовпадений. Задачи еще не
|
расхождениях с источником — дни и примеры несовпадений. Задачи еще не
|
||||||
созданных витрин будут красными; это ожидаемо и не мешает проверке первой.
|
созданных витрин будут красными; это ожидаемо и не мешает проверке первой.
|
||||||
|
|
||||||
Деловой ключ — `event_id` внутри дня. Получите результат прямо из
|
Выберите день и прочитайте его события из `dds.event_v` и своей витрины.
|
||||||
`dds.event_v` за тот же день. Соедините его с витриной полным внешним
|
Сопоставьте строки по `event_id` через `FULL OUTER JOIN`.
|
||||||
соединением по `event_id`. Посчитайте строки, где ключ отсутствует с одной
|
Найдите события, которые есть только с одной стороны, и события, у которых
|
||||||
из сторон или различаются значения деловых колонок. Расхождений должно быть
|
различаются перенесенные из источника поля. Расхождений должно быть ноль.
|
||||||
ноль.
|
|
||||||
|
|
||||||
Отдельно сверьте `event_hour`, `page_path` и `referer_host` с теми же
|
Отдельно сверьте `event_hour`, `page_path` и `referer_host` с теми же
|
||||||
выражениями, рассчитанными прямо по `dds.event_v`.
|
выражениями, рассчитанными прямо по `dds.event_v`.
|
||||||
|
|
||||||
## Что должно получиться
|
## Результат работы
|
||||||
|
|
||||||
- [ ] у вас есть `dm.events_enriched_v` с явно перечисленными колонками;
|
- [ ] у вас есть `dm.events_enriched_v` с явно перечисленными колонками;
|
||||||
- [ ] вы сверили число строк и набор `event_id` с `dds.event_v` за каждый
|
- [ ] вы сверили число строк и набор `event_id` с `dds.event_v` за каждый
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
# Задание 2: `dm.top_pages_daily`
|
# Задание 2: показатели страниц по дням, `dm.top_pages_daily`
|
||||||
|
|
||||||
**Чему учит:** собрать дневные показатели страниц и настроить их
|
В этой работе вы рассчитаете показатели страниц за каждый день и сохраните
|
||||||
обновление в `dm_transform` через замену партиций.
|
их в таблице. Затем подключите обновление витрины к `dm_transform`:
|
||||||
|
новый расчет будет заменять дневные партиции таблицы.
|
||||||
|
|
||||||
## Сначала прочитать
|
## Подготовка
|
||||||
|
|
||||||
- [«Хранилище и его слои»](../handbook/storage-layers.md);
|
- [«Хранилище и его слои»](../handbook/storage-layers.md);
|
||||||
- [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md).
|
- [«Генерация и обработка данных в Airflow»](../handbook/airflow-dags.md).
|
||||||
|
|
||||||
## Для чего нужна витрина
|
## Дневные показатели страниц
|
||||||
|
|
||||||
По дневным показателям страниц аналитик сравнивает просмотры, посетителей и
|
По дневным показателям страниц аналитик сравнивает просмотры, посетителей и
|
||||||
действия с товарами. Добавление в корзину происходит на карточке товара,
|
действия с товарами. Добавление в корзину происходит на карточке товара,
|
||||||
@@ -19,19 +20,23 @@
|
|||||||
действия происходят на разных страницах, а связи между ними в агрегате нет.
|
действия происходят на разных страницах, а связи между ними в агрегате нет.
|
||||||
Путь человека исследуют по событиям или сессиям.
|
Путь человека исследуют по событиям или сессиям.
|
||||||
|
|
||||||
## Что сделать
|
## Создание витрины
|
||||||
|
|
||||||
Источник — `dds.event_v`. Зерно витрины — **день события × страница**.
|
Читайте события из `dds.event_v`. В результате нужна одна строка на страницу
|
||||||
Читайте DDS напрямую: так выполнение задания не зависит от витрины
|
за день. Например, все просмотры одной карточки товара за один день попадут
|
||||||
задания 1.
|
в одну строку. Витрина задания 1 для этого расчета не нужна.
|
||||||
|
|
||||||
На стенде витрину с новым зерном хранят как готовый агрегат, чтобы не
|
Сохраните рассчитанные показатели в таблице, чтобы отчеты читали готовые
|
||||||
рассчитывать ее заново для каждого запроса. Причины описаны в разделе
|
итоги. Создайте пять объектов:
|
||||||
[«Как принято на ступени 1»](README.md#как-принято-на-ступени-1). Создайте
|
|
||||||
пять объектов:
|
- основные таблицы `dm.top_pages_daily_rep` и `dm.top_pages_daily_dist`;
|
||||||
`dm.top_pages_daily_rep` / `_dist`, донор `_stage_rep` / `_stage_dist` и
|
- промежуточные таблицы `dm.top_pages_daily_stage_rep` и `dm.top_pages_daily_stage_dist`;
|
||||||
публичное представление `dm.top_pages_daily_v`. Ключ партиции — день, он же
|
- представление для чтения `dm.top_pages_daily_v`.
|
||||||
единица пересборки.
|
|
||||||
|
Их назначение и порядок обновления разобраны во вводной:
|
||||||
|
[«Создание и обновление витрин»](README.md#создание-и-обновление-витрин).
|
||||||
|
Разбейте данные на партиции по дням, чтобы можно было заменить результат
|
||||||
|
одного дня целиком.
|
||||||
|
|
||||||
### Состав колонок
|
### Состав колонок
|
||||||
|
|
||||||
@@ -50,25 +55,26 @@
|
|||||||
адрес разделил бы показатели одной страницы на сотни строк. Учитывайте все
|
адрес разделил бы показатели одной страницы на сотни строк. Учитывайте все
|
||||||
три типа событий, а их счетчики вычисляйте через `countIf`.
|
три типа событий, а их счетчики вычисляйте через `countIf`.
|
||||||
|
|
||||||
### Ритм пересборки
|
### Дни для пересчета
|
||||||
|
|
||||||
События прожитого дня больше не меняются. Определяйте объем прогона по
|
События завершившегося модельного дня больше не меняются. Найдите первый
|
||||||
состоянию витрины: от первого несобранного прожитого дня до последнего
|
такой день, которого еще нет в витрине, и рассчитайте все дни от него до
|
||||||
прожитого. Отсчет от последнего собранного дня оставит пропуски после
|
последнего завершившегося. Если начать после последнего дня, который уже
|
||||||
частичного сбоя.
|
есть в витрине, можно пропустить более ранний день, не загруженный из-за сбоя.
|
||||||
|
|
||||||
Как у `revenue_daily`, заполните донор за весь диапазон одной вставкой,
|
Как у `revenue_daily`, запишите расчет за все выбранные дни в промежуточную
|
||||||
затем замените дневные партиции по одной.
|
таблицу одной вставкой. Затем замените дневные партиции основной таблицы по
|
||||||
|
одной. Добавьте запуск этих запросов в `dm_transform`.
|
||||||
|
|
||||||
### Ключ шардирования
|
### Ключ шардирования
|
||||||
|
|
||||||
Выберите ключ самостоятельно. У `revenue_daily` кроме дня одно измерение с
|
Выберите ключ самостоятельно. У `revenue_daily` строки сгруппированы по дню
|
||||||
шестью близкими по объему значениями. Здесь страниц много, и число событий
|
и категории товара; категорий всего шесть. Здесь страниц много, и число
|
||||||
на них сильно различается.
|
событий на них сильно различается.
|
||||||
|
|
||||||
У цели и донора используйте одинаковый ключ: иначе при замене партиции
|
У основной и промежуточной таблиц используйте одинаковый ключ шардирования.
|
||||||
строка потеряется на одном шарде и удвоится на другом. О том, [как устроен
|
Замена партиции не перераспределяет строки между шардами. Подробнее об этом
|
||||||
донор](../architecture/storage.md), читайте в разделе «Раскладка по шардам».
|
в разделе [«Раскладка по шардам»](../architecture/storage.md#раскладка-по-шардам).
|
||||||
|
|
||||||
## Типичная ошибка
|
## Типичная ошибка
|
||||||
|
|
||||||
@@ -82,13 +88,13 @@
|
|||||||
Выбирайте проверку под возможную ошибку. Этот подход описан в
|
Выбирайте проверку под возможную ошибку. Этот подход описан в
|
||||||
[справочнике проверок](../architecture/testing.md).
|
[справочнике проверок](../architecture/testing.md).
|
||||||
|
|
||||||
## Как проверить себя
|
## Проверка результата
|
||||||
|
|
||||||
Деловой ключ — `page_path` внутри дня. Получите прямой агрегат из
|
Рассчитайте те же показатели по `dds.event_v` за выбранный день.
|
||||||
`dds.event_v` за день и соедините его с `dm.top_pages_daily_v` полным
|
Сопоставьте результат со строками `dm.top_pages_daily_v` за этот день
|
||||||
внешним соединением по ключу. Посчитайте строки, где ключ отсутствует с
|
через `FULL OUTER JOIN` по `page_path`. Найдите страницы, которые есть
|
||||||
одной из сторон или различается любой из четырех счетчиков. Расхождений
|
только с одной стороны, и строки, где различается хотя бы один из четырех
|
||||||
должно быть ноль.
|
счетчиков. Расхождений должно быть ноль.
|
||||||
|
|
||||||
Перед полным сравнением проверьте дневные суммы:
|
Перед полным сравнением проверьте дневные суммы:
|
||||||
|
|
||||||
@@ -98,10 +104,10 @@
|
|||||||
Сумма `visitors` по страницам повторно учитывает посетителя, открывшего
|
Сумма `visitors` по страницам повторно учитывает посетителя, открывшего
|
||||||
несколько страниц. Число посетителей за день берите из `dm.daily_traffic_v`.
|
несколько страниц. Число посетителей за день берите из `dm.daily_traffic_v`.
|
||||||
|
|
||||||
## Что должно получиться
|
## Результат работы
|
||||||
|
|
||||||
- [ ] у вас есть пять объектов витрины; донор объявлен через `AS` и
|
- [ ] у вас есть пять объектов витрины; промежуточная таблица объявлена через
|
||||||
использует тот же ключ шардирования, что и целевая таблица;
|
`AS` и использует тот же ключ шардирования, что и основная;
|
||||||
- [ ] в `dm.top_pages_daily_v` явно перечислены колонки, включая
|
- [ ] в `dm.top_pages_daily_v` явно перечислены колонки, включая
|
||||||
`_load_id` и `_load_ts`;
|
`_load_id` и `_load_ts`;
|
||||||
- [ ] ваш запрос сам определяет дни пересборки по состоянию витрины;
|
- [ ] ваш запрос сам определяет дни пересборки по состоянию витрины;
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
# Задание 3: `dm.session_overview`
|
# Задание 3: визиты по устройствам, `dm.session_overview`
|
||||||
|
|
||||||
**Чему учит:** хранить слагаемые для расчета средних и долей и понимать,
|
Вы соберете дневные показатели визитов по устройствам. В таблице будут
|
||||||
|
храниться суммы и счетчики для расчета средних и долей. Заодно проверите,
|
||||||
когда сложение числа посетителей по устройствам завышает аудиторию.
|
когда сложение числа посетителей по устройствам завышает аудиторию.
|
||||||
|
|
||||||
## Сначала прочитать
|
## Подготовка
|
||||||
|
|
||||||
- [«Хранилище и его слои»](../handbook/storage-layers.md);
|
- [«Хранилище и его слои»](../handbook/storage-layers.md);
|
||||||
- [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md).
|
- [«Генерация и обработка данных в Airflow»](../handbook/airflow-dags.md).
|
||||||
|
|
||||||
## Для чего нужна витрина
|
## Показатели визитов
|
||||||
|
|
||||||
Витрина нужна, чтобы по дням сравнивать визиты с телефона и компьютера: их
|
Витрина нужна, чтобы по дням сравнивать визиты с телефона и компьютера: их
|
||||||
число, длительность, просмотры страниц и частоту покупок.
|
число, длительность, просмотры страниц и частоту покупок.
|
||||||
@@ -17,13 +18,19 @@
|
|||||||
устройствам. Почему такие итоги хранят в отдельной таблице, объясняет
|
устройствам. Почему такие итоги хранят в отдельной таблице, объясняет
|
||||||
[справочник DM](../architecture/dm.md).
|
[справочник DM](../architecture/dm.md).
|
||||||
|
|
||||||
## Что сделать
|
## Создание витрины
|
||||||
|
|
||||||
Источник — `dds.session_v`. Зерно — **день сессии × категория устройства**.
|
Читайте готовые сессии из `dds.session_v`. В результате нужна одна строка
|
||||||
Зерно новое, поэтому витрина хранится готовым агрегатом — см. [«Как принято
|
на категорию устройства за день: например, итоги всех визитов с телефонов
|
||||||
на ступени 1»](README.md#как-принято-на-ступени-1). Создайте пять объектов:
|
за один день.
|
||||||
`dm.session_overview_rep` / `_dist`, донор `_stage_rep` / `_stage_dist` и
|
|
||||||
публичное представление `dm.session_overview_v`. Ключ партиции — день.
|
Создайте пять объектов по [общим правилам ступени 1](README.md#создание-и-обновление-витрин):
|
||||||
|
|
||||||
|
- основные таблицы `dm.session_overview_rep` и `dm.session_overview_dist`;
|
||||||
|
- промежуточные таблицы `dm.session_overview_stage_rep` и `dm.session_overview_stage_dist`;
|
||||||
|
- представление для чтения `dm.session_overview_v`.
|
||||||
|
|
||||||
|
Разбейте данные на партиции по дням.
|
||||||
|
|
||||||
### Состав колонок
|
### Состав колонок
|
||||||
|
|
||||||
@@ -47,19 +54,20 @@
|
|||||||
категорий встречаются `desktop`, `mobile` и `tablet`. Строку для `tv`
|
категорий встречаются `desktop`, `mobile` и `tablet`. Строку для `tv`
|
||||||
создавайте только при появлении таких визитов.
|
создавайте только при появлении таких визитов.
|
||||||
|
|
||||||
Вместо `avg_duration_seconds` храните слагаемые. Среднее из средних по трем
|
Для расчета средней длительности храните общую длительность и число сессий.
|
||||||
устройствам даст неверный ответ за день: число сессий в категориях
|
Готовую среднюю `avg_duration_seconds` сохранять не нужно. Число сессий
|
||||||
различается, поэтому средние нужно взвешивать. Потребитель делит сумму
|
в категориях различается, поэтому среднее из трех средних даст неверный
|
||||||
`duration_seconds_total` на сумму `sessions`. Долю отказов считают так же:
|
ответ за день. Чтобы получить его правильно, разделите сумму
|
||||||
сумму `bounce_sessions` делят на сумму `sessions`, а не усредняют готовые
|
`duration_seconds_total` на сумму `sessions`. Для доли отказов разделите
|
||||||
доли.
|
сумму `bounce_sessions` на сумму `sessions`. Усреднение готовых долей тоже
|
||||||
|
даст неверный результат.
|
||||||
|
|
||||||
### Ритм пересборки
|
### Дни для пересчета
|
||||||
|
|
||||||
Сессии прожитого дня больше не меняются. Как в задании 2, определяйте
|
Сессии завершившегося модельного дня больше не меняются. Как в задании 2,
|
||||||
диапазон от первого несобранного прожитого дня до последнего прожитого.
|
найдите первый завершившийся день, которого еще нет в витрине. Рассчитайте
|
||||||
Соберите его в донор и замените дневные партиции. Добавьте задачу в
|
все дни от него до последнего завершившегося, запишите их в промежуточную
|
||||||
`dm_transform`.
|
таблицу и замените дневные партиции основной. Добавьте задачу в `dm_transform`.
|
||||||
|
|
||||||
### Ключ шардирования
|
### Ключ шардирования
|
||||||
|
|
||||||
@@ -68,18 +76,18 @@
|
|||||||
`device_category`, сначала посчитайте строки на каждом шарде. Затем
|
`device_category`, сначала посчитайте строки на каждом шарде. Затем
|
||||||
объясните, почему такое распределение вас устраивает.
|
объясните, почему такое распределение вас устраивает.
|
||||||
|
|
||||||
Ключ донора — тот же, что у цели (см. [«Как принято на ступени
|
У основной и промежуточной таблиц должен быть одинаковый ключ шардирования:
|
||||||
1»](README.md#как-принято-на-ступени-1) и раздел «Раскладка по шардам»
|
см. [общие правила ступени 1](README.md#создание-и-обновление-витрин).
|
||||||
[справочника хранилища](../architecture/storage.md)).
|
|
||||||
|
|
||||||
## Почему сумма `visitors` сходится с `dm.daily_traffic_v`
|
## Число посетителей за день
|
||||||
|
|
||||||
Сравните сумму `visitors` по устройствам за день с `dm.daily_traffic_v`. На
|
Сравните сумму `visitors` по устройствам за день с `dm.daily_traffic_v`. На
|
||||||
стенде значения совпадут: устройство приписано куке на всю жизнь, поэтому за
|
стенде значения совпадут: одному `client_id` всегда соответствует одно
|
||||||
день она учитывается только в одной строке. Это свойство мира стенда; см.
|
устройство, поэтому за день он учитывается только в одной категории.
|
||||||
|
Это свойство мира стенда; см.
|
||||||
раздел «Паспорт куки» в [словаре понятий](../../CONTEXT.md).
|
раздел «Паспорт куки» в [словаре понятий](../../CONTEXT.md).
|
||||||
|
|
||||||
Если появятся визиты одной куки с двух устройств, сумма начнет завышать
|
Если появятся визиты одного `client_id` с двух устройств, сумма начнет завышать
|
||||||
аудиторию. Проверки счетчиков по категориям этого не обнаружат. Число
|
аудиторию. Проверки счетчиков по категориям этого не обнаружат. Число
|
||||||
посетителей дня берите из `dm.daily_traffic_v`, где оно рассчитано для дня
|
посетителей дня берите из `dm.daily_traffic_v`, где оно рассчитано для дня
|
||||||
целиком.
|
целиком.
|
||||||
@@ -91,12 +99,13 @@
|
|||||||
событий совпало с источником, а число сессий завышено, вероятная причина —
|
событий совпало с источником, а число сессий завышено, вероятная причина —
|
||||||
сборка витрины из событий вместо сессий.
|
сборка витрины из событий вместо сессий.
|
||||||
|
|
||||||
## Как проверить себя
|
## Проверка результата
|
||||||
|
|
||||||
Деловой ключ — `device_category` внутри дня. Получите прямой агрегат из
|
Рассчитайте те же показатели по `dds.session_v` за выбранный день.
|
||||||
`dds.session_v` за день. Соедините его с `dm.session_overview_v` полным
|
Сопоставьте результат со строками `dm.session_overview_v` за этот день
|
||||||
внешним соединением по ключу. Посчитайте строки, где ключ отсутствует с
|
через `FULL OUTER JOIN` по `device_category`. Найдите категории, которые
|
||||||
одной из сторон или различается любой счетчик. Расхождений должно быть ноль.
|
есть только с одной стороны, и строки, где различается хотя бы один
|
||||||
|
счетчик. Расхождений должно быть ноль.
|
||||||
|
|
||||||
Еще две проверки дневных итогов:
|
Еще две проверки дневных итогов:
|
||||||
|
|
||||||
@@ -104,12 +113,12 @@
|
|||||||
повторного учета визитов;
|
повторного учета визитов;
|
||||||
- сумма `events_total` равна сумме `events_total` по сессиям того же дня.
|
- сумма `events_total` равна сумме `events_total` по сессиям того же дня.
|
||||||
|
|
||||||
## Что должно получиться
|
## Результат работы
|
||||||
|
|
||||||
- [ ] у вас есть пять объектов витрины; донор объявлен через `AS` и
|
- [ ] у вас есть пять объектов витрины; промежуточная таблица объявлена через
|
||||||
использует ключ шардирования целевой таблицы;
|
`AS` и использует ключ шардирования основной;
|
||||||
- [ ] вы явно перечислили колонки в `dm.session_overview_v` и собрали
|
- [ ] вы явно перечислили колонки в `dm.session_overview_v` и собрали
|
||||||
показатели из готовых сессий `dds.session_v`;
|
показатели из готовых сессий `dds.session_v`;
|
||||||
- [ ] в таблице хранятся слагаемые для средних и долей;
|
- [ ] в таблице хранятся суммы и счетчики для расчета средних и долей;
|
||||||
- [ ] проверка `session_overview` в `homework_check` прошла успешно;
|
- [ ] проверка `session_overview` в `homework_check` прошла успешно;
|
||||||
- [ ] витрина обновляется при запуске `dm_transform`.
|
- [ ] витрина обновляется при запуске `dm_transform`.
|
||||||
@@ -1,28 +1,42 @@
|
|||||||
# Задание 4: `dm.dq_errors_daily`
|
# Задание 4: ошибки загрузки по дням, `dm.dq_errors_daily`
|
||||||
|
|
||||||
**Чему учит:** считать брак по дню загрузки и сохранять сводку дольше
|
Вы соберете дневную сводку ошибок загрузки. Ее итоги должны сохраниться
|
||||||
исходных строк.
|
и после того, как исходные сообщения будут удалены из хранилища.
|
||||||
|
|
||||||
## Сначала прочитать
|
## Подготовка
|
||||||
|
|
||||||
- [«Хранилище и его слои»](../handbook/storage-layers.md);
|
- [«Хранилище и его слои»](../handbook/storage-layers.md);
|
||||||
- [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md).
|
- [«Генерация и обработка данных в Airflow»](../handbook/airflow-dags.md).
|
||||||
|
|
||||||
## Для чего нужна витрина
|
## Ошибки загрузки и их источники
|
||||||
|
|
||||||
Сводку открывают при росте брака в потоке: смотрят, каких ошибок стало
|
Если сообщение не удалось разобрать, стенд записывает его в таблицу ошибок.
|
||||||
больше и сколько строк забраковано. Причину конкретной ошибки исследуют
|
Такие строки дальше называются браком. Вы соберете сводку, по которой можно
|
||||||
в `ods.event_errors_dist` и `ods.order_errors_dist`, где есть сырой текст и
|
увидеть, каких ошибок стало больше и сколько строк забраковано.
|
||||||
координаты доставки.
|
|
||||||
|
|
||||||
## Что сделать
|
Источники сводки:
|
||||||
|
|
||||||
Зерно — **день загрузки × источник × класс брака**. В источнике зерно —
|
- `ods.event_errors_dist`: ошибки разбора событий;
|
||||||
строка брака. Зерно новое, поэтому витрина хранится готовым агрегатом — см.
|
- `ods.order_errors_dist`: ошибки разбора выгрузки заказов.
|
||||||
[«Как принято на ступени 1»](README.md#как-принято-на-ступени-1). Создайте
|
|
||||||
пять объектов: `dm.dq_errors_daily_rep` / `_dist`, донор `_stage_rep` /
|
В этих таблицах есть исходный текст сообщения и сведения о его доставке:
|
||||||
`_stage_dist` и публичное представление `dm.dq_errors_daily_v`. Ключ
|
они пригодятся при поиске причины ошибки. Представлений `_v` у таблиц ошибок
|
||||||
партиции — день загрузки.
|
нет, создавать их для задания не нужно. Чтение локальной таблицы `_rep`
|
||||||
|
даст строки только одного шарда из двух, поэтому используйте `_dist`.
|
||||||
|
|
||||||
|
## Создание сводки
|
||||||
|
|
||||||
|
В результате нужна одна строка на день загрузки, источник и класс ошибки.
|
||||||
|
Например, ошибки `field_invalid` в заказах за один день загрузки попадут
|
||||||
|
в одну строку с их количеством.
|
||||||
|
|
||||||
|
Создайте пять объектов по [общим правилам ступени 1](README.md#создание-и-обновление-витрин):
|
||||||
|
|
||||||
|
- основные таблицы `dm.dq_errors_daily_rep` и `dm.dq_errors_daily_dist`;
|
||||||
|
- промежуточные таблицы `dm.dq_errors_daily_stage_rep` и `dm.dq_errors_daily_stage_dist`;
|
||||||
|
- представление для чтения `dm.dq_errors_daily_v`.
|
||||||
|
|
||||||
|
Разбейте данные на партиции по дням загрузки.
|
||||||
|
|
||||||
### Состав колонок
|
### Состав колонок
|
||||||
|
|
||||||
@@ -41,16 +55,17 @@
|
|||||||
Служебная `_load_ts` витрины описывает сборку сводки; ее дата может
|
Служебная `_load_ts` витрины описывает сборку сводки; ее дата может
|
||||||
отличаться от `load_date`.
|
отличаться от `load_date`.
|
||||||
|
|
||||||
У событий и заказов свои классы брака. `source` входит в зерно, чтобы
|
У событий и заказов свои классы брака. Группируйте строки также по `source`,
|
||||||
одинаковые имена классов с разным смыслом не объединялись в одну строку.
|
чтобы одноименные классы разных источников не объединялись.
|
||||||
|
|
||||||
### Ритм пересборки
|
### Пересчет и хранение итогов
|
||||||
|
|
||||||
Брак хранится месяц по `TTL`, сырые данные — трое суток. Сводка хранится
|
Правило `TTL` удаляет строки брака через месяц, сырые данные через трое суток.
|
||||||
бессрочно: итоги нужны и после удаления исходных строк.
|
Сводку храните бессрочно: итоги нужны и после удаления исходных строк.
|
||||||
|
|
||||||
Получите список дней загрузки, еще имеющихся в источниках. Соберите их в
|
Получите список дней загрузки, еще имеющихся в источниках. Рассчитайте их
|
||||||
донор и замените только эти партиции, сохранив остальные.
|
итоги в промежуточной таблице и замените только эти партиции основной таблицы.
|
||||||
|
Более старые итоги сохраните.
|
||||||
|
|
||||||
В течение текущих суток ошибки еще поступают: пересчитайте этот день на
|
В течение текущих суток ошибки еще поступают: пересчитайте этот день на
|
||||||
следующем прогоне.
|
следующем прогоне.
|
||||||
@@ -60,36 +75,23 @@
|
|||||||
Выберите ключ самостоятельно. Классов брака у событий и заказов вместе
|
Выберите ключ самостоятельно. Классов брака у событий и заказов вместе
|
||||||
меньше десятка. Объясните, насколько существенен перекос при таком объеме.
|
меньше десятка. Объясните, насколько существенен перекос при таком объеме.
|
||||||
|
|
||||||
Ключ донора — тот же, что у цели (см. [«Как принято на ступени
|
У основной и промежуточной таблиц должен быть одинаковый ключ шардирования:
|
||||||
1»](README.md#как-принято-на-ступени-1) и раздел «Раскладка по шардам»
|
см. [общие правила ступени 1](README.md#создание-и-обновление-витрин).
|
||||||
[справочника хранилища](../architecture/storage.md)).
|
|
||||||
|
|
||||||
## Источники: таблицы `_dist`
|
|
||||||
|
|
||||||
Только это задание читает данные не через `_v`: брак нужен для диагностики
|
|
||||||
приема и не входит в бизнес-модель. Публичных представлений у таблиц ошибок
|
|
||||||
нет, создавать их для задания не нужно. Источники:
|
|
||||||
|
|
||||||
- `ods.event_errors_dist` — ошибки разбора событий;
|
|
||||||
- `ods.order_errors_dist` — ошибки разбора слепка заказов.
|
|
||||||
|
|
||||||
Чтение локальной `_rep` даст строки одного шарда из двух: часть брака не
|
|
||||||
попадет в сводку, хотя запрос выполнится без ошибки.
|
|
||||||
|
|
||||||
## Типичная ошибка
|
## Типичная ошибка
|
||||||
|
|
||||||
Если заменить всю таблицу данными из нового донора, исчезнут итоги дней,
|
Если заменить всю сводку новым расчетом, исчезнут итоги дней, за которые
|
||||||
истекших по `TTL` источника. Восстановить их неоткуда.
|
исходные строки уже удалены по `TTL`. Восстановить эти итоги неоткуда.
|
||||||
|
|
||||||
## Как проверить себя
|
## Проверка результата
|
||||||
|
|
||||||
Деловой ключ — `source` и `error_class` внутри `load_date`. Проверяйте дни,
|
Сопоставляйте строки по `load_date`, `source` и `error_class`. Проверяйте дни,
|
||||||
оставшиеся хотя бы в одной таблице ошибок. Если дня за пределами `TTL` уже
|
оставшиеся хотя бы в одной таблице ошибок. Старые итоги, для которых исходные
|
||||||
нет в источнике, это не ошибка: сверять его уже не с чем.
|
строки уже удалены по `TTL`, сверять не с чем.
|
||||||
|
|
||||||
Посчитайте строки прямо в обеих таблицах ошибок и соедините результат с
|
Посчитайте строки прямо в обеих таблицах ошибок и соедините результат с
|
||||||
`dm.dq_errors_daily_v` полным внешним соединением по ключу. Затем посчитайте
|
`dm.dq_errors_daily_v` через `FULL OUTER JOIN` по этим трем полям. Посчитайте
|
||||||
строки, где ключ отсутствует с одной из сторон или различается `error_rows`.
|
строки, которые есть только с одной стороны или у которых различается `error_rows`.
|
||||||
Расхождений должно быть ноль.
|
Расхождений должно быть ноль.
|
||||||
|
|
||||||
Сумма `error_rows` по проверяемым дням равна общему числу строк в
|
Сумма `error_rows` по проверяемым дням равна общему числу строк в
|
||||||
@@ -99,10 +101,10 @@
|
|||||||
Если в источниках нет брака, витрина должна быть пустой. В этом случае
|
Если в источниках нет брака, витрина должна быть пустой. В этом случае
|
||||||
расхождений тоже ноль и проверка успешна.
|
расхождений тоже ноль и проверка успешна.
|
||||||
|
|
||||||
## Что должно получиться
|
## Результат работы
|
||||||
|
|
||||||
- [ ] у вас есть пять объектов витрины; донор объявлен через `AS` и
|
- [ ] у вас есть пять объектов витрины; промежуточная таблица объявлена через
|
||||||
использует ключ шардирования целевой таблицы;
|
`AS` и использует ключ шардирования основной;
|
||||||
- [ ] сводка включает ошибки событий и заказов из таблиц `_dist`;
|
- [ ] сводка включает ошибки событий и заказов из таблиц `_dist`;
|
||||||
- [ ] ваш запрос пересобирает только дни, еще доступные в источниках,
|
- [ ] ваш запрос пересобирает только дни, еще доступные в источниках,
|
||||||
сохраняя более старые итоги;
|
сохраняя более старые итоги;
|
||||||
|
|||||||
@@ -1,30 +1,38 @@
|
|||||||
# Задание 5: `dm.utm_effectiveness`
|
# Задание 5: показатели рекламы, `dm.utm_effectiveness`
|
||||||
|
|
||||||
**Чему учит:** различать сумму покупки со слов клиента (`declared_`) и
|
Вы подготовите показатели рекламных каналов по событиям сайта. Суммы покупок
|
||||||
серверную выручку, находить причины их расхождения.
|
в этой витрине будут отличаться от выручки по заказам магазина. В конце
|
||||||
|
работы вы сравните их и объясните причины расхождения.
|
||||||
|
|
||||||
## Сначала прочитать
|
## Подготовка
|
||||||
|
|
||||||
- [«Хранилище и его слои»](../handbook/storage-layers.md);
|
- [«Хранилище и его слои»](../handbook/storage-layers.md);
|
||||||
- [«Потоки Airflow и даги модельного времени»](../handbook/airflow-dags.md).
|
- [«Генерация и обработка данных в Airflow»](../handbook/airflow-dags.md).
|
||||||
|
|
||||||
## Для чего нужна витрина
|
## Покупки по рекламным каналам
|
||||||
|
|
||||||
Аналитик сравнивает рекламные каналы по дням: трафик, добавления в корзину,
|
Аналитик сравнивает рекламные каналы по дням: трафик, добавления в корзину,
|
||||||
покупки и заявленные клиентом суммы.
|
покупки и их суммы из событий сайта.
|
||||||
|
|
||||||
На стенде поведение и атрибуцию считают по трекеру, деньги — по бэкенду.
|
События сайта показывают действия посетителя и рекламные метки его переходов.
|
||||||
Здесь сделано исключение: UTM-метки есть только в кликстриме, поэтому для
|
Заказы магазина служат источником выручки. Но в заказах нет UTM-меток, поэтому
|
||||||
оценки покупок по меткам используют клиентскую сумму.
|
в этой витрине вы возьмете суммы из событий покупки. Их передает браузер
|
||||||
|
посетителя; дальше они называются клиентскими суммами. Приставка `declared_`
|
||||||
|
в имени показателя напоминает об этом источнике.
|
||||||
|
|
||||||
## Что сделать
|
## Создание витрины
|
||||||
|
|
||||||
Читайте `dds.event_v` напрямую, без соседних витрин DM. Зерно — **день
|
Читайте события из `dds.event_v`, без других витрин DM. В результате нужна
|
||||||
события × `utm_source` × `utm_medium` × `utm_campaign`**. Зерно новое,
|
одна строка на день и сочетание трех меток: `utm_source`, `utm_medium`,
|
||||||
поэтому витрина хранится готовым агрегатом — см. [«Как принято на ступени
|
`utm_campaign`. События одного дня с одинаковыми метками попадают в одну группу.
|
||||||
1»](README.md#как-принято-на-ступени-1). Создайте пять объектов:
|
|
||||||
`dm.utm_effectiveness_rep` / `_dist`, донор `_stage_rep` / `_stage_dist` и
|
Создайте пять объектов по [общим правилам ступени 1](README.md#создание-и-обновление-витрин):
|
||||||
публичное представление `dm.utm_effectiveness_v`. Ключ партиции — день.
|
|
||||||
|
- основные таблицы `dm.utm_effectiveness_rep` и `dm.utm_effectiveness_dist`;
|
||||||
|
- промежуточные таблицы `dm.utm_effectiveness_stage_rep` и `dm.utm_effectiveness_stage_dist`;
|
||||||
|
- представление для чтения `dm.utm_effectiveness_v`.
|
||||||
|
|
||||||
|
Разбейте данные на партиции по дням.
|
||||||
|
|
||||||
### Состав колонок
|
### Состав колонок
|
||||||
|
|
||||||
@@ -54,31 +62,32 @@
|
|||||||
Для `utm_campaign` выбран тип `String`: имен кампаний со временем становится
|
Для `utm_campaign` выбран тип `String`: имен кампаний со временем становится
|
||||||
много, и затраты на словарь `LowCardinality` перестают окупаться.
|
много, и затраты на словарь `LowCardinality` перестают окупаться.
|
||||||
|
|
||||||
### Ритм пересборки
|
### Дни для пересчета
|
||||||
|
|
||||||
События прожитого дня больше не меняются. Как в задании 2, соберите в донор
|
События завершившегося модельного дня больше не меняются. Как в задании 2,
|
||||||
дни от первого несобранного до последнего прожитого и замените дневные
|
найдите первый завершившийся день, которого еще нет в витрине. Рассчитайте
|
||||||
партиции. Добавьте задачу в `dm_transform`.
|
все дни от него до последнего завершившегося, запишите их в промежуточную
|
||||||
|
таблицу и замените дневные партиции основной. Добавьте задачу в `dm_transform`.
|
||||||
|
|
||||||
### Ключ шардирования
|
### Ключ шардирования
|
||||||
|
|
||||||
Выберите ключ самостоятельно. Если берете хеш одной из трех UTM-колонок,
|
Выберите ключ самостоятельно. Если берете хеш одной из трех UTM-колонок,
|
||||||
посчитайте долю строк с ее самым частым значением и обоснуйте выбор.
|
посчитайте долю строк с ее самым частым значением и обоснуйте выбор.
|
||||||
|
|
||||||
Ключ донора — тот же, что у цели (см. [«Как принято на ступени
|
У основной и промежуточной таблиц должен быть одинаковый ключ шардирования:
|
||||||
1»](README.md#как-принято-на-ступени-1) и раздел «Раскладка по шардам»
|
см. [общие правила ступени 1](README.md#создание-и-обновление-витрин).
|
||||||
[справочника хранилища](../architecture/storage.md)).
|
|
||||||
|
|
||||||
## Атрибуция по событию, а не по визиту
|
## Рекламные метки событий
|
||||||
|
|
||||||
Группируйте события по их собственным меткам. Модели «первое касание» и
|
Группируйте события по их собственным меткам. Модели «первое касание» и
|
||||||
«последнее касание» здесь не рассчитываются.
|
«последнее касание» здесь не рассчитываются.
|
||||||
|
|
||||||
В `dds.session_v` записаны UTM первого события визита. Распределение по
|
В `dds.session_v` записаны UTM первого события визита. Распределение по
|
||||||
каналам может отличаться от этой витрины, и это не означает ошибку в
|
каналам может отличаться от этой витрины, и это не означает ошибку в
|
||||||
расчете. В отчете различайте атрибуцию визитов и событий.
|
расчете. Укажите в отчете, что распределяете по рекламным каналам отдельные
|
||||||
|
события. Такое распределение называют атрибуцией.
|
||||||
|
|
||||||
## Прямой трафик остается в витрине
|
## События без рекламных меток
|
||||||
|
|
||||||
Сохраните события без меток: прямой трафик обычно крупнейший канал, и без
|
Сохраните события без меток: прямой трафик обычно крупнейший канал, и без
|
||||||
него доля рекламы будет завышена. Если отфильтровать такие строки, дневные
|
него доля рекламы будет завышена. Если отфильтровать такие строки, дневные
|
||||||
@@ -108,13 +117,13 @@
|
|||||||
причины можно посмотреть в `dm.purchase_vs_orders_v`, где выделены классы
|
причины можно посмотреть в `dm.purchase_vs_orders_v`, где выделены классы
|
||||||
расхождений.
|
расхождений.
|
||||||
|
|
||||||
## Как проверить себя
|
## Проверка результата
|
||||||
|
|
||||||
Деловой ключ — `utm_source`, `utm_medium`, `utm_campaign` внутри дня.
|
Рассчитайте те же показатели по `dds.event_v` за выбранный день.
|
||||||
Получите прямой агрегат из `dds.event_v` за день и соедините его с
|
Сопоставьте результат со строками `dm.utm_effectiveness_v` за этот день
|
||||||
`dm.utm_effectiveness_v` полным внешним соединением по ключу. Посчитайте
|
через `FULL OUTER JOIN` по `utm_source`, `utm_medium` и `utm_campaign`.
|
||||||
строки, где ключ отсутствует с одной из сторон или различается любая из пяти
|
Найдите сочетания меток, которые есть только с одной стороны, и строки,
|
||||||
величин. Расхождений должно быть ноль.
|
где различается хотя бы один из пяти показателей. Расхождений должно быть ноль.
|
||||||
|
|
||||||
Дополнительно сверьте дневные итоги:
|
Дополнительно сверьте дневные итоги:
|
||||||
|
|
||||||
@@ -126,10 +135,10 @@
|
|||||||
Сумма `visitors` по строкам повторно учитывает одного посетителя,
|
Сумма `visitors` по строкам повторно учитывает одного посетителя,
|
||||||
приходящего по разным меткам. Для числа посетителей дня она не подходит.
|
приходящего по разным меткам. Для числа посетителей дня она не подходит.
|
||||||
|
|
||||||
## Что должно получиться
|
## Результат работы
|
||||||
|
|
||||||
- [ ] у вас есть пять объектов витрины; донор объявлен через `AS` и
|
- [ ] у вас есть пять объектов витрины; промежуточная таблица объявлена через
|
||||||
использует ключ шардирования целевой таблицы;
|
`AS` и использует ключ шардирования основной;
|
||||||
- [ ] строки без UTM-меток сохранены, а `declared_revenue` хранится
|
- [ ] строки без UTM-меток сохранены, а `declared_revenue` хранится
|
||||||
в `Decimal(18, 2)`;
|
в `Decimal(18, 2)`;
|
||||||
- [ ] вы записали, почему сумма отличается от `dm.revenue_daily_v`;
|
- [ ] вы записали, почему сумма отличается от `dm.revenue_daily_v`;
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
# Свой график в Superset
|
# Свой график в Superset
|
||||||
|
|
||||||
**Чему учит:** увидеть, как собственный результат из хранилища становится
|
В этой работе вы построите график по своей витрине и проверите числа
|
||||||
отчетом в BI.
|
на нем SQL-запросом.
|
||||||
|
|
||||||
## Сначала прочитать
|
## Подготовка
|
||||||
|
|
||||||
- [«Немного Superset»](../handbook/superset.md);
|
- [«Отчеты в Superset»](../handbook/superset.md);
|
||||||
- [задание 3: `dm.session_overview`](1-3-session-overview.md).
|
- [задание 3: `dm.session_overview`](1-3-session-overview.md).
|
||||||
|
|
||||||
## На какой вопрос ответит график
|
## График отказов по устройствам
|
||||||
|
|
||||||
В задании 3 вы собрали витрину `dm.session_overview_v`. Теперь покажите, с
|
В задании 3 первой ступени вы собрали витрину `dm.session_overview_v`.
|
||||||
каких устройств чаще уходят после первой страницы. Постройте столбиковый
|
Теперь покажите, с каких устройств чаще уходят после единственного события.
|
||||||
график доли отказов по `device_category` за весь период витрины. Измерение
|
Такие визиты в стенде считаются отказами. Постройте столбиковый график доли
|
||||||
одно — `device_category`, метрика тоже одна.
|
отказов по `device_category` за весь период витрины. Каждый столбик должен
|
||||||
|
показывать долю для одной категории устройств.
|
||||||
|
|
||||||
## Что сделать
|
## Настройка графика
|
||||||
|
|
||||||
Заведите физический датасет в подключении к `ClickHouse`: схема `dm`, объект
|
Добавьте представление как источник данных для графиков. В Superset такой
|
||||||
`session_overview_v`. Назовите по-русски хотя бы две колонки:
|
источник называется физическим датасетом. Выберите подключение `ClickHouse`,
|
||||||
|
схему `dm` и объект `session_overview_v`. Задайте русские подписи хотя бы двум
|
||||||
|
колонкам:
|
||||||
|
|
||||||
- `device_category` — «Категория устройства»;
|
- `device_category` — «Категория устройства»;
|
||||||
- `report_date` — «Дата отчета».
|
- `report_date` — «Дата отчета».
|
||||||
@@ -32,10 +35,10 @@
|
|||||||
| `SQL expression` | `SUM(bounce_sessions) / SUM(sessions)` |
|
| `SQL expression` | `SUM(bounce_sessions) / SUM(sessions)` |
|
||||||
| `D3 format` | `.1%` |
|
| `D3 format` | `.1%` |
|
||||||
|
|
||||||
Доля считается из сумм слагаемых: среднее готовых долей соврет. Поэтому
|
Выражение делит общее число отказов на общее число сессий. Усреднение дневных
|
||||||
метрике нужно это выражение. Сохраните ее в датасете, а не в графике:
|
долей может дать неверный результат: число сессий в разные дни различается.
|
||||||
подписи и формат задаются в датасете, а график их использует. Так во всех
|
Сохраните метрику в датасете, чтобы все будущие графики использовали одно
|
||||||
будущих графиках у метрики будут одинаковые подписи. Это правило описано в
|
выражение, подпись и формат. Это правило описано в
|
||||||
[справочнике Superset](../architecture/superset.md#дерево-импорта).
|
[справочнике Superset](../architecture/superset.md#дерево-импорта).
|
||||||
|
|
||||||
Не используйте вкладку «Custom SQL» конструктора графика. Созданная там
|
Не используйте вкладку «Custom SQL» конструктора графика. Созданная там
|
||||||
@@ -56,9 +59,9 @@ FROM dm.session_overview_v;
|
|||||||
```
|
```
|
||||||
|
|
||||||
Стендовые объекты не меняйте. Что переживает `make up`, а что стирает
|
Стендовые объекты не меняйте. Что переживает `make up`, а что стирает
|
||||||
`make clean`, описано в главе [«Немного Superset»](../handbook/superset.md#стендовые-объекты-и-ваши).
|
`make clean`, описано в главе [«Отчеты в Superset»](../handbook/superset.md#готовые-и-собственные-отчеты).
|
||||||
|
|
||||||
## Как проверить себя
|
## Проверка результата
|
||||||
|
|
||||||
Автоматической проверки этого задания нет. Сверьте числа на графике
|
Автоматической проверки этого задания нет. Сверьте числа на графике
|
||||||
с результатом запроса в SQL Lab:
|
с результатом запроса в SQL Lab:
|
||||||
@@ -76,7 +79,7 @@ ORDER BY device_category;
|
|||||||
проценты с одним знаком, как формат `.1%`, поэтому числа должны совпасть
|
проценты с одним знаком, как формат `.1%`, поэтому числа должны совпасть
|
||||||
один в один.
|
один в один.
|
||||||
|
|
||||||
## Что должно получиться
|
## Результат работы
|
||||||
|
|
||||||
- [ ] у вас есть физический датасет `dm.session_overview_v` в подключении
|
- [ ] у вас есть физический датасет `dm.session_overview_v` в подключении
|
||||||
`ClickHouse`;
|
`ClickHouse`;
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
# Нарежьте сессии сами
|
# Сессии из событий
|
||||||
|
|
||||||
**Чему учит:** собрать сессии из событий, сравнить их число с `VisitID`
|
Вы самостоятельно объедините события сайта в сессии и сравните их число
|
||||||
источника и найти события, из-за которых расчеты расходятся.
|
с готовыми визитами источника, обозначенными `VisitID`. Затем найдете
|
||||||
|
события, из-за которых расчеты расходятся.
|
||||||
|
|
||||||
## Сначала прочитать
|
## Подготовка
|
||||||
|
|
||||||
- [Хранилище и его слои, раздел DDS](../handbook/storage-layers.md#dds-язык-бизнеса);
|
- [Хранилище и его слои, раздел DDS](../handbook/storage-layers.md#dds-события-заказы-и-сессии);
|
||||||
- [формат события: Идентификаторы и время](../formats/clickstream-event.md#идентификаторы-и-время).
|
- [формат события: Идентификаторы и время](../formats/clickstream-event.md#идентификаторы-и-время).
|
||||||
|
|
||||||
## Для чего нужны сессии
|
## События и визиты
|
||||||
|
|
||||||
Визит, или сессия, объединяет действия посетителя за один заход на сайт.
|
Визит, или сессия, объединяет действия посетителя за один заход на сайт.
|
||||||
По визитам аналитик считает длительность и глубину просмотра.
|
По визитам аналитик считает длительность и глубину просмотра.
|
||||||
В хранилище сессии собираются из отдельных событий.
|
В хранилище сессии собираются из отдельных событий.
|
||||||
Сверьте самостоятельный расчет с `VisitID`, чтобы увидеть, как из событий
|
|
||||||
получается сессия.
|
|
||||||
|
|
||||||
## Что сделать
|
## Расчет сессий
|
||||||
|
|
||||||
### Нарезка
|
### Границы визитов
|
||||||
|
|
||||||
В SQL Lab или DBeaver под `analyst` напишите запрос к `ods.event_v`.
|
В SQL Lab или DBeaver под `analyst` напишите запрос к `ods.event_v`.
|
||||||
Для своей нарезки и эталона возьмите одни и те же строки за все доступные
|
Для своего расчета и эталона возьмите одни и те же строки за все доступные
|
||||||
дни: для расследования понадобятся события соседних дней.
|
дни: для расследования понадобятся события соседних дней.
|
||||||
Выберите `ClientID`, `UTCEventTime`, `EventDate`, `EventType` и `VisitID`.
|
Выберите `ClientID`, `UTCEventTime`, `EventDate`, `EventType` и `VisitID`.
|
||||||
В `dds.event_v` эталонного `VisitID` нет.
|
В `dds.event_v` эталонного `VisitID` нет.
|
||||||
|
|
||||||
Для начала считайте визитом последовательные события одной куки (`ClientID`);
|
`ClientID` хранится в cookie браузера и обозначает посетителя в событиях
|
||||||
пауза дольше 30 минут открывает новый визит.
|
сайта. Для начала считайте визитом последовательные события одного
|
||||||
|
`ClientID`: пауза дольше 30 минут открывает новый визит.
|
||||||
Учитывайте события всех типов. Упорядочите их по `UTCEventTime`.
|
Учитывайте события всех типов. Упорядочите их по `UTCEventTime`.
|
||||||
Присвойте каждому событию номер своей сессии внутри куки.
|
Присвойте каждому событию номер своей сессии внутри `ClientID`.
|
||||||
Подсказка: оконные функции.
|
Подсказка: оконные функции.
|
||||||
|
|
||||||
Днем сессии считайте `EventDate` ее первого события: дата из `UTCEventTime`
|
Днем сессии считайте `EventDate` ее первого события: дата из `UTCEventTime`
|
||||||
@@ -38,7 +38,8 @@
|
|||||||
|
|
||||||
### Сверка с эталоном
|
### Сверка с эталоном
|
||||||
|
|
||||||
В эталоне посчитайте число визитов каждой куки за модельный день:
|
Посчитайте по эталонному `VisitID` число визитов каждого `ClientID`
|
||||||
|
за модельный день:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT ClientID, EventDate, uniqExact(VisitID) AS reference_sessions
|
SELECT ClientID, EventDate, uniqExact(VisitID) AS reference_sessions
|
||||||
@@ -47,46 +48,48 @@ GROUP BY ClientID, EventDate
|
|||||||
ORDER BY EventDate, ClientID;
|
ORDER BY EventDate, ClientID;
|
||||||
```
|
```
|
||||||
|
|
||||||
Посчитайте свои сессии по куке и дню сессии.
|
Посчитайте свои сессии по `ClientID` и дню сессии. Соедините результаты двух
|
||||||
Соедините два готовых агрегата через `FULL OUTER JOIN` по куке и дню.
|
расчетов через `FULL OUTER JOIN` по этим полям. Учтите строки, которые есть
|
||||||
В результате полного соединения останутся ключи обеих сторон: при расхождении
|
только с одной стороны: полное соединение сохранит их в результате.
|
||||||
на вашей стороне ключа может не быть совсем. Учтите такие строки при сравнении.
|
|
||||||
Выведите сводку по дням с числом разошедшихся кук и список этих кук
|
Выведите сводку по дням с числом `ClientID`, для которых расчеты разошлись,
|
||||||
с днем и обоими значениями счета. Общая сумма за день могла бы скрыть
|
и список этих идентификаторов с днем и обоими значениями. Общая сумма за день
|
||||||
взаимную компенсацию ошибок разных кук. По списку видно, у кого искать причину.
|
могла бы скрыть ошибки: для одного посетителя вы посчитали лишнюю сессию,
|
||||||
|
для другого пропустили. По списку видно, чьи события нужно исследовать.
|
||||||
|
|
||||||
Расхождений будет всего несколько. Если их нет, мир прожил мало дней.
|
Расхождений будет всего несколько. Если их нет, мир прожил мало дней.
|
||||||
Прогоните несколько дней через [даги модельного времени](../handbook/airflow-dags.md#даги-мира)
|
Прогоните несколько дней через [даги модельного времени](../handbook/airflow-dags.md#генерация-модельных-дней)
|
||||||
и повторите сверку.
|
и повторите сверку.
|
||||||
|
|
||||||
### Расследование
|
### Поиск причины расхождения
|
||||||
|
|
||||||
Выберите одну разошедшуюся куку. Выведите ее события за два соседних дня
|
Выберите один `ClientID` с расхождением. Выведите его события за два соседних дня
|
||||||
с `UTCEventTime`, `EventDate`, `VisitID` и номером своей сессии.
|
с `UTCEventTime`, `EventDate`, `VisitID` и номером своей сессии.
|
||||||
Сопоставьте сессии через события: общего ключа у двух нарезок нет,
|
Сопоставьте сессии через события: общего ключа у двух расчетов нет,
|
||||||
зато у каждого события есть и `VisitID`, и присвоенный вами номер.
|
зато у каждого события есть и `VisitID`, и присвоенный вами номер.
|
||||||
Найдите два соседних события, между которыми границы визитов разошлись.
|
Найдите два соседних события, между которыми границы визитов разошлись.
|
||||||
Запишите куку, день, время обоих событий и причину одним предложением.
|
Запишите `ClientID`, день, время обоих событий и причину одним предложением.
|
||||||
Уточните определение визита и добейтесь нуля расхождений во всех днях.
|
Уточните определение визита и добейтесь нуля расхождений во всех днях.
|
||||||
|
|
||||||
## Как проверить себя
|
## Проверка результата
|
||||||
|
|
||||||
Сохраните исходный и уточненный расчеты, результаты сверки и найденные строки.
|
Сохраните исходный и уточненный расчеты, результаты сверки и найденные строки.
|
||||||
Таблицы и представления создавать не нужно. Задачи в `homework_check`
|
Таблицы и представления создавать не нужно. Задачи в `homework_check`
|
||||||
для этого задания нет: вы проверяете расчет своим запросом.
|
для этого задания нет: вы проверяете расчет своим запросом.
|
||||||
|
|
||||||
В `sql/dds/session_rebuild.sql` и
|
В `sql/dds/session_rebuild.sql` и
|
||||||
`sql/dq/sessions_vs_reference.sql` лежат готовые запросы нарезки и сверки.
|
`sql/dq/sessions_vs_reference.sql` лежат готовые запросы расчета сессий и сверки.
|
||||||
Откройте их после собственного решения и расследования. Сравните запросы
|
Откройте их после собственного решения и расследования. Сравните запросы
|
||||||
со своими.
|
со своими.
|
||||||
Сопоставление со стендовой нарезкой по `session_id` остается на ступень 4.
|
На ступени 4 вы сравните свой результат с готовыми сессиями стенда
|
||||||
|
по `session_id`.
|
||||||
|
|
||||||
## Что должно получиться
|
## Результат работы
|
||||||
|
|
||||||
- [ ] ваш запрос собирает сессии из всех событий `ods.event_v` за все
|
- [ ] ваш запрос собирает сессии из всех событий `ods.event_v` за все
|
||||||
доступные дни;
|
доступные дни;
|
||||||
- [ ] у вас есть сводка расхождений по дням и список кук, полученные через
|
- [ ] у вас есть сводка расхождений по дням и список `ClientID`, полученные через
|
||||||
`FULL OUTER JOIN`;
|
`FULL OUTER JOIN`;
|
||||||
- [ ] вы объяснили причину одного расхождения и записали куку, день и два
|
- [ ] вы объяснили причину одного расхождения и записали `ClientID`, день и два
|
||||||
события, на которых оно видно;
|
события, на которых оно видно;
|
||||||
- [ ] уточненный расчет дает ноль расхождений во всех днях.
|
- [ ] уточненный расчет дает ноль расхождений во всех днях.
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
# Перепишите нарезку стенда
|
# Расчет сессий в стенде
|
||||||
|
|
||||||
**Чему учит:** переписать расчет сессий в готовом конвейере и проверить
|
На прошлой ступени вы рассчитали сессии отдельным запросом. Теперь
|
||||||
|
перепишете расчет внутри стенда, запустите его через Airflow и проверите
|
||||||
результат по исходным событиям.
|
результат по исходным событиям.
|
||||||
|
|
||||||
## Сначала прочитать
|
## Подготовка
|
||||||
|
|
||||||
- [задание «Нарежьте сессии сами»](3-1-sessions-by-hand.md);
|
- [задание «Сессии из событий»](3-1-sessions-by-hand.md);
|
||||||
- [модель сессии DDS](../architecture/dds/session.md);
|
- [модель сессии DDS](../architecture/dds/session.md);
|
||||||
- [дневной конвейер трансформаций](../architecture/etl.md#объём-прогона);
|
- [дневной конвейер трансформаций](../architecture/etl.md#объём-прогона);
|
||||||
- [раскладка по шардам](../architecture/storage.md#раскладка-по-шардам).
|
- [раскладка по шардам](../architecture/storage.md#раскладка-по-шардам).
|
||||||
|
|
||||||
## Что предстоит сделать
|
## Файлы для изменения
|
||||||
|
|
||||||
Даг, таблицы сессий и проверка их числа уже созданы.
|
Даг, таблицы сессий и проверка их числа уже созданы.
|
||||||
Перепишите только три файла:
|
Перепишите только три файла:
|
||||||
@@ -19,56 +20,57 @@
|
|||||||
- `sql/dds/session_rebuild.sql`;
|
- `sql/dds/session_rebuild.sql`;
|
||||||
- `sql/dds/session_replace.sql`.
|
- `sql/dds/session_replace.sql`.
|
||||||
|
|
||||||
Удалите из них готовый SQL и соберите свою реализацию. Параметры и порядок
|
Удалите из них готовый SQL и напишите свой. Даг `dds_transform` передаст
|
||||||
запросов заданы в `dds_transform`. DDL задает целевые таблицы и донор. Донор —
|
параметры и выполнит запросы по порядку. Таблицы уже созданы: основные хранят
|
||||||
служебная таблица, где день собирается перед заменой. В
|
готовые сессии, промежуточные нужны для нового расчета перед заменой данных.
|
||||||
`sessions_vs_reference.sql` число сессий сравнивается с `VisitID` из ODS.
|
Запрос `sessions_vs_reference.sql` сравнивает число сессий с `VisitID` из ODS.
|
||||||
|
|
||||||
## Как даг запускает ваши запросы
|
## Выполнение запросов в Airflow
|
||||||
|
|
||||||
Объем прогона — дни, которые соберет один запуск. Его вычисляет первый файл.
|
Объем прогона — дни, которые соберет один запуск. Его вычисляет первый файл.
|
||||||
|
|
||||||
| Файл | Вход | Результат |
|
| Файл | Вход | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `session_scope.sql` | `{position:UInt16}` — номер первого несыгранного дня | `first_day` и `last_day` |
|
| `session_scope.sql` | `{position:UInt16}` — номер первого еще не прожитого модельного дня | `first_day` и `last_day` |
|
||||||
| `session_rebuild.sql` | `{first_day:Date}`, `{last_day:Date}`, `{load_id:String}` | Сессии в `dds.session_stage_dist` |
|
| `session_rebuild.sql` | `{first_day:Date}`, `{last_day:Date}`, `{load_id:String}` | Сессии в `dds.session_stage_dist` |
|
||||||
| `session_replace.sql` | День в `{{ params.day }}` | Партиция дня в `dds.session_rep` |
|
| `session_replace.sql` | День в `{{ params.day }}` | Партиция дня в `dds.session_rep` |
|
||||||
|
|
||||||
Первый прожитый день, которого еще нет в DDS, становится `first_day`.
|
`first_day` должен быть первым завершившимся модельным днем, которого еще
|
||||||
`last_day` — последний прожитый день. Начало шкалы нужно взять из событий ODS:
|
нет в DDS, а `last_day` последним завершившимся днем. Начальную дату возьмите
|
||||||
`position` считает дни от него. Живой модельный день еще не закончен, поэтому
|
из событий ODS: `position` считает дни от нее. Текущий модельный день еще
|
||||||
сессии за него не собираются.
|
не закончен, поэтому сессии за него не рассчитываются.
|
||||||
|
|
||||||
Если в ODS нет ни одного события, запрос не возвращает строк. Если DDS уже
|
Если в ODS нет ни одного события, запрос не возвращает строк. Если все
|
||||||
догнал мир, запрос возвращает строку с `first_day = NULL`. В этом случае группа
|
завершившиеся дни уже есть в DDS, запрос возвращает строку с `first_day = NULL`.
|
||||||
сессий в Airflow получает состояние `skipped`.
|
Тогда Airflow пропускает группу задач расчета сессий: ее состояние будет `skipped`.
|
||||||
|
|
||||||
`session_rebuild.sql` сначала очищает `dds.session_stage_rep` на всем кластере.
|
`session_rebuild.sql` сначала очищает `dds.session_stage_rep` на всем кластере.
|
||||||
Затем он собирает в `dds.session_stage_dist` все дни отрезка. Добавьте к
|
Затем он записывает в `dds.session_stage_dist` расчет за все выбранные дни. Добавьте к
|
||||||
вставке `SETTINGS distributed_foreground_insert = 1`. Так строки успеют попасть
|
вставке `SETTINGS distributed_foreground_insert = 1`. Так строки успеют попасть
|
||||||
на шарды до замены партиций.
|
на шарды до замены партиций.
|
||||||
|
|
||||||
Цель `session_replace.sql` — локальная таблица `dds.session_rep`, источник —
|
Запрос `session_replace.sql` заменяет партицию в основной `dds.session_rep`
|
||||||
локальный донор `dds.session_stage_rep`. Операция выполняется на всем кластере.
|
готовой партицией из промежуточной `dds.session_stage_rep`.
|
||||||
|
Операция выполняется на всем кластере, на локальных таблицах каждого шарда.
|
||||||
|
|
||||||
Не ставьте точку с запятой в конце `session_scope.sql`. Драйвер добавляет формат
|
Не ставьте точку с запятой в конце `session_scope.sql`. Драйвер добавляет формат
|
||||||
ответа, и точка с запятой превратит строку в мультизапрос, который ClickHouse
|
ответа. С точкой с запятой получится несколько команд, которые ClickHouse
|
||||||
отклонит. День для замены подставляет шаблон Jinja в Airflow. Параметр
|
отклонит. День для замены подставляет шаблон Jinja в Airflow. Параметр
|
||||||
ClickHouse в `ALTER ... PARTITION` здесь не работает.
|
ClickHouse в `ALTER ... PARTITION` здесь не работает.
|
||||||
|
|
||||||
## Нарежьте сессии
|
## Правила расчета сессий
|
||||||
|
|
||||||
Источник сборки — `dds.event_v`. Одна строка результата представляет один
|
Источник сборки — `dds.event_v`. Одна строка результата представляет один
|
||||||
визит. На третьей ступени вы уже нарезали сессии:
|
визит. На третьей ступени вы уже получили правила разделения событий на сессии:
|
||||||
|
|
||||||
- сессия не пересекает модельные сутки;
|
- сессия не пересекает модельные сутки;
|
||||||
- пауза больше 30 минут открывает новую сессию;
|
- пауза больше 30 минут открывает новую сессию;
|
||||||
- в нарезке участвуют события всех типов.
|
- в расчете участвуют события всех типов.
|
||||||
|
|
||||||
Пересборка добавляет требования к повторяемости. При одинаковом времени
|
Повторный расчет должен давать тот же результат. При одинаковом времени
|
||||||
порядок событий задает `event_id`, поэтому полный порядок окон —
|
упорядочивайте события по `event_id`: порядок в оконных функциях должен быть
|
||||||
`(event_time, event_id)`. Один визит должен получать одинаковый `session_id`
|
`(event_time, event_id)`. Один визит должен получать одинаковый `session_id`
|
||||||
при каждом запуске. Тогда его можно сравнить с сохраненным результатом.
|
при каждом запуске.
|
||||||
Вычислите идентификатор как `cityHash64(client_id, min(event_time))`.
|
Вычислите идентификатор как `cityHash64(client_id, min(event_time))`.
|
||||||
|
|
||||||
Состав и типы строки заданы в `dds.session_rep`. Заполните начало, конец и
|
Состав и типы строки заданы в `dds.session_rep`. Заполните начало, конец и
|
||||||
@@ -76,11 +78,11 @@ ClickHouse в `ALTER ... PARTITION` здесь не работает.
|
|||||||
покупки. Возьмите входную и выходную страницы, UTM первого события, устройство
|
покупки. Возьмите входную и выходную страницы, UTM первого события, устройство
|
||||||
и город. Заполните также служебные колонки из схемы таблицы.
|
и город. Заполните также служебные колонки из схемы таблицы.
|
||||||
|
|
||||||
Пишите через `dds.session_stage_dist`: место строки определит ключ
|
Пишите через `dds.session_stage_dist`: шард для строки определяется по
|
||||||
`client_id`. При записи прямо в локальную таблицу оно зависело бы от ноды,
|
`client_id`. При записи прямо в локальную таблицу он зависел бы от узла,
|
||||||
которая выполнила запрос.
|
который выполнил запрос.
|
||||||
|
|
||||||
## Запустите свою реализацию
|
## Запуск нового расчета
|
||||||
|
|
||||||
Готовые дни повторно не собираются. Чтобы новый SQL точно выполнился, выберите
|
Готовые дни повторно не собираются. Чтобы новый SQL точно выполнился, выберите
|
||||||
последний собранный день:
|
последний собранный день:
|
||||||
@@ -90,8 +92,8 @@ SELECT max(session_date)
|
|||||||
FROM dds.session_v;
|
FROM dds.session_v;
|
||||||
```
|
```
|
||||||
|
|
||||||
Берите последний день: более ранняя дыра расширит объем до всех последующих
|
Берите последний день: если удалить более ранний, расчет придется повторить
|
||||||
прожитых дней.
|
и за все последующие завершившиеся дни.
|
||||||
|
|
||||||
Сохраните десять строк с идентификатором, границами, длительностью и счетчиками:
|
Сохраните десять строк с идентификатором, границами, длительностью и счетчиками:
|
||||||
|
|
||||||
@@ -113,7 +115,7 @@ ORDER BY client_id, started_at, session_id
|
|||||||
LIMIT 10;
|
LIMIT 10;
|
||||||
```
|
```
|
||||||
|
|
||||||
Создайте в DBeaver подключение к первой ноде по [инструкции из
|
Создайте в DBeaver подключение к первому узлу по [инструкции из
|
||||||
README](../../README.md#подключиться-из-dbeaver). Укажите пользователя `etl` и
|
README](../../README.md#подключиться-из-dbeaver). Укажите пользователя `etl` и
|
||||||
пароль `CLICKHOUSE_ETL_PASSWORD` из `.env`. У этой учетной записи есть право
|
пароль `CLICKHOUSE_ETL_PASSWORD` из `.env`. У этой учетной записи есть право
|
||||||
менять таблицы DDS на кластере. Подставьте найденную дату и удалите только ее
|
менять таблицы DDS на кластере. Подставьте найденную дату и удалите только ее
|
||||||
@@ -128,19 +130,18 @@ DROP PARTITION 'YYYY-MM-DD';
|
|||||||
группу `session`: в ней должны выполниться задачи `scope` и `rebuild`, а также
|
группу `session`: в ней должны выполниться задачи `scope` и `rebuild`, а также
|
||||||
`replace` для выбранного дня. Если после третьей ступени мир ушел вперед,
|
`replace` для выбранного дня. Если после третьей ступени мир ушел вперед,
|
||||||
в группе появятся задачи `replace` и для последующих прожитых дней. Следом
|
в группе появятся задачи `replace` и для последующих прожитых дней. Следом
|
||||||
выполнятся витрины и `dq_check`.
|
обновятся витрины и выполнится `dq_check`.
|
||||||
|
|
||||||
Если прогон дошел до замены, но упал на проверке, ошибочная партиция уже
|
Если прогон дошел до замены, но упал на проверке, ошибочная партиция уже
|
||||||
находится в DDS. После правки снова удалите тот же день перед следующим
|
находится в DDS. После правки снова удалите тот же день перед следующим
|
||||||
запуском. Иначе день останется заполненным, а новая сборка для него не
|
запуском. Иначе день останется заполненным, а новая сборка для него не
|
||||||
выполнится.
|
выполнится.
|
||||||
|
|
||||||
## Как проверить себя
|
## Проверка результата
|
||||||
|
|
||||||
Сначала повторите запрос с десятью строками. `session_id`, границы,
|
Сначала повторите запрос с десятью строками. `session_id`, границы,
|
||||||
длительность и счетчики должны совпасть с сохраненным результатом. Так вы
|
длительность и счетчики должны совпасть с сохраненным результатом.
|
||||||
завершите отложенное с третьей ступени сопоставление со стендовой нарезкой по
|
Это проверит, что ваш расчет восстановил те же сессии с теми же идентификаторами.
|
||||||
`session_id`.
|
|
||||||
|
|
||||||
Задача `dq_check.sessions_vs_reference` должна стать зеленой. Та же проверка
|
Задача `dq_check.sessions_vs_reference` должна стать зеленой. Та же проверка
|
||||||
видна в хранилище:
|
видна в хранилище:
|
||||||
@@ -152,33 +153,34 @@ WHERE check_name = 'sessions_vs_reference'
|
|||||||
ORDER BY data_date;
|
ORDER BY data_date;
|
||||||
```
|
```
|
||||||
|
|
||||||
Эта сверка сравнивает число сессий каждой куки за день с `VisitID`. Нулевой
|
Эта сверка сравнивает число сессий каждого `client_id` за день с `VisitID`.
|
||||||
`failed_rows` не подтверждает границы, страницы и UTM: две ошибки могут
|
При `failed_rows = 0` число сессий совпало, но их границы, страницы и UTM
|
||||||
оставить тот же счет. Возьмите поштучную сверку из третьей ступени. Для одной
|
еще могут различаться. Как на третьей ступени, проверьте отдельные сессии.
|
||||||
куки выбранного дня сопоставьте границы `VisitID` по событиям с границами
|
Для одного `client_id` выбранного дня найдите начало и конец каждого
|
||||||
строк `dds.session_v`. По исходным событиям проверьте страницы, UTM, устройство
|
`VisitID` по событиям и сравните с границами строк `dds.session_v`.
|
||||||
|
По исходным событиям проверьте страницы, UTM, устройство
|
||||||
и город. Страницы и UTM не входят в сохраненные десять строк: готовый SQL
|
и город. Страницы и UTM не входят в сохраненные десять строк: готовый SQL
|
||||||
стенда выбирал их через `argMin` и `argMax` только по `event_time`. При равном
|
стенда выбирал их через `argMin` и `argMax` только по `event_time`. При равном
|
||||||
времени ожидаемое значение определяйте по исходным событиям и полному порядку
|
времени ожидаемое значение определяйте по исходным событиям и полному порядку
|
||||||
`(event_time, event_id)`.
|
`(event_time, event_id)`.
|
||||||
|
|
||||||
Осталась физическая раскладка. Напишите отдельный запрос к
|
Проверьте также, на каком шарде лежит каждая строка. Напишите отдельный
|
||||||
`dds.session_dist`. Для каждой строки он должен:
|
запрос к `dds.session_dist`. Он должен:
|
||||||
|
|
||||||
1. получить фактическую ноду через `hostName()`;
|
1. получить фактический узел строки через `hostName()`;
|
||||||
2. пересчитать ожидаемую ноду по `cityHash64(client_id)`;
|
2. определить ожидаемый узел по `cityHash64(client_id)`;
|
||||||
3. посчитать строки, у которых ноды не совпали.
|
3. посчитать строки, у которых фактический и ожидаемый узлы не совпали.
|
||||||
|
|
||||||
У кластера два равных шарда: остаток 0 соответствует `clickhouse-01`, остаток
|
У кластера два равных шарда: остаток 0 соответствует `clickhouse-01`, остаток
|
||||||
1 — `clickhouse-02`. Сгруппируйте ответ по фактической ноде. На каждой ноде
|
1 — `clickhouse-02`. Сгруппируйте ответ по фактическому узлу. На каждом узле
|
||||||
число строк на чужом шарде должно быть нулевым. Проверка числа сессий читает
|
число строк на чужом шарде должно быть нулевым. Проверка числа сессий читает
|
||||||
Distributed-таблицу и такую ошибку не заметит.
|
Distributed-таблицу и такую ошибку не заметит.
|
||||||
|
|
||||||
После зеленого результата запустите `etl_pipeline` еще раз, не удаляя
|
После зеленого результата запустите `etl_pipeline` еще раз, не удаляя
|
||||||
партицию. Расчет объема должен пропустить группу сессий: новых прожитых дней и
|
партицию. Группа сессий должна быть пропущена: все завершившиеся дни уже
|
||||||
дыр в слое нет.
|
рассчитаны, новых дней и пропусков нет.
|
||||||
|
|
||||||
## Что должно получиться
|
## Результат работы
|
||||||
|
|
||||||
- [ ] вы переписали три файла `sql/dds/session_*.sql`, сохранив даг,
|
- [ ] вы переписали три файла `sql/dds/session_*.sql`, сохранив даг,
|
||||||
таблицы и проверку стенда;
|
таблицы и проверку стенда;
|
||||||
@@ -186,6 +188,6 @@ Distributed-таблицу и такую ошибку не заметит.
|
|||||||
- [ ] вы сверили десять сохраненных строк: `session_id`, границы,
|
- [ ] вы сверили десять сохраненных строк: `session_id`, границы,
|
||||||
длительность и счетчики совпали после пересборки;
|
длительность и счетчики совпали после пересборки;
|
||||||
- [ ] вы получили ноль расхождений в `sessions_vs_reference` за все
|
- [ ] вы получили ноль расхождений в `sessions_vs_reference` за все
|
||||||
прожитые дни и вручную сверили границы и атрибуты сессии одной куки;
|
прожитые дни и вручную сверили границы и поля сессии одного `client_id`;
|
||||||
- [ ] ваш запрос с `hostName()` не нашел строк на чужом шарде;
|
- [ ] ваш запрос с `hostName()` не нашел строк на чужом шарде;
|
||||||
- [ ] повторный прогон без удаления данных пропустил группу сессий.
|
- [ ] повторный прогон без удаления данных пропустил группу сессий.
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
# Научите проверки замечать ошибки в заказах
|
# Проверки качества заказов
|
||||||
|
|
||||||
**Чему учит:** написать проверки заказов до изменения их обработки
|
Перед изменением обработки заказов вы добавите две проверки качества данных.
|
||||||
и убедиться на намеренной ошибке, что проверки ее замечают.
|
Затем намеренно испортите один заказ и убедитесь, что проверки обнаружили
|
||||||
|
ошибки.
|
||||||
|
|
||||||
## Сначала прочитать
|
## Подготовка
|
||||||
|
|
||||||
- [модель заказа DDS](../architecture/dds/order.md);
|
- [модель заказа DDS](../architecture/dds/order.md);
|
||||||
- [дневной конвейер](../architecture/etl.md#объём-прогона);
|
- [дневной конвейер](../architecture/etl.md#объём-прогона);
|
||||||
@@ -13,30 +14,29 @@
|
|||||||
и на ее включение в [`dq_check`](../../dags/dq_check.py). Готового ответа для
|
и на ее включение в [`dq_check`](../../dags/dq_check.py). Готового ответа для
|
||||||
двух проверок из этого задания в репозитории нет.
|
двух проверок из этого задания в репозитории нет.
|
||||||
|
|
||||||
## Что предстоит сделать
|
## Новые проверки
|
||||||
|
|
||||||
Добавьте две проверки и включите их в даг:
|
Добавьте две проверки и включите их в даг:
|
||||||
|
|
||||||
- `sql/dq/order_items_total.sql` проверяет сумму позиций заказа;
|
- `sql/dq/order_items_total.sql` проверяет сумму позиций заказа;
|
||||||
- `sql/dq/orders_vs_ods.sql` сверяет DDS с его источником;
|
- `sql/dq/orders_vs_ods.sql` сверяет DDS с его источником;
|
||||||
- `dags/dq_check.py` запускает обе проверки, публикует их строки в
|
- `dags/dq_check.py` запускает обе проверки, записывает их результаты в
|
||||||
`dm.dq_summary` и создает для них отдельные задачи.
|
`dm.dq_summary` и создает для них отдельные задачи.
|
||||||
|
|
||||||
Разбор ODS и сборку DDS вы перепишете в следующих заданиях. Здесь сохраните
|
Разбор ODS и сборку DDS вы перепишете в следующих заданиях. Здесь сохраните
|
||||||
их, даги загрузки, DDL, таблицы-доноры, Kafka и службы как есть.
|
их, даги загрузки, DDL, промежуточные таблицы, Kafka и службы как есть.
|
||||||
|
|
||||||
Перед опытами остановите автоматическое движение мира: поставьте `world_live`
|
Перед опытами остановите автоматическое движение мира: поставьте `world_live`
|
||||||
на паузу и дождитесь завершения активных прогонов. До завершения опытов не
|
на паузу и дождитесь завершения активных прогонов. До завершения опытов не
|
||||||
запускайте руками `world_next_day`, `world_live_day`, `world_initialize` и
|
запускайте руками `world_next_day`, `world_live_day`, `world_initialize` и
|
||||||
`world_recreate`. Это условие стабильного модельного времени: одной паузы
|
`world_recreate`, чтобы новые данные не меняли результат между проверками.
|
||||||
`world_live` для него мало.
|
|
||||||
|
|
||||||
## Что будут проверять ваши запросы
|
## Требования к запросам
|
||||||
|
|
||||||
Чтобы `dq_check` мог запустить проверку, ее запрос возвращает по одной строке
|
Чтобы `dq_check` мог запустить проверку, ее запрос возвращает по одной строке
|
||||||
на день с колонками `data_date`, `reference_rows`, `actual_rows`,
|
на день с колонками `data_date`, `reference_rows`, `actual_rows`,
|
||||||
`failed_rows`, `diagnostics`. Даг сохраняет в `dm.dq_summary` только счетчики,
|
`failed_rows`, `diagnostics`. Даг сохраняет в `dm.dq_summary` только счетчики,
|
||||||
статус и координаты запуска. Примеры из `diagnostics` ищите в
|
статус и сведения о запуске. Примеры из `diagnostics` ищите в
|
||||||
журнале упавшей задачи Airflow. Ограничьте их тем же малым числом, что и
|
журнале упавшей задачи Airflow. Ограничьте их тем же малым числом, что и
|
||||||
существующие запросы в `sql/dq/`.
|
существующие запросы в `sql/dq/`.
|
||||||
|
|
||||||
@@ -44,46 +44,48 @@
|
|||||||
|
|
||||||
Имя проверки - `order_items_total`. Для каждого заказа из `dds.order_v`
|
Имя проверки - `order_items_total`. Для каждого заказа из `dds.order_v`
|
||||||
посчитайте сумму `item_price * item_quantity` и сравните ее с `items_total`.
|
посчитайте сумму `item_price * item_quantity` и сравните ее с `items_total`.
|
||||||
`reference_rows` считает ключи со стороны `items_total`, `actual_rows` - ключи
|
В `reference_rows` запишите число заказов со стороны `items_total`,
|
||||||
со стороны вычисленной суммы, а `failed_rows` - несовпавшие значения. В
|
в `actual_rows` число заказов со стороны вычисленной суммы, в `failed_rows`
|
||||||
исправном наборе первые два счетчика равны; несовпадения сумм считает
|
число заказов, у которых суммы не совпали. На исправных данных первые два
|
||||||
`failed_rows`. Для диагностики достаточно `order_id`, ожидаемой суммы и
|
счетчика равны, а `failed_rows` равен нулю. Для примеров расхождений
|
||||||
суммы позиций.
|
достаточно `order_id`, значения `items_total` и вычисленной суммы позиций.
|
||||||
|
|
||||||
Проверка относится к типизированному разбору массива `items` на границе
|
Проверка поможет заметить ошибки разбора `items` при переносе заказа
|
||||||
ODS -> DDS. Скидку, доставку и равенство `total` здесь проверять не нужно.
|
из ODS в DDS. Скидку, доставку и равенство `total` здесь проверять не нужно.
|
||||||
|
|
||||||
### DDS против ODS
|
### Полнота заказов в DDS
|
||||||
|
|
||||||
Имя проверки - `orders_vs_ods`. Источник модели DDS здесь - текущее состояние
|
Имя проверки - `orders_vs_ods`. Источник модели DDS здесь - текущее состояние
|
||||||
`ods.order_v`. С ним вы и сравниваете DDS; обращаться к внутреннему состоянию
|
`ods.order_v`. С ним вы и сравниваете DDS; обращаться к внутреннему состоянию
|
||||||
генератора для этой проверки не нужно.
|
генератора для этой проверки не нужно.
|
||||||
Одним запросом соберите обе стороны на зерне дня заказа и сравните:
|
Одним запросом рассчитайте итоги каждого дня заказа в обоих слоях и сравните:
|
||||||
|
|
||||||
- число заказов;
|
- число заказов;
|
||||||
- максимальный `updated_at`.
|
- максимальный `updated_at`.
|
||||||
|
|
||||||
День ODS вычисляется в `Europe/Samara`, как `order_date` в DDS. Область
|
День заказа в ODS вычисляйте в `Europe/Samara`, как `order_date` в DDS.
|
||||||
проверки - объединение дней обеих сторон, включая день, который полностью
|
Проверьте все дни, которые есть хотя бы в одном слое: так вы заметите день,
|
||||||
пропал из одного слоя. Перед сравнением приведите времена к одному часовому
|
целиком пропавший из другого. Перед сравнением приведите времена к одному
|
||||||
поясу. `reference_rows` - число заказов ODS, `actual_rows` - число заказов DDS,
|
часовому поясу. `reference_rows` содержит число заказов ODS, `actual_rows`
|
||||||
а `failed_rows` равен единице для дня, где расходится хотя бы одно значение.
|
число заказов DDS. В `failed_rows` запишите 1, если за день расходится
|
||||||
|
хотя бы одно значение, и 0, если оба совпали.
|
||||||
|
|
||||||
Обе стороны распределены по `order_id`. Дневные агрегаты соедините через
|
Обе стороны распределены по `order_id`. Дневные агрегаты соедините через
|
||||||
`GLOBAL FULL OUTER JOIN` с `join_use_nulls = 1`, как в существующих сверках.
|
`GLOBAL FULL OUTER JOIN` с `join_use_nulls = 1`, как в существующих сверках.
|
||||||
|
|
||||||
Если разбор ODS и сборка DDS одинаково потеряют строку, слои останутся равны
|
Если строка потерялась при разборе в ODS, она не попадет и в DDS. Эта проверка
|
||||||
по этой проверке. Общая потеря при разборе проявится в отдельном балансе по
|
не заметит потерю: данные слоев совпадут. В [следующем задании](5-2-order-parsing.md)
|
||||||
`_load_id` из [следующего задания](5-2-order-parsing.md).
|
вы отдельно сравните число исходных строк с числом разобранных заказов
|
||||||
|
и ошибок за один запуск, обозначенный `_load_id`.
|
||||||
|
|
||||||
## Получите красный и зеленый результаты
|
## Проверка на намеренных ошибках
|
||||||
|
|
||||||
Сначала запустите `dq_check` на нетронутых данных. Обе новые задачи должны
|
Сначала запустите `dq_check` на нетронутых данных. Обе новые задачи должны
|
||||||
завершиться успешно, а в `dm.dq_summary_v` у обоих имен не должно быть
|
завершиться успешно, а в `dm.dq_summary_v` у обоих имен не должно быть
|
||||||
нарушенных дней.
|
дней с расхождениями.
|
||||||
|
|
||||||
Выберите оплаченный заказ из последнего слепка с непустым массивом позиций и
|
Выберите оплаченный заказ из последнего слепка с непустым массивом позиций и
|
||||||
сохраните его `order_id` и `order_date`. Подключитесь к первой ноде из DBeaver
|
сохраните его `order_id` и `order_date`. Подключитесь к первому узлу из DBeaver
|
||||||
по [инструкции из README](../../README.md#подключиться-из-dbeaver) под
|
по [инструкции из README](../../README.md#подключиться-из-dbeaver) под
|
||||||
пользователем `etl`. Прибавьте одну копейку к `items_total` этого заказа в
|
пользователем `etl`. Прибавьте одну копейку к `items_total` этого заказа в
|
||||||
локальной таблице `dds.order_rep` на всем кластере:
|
локальной таблице `dds.order_rep` на всем кластере:
|
||||||
@@ -102,7 +104,7 @@ SETTINGS mutations_sync = 2;
|
|||||||
|
|
||||||
Запустите `etl_pipeline`. Текущее окно заказов будет заново собрано из ODS, и
|
Запустите `etl_pipeline`. Текущее окно заказов будет заново собрано из ODS, и
|
||||||
исходная строка вернется в DDS. Повторный `dq_check` должен дать зеленый
|
исходная строка вернется в DDS. Повторный `dq_check` должен дать зеленый
|
||||||
результат для нового сторожа.
|
результат для новой проверки.
|
||||||
|
|
||||||
Теперь удалите тот же заказ из `dds.order_rep` на всем кластере:
|
Теперь удалите тот же заказ из `dds.order_rep` на всем кластере:
|
||||||
|
|
||||||
@@ -118,9 +120,9 @@ SETTINGS mutations_sync = 2;
|
|||||||
Если `orders_vs_ods` не обнаружил удаление, исправьте запрос до следующего
|
Если `orders_vs_ods` не обнаружил удаление, исправьте запрос до следующего
|
||||||
задания.
|
задания.
|
||||||
|
|
||||||
## Как проверить себя
|
## Проверка результата
|
||||||
|
|
||||||
В `dm.dq_summary_v` должны быть зелены обе новые и пять прежних проверок:
|
В `dm.dq_summary_v` обе новые и пять прежних проверок должны быть успешны:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT check_name, sum(failed_rows) AS failed_rows, groupUniqArray(status)
|
SELECT check_name, sum(failed_rows) AS failed_rows, groupUniqArray(status)
|
||||||
@@ -132,12 +134,12 @@ ORDER BY check_name;
|
|||||||
Проверка суммы должна охватывать каждый день `dds.order_v`. Сверка слоев
|
Проверка суммы должна охватывать каждый день `dds.order_v`. Сверка слоев
|
||||||
должна охватывать объединение дней ODS и DDS, включая дни только одной стороны.
|
должна охватывать объединение дней ODS и DDS, включая дни только одной стороны.
|
||||||
|
|
||||||
## Завершите опыт
|
## Завершение работы
|
||||||
|
|
||||||
После восстановления заказа и успешного `dq_check` можно закончить работу или
|
После восстановления заказа и успешного `dq_check` можно закончить работу или
|
||||||
перейти к разбору заказов, оставив модельное время на паузе.
|
перейти к разбору заказов, оставив модельное время на паузе.
|
||||||
|
|
||||||
## Что должно получиться
|
## Результат работы
|
||||||
|
|
||||||
- [ ] две новые проверки запускаются в `dq_check`, а их дневные результаты
|
- [ ] две новые проверки запускаются в `dq_check`, а их дневные результаты
|
||||||
появляются в `dm.dq_summary`;
|
появляются в `dm.dq_summary`;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
# Перепишите разбор заказов в ODS
|
# Разбор заказов в ODS
|
||||||
|
|
||||||
**Чему учит:** переписать разбор заказов и на пробных сообщениях проверить,
|
Вы перепишете разбор заказов в ODS. На четырех пробных сообщениях проверите,
|
||||||
что годные строки и брак попадают в свои таблицы без потерь.
|
что правильно заполненный заказ и сообщения с ошибками попадают
|
||||||
|
в соответствующие таблицы без потерь.
|
||||||
|
|
||||||
## Сначала прочитать
|
## Подготовка
|
||||||
|
|
||||||
- [проверки заказов](5-1-order-checks.md);
|
- [проверки заказов](5-1-order-checks.md);
|
||||||
- [прием заказов в ODS](../architecture/orders/ingestion.md);
|
- [прием заказов в ODS](../architecture/orders/ingestion.md);
|
||||||
@@ -11,24 +12,22 @@
|
|||||||
- [`orders_ingest`](../../dags/orders_ingest.py);
|
- [`orders_ingest`](../../dags/orders_ingest.py);
|
||||||
- [`order_parse.sql`](../../sql/ods/order_parse.sql).
|
- [`order_parse.sql`](../../sql/ods/order_parse.sql).
|
||||||
|
|
||||||
Запрос для проверки пробных строк вы напишете сами.
|
## Порядок работы
|
||||||
|
|
||||||
## Что предстоит сделать
|
|
||||||
|
|
||||||
Сначала проведите опыт на готовом `order_parse.sql`. Затем удалите его SQL,
|
Сначала проведите опыт на готовом `order_parse.sql`. Затем удалите его SQL,
|
||||||
соберите разбор заново и повторите опыт с другим маркером.
|
соберите разбор заново и повторите опыт с другим маркером.
|
||||||
|
|
||||||
Не меняйте устройство `orders_ingest`, DDL, Kafka, таблиц и преобразований
|
Сохраните устройство `orders_ingest`, DDL, Kafka, таблиц и преобразований
|
||||||
DDS. Оба опыта должны закончиться адресной очисткой только своих строк.
|
DDS. После каждого опыта удалите только добавленные вами строки.
|
||||||
|
|
||||||
Перед началом убедитесь, что после [предыдущего задания](5-1-order-checks.md)
|
Перед началом убедитесь, что после [предыдущего задания](5-1-order-checks.md)
|
||||||
все семь DQ остались зелеными. Поставьте `world_live` на паузу, дождитесь
|
все семь проверок качества прошли успешно. Поставьте `world_live` на паузу, дождитесь
|
||||||
активных прогонов и до завершения опыта не запускайте `world_next_day`,
|
активных прогонов и до завершения опыта не запускайте `world_next_day`,
|
||||||
`world_live_day`, `world_initialize` и `world_recreate`. Не запускайте
|
`world_live_day`, `world_initialize` и `world_recreate`. Не запускайте
|
||||||
`etl_pipeline` между
|
`etl_pipeline` между
|
||||||
отправкой пробной пачки и ее очисткой.
|
отправкой пробной пачки и ее очисткой.
|
||||||
|
|
||||||
## Четыре строки для двух ветвей
|
## Пробные сообщения
|
||||||
|
|
||||||
Сделайте четыре сообщения с общим уникальным маркером:
|
Сделайте четыре сообщения с общим уникальным маркером:
|
||||||
|
|
||||||
@@ -39,9 +38,9 @@ DDS. Оба опыта должны закончиться адресной оч
|
|||||||
`field_invalid`.
|
`field_invalid`.
|
||||||
|
|
||||||
`not_an_object` означает, что корень JSON не объект. `keyset_mismatch` означает,
|
`not_an_object` означает, что корень JSON не объект. `keyset_mismatch` означает,
|
||||||
что у объекта отличается точный набор верхнеуровневых ключей. `field_invalid`
|
что у объекта есть лишние верхнеуровневые ключи или не хватает обязательных.
|
||||||
означает, что ключи на месте, но значение не прошло проверку типа, формата или
|
`field_invalid` означает, что ключи на месте, но значение имеет неверный тип,
|
||||||
вместимости.
|
формат или не помещается в заданный тип данных.
|
||||||
|
|
||||||
Соберите строки в `/tmp/<маркер>.jsonl`. Заготовка дает одну годную строку и
|
Соберите строки в `/tmp/<маркер>.jsonl`. Заготовка дает одну годную строку и
|
||||||
по одному примеру каждого класса брака:
|
по одному примеру каждого класса брака:
|
||||||
@@ -79,31 +78,33 @@ docker compose exec -T kafka /opt/kafka/bin/kafka-console-producer.sh \
|
|||||||
--bootstrap-server kafka:9092 --topic orders < "$LAB_BATCH"
|
--bootstrap-server kafka:9092 --topic orders < "$LAB_BATCH"
|
||||||
```
|
```
|
||||||
|
|
||||||
Запустите `orders_ingest` и найдите `_load_id` по маркеру в
|
Запустите `orders_ingest`. Найдите свои строки по маркеру в
|
||||||
`stg.orders_raw_dist`. У четырех строк должен быть один идентификатор прогона.
|
`stg.orders_raw_dist` и сохраните их `_load_id`: это идентификатор запуска,
|
||||||
|
который принял сообщения. Он должен быть одинаковым у всех четырех строк.
|
||||||
|
|
||||||
## Зафиксируйте исходный результат
|
## Проверка готового разбора
|
||||||
|
|
||||||
До любого запуска `etl_pipeline` проверьте срез по найденному `_load_id`:
|
До запуска `etl_pipeline` проверьте строки с найденным `_load_id`:
|
||||||
|
|
||||||
- в STG четыре строки;
|
- в STG четыре строки;
|
||||||
- в физической `ods.order_dist` одна годная строка;
|
- в таблице `ods.order_dist` одна годная строка;
|
||||||
- в `ods.order_errors_dist` три строки, по одной каждого класса;
|
- в `ods.order_errors_dist` три строки, по одной каждого класса;
|
||||||
- `STG = годные + брак`;
|
- `STG = годные + брак`;
|
||||||
- текущая строка `ods.order_v` совпадает по деловым полям с результатом
|
- поля заказа в текущей строке `ods.order_v` совпадают с полями из годного
|
||||||
`argMax` по `snapshot_date` из годного сырья среза.
|
исходного сообщения; последнюю версию для сравнения выберите через
|
||||||
|
`argMax` по `snapshot_date`.
|
||||||
|
|
||||||
Счет физической таблицы нужен именно по `_load_id`: представление
|
Считайте строки `ods.order_dist` именно по `_load_id`: представление
|
||||||
`ods.order_v` оставляет текущую версию сущности и не показывает, сколько строк
|
`ods.order_v` оставляет только текущую версию каждого заказа и не показывает,
|
||||||
разобрал запуск. Запрос сверки с `argMax` напишите сами. Эталонные деловые поля
|
сколько строк разобрал запуск. Запрос сверки с `argMax` напишите сами.
|
||||||
стройте только из `raw`; отдельно сравните различающиеся `created_at` и
|
Значения для сравнения извлеките из `raw` пробных сообщений. Отдельно
|
||||||
`updated_at`.
|
сравните `created_at` и `updated_at`: в заготовке они намеренно различаются.
|
||||||
|
|
||||||
Проверьте раскладку годной строки через `hostName()` на `ods.order_dist`.
|
Проверьте раскладку годной строки через `hostName()` на `ods.order_dist`.
|
||||||
Фактическая нода должна совпасть с остатком `cityHash64(order_id) % 2`:
|
Узел, на котором лежит строка, должен соответствовать остатку `cityHash64(order_id) % 2`:
|
||||||
0 - `clickhouse-01`, 1 - `clickhouse-02`.
|
0 - `clickhouse-01`, 1 - `clickhouse-02`.
|
||||||
|
|
||||||
## Уберите пробную пачку
|
## Удаление пробных сообщений
|
||||||
|
|
||||||
Удалите только строки маркера из локальных таблиц:
|
Удалите только строки маркера из локальных таблиц:
|
||||||
|
|
||||||
@@ -135,40 +136,41 @@ SETTINGS mutations_sync = 2;
|
|||||||
запустите `etl_pipeline`: пересборка текущего окна удалит заказ из DDS и
|
запустите `etl_pipeline`: пересборка текущего окна удалит заказ из DDS и
|
||||||
обновит витрины.
|
обновит витрины.
|
||||||
|
|
||||||
## Перепишите разбор
|
## Собственный разбор заказов
|
||||||
|
|
||||||
Удалите готовый SQL из `sql/ods/order_parse.sql` и напишите свой. Чтобы
|
Удалите готовый SQL из `sql/ods/order_parse.sql` и напишите свой. Чтобы
|
||||||
`orders_ingest` мог его использовать, сохраните следующие условия:
|
`orders_ingest` мог его использовать, сохраните следующие условия:
|
||||||
|
|
||||||
- вход - строки `stg.orders_raw_dist` одного `{load_id:String}`;
|
- запрос читает строки `stg.orders_raw_dist` с переданным `{load_id:String}`;
|
||||||
- каждая строка среза попадает ровно в один результат: годный заказ или брак;
|
- каждая прочитанная строка попадает ровно в один результат: годный заказ или брак;
|
||||||
- произвольный `raw` не останавливает весь разбор исключением;
|
- ошибка в `raw` одной строки не прерывает разбор остальных;
|
||||||
- годные строки записываются в `ods.order_dist`, остальные - в
|
- годные строки записываются в `ods.order_dist`, остальные - в
|
||||||
`ods.order_errors_dist`;
|
`ods.order_errors_dist`;
|
||||||
- классы брака и их порядок - `not_an_object`, `keyset_mismatch`,
|
- классы брака и их порядок - `not_an_object`, `keyset_mismatch`,
|
||||||
`field_invalid`;
|
`field_invalid`;
|
||||||
- метки доставки и `_load_id` переходят из STG без пересчета;
|
- метки доставки и `_load_id` переходят из STG без пересчета;
|
||||||
- задача завершается после доставки обоих результатов через Distributed, а
|
- задача завершается после записи и заказов, и ошибок на шарды через
|
||||||
строки распределяются по ключу целевой таблицы.
|
Distributed; строки распределяются по ключу соответствующей целевой таблицы.
|
||||||
|
|
||||||
Точный набор полей, типы и граница строгого приема описаны в
|
Точный набор полей, типы и условия проверки значений описаны в
|
||||||
[документе приема](../architecture/orders/ingestion.md#граница-строгого-приёма),
|
[документе приема](../architecture/orders/ingestion.md#граница-строгого-приёма),
|
||||||
а целевые колонки - в [`20-ods-tables.sql`](../../sql/ddl/20-ods-tables.sql).
|
а целевые колонки - в [`20-ods-tables.sql`](../../sql/ddl/20-ods-tables.sql).
|
||||||
Внутренность `items` остается сырым JSON: ее разбирает DDS.
|
Содержимое `items` сохраните как JSON: его разбирает DDS.
|
||||||
|
|
||||||
Повторите опыт с новым маркером. Получите тот же баланс 4 = 1 + 3, те же три
|
Повторите опыт с новым маркером. Получите тот же баланс 4 = 1 + 3, те же три
|
||||||
класса, верную текущую строку по `snapshot_date`, разные времена и правильный
|
класса, верную текущую версию заказа по `snapshot_date`, разные значения
|
||||||
шард. Затем очистите второй срез теми же адресными командами и запустите
|
`created_at` и `updated_at` и правильный шард. Затем удалите строки второго
|
||||||
|
опыта теми же командами и запустите
|
||||||
`dq_check`.
|
`dq_check`.
|
||||||
|
|
||||||
## Завершите опыт
|
## Завершение работы
|
||||||
|
|
||||||
Убедитесь, что оба маркера отсутствуют в STG, ODS, таблице брака и DDS,
|
Убедитесь, что оба маркера отсутствуют в STG, ODS, таблице брака и DDS,
|
||||||
временные файлы удалены, а семь проверок успешны. Теперь можно перейти к
|
временные файлы удалены, а семь проверок успешны. Теперь можно перейти к
|
||||||
сборке DDS, не снимая паузу с модельного времени. Если вы прерываетесь и до
|
сборке DDS, не снимая паузу с модельного времени. Если вы прерываетесь и до
|
||||||
опыта `world_live` работал, снимите его с паузы.
|
опыта `world_live` работал, снимите его с паузы.
|
||||||
|
|
||||||
## Что должно получиться
|
## Результат работы
|
||||||
|
|
||||||
- [ ] у вас сохранены результаты до переписывания: четыре строки дали
|
- [ ] у вас сохранены результаты до переписывания: четыре строки дали
|
||||||
один заказ и три класса брака; вы сверили поля текущей версии и шард;
|
один заказ и три класса брака; вы сверили поля текущей версии и шард;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
# Перепишите сборку заказов в DDS
|
# Сборка заказов в DDS
|
||||||
|
|
||||||
**Чему учит:** настроить пересборку заказов по состоянию ODS и DDS
|
Вы перепишете загрузку заказов из ODS в DDS и выбор дней для пересчета.
|
||||||
и проверить, что она восстанавливает пропущенный день за пределами текущего окна.
|
Затем удалите данные за один из прошлых дней и проверите, что ваша
|
||||||
|
загрузка восстановила их.
|
||||||
|
|
||||||
## Сначала прочитать
|
## Подготовка
|
||||||
|
|
||||||
- [проверки заказов](5-1-order-checks.md);
|
- [проверки заказов](5-1-order-checks.md);
|
||||||
- [разбор заказов в ODS](5-2-order-parsing.md);
|
- [разбор заказов в ODS](5-2-order-parsing.md);
|
||||||
@@ -11,7 +12,7 @@
|
|||||||
- [дневной конвейер](../architecture/etl.md#объём-прогона);
|
- [дневной конвейер](../architecture/etl.md#объём-прогона);
|
||||||
- [раскладку по шардам](../architecture/storage.md#раскладка-по-шардам).
|
- [раскладку по шардам](../architecture/storage.md#раскладка-по-шардам).
|
||||||
|
|
||||||
## Что предстоит сделать
|
## Файлы для изменения
|
||||||
|
|
||||||
Удалите готовый SQL и перепишите только три файла:
|
Удалите готовый SQL и перепишите только три файла:
|
||||||
|
|
||||||
@@ -19,18 +20,18 @@
|
|||||||
- `sql/dds/order_rebuild.sql`;
|
- `sql/dds/order_rebuild.sql`;
|
||||||
- `sql/dds/order_replace.sql`.
|
- `sql/dds/order_replace.sql`.
|
||||||
|
|
||||||
Даг `dds_transform`, DDL, донор, ODS и обе проверки из первого задания
|
Даг `dds_transform`, DDL, промежуточную таблицу, ODS и обе проверки из первого задания
|
||||||
оставьте как есть. Перед началом убедитесь, что пробные строки предыдущего
|
оставьте как есть. Перед началом убедитесь, что пробные строки предыдущего
|
||||||
задания удалены, а семь DQ дают зеленый результат.
|
задания удалены, а семь проверок качества дают зеленый результат.
|
||||||
|
|
||||||
Поставьте `world_live` на паузу и дождитесь завершения активных прогонов. До
|
Поставьте `world_live` на паузу и дождитесь завершения активных прогонов. До
|
||||||
завершения опыта не запускайте `world_live_day`, `world_initialize` и
|
завершения опыта не запускайте `world_live_day`, `world_initialize` и
|
||||||
`world_recreate`. `world_next_day` понадобится только в явно указанном ниже
|
`world_recreate`. `world_next_day` понадобится только в явно указанном ниже
|
||||||
случае.
|
случае.
|
||||||
|
|
||||||
## Как даг запускает ваши запросы
|
## Выполнение запросов в Airflow
|
||||||
|
|
||||||
Сохраните интерфейс `dds_transform`:
|
Даг `dds_transform` ожидает от файлов следующие параметры и результаты:
|
||||||
|
|
||||||
| Файл | Вход | Результат |
|
| Файл | Вход | Результат |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -38,39 +39,46 @@
|
|||||||
| `order_rebuild.sql` | `{first_day:Date}`, `{last_day:Date}`, `{load_id:String}` | выбранные дни в `dds.order_stage_dist` |
|
| `order_rebuild.sql` | `{first_day:Date}`, `{last_day:Date}`, `{load_id:String}` | выбранные дни в `dds.order_stage_dist` |
|
||||||
| `order_replace.sql` | день в `{{ params.day }}` | партиция дня в `dds.order_rep` |
|
| `order_replace.sql` | день в `{{ params.day }}` | партиция дня в `dds.order_rep` |
|
||||||
|
|
||||||
Объем состоит из текущего окна источника и хвоста от первого дня, который есть
|
Сначала определите дни, за которые заказы еще могут измениться. Возьмите
|
||||||
в ODS, но отсутствует в DDS. Текущее окно дают строки последнего
|
строки с последним `snapshot_date` и найдите минимальную и максимальную
|
||||||
`snapshot_date`: их минимальный и максимальный день заказа. Если источник
|
дату заказа. Этот диапазон далее называется текущим окном источника.
|
||||||
пуст, `order_scope.sql` не возвращает строку. Если дыр нет, `first_day` равен
|
|
||||||
началу окна. Если дыра старше, непрерывный отрезок начинается с нее.
|
Затем найдите первый день, который есть в ODS, но отсутствует в DDS.
|
||||||
|
Если он раньше начала текущего окна, начните расчет с него. Иначе начните
|
||||||
|
с начала окна. Полученная дата станет `first_day`, а конец окна `last_day`.
|
||||||
|
Один запуск пересчитывает все дни между ними. Если источник пуст,
|
||||||
|
`order_scope.sql` не возвращает строку.
|
||||||
|
|
||||||
`order_rebuild.sql` сначала очищает все партиции `dds.order_stage_rep` на
|
`order_rebuild.sql` сначала очищает все партиции `dds.order_stage_rep` на
|
||||||
кластере, затем записывает в `dds.order_stage_dist` весь выбранный отрезок. Он
|
кластере, затем записывает в `dds.order_stage_dist` весь выбранный отрезок. Он
|
||||||
читает текущее состояние `ods.order_v`, вычисляет `order_date`, переводит
|
читает текущее состояние `ods.order_v`, вычисляет `order_date`, переводит
|
||||||
времена в `Europe/Samara`, разбирает `items` в три параллельных массива и
|
времена в `Europe/Samara`, разбирает `items` в три параллельных массива и
|
||||||
ставит координаты запуска.
|
заполняет служебные поля запуска.
|
||||||
|
|
||||||
Запись через Distributed должна завершиться до замены и использовать ключ
|
Дождитесь записи всех строк на шарды через Distributed перед заменой партиций.
|
||||||
целевой таблицы для раскладки строк. Способ собрать весь отрезок внутри файла
|
Строки должны распределяться по ключу целевой таблицы. Способ рассчитать
|
||||||
выберите сами.
|
выбранные дни внутри файла выберите сами.
|
||||||
|
|
||||||
`order_replace.sql` работает с локальными `dds.order_rep` и
|
`order_replace.sql` работает с локальными `dds.order_rep` и
|
||||||
`dds.order_stage_rep` на всем кластере. `REPLACE PARTITION` переносит готовую
|
`dds.order_stage_rep` на всем кластере. `REPLACE PARTITION` заменяет дневную
|
||||||
дневную партицию из донора и оставляет донор на месте. Структура, ключи и
|
партицию основной таблицы готовой партицией из промежуточной. Промежуточная
|
||||||
политика хранения совпадают: DDL объявляет донор через `AS dds.order_rep`.
|
партиция при этом сохраняется. Структура, ключи и политика хранения таблиц
|
||||||
|
совпадают: DDL создает промежуточную через `AS dds.order_rep`.
|
||||||
|
|
||||||
## Восстановите ближайший закрытый день
|
## Восстановление пропущенного дня
|
||||||
|
|
||||||
Найдите последний по дате день DDS, который лежит левее начала текущего окна
|
Найдите самый поздний день DDS перед началом текущего окна ODS. Заказы за этот
|
||||||
ODS. Это ближайший к окну закрытый день: его удаление показывает расширение
|
день уже не меняются, поэтому он считается закрытым. Удалив его, вы проверите,
|
||||||
объема на дыру без пересборки всей истории.
|
что ваш запрос заметил пропуск и включил этот день в расчет.
|
||||||
|
|
||||||
Если такого дня еще нет, один раз продвиньте мир через `world_next_day`,
|
Если такого дня еще нет, один раз продвиньте мир через `world_next_day`,
|
||||||
дождитесь `orders_ingest`, затем запустите `etl_pipeline`. После этого снова
|
дождитесь `orders_ingest`, затем запустите `etl_pipeline`. После этого снова
|
||||||
зафиксируйте модельное время до конца опыта и выберите ближайший закрытый день.
|
оставьте модельное время неизменным до конца опыта и выберите этот день.
|
||||||
|
|
||||||
Сохраните для него число заказов и контрольную сумму деловых полей без
|
Сохраните для него число заказов и контрольную сумму их полей, исключив
|
||||||
`_load_id` и `_load_ts`. Удалите только его партицию:
|
служебные `_load_id` и `_load_ts`. Контрольная сумма позволит проверить,
|
||||||
|
что после восстановления значения остались прежними. Удалите только
|
||||||
|
партицию выбранного дня:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
ALTER TABLE dds.order_rep ON CLUSTER clickstream_cluster
|
ALTER TABLE dds.order_rep ON CLUSTER clickstream_cluster
|
||||||
@@ -90,29 +98,30 @@ DROP PARTITION 'YYYY-MM-DD';
|
|||||||
прогона число заказов и контрольная сумма выбранного дня должны совпасть с
|
прогона число заказов и контрольная сумма выбранного дня должны совпасть с
|
||||||
сохраненными.
|
сохраненными.
|
||||||
|
|
||||||
## Проверьте раскладку и повтор
|
## Распределение по шардам и повторный запуск
|
||||||
|
|
||||||
Напишите отдельный запрос к `dds.order_dist`, который получает фактическую
|
Напишите отдельный запрос к `dds.order_dist`, который получает фактический
|
||||||
ноду через `hostName()`, вычисляет ожидаемую по `cityHash64(order_id) % 2` и
|
узел каждой строки через `hostName()`, вычисляет ожидаемый по
|
||||||
считает несовпадения по каждой ноде. Остаток 0 соответствует
|
`cityHash64(order_id) % 2` и считает несовпадения по каждому узлу.
|
||||||
`clickhouse-01`, остаток 1 - `clickhouse-02`. На обеих нодах число строк на
|
Остаток 0 соответствует `clickhouse-01`, остаток 1 - `clickhouse-02`.
|
||||||
чужом шарде должно быть нулевым. Счетные DQ читают Distributed-таблицы и такую
|
На обоих узлах число строк на чужом шарде должно быть нулевым.
|
||||||
ошибку раскладки не обнаруживают.
|
Проверки количества строк читают данные со всех шардов через Distributed
|
||||||
|
и такую ошибку не обнаруживают.
|
||||||
|
|
||||||
Запустите `etl_pipeline` еще раз при неизменном модельном времени. Группа
|
Запустите `etl_pipeline` еще раз при неизменном модельном времени. Группа
|
||||||
заказов снова должна пересобрать текущее изменяемое окно. Контрольные суммы
|
заказов снова должна пересобрать текущее изменяемое окно. Контрольные суммы
|
||||||
деловых полей остаются прежними, а все семь проверок дают зеленый результат.
|
полей заказов остаются прежними, а все семь проверок дают зеленый результат.
|
||||||
|
|
||||||
## Завершите опыт
|
## Завершение работы
|
||||||
|
|
||||||
После восстановления партиции и успешных проверок снимите `world_live`
|
После восстановления партиции и успешных проверок снимите `world_live`
|
||||||
с паузы, если он работал до упражнения.
|
с паузы, если он работал до упражнения.
|
||||||
|
|
||||||
## Что должно получиться
|
## Результат работы
|
||||||
|
|
||||||
- [ ] вы переписали три файла `sql/dds/order_*.sql`, сохранив остальные
|
- [ ] вы переписали три файла `sql/dds/order_*.sql`, сохранив остальные
|
||||||
части стенда;
|
части стенда;
|
||||||
- [ ] вы удалили ближайший к окну закрытый день и увидели ошибку DQ до
|
- [ ] вы удалили ближайший к окну закрытый день и увидели ошибку проверки до
|
||||||
восстановления;
|
восстановления;
|
||||||
- [ ] ваш расчет выбрал `first_day` с удаленного дня и заменил партиции
|
- [ ] ваш расчет выбрал `first_day` с удаленного дня и заменил партиции
|
||||||
всего отрезка до конца окна;
|
всего отрезка до конца окна;
|
||||||
@@ -120,4 +129,4 @@ DROP PARTITION 'YYYY-MM-DD';
|
|||||||
с сохраненными значениями; они совпали;
|
с сохраненными значениями; они совпали;
|
||||||
- [ ] `order_items_total`, `orders_vs_ods` и пять прежних проверок успешны;
|
- [ ] `order_items_total`, `orders_vs_ods` и пять прежних проверок успешны;
|
||||||
- [ ] ваш запрос с `hostName()` не нашел заказов на чужом шарде;
|
- [ ] ваш запрос с `hostName()` не нашел заказов на чужом шарде;
|
||||||
- [ ] повторный `etl_pipeline` сохранил деловые данные, проверки прошли.
|
- [ ] после повторного `etl_pipeline` поля заказов остались прежними, проверки прошли.
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# Разберите сырые события рядом со стендом
|
# Разбор сырых событий
|
||||||
|
|
||||||
**Чему учит:** пакетно разобрать сообщения отдельно от живого приема и
|
В этой работе вы разберете сохраненные сообщения о событиях в свою таблицу.
|
||||||
построчно проверить свой результат по нетронутому ODS.
|
Каждую полученную строку сравните с результатом обработки в стенде.
|
||||||
|
|
||||||
## Сначала прочитать
|
## Подготовка
|
||||||
|
|
||||||
- [формат события](../formats/clickstream-event.md);
|
- [формат события](../formats/clickstream-event.md);
|
||||||
- [справочник хранилища](../architecture/storage.md), разделы про имена
|
- [справочник хранилища](../architecture/storage.md), разделы про имена
|
||||||
@@ -15,26 +15,26 @@
|
|||||||
Готовый стендовый разбор находится в `sql/ddl/30-ods-views.sql`. Откройте его
|
Готовый стендовый разбор находится в `sql/ddl/30-ods-views.sql`. Откройте его
|
||||||
после собственной сверки: в нем лежит ответ на основную часть задания.
|
после собственной сверки: в нем лежит ответ на основную часть задания.
|
||||||
|
|
||||||
## Что предстоит сделать
|
## Порядок работы
|
||||||
|
|
||||||
Создайте в базе `default` одну свою таблицу годных событий и наполните ее одним
|
Создайте в базе `default` свою таблицу для правильно заполненных событий.
|
||||||
пакетным `INSERT SELECT` из `stg.hits_raw_dist`. Работайте с первой нодой под
|
Выберите небольшой набор сообщений из `stg.hits_raw_dist`, разберите их
|
||||||
пользователем `etl`.
|
и загрузите в таблицу одним `INSERT SELECT`. Работайте с первым узлом под
|
||||||
|
пользователем `etl`. Затем сравните результат с событиями в ODS.
|
||||||
|
|
||||||
Матвью `ods.event_mv` и `ods.event_errors_mv` продолжают принимать живой
|
Материализованные представления `ods.event_mv` и `ods.event_errors_mv`
|
||||||
поток. Оставьте их, STG, ODS, Kafka и даги без изменений. Пересоздание матвью
|
продолжают обрабатывать поступающие сообщения. Оставьте их, STG, ODS, Kafka
|
||||||
на работающем стенде оставило бы зазор в приеме, а для пакетного опыта оно не
|
и даги без изменений. Если пересоздать представления на работающем стенде,
|
||||||
нужно.
|
часть сообщений может остаться без обработки.
|
||||||
|
|
||||||
Дайте своей таблице имя, которое не пересечется с чужой работой, и запишите
|
Дайте своей таблице имя, которое не пересечется с чужой работой, и запишите
|
||||||
его вместе с запросами. В конце вы удалите только эту таблицу.
|
его вместе с запросами.
|
||||||
|
|
||||||
## Выберите сырой срез
|
## Выбор исходных сообщений
|
||||||
|
|
||||||
STG хранит строки примерно три дня по `_load_ts`. Эта колонка отмечает время
|
STG хранит строки примерно три дня по `_load_ts`. Эта колонка отмечает время
|
||||||
поступления в хранилище. Модельный день события лежит в `EventDate`. Сначала
|
поступления в хранилище. Модельный день события лежит в `EventDate`.
|
||||||
найдите среди недавних данных завершившийся интервал со строками. Посмотрите
|
Посмотрите, сколько строк поступило за каждую из последних минут:
|
||||||
счет по минутам:
|
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT
|
SELECT
|
||||||
@@ -48,7 +48,8 @@ ORDER BY minute DESC;
|
|||||||
```
|
```
|
||||||
|
|
||||||
Выберите короткий интервал `[slice_from, slice_to)`, который уже закончился и
|
Выберите короткий интервал `[slice_from, slice_to)`, который уже закончился и
|
||||||
дает удобное для построчной проверки число строк. Если одна минута велика,
|
дает удобное для построчной проверки число строк. Этот набор строк дальше
|
||||||
|
называется срезом. Если за одну минуту пришло слишком много строк,
|
||||||
сузьте ее точными границами `_load_ts`. Если недавних строк нет, откройте
|
сузьте ее точными границами `_load_ts`. Если недавних строк нет, откройте
|
||||||
Airflow. При включенном `world_live` посмотрите его текущий запуск и дочерний
|
Airflow. При включенном `world_live` посмотрите его текущий запуск и дочерний
|
||||||
`world_live_day`: дождитесь свежих строк от текущего или следующего планового
|
`world_live_day`: дождитесь свежих строк от текущего или следующего планового
|
||||||
@@ -64,9 +65,9 @@ Airflow. При включенном `world_live` посмотрите его т
|
|||||||
|
|
||||||
Во всех дальнейших запросах читайте `stg.hits_raw_dist` только с этими
|
Во всех дальнейших запросах читайте `stg.hits_raw_dist` только с этими
|
||||||
границами. Новые строки с более поздним `_load_ts` в срез не попадут. Повторите
|
границами. Новые строки с более поздним `_load_ts` в срез не попадут. Повторите
|
||||||
опыт до очистки по TTL; после нее прежний срез уже не восстановить из ODS.
|
опыт до удаления исходных строк по TTL; после него прежний срез уже не восстановить из ODS.
|
||||||
|
|
||||||
## Соберите собственную таблицу
|
## Таблица для результата
|
||||||
|
|
||||||
Таблица содержит все 47 полей источника с именами и типами
|
Таблица содержит все 47 полей источника с именами и типами
|
||||||
`ods.event_rep`, а также `_load_ts`. Тип `purchaseRevenue` возьмите из ODS:
|
`ods.event_rep`, а также `_load_ts`. Тип `purchaseRevenue` возьмите из ODS:
|
||||||
@@ -75,14 +76,15 @@ Airflow. При включенном `world_live` посмотрите его т
|
|||||||
Обработайте повторные доставки так же, как ODS. Возьмите
|
Обработайте повторные доставки так же, как ODS. Возьмите
|
||||||
`ReplacingMergeTree(_load_ts)`, партицию `EventDate` и полный ключ замены:
|
`ReplacingMergeTree(_load_ts)`, партицию `EventDate` и полный ключ замены:
|
||||||
`(CounterID, EventDate, intHash32(ClientID), WatchID)`. Достаточно одной
|
`(CounterID, EventDate, intHash32(ClientID), WatchID)`. Достаточно одной
|
||||||
локальной таблицы: загрузка и сверка в этом опыте выполняются на одной ноде.
|
локальной таблицы: загрузка и сверка в этом опыте выполняются на одном узле.
|
||||||
|
|
||||||
`WatchID` — логический идентификатор события, уникальный по
|
`WatchID` обозначает событие и уникален по
|
||||||
[договору генератора](../specs/2026-08-01-generator.md). Физический ключ ODS
|
[правилам генератора](../specs/2026-08-01-generator.md). Но `ReplacingMergeTree`
|
||||||
шире: в `ReplacingMergeTree` версиями считаются строки с одинаковым полным
|
считает версиями строки, у которых совпадает весь ключ сортировки.
|
||||||
ключом сортировки. Используйте его при загрузке и сверке.
|
Поэтому для загрузки и сверки используйте все четыре части ключа ODS,
|
||||||
|
перечисленные выше.
|
||||||
|
|
||||||
## Разберите годные строки
|
## Разбор сообщений
|
||||||
|
|
||||||
Для каждой строки среза проверьте те же условия приема, что и на стенде:
|
Для каждой строки среза проверьте те же условия приема, что и на стенде:
|
||||||
|
|
||||||
@@ -92,8 +94,10 @@ Airflow. При включенном `world_live` посмотрите его т
|
|||||||
в ожидаемые типы.
|
в ожидаемые типы.
|
||||||
|
|
||||||
Напишите разбор, который не прерывает пакет из-за одной грязной строки. Сами
|
Напишите разбор, который не прерывает пакет из-за одной грязной строки. Сами
|
||||||
выберите способ безопасного извлечения пяти опорных полей, форму общих выражений
|
выберите, как извлечь пять перечисленных полей без ошибки запроса, какие
|
||||||
и место фильтра. Остальные поля извлеките по описанию формата.
|
выражения вынести в общую часть и где отфильтровать неподходящие строки.
|
||||||
|
Эти пять полей дальше называются опорными. Остальные поля извлеките
|
||||||
|
по описанию формата.
|
||||||
|
|
||||||
Перенесите `_load_ts` из STG без пересчета: это время исходной доставки и
|
Перенесите `_load_ts` из STG без пересчета: это время исходной доставки и
|
||||||
колонка версии. Время запуска вашего запроса сюда не входит. Вставьте в свою
|
колонка версии. Время запуска вашего запроса сюда не входит. Вставьте в свою
|
||||||
@@ -114,25 +118,25 @@ Airflow. При включенном `world_live` посмотрите его т
|
|||||||
условия отбора на этом срезе, но не классификатор ошибок. Подкладывать грязные
|
условия отбора на этом срезе, но не классификатор ошибок. Подкладывать грязные
|
||||||
сообщения ради задания не требуется.
|
сообщения ради задания не требуется.
|
||||||
|
|
||||||
## Приведите стороны к одному зерну
|
## Подготовка к сравнению
|
||||||
|
|
||||||
### Учтите повторные доставки
|
### Повторные доставки
|
||||||
|
|
||||||
В STG одна строка представляет доставку. Повтор сообщения остается второй
|
В STG одна строка представляет доставку. Повтор сообщения остается второй
|
||||||
строкой. В `ods.event_v` одна строка представляет актуальное событие, потому
|
строкой. В `ods.event_v` одна строка представляет актуальное событие, потому
|
||||||
что представление читает физическую таблицу с `FINAL`.
|
что представление читает физическую таблицу с `FINAL`.
|
||||||
|
|
||||||
Свою сторону тоже читайте с `FINAL`. Ожидаемый счет равен числу различных
|
Свою таблицу тоже читайте с `FINAL`. Число строк должно совпасть с числом
|
||||||
полных ключей среди годных доставок. Разница между числом доставок и этим
|
различных полных ключей среди годных исходных сообщений. Разница между числом
|
||||||
счетом показывает повторы в исходном срезе.
|
годных доставок и этим числом показывает повторы в исходном срезе.
|
||||||
|
|
||||||
### Выберите события ODS для сравнения
|
### События ODS для сравнения
|
||||||
|
|
||||||
Теперь нужно найти в ODS события из выбранного среза. Список стройте по
|
Теперь нужно найти в ODS события из выбранного среза. Список стройте по
|
||||||
исходным доставкам: если взять его из своей таблицы, ошибочно отвергнутое
|
исходным доставкам: если взять его из своей таблицы, ошибочно отвергнутое
|
||||||
событие исчезнет с обеих сторон сверки и потеря останется незамеченной.
|
событие исчезнет с обеих сторон сверки и потеря останется незамеченной.
|
||||||
|
|
||||||
Снова возьмите весь исходный STG-срез. По координатам Kafka исключите
|
Снова возьмите все выбранные строки STG. По координатам Kafka исключите
|
||||||
доставки, которые есть в `ods.event_errors_dist`. У оставшихся извлеките
|
доставки, которые есть в `ods.event_errors_dist`. У оставшихся извлеките
|
||||||
`WatchID` без риска прервать запрос и выберите эти события из свежего
|
`WatchID` без риска прервать запрос и выберите эти события из свежего
|
||||||
`ods.event_v`. Свое условие отбора здесь не применяйте: решение о годности
|
`ods.event_v`. Свое условие отбора здесь не применяйте: решение о годности
|
||||||
@@ -141,36 +145,38 @@ Airflow. При включенном `world_live` посмотрите его т
|
|||||||
`WatchID` связывает доставку с событием в ODS. Для удаления повторов и
|
`WatchID` связывает доставку с событием в ODS. Для удаления повторов и
|
||||||
сравнения полей дальше используйте полный физический ключ из схемы таблицы.
|
сравнения полей дальше используйте полный физический ключ из схемы таблицы.
|
||||||
|
|
||||||
## Сверьте 47 полей
|
## Сверка полей
|
||||||
|
|
||||||
Соедините свою таблицу и выбранные строки `ods.event_v` через `FULL OUTER
|
Соедините свою таблицу и выбранные строки `ods.event_v` через `FULL OUTER
|
||||||
JOIN` по полному ключу замены. Полное соединение сохраняет строки,
|
JOIN` по полному ключу замены. Полное соединение сохраняет строки,
|
||||||
которых нет на одной из сторон. Разделите результат на три исхода:
|
которых нет на одной из сторон. Найдите три вида расхождений:
|
||||||
|
|
||||||
- строка есть только в ODS — вы ее потеряли;
|
- строка есть только в ODS — вы ее потеряли;
|
||||||
- строка есть только у вас — вы приняли лишнее или иначе разобрали часть
|
- строка есть только у вас — вы приняли лишнее или иначе разобрали часть
|
||||||
ключа;
|
ключа;
|
||||||
- ключ есть с обеих сторон, но различается хотя бы одно из 47 полей.
|
- ключ есть с обеих сторон, но различается хотя бы одно из 47 полей.
|
||||||
|
|
||||||
ClickHouse по умолчанию заполняет отсутствующую сторону значениями типов.
|
ClickHouse по умолчанию заполняет отсутствующую сторону значениями по умолчанию
|
||||||
|
для соответствующих типов, например нулем или пустой строкой.
|
||||||
Включите для сверки `join_use_nulls = 1` или добавьте явный признак присутствия
|
Включите для сверки `join_use_nulls = 1` или добавьте явный признак присутствия
|
||||||
в каждую сторону. Не определяйте отсутствие по значениям полей события.
|
в каждую сторону. Не определяйте отсутствие по значениям полей события.
|
||||||
|
|
||||||
Сравните все поля явно, включая массивы, `ecommerce` и точное десятичное
|
Сравните все поля явно, включая массивы, `ecommerce` и точное десятичное
|
||||||
`purchaseRevenue`. Удобный вид диагностики выберите сами: важно, чтобы по
|
`purchaseRevenue`. Выберите, как показать расхождения, чтобы было видно,
|
||||||
результату можно было назвать отличающиеся колонки одной строки. Выведите число
|
какие колонки различаются в конкретной строке. Выведите число строк каждого
|
||||||
строк каждого из трех исходов. В верном разборе все три числа равны нулю.
|
из трех видов расхождений. В верном разборе все три числа равны нулю.
|
||||||
|
|
||||||
`_load_ts` не входит в 47 полей источника и не проверяет JSON-разбор.
|
`_load_ts` не входит в 47 полей источника и не проверяет JSON-разбор.
|
||||||
Проверьте его отдельно. Свяжите каждую строку своей таблицы с меткой одной из
|
Проверьте его отдельно. Для каждой строки своей таблицы найдите самую позднюю
|
||||||
доставок того же полного ключа в исходном срезе. После `FINAL` остается самая
|
метку доставки того же полного ключа в исходном срезе. Она должна совпасть
|
||||||
поздняя метка этого ключа внутри среза. С ODS ее не сравнивайте: там может
|
с `_load_ts`, который вы читаете после `FINAL`. С ODS эту метку не сравнивайте: там может
|
||||||
храниться более поздняя доставка того же события из-за границ среза.
|
храниться более поздняя доставка того же события из-за границ среза.
|
||||||
|
|
||||||
## Расследуйте расхождение
|
## Поиск ошибок разбора
|
||||||
|
|
||||||
Если сверка не пуста, возьмите одну строку. Вернитесь к независимому списку
|
Если нашлись расхождения, выберите одну строку. Найдите ее ключ в списке,
|
||||||
ключей и исходному `raw`, выведите рядом координаты Kafka и `_load_ts`.
|
который вы получили из STG, и исходное сообщение `raw`. Выведите рядом
|
||||||
|
координаты Kafka и `_load_ts`.
|
||||||
|
|
||||||
Для измененной строки назовите поля, которые расходятся, и сравните каждое с
|
Для измененной строки назовите поля, которые расходятся, и сравните каждое с
|
||||||
сырой строкой. Для пропавшей или лишней строки проверьте пять опорных полей и
|
сырой строкой. Для пропавшей или лишней строки проверьте пять опорных полей и
|
||||||
@@ -178,28 +184,28 @@ ClickHouse по умолчанию заполняет отсутствующую
|
|||||||
`ods.event_errors_dist`, посмотрите `error_class`.
|
`ods.event_errors_dist`, посмотрите `error_class`.
|
||||||
|
|
||||||
Исправьте разбор, удалите и заново создайте только собственную таблицу, затем
|
Исправьте разбор, удалите и заново создайте только собственную таблицу, затем
|
||||||
повторите вставку того же среза. Остановитесь, когда счет исходных доставок по
|
повторите вставку того же среза. Остановитесь, когда число исходных доставок по
|
||||||
прежним границам не изменился, число годных и отвергнутых строк в сумме дает
|
прежним границам не изменилось, число годных и отвергнутых строк в сумме дает
|
||||||
число исходных доставок, а построчная сверка пуста.
|
число исходных доставок, а построчная сверка не находит расхождений.
|
||||||
|
|
||||||
## Чего не подтверждает сравнение с ODS
|
## Ограничения проверки
|
||||||
|
|
||||||
`ods.event_v` — образец для сравнения. Оба разбора могли повторить одну
|
`ods.event_v` — образец для сравнения. Оба разбора могли повторить одну
|
||||||
ошибку. Для пяти опорных полей у вас есть проверка преобразования в нужный тип
|
ошибку. Для пяти опорных полей у вас есть проверка преобразования в нужный тип
|
||||||
и исходный JSON. Остальные 42 поля можно вручную сопоставить с `raw`, однако
|
и исходный JSON. Остальные 42 поля можно вручную сопоставить с `raw`, однако
|
||||||
второго независимого источника для всех значений на стенде нет.
|
второго независимого источника для всех значений на стенде нет.
|
||||||
|
|
||||||
## Уберите свой объект
|
## Сохранение результата и удаление таблицы
|
||||||
|
|
||||||
Сохраните DDL таблицы, запрос разбора, границы среза, число исходных, годных
|
Сохраните DDL таблицы, запрос разбора, границы среза, число исходных, годных
|
||||||
и отвергнутых строк и итоговую сверку без расхождений. Запишите также, чего
|
и отвергнутых строк и итоговую сверку без расхождений. Запишите также, чего
|
||||||
не подтверждает сравнение с ODS и как TTL ограничивает время для повторения
|
не подтверждает сравнение с ODS и как TTL ограничивает время для повторения
|
||||||
опыта. Затем удалите свою таблицу командой `DROP TABLE` с ее точным именем.
|
опыта. Затем удалите свою таблицу командой `DROP TABLE` с ее точным именем.
|
||||||
Убедитесь через `system.tables`, что объекта больше нет.
|
Убедитесь через `system.tables`, что объекта больше нет.
|
||||||
Прикладные строки стенда вы не меняли, поэтому удалять данные из STG, ODS или
|
Данные стенда вы не меняли, поэтому удалять строки из STG, ODS или
|
||||||
Kafka после опыта не нужно.
|
Kafka после опыта не нужно.
|
||||||
|
|
||||||
## Что должно получиться
|
## Результат работы
|
||||||
|
|
||||||
- [ ] у вас сохранены границы выбранного среза, число строк и координаты
|
- [ ] у вас сохранены границы выбранного среза, число строк и координаты
|
||||||
доставки по партициям Kafka;
|
доставки по партициям Kafka;
|
||||||
|
|||||||
+32
-28
@@ -1,9 +1,10 @@
|
|||||||
# Мониторинг: куда смотреть
|
# Мониторинг: куда смотреть
|
||||||
|
|
||||||
**Чему учит:** начать расследование с подходящего экрана, найти источник
|
Вы познакомитесь с дашбордами мониторинга и проведете два расследования:
|
||||||
нагрузки и объяснить свежесть данных через путь доставки.
|
найдете запросы, которые создают нагрузку, и выясните, почему события сайта
|
||||||
|
и заказы обновляются с разной частотой.
|
||||||
|
|
||||||
## Перед началом
|
## Знакомство с дашбордами
|
||||||
|
|
||||||
Откройте Grafana и Airflow по адресам из раздела [«Адреса и
|
Откройте Grafana и Airflow по адресам из раздела [«Адреса и
|
||||||
пароли»](../../README.md#адреса-и-пароли). Для первого знакомства в Grafana
|
пароли»](../../README.md#адреса-и-пароли). Для первого знакомства в Grafana
|
||||||
@@ -11,7 +12,7 @@
|
|||||||
|
|
||||||
- «Данные» — путь сообщений от источника до хранилища;
|
- «Данные» — путь сообщений от источника до хранилища;
|
||||||
- «Кластер» — процессор, память, диски и внутренняя работа ClickHouse;
|
- «Кластер» — процессор, память, диски и внутренняя работа ClickHouse;
|
||||||
- «Запросы» — выполненные запросы, их стоимость и ошибки.
|
- «Запросы» — выполненные запросы, затраченные на них ресурсы и ошибки.
|
||||||
|
|
||||||
Откройте все три дашборда и запишите по одному вопросу, на который каждый из
|
Откройте все три дашборда и запишите по одному вопросу, на который каждый из
|
||||||
них помогает ответить. «Данные» оставьте открытым в соседней вкладке.
|
них помогает ответить. «Данные» оставьте открытым в соседней вкладке.
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
Проведите два расследования на работающем стенде. Службы останавливать и данные
|
Проведите два расследования на работающем стенде. Службы останавливать и данные
|
||||||
портить не понадобится.
|
портить не понадобится.
|
||||||
|
|
||||||
## Расследование 1. Кто читает больше всех
|
## Расследование 1. Какие запросы читают больше всего данных
|
||||||
|
|
||||||
Начните в спокойный момент: не запускайте одновременно свои тяжелые запросы.
|
Начните в спокойный момент: не запускайте одновременно свои тяжелые запросы.
|
||||||
Откройте дашборд «Запросы». На панели «Тяжёлые запросы: что прочитало больше
|
Откройте дашборд «Запросы». На панели «Тяжёлые запросы: что прочитало больше
|
||||||
@@ -27,8 +28,8 @@
|
|||||||
|
|
||||||
Сохраните:
|
Сохраните:
|
||||||
|
|
||||||
- границы окна и время запроса;
|
- начало и конец выбранного периода, время запроса;
|
||||||
- место запроса в списке, пользователя и ноду;
|
- место запроса в списке, пользователя и узел;
|
||||||
- текст запроса;
|
- текст запроса;
|
||||||
- длительность, память, число прочитанных и отданных строк.
|
- длительность, память, число прочитанных и отданных строк.
|
||||||
|
|
||||||
@@ -44,22 +45,25 @@
|
|||||||
В меню панели «Кто спрашивает: сколько запросов и какой у них аппетит»
|
В меню панели «Кто спрашивает: сколько запросов и какой у них аппетит»
|
||||||
откройте Inspect, затем Query. Ответьте на два вопроса:
|
откройте Inspect, затем Query. Ответьте на два вопроса:
|
||||||
|
|
||||||
1. Почему журнал читают сразу с обеих нод?
|
1. Почему журнал читают сразу с обоих узлов?
|
||||||
2. Почему из счета исключены строки начала запроса и работа шардов?
|
2. Почему при подсчете запросов исключают записи о начале выполнения и
|
||||||
|
записи о выполнении частей запроса на шардах?
|
||||||
|
|
||||||
Проверьте второе объяснение по панели ошибок: ее фильтр не исключает записи
|
Проверьте второе объяснение по панели ошибок: ее фильтр не исключает записи
|
||||||
шардов. Объясните, почему различаются вопросы панелей, а не только их SQL.
|
шардов. Объясните, на какие разные вопросы отвечают эти две панели
|
||||||
|
и зачем им разные условия отбора.
|
||||||
|
|
||||||
## Расследование 2. Как меняется свежесть заказов
|
## Расследование 2. Как меняется свежесть заказов
|
||||||
|
|
||||||
Откройте дашборд «Данные». Панель «Свежесть: сколько прошло с последней
|
Откройте дашборд «Данные». Панель «Свежесть: сколько прошло с последней
|
||||||
доехавшей строки» показывает текущее число, а не историю. До наблюдения
|
доехавшей строки» показывает, сколько времени прошло с последней загрузки.
|
||||||
нарисуйте ожидаемую форму свежести событий и заказов в течение одного живого
|
Истории значений на ней нет. До наблюдения нарисуйте, как, по вашему ожиданию,
|
||||||
дня. Отдельно отметьте момент доставки слепка заказов.
|
будут меняться эти числа для событий и заказов в течение одного запуска
|
||||||
|
`world_live_day`. Отдельно отметьте ожидаемый момент доставки слепка заказов.
|
||||||
|
|
||||||
Если `world_live` снят с паузы, наблюдайте его текущий или следующий
|
Если `world_live` снят с паузы, наблюдайте его текущий или следующий
|
||||||
`world_live_day`: новый ручной запуск встанет в очередь. Если выключатель на
|
`world_live_day`: новый ручной запуск встанет в очередь. Если `world_live` на
|
||||||
паузе и активного живого дня нет, запустите один `world_live_day` вручную.
|
паузе и активного `world_live_day` нет, запустите один `world_live_day` вручную.
|
||||||
Модельные сутки занимают около 24 реальных минут.
|
Модельные сутки занимают около 24 реальных минут.
|
||||||
|
|
||||||
Запишите несколько наблюдений до конца дня и сразу после него:
|
Запишите несколько наблюдений до конца дня и сразу после него:
|
||||||
@@ -69,29 +73,29 @@
|
|||||||
| | | | |
|
| | | | |
|
||||||
|
|
||||||
Дашборд обновляется раз в минуту, поэтому точного совпадения момента
|
Дашборд обновляется раз в минуту, поэтому точного совпадения момента
|
||||||
доставки со строкой таблицы ждать не нужно. Рост и сброс свежести заказов
|
доставки с записью наблюдения ждать не нужно. Перенесите рост и сброс
|
||||||
перенесите на свой набросок. Объясните форму линии: какую метку времени
|
показателя заказов на свой рисунок. Объясните форму линии: какую метку времени
|
||||||
вычитает панель из текущего времени, почему число растет и какое событие его
|
вычитает панель из текущего времени, почему число растет и какое событие его
|
||||||
сбрасывает. Модельную дату сыгранного дня подпишите отдельно от реального
|
сбрасывает. Модельную дату наблюдаемого дня подпишите отдельно от реального
|
||||||
времени наблюдения.
|
времени наблюдения.
|
||||||
|
|
||||||
Подтвердите сброс двумя следами:
|
Подтвердите, что сброс совпал с загрузкой заказов:
|
||||||
|
|
||||||
- в Airflow завершился прием слепка `orders_ingest`;
|
- в Airflow завершился прием слепка `orders_ingest`;
|
||||||
- на панели «Дневной слепок заказов: какой доехал, когда и после какой паузы»
|
- на панели «Дневной слепок заказов: какой доехал, когда и после какой паузы»
|
||||||
появилась новая верхняя строка.
|
появилась новая верхняя строка.
|
||||||
|
|
||||||
Если вы начали после доставки, дождитесь следующего дня либо честно запишите,
|
Если вы начали после доставки, дождитесь следующего дня либо запишите,
|
||||||
какую часть формы увидели. Одно текущее значение не доказывает ни рост, ни
|
какую часть изменений успели увидеть. Одно текущее значение не доказывает ни рост, ни
|
||||||
сброс.
|
сброс.
|
||||||
|
|
||||||
## Объясните путь данных
|
## Источники показателей
|
||||||
|
|
||||||
Не открывая справочник, объясните своими словами:
|
Не открывая справочник, объясните своими словами:
|
||||||
|
|
||||||
- почему свежесть считают в ODS, а поток — в STG;
|
- почему время с последней загрузки считают по ODS, а число поступивших сообщений по STG;
|
||||||
- почему знаменатель доли брака берут из STG;
|
- почему знаменатель доли брака берут из STG;
|
||||||
- почему у числителя и знаменателя доли брака одно окно.
|
- почему числитель и знаменатель доли брака берут за один период.
|
||||||
|
|
||||||
Свяжите ответы с тем, что вы видели на панелях. Затем сверьте объяснение с
|
Свяжите ответы с тем, что вы видели на панелях. Затем сверьте объяснение с
|
||||||
[главой о мониторинге](../handbook/monitoring.md) и справочниками дашбордов
|
[главой о мониторинге](../handbook/monitoring.md) и справочниками дашбордов
|
||||||
@@ -99,20 +103,20 @@
|
|||||||
[«Данные»](../architecture/monitoring/data.md). Устройство живого дня и
|
[«Данные»](../architecture/monitoring/data.md). Устройство живого дня и
|
||||||
доставки слепка описано в [главе об Airflow](../handbook/airflow-dags.md).
|
доставки слепка описано в [главе об Airflow](../handbook/airflow-dags.md).
|
||||||
|
|
||||||
## Что должно получиться
|
## Результат работы
|
||||||
|
|
||||||
- [ ] для каждого из трех дашбордов записан вопрос, на который он отвечает;
|
- [ ] для каждого из трех дашбордов записан вопрос, на который он отвечает;
|
||||||
- [ ] у вас сохранены окно, текст и показатели верхнего запроса;
|
- [ ] у вас сохранены выбранный период, текст и показатели верхнего запроса;
|
||||||
вы выяснили, кто его отправил и зачем;
|
вы выяснили, кто его отправил и зачем;
|
||||||
- [ ] вы нашли запрос открытых дашбордов, который прочитал больше всего
|
- [ ] вы нашли запрос открытых дашбордов, который прочитал больше всего
|
||||||
строк, и панель, которая его отправила;
|
строк, и панель, которая его отправила;
|
||||||
- [ ] вы можете объяснить, зачем журнал собирают с двух нод и какие записи
|
- [ ] вы можете объяснить, зачем журнал собирают с двух узлов и какие записи
|
||||||
учитывают при подсчете запросов;
|
учитывают при подсчете запросов;
|
||||||
- [ ] у вас есть прогноз свежести, сделанный до наблюдения, и несколько
|
- [ ] у вас есть прогноз свежести, сделанный до наблюдения, и несколько
|
||||||
замеров; рост и сброс на рисунке объяснены через время последней доставки;
|
замеров; рост и сброс на рисунке объяснены через время последней доставки;
|
||||||
- [ ] вы подтвердили доставку заказов в Airflow и на панели слепков,
|
- [ ] вы подтвердили доставку заказов в Airflow и на панели слепков,
|
||||||
подписали модельную дату отдельно от реального времени наблюдений;
|
подписали модельную дату отдельно от реального времени наблюдений;
|
||||||
- [ ] вы можете объяснить выбор слоев для свежести, потока и доли брака
|
- [ ] вы можете объяснить выбор слоев для свежести, потока и доли брака
|
||||||
и почему числитель и знаменатель берут за одно окно;
|
и почему числитель и знаменатель берут за один период;
|
||||||
- [ ] после наблюдений службы продолжают работать, данные и стендовые
|
- [ ] после наблюдений службы продолжают работать, данные и стендовые
|
||||||
дашборды остались без изменений.
|
дашборды остались без изменений.
|
||||||
+142
-96
@@ -1,84 +1,121 @@
|
|||||||
# Учебные задания
|
# Учебные задания
|
||||||
|
|
||||||
Программа состоит из двух частей: основная включает семь ступеней, которые вы
|
Вы будете достраивать хранилище небольшого интернет-магазина. Стенд уже
|
||||||
проходите по порядку, а факультативы выбираете по желанию. Причины такого
|
загружает события сайта и заказы, обрабатывает их и строит несколько отчетов.
|
||||||
порядка и формы заданий описаны в [программе учебных лаб](../specs/2026-09-01-labs-program.md#2-устройство-программы-основная-программа-и-факультативы).
|
Сначала вы подготовите данные для новых отчетов, затем разберетесь, как
|
||||||
|
работают расчеты и загрузка, и перепишете часть из них.
|
||||||
|
|
||||||
## Основная программа
|
Основная программа состоит из семи ступеней. Проходите их по порядку,
|
||||||
|
факультативы выбирайте по желанию. Перед первым заданием прочитайте главу
|
||||||
|
[«Хранилище и его слои»](../handbook/storage-layers.md) и выполните запросы
|
||||||
|
из нее: они покажут данные, с которыми предстоит работать.
|
||||||
|
|
||||||
| Ступень | Задание | Чему учит |
|
## Порядок заданий
|
||||||
|
|
||||||
|
| Ступень | Задания | Содержание |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 1 | Пять витрин слоя DM | построить результат для потребителя хранилища; уроки каждой витрины — в таблице ниже |
|
| 1 | Пять витрин для отчетов | подготовить данные о событиях, страницах, визитах, ошибках загрузки и рекламе; задания перечислены ниже |
|
||||||
| 2 | [Свой график в Superset](2-1-superset-chart.md) | превратить свой результат из хранилища в отчет в BI |
|
| 2 | [Свой график в Superset](2-1-superset-chart.md) | построить график по своей витрине и проверить числа на нем |
|
||||||
| 3 | [Нарежьте сессии сами](3-1-sessions-by-hand.md) | сверить число сессий в своей нарезке с эталоном и найти события, из-за которых оно расходится |
|
| 3 | [Сессии из событий](3-1-sessions-by-hand.md) | объединить действия посетителя в визиты и сравнить их с визитами источника |
|
||||||
| 4 | [Перепишите нарезку стенда](4-1-session-pipeline.md) | переписать расчет сессий в готовом конвейере и проверить по исходным событиям |
|
| 4 | [Расчет сессий в стенде](4-1-session-pipeline.md) | включить свой расчет в готовую обработку данных и проверить результат |
|
||||||
| 5 | [Научите проверки замечать ошибки в заказах](5-1-order-checks.md)<br>[Перепишите разбор заказов в ODS](5-2-order-parsing.md)<br>[Перепишите сборку заказов в DDS](5-3-order-pipeline.md) | написать проверки качества до рискованного изменения, затем переписать разбор и сборку заказов |
|
| 5 | [Проверки качества заказов](5-1-order-checks.md)<br>[Разбор заказов в ODS](5-2-order-parsing.md)<br>[Сборка заказов в DDS](5-3-order-pipeline.md) | сначала написать проверки качества, затем с их помощью проверить свою обработку заказов |
|
||||||
| 6 | [Разберите сырые события рядом со стендом](6-1-event-parsing.md) | изучить разбор потока отдельно от живого приема и сверить его со стендовым результатом |
|
| 6 | [Разбор сырых событий](6-1-event-parsing.md) | извлечь поля из сообщений и сравнить результат с готовым разбором стенда |
|
||||||
| 7 | [Мониторинг: куда смотреть](7-1-monitoring.md) | выбрать, куда и на что смотреть в живой системе |
|
| 7 | [Мониторинг: куда смотреть](7-1-monitoring.md) | найти тяжелые запросы и разобраться, почему данные обновляются с разной частотой |
|
||||||
|
|
||||||
## Ступень 1: пять витрин слоя DM
|
## Ступень 1: витрины для отчетов
|
||||||
|
|
||||||
Слой DM в стенде построен наполовину: витрины трех сюжетов дашборда и контроль
|
Витрина готовит данные для конкретного отчета. В стенде они находятся в базе
|
||||||
качества уже построены, остальные пять витрин строите вы. Каждая из них —
|
`dm`: это слой DM. Витрины выручки, трафика, сверки покупок с заказами и
|
||||||
самостоятельное задание: в нем есть состав витрины, зерно, источники и способ
|
сводка качества уже готовы. Вы добавите еще пять.
|
||||||
проверки.
|
|
||||||
|
|
||||||
| № | Результат | Чему учит |
|
| № | Витрина | Содержание задания |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 1 | [`events_enriched_v`](1-1-events-enriched.md) | подготовить удобный для анализа набор полей, сохранив одну строку на событие |
|
| 1 | [События для анализа: `events_enriched_v`](1-1-events-enriched.md) | выбрать нужные поля и добавить вычисляемые, сохранив одну строку на событие |
|
||||||
| 2 | [`top_pages_daily`](1-2-top-pages-daily.md) | создавать материализованную витрину с дневной пересборкой в конвейере |
|
| 2 | [Показатели страниц по дням: `top_pages_daily`](1-2-top-pages-daily.md) | сохранить итоги в таблице и настроить их обновление через Airflow |
|
||||||
| 3 | [`session_overview`](1-3-session-overview.md) | хранить слагаемые для расчета средних и долей |
|
| 3 | [Визиты по устройствам: `session_overview`](1-3-session-overview.md) | хранить суммы и счетчики для расчета средних и долей |
|
||||||
| 4 | [`dq_errors_daily`](1-4-dq-errors-daily.md) | считать брак по дню загрузки и хранить итоги дольше источника |
|
| 4 | [Ошибки загрузки по дням: `dq_errors_daily`](1-4-dq-errors-daily.md) | сохранить число ошибок даже после удаления исходных строк |
|
||||||
| 5 | [`utm_effectiveness`](1-5-utm-effectiveness.md) | читать `declared_` как предупреждение о сумме со слов клиента |
|
| 5 | [Показатели рекламы: `utm_effectiveness`](1-5-utm-effectiveness.md) | различать суммы покупок из событий сайта и выручку по заказам магазина |
|
||||||
|
|
||||||
Общее устройство слоя — [справочник DM](../architecture/dm.md). Он отвечает на
|
Начните с [первого задания](1-1-events-enriched.md): для него достаточно
|
||||||
вопросы «почему так», задание отвечает на вопрос «что сделать».
|
одного представления. В заданиях 2–5 понадобятся таблицы и их обновление
|
||||||
|
по дням. К общим правилам ниже возвращайтесь по мере работы.
|
||||||
|
|
||||||
## Как принято на ступени 1
|
## Создание и обновление витрин
|
||||||
|
|
||||||
**Читать — через `_v`.** Физические `_rep` и `_dist` служат загрузке и
|
### Источники данных
|
||||||
обслуживанию, а публичный договор DDS — `dds.event_v` или `dds.session_v` —
|
|
||||||
остается прежним даже при изменении физических объектов. Чтение DDS
|
|
||||||
напрямую делает задания независимыми: соседняя витрина может быть еще не
|
|
||||||
построена.
|
|
||||||
Исключение — задание 4: у таблиц брака публичного договора нет.
|
|
||||||
|
|
||||||
**Писать — в `_dist`.** Так строки распределяются по ключу шардирования. При
|
События читайте из `dds.event_v`, сессии из `dds.session_v`. Суффикс `_v`
|
||||||
записи в локальную таблицу распределение зависит от узла, выполнившего
|
обозначает обычное представление: оно выполняет сохраненный запрос при
|
||||||
запрос, и при следующем запуске может измениться. Подробности — раздел
|
чтении. Такие представления сохраняют привычные имена и состав колонок,
|
||||||
«Раскладка по шардам» [справочника хранилища](../architecture/storage.md).
|
когда внутреннее устройство таблиц меняется.
|
||||||
|
|
||||||
**Форма — по зерну.** На стенде агрегат с новым зерном принято хранить
|
Каждая учебная витрина читает готовый источник и не зависит от других
|
||||||
готовым: потребитель читает его много раз без повторного расчета по
|
учебных витрин. В задании 4 источниками служат таблицы ошибок ODS:
|
||||||
источнику. Создайте `dm.<имя>_rep` / `_dist`, донор `dm.<имя>_stage_rep` /
|
представлений для них нет.
|
||||||
`_stage_dist` и публичное `dm.<имя>_v` с явным списком колонок. При прежнем
|
|
||||||
зерне достаточно представления: таблица дублировала бы данные. Доводы —
|
|
||||||
раздел «Таблица или представление» [справочника
|
|
||||||
DM](../architecture/dm.md).
|
|
||||||
|
|
||||||
**Ключ шардирования — ваше решение.** Объясните, почему выбранный ключ не
|
### Таблицы и представления
|
||||||
дает существенного перекоса и почему более сложный ничего не добавит.
|
|
||||||
Объявите донор через `AS <цель>_rep` с ключом цели: иначе замена партиции
|
В заданиях 2–5 вы группируете исходные строки. Например, в `top_pages_daily`
|
||||||
потеряет строку на одном шарде и удвоит на другом. Детали — раздел
|
одна строка содержит итоги для одной страницы за день. То, чему соответствует
|
||||||
«Раскладка по шардам» [справочника хранилища](../architecture/storage.md).
|
одна строка, называют **зерном**. На стенде итоги с новым зерном сохраняют
|
||||||
Пример обоснования — `dm.revenue_daily_dist` в
|
в таблицах. Отчеты читают готовые числа.
|
||||||
|
|
||||||
|
Новый расчет сначала записывают в промежуточную таблицу. Пока он готовится,
|
||||||
|
отчеты читают прежние данные из основной. Затем готовыми разделами
|
||||||
|
промежуточной таблицы заменяют разделы основной. Раздел называется
|
||||||
|
**партицией**; в этих заданиях он содержит данные одного дня.
|
||||||
|
Промежуточную таблицу также называют **донором**.
|
||||||
|
|
||||||
|
На примере `top_pages_daily` пять объектов выглядят так:
|
||||||
|
|
||||||
|
| Объект | Для чего нужен |
|
||||||
|
|---|---|
|
||||||
|
| `dm.top_pages_daily_rep` | Хранит рассчитанные строки на каждом шарде |
|
||||||
|
| `dm.top_pages_daily_dist` | Читает строки со всех шардов и распределяет запись между ними |
|
||||||
|
| `dm.top_pages_daily_stage_rep` | Хранит новый расчет до замены данных в основной таблице |
|
||||||
|
| `dm.top_pages_daily_stage_dist` | Распределяет запись нового расчета между шардами |
|
||||||
|
| `dm.top_pages_daily_v` | Дает отчетам доступ к готовой витрине; явно перечисляет ее колонки |
|
||||||
|
|
||||||
|
### Распределение по шардам
|
||||||
|
|
||||||
|
Каждый из двух шардов стенда хранит свою часть данных. Записывайте строки
|
||||||
|
через таблицы `_dist`: они выбирают шард по ключу шардирования. Прямая запись
|
||||||
|
в `_rep` оставит строки на том узле, где выполнен запрос.
|
||||||
|
|
||||||
|
Ключ для своей витрины выберите сами. Проверьте распределение строк между
|
||||||
|
шардами и объясните, почему выбранного ключа достаточно.
|
||||||
|
Пример обоснования для `dm.revenue_daily_dist` есть в
|
||||||
[`sql/ddl/55-dm-tables.sql`](../../sql/ddl/55-dm-tables.sql).
|
[`sql/ddl/55-dm-tables.sql`](../../sql/ddl/55-dm-tables.sql).
|
||||||
|
|
||||||
**Где лежит код.** DDL заданий — в `sql/ddl/70-labs.sql`.
|
Создайте промежуточную таблицу через `AS <цель>_rep`, сохранив структуру и
|
||||||
При создании мира он применяется после стендовых файлов.
|
ключи основной. У обеих таблиц `_dist` должен быть одинаковый ключ
|
||||||
В уже работающем стенде сохранение файла и повторный `make up` новый DDL
|
шардирования: замена партиции выполняется внутри каждого шарда и не
|
||||||
не применят — выполните его сами, как описано ниже. В
|
перераспределяет строки. Подробности есть в разделе
|
||||||
`sql/dm/<витрина>_scope.sql` определяются дни прогона, в `_rebuild.sql` они
|
[«Раскладка по шардам»](../architecture/storage.md#раскладка-по-шардам).
|
||||||
собираются в донор, в `_replace.sql` заменяется партиция. Задачу сборки
|
|
||||||
добавьте в [`dags/dm_transform.py`](../../dags/dm_transform.py): без нее
|
|
||||||
конвейер не обновит витрину. Представлению достаточно `CREATE VIEW`.
|
|
||||||
|
|
||||||
**Образец — витрина `revenue_daily`.** У нее есть пять объектов, донор,
|
### SQL-файлы и запуск расчета
|
||||||
сборка, замена дневных партиций и задача в конвейере. Используйте ее как
|
|
||||||
образец всего порядка пересборки. Состав колонок берите из своего ТЗ, иначе
|
|
||||||
получите показатели другой витрины.
|
|
||||||
|
|
||||||
## Как применить свой DDL
|
Команды создания таблиц и представлений (DDL) сохраняйте в
|
||||||
|
`sql/ddl/70-labs.sql`. При создании мира этот файл выполняется после
|
||||||
|
стендовых файлов. В работающем стенде примените команды сами по инструкции
|
||||||
|
ниже: сохранение файла и повторный `make up` их не выполнят.
|
||||||
|
|
||||||
|
Для витрины с таблицами нужны три файла в `sql/dm/`:
|
||||||
|
|
||||||
|
- `<витрина>_scope.sql` определяет, какие дни пересчитать;
|
||||||
|
- `<витрина>_rebuild.sql` рассчитывает их и записывает в промежуточную таблицу;
|
||||||
|
- `<витрина>_replace.sql` заменяет партицию одного дня в основной таблице.
|
||||||
|
|
||||||
|
Добавьте запуск расчета в [`dags/dm_transform.py`](../../dags/dm_transform.py),
|
||||||
|
чтобы витрина обновлялась вместе с остальными. Для представления из задания 1
|
||||||
|
достаточно выполнить `CREATE VIEW`.
|
||||||
|
|
||||||
|
Образец этого порядка работы есть у готовой витрины `revenue_daily`.
|
||||||
|
Ее таблицы, SQL-файлы и задача Airflow разобраны в
|
||||||
|
[справочнике DM](../architecture/dm.md#dmrevenue_daily_v).
|
||||||
|
Состав колонок и выбор дней для пересчета берите из своего задания.
|
||||||
|
|
||||||
|
## Выполнение SQL в DBeaver
|
||||||
|
|
||||||
В DBeaver создайте подключение по [инструкции в README](../../README.md#подключиться-из-dbeaver),
|
В DBeaver создайте подключение по [инструкции в README](../../README.md#подключиться-из-dbeaver),
|
||||||
но укажите пользователя `etl` и пароль из `CLICKHOUSE_ETL_PASSWORD` в `.env`.
|
но укажите пользователя `etl` и пароль из `CLICKHOUSE_ETL_PASSWORD` в `.env`.
|
||||||
@@ -88,51 +125,62 @@ DM](../architecture/dm.md).
|
|||||||
Создайте файл `sql/ddl/70-labs.sql` и сохраняйте в нем DDL своих витрин.
|
Создайте файл `sql/ddl/70-labs.sql` и сохраняйте в нем DDL своих витрин.
|
||||||
Откройте его в SQL-редакторе этого подключения и выполните DDL текущего
|
Откройте его в SQL-редакторе этого подключения и выполните DDL текущего
|
||||||
задания. Создавайте объекты с
|
задания. Создавайте объекты с
|
||||||
`ON CLUSTER clickstream_cluster`, чтобы они появились на обеих нодах;
|
`ON CLUSTER clickstream_cluster`, чтобы они появились на обоих узлах;
|
||||||
образец для представления — [`sql/ddl/60-dm-views.sql`](../../sql/ddl/60-dm-views.sql).
|
образец для представления — [`sql/ddl/60-dm-views.sql`](../../sql/ddl/60-dm-views.sql).
|
||||||
Сохраненный файл позволит воссоздать ваши объекты при пересборке мира.
|
Сохраненный файл позволит воссоздать ваши объекты при пересборке мира.
|
||||||
|
|
||||||
## Самопроверка ступени 1
|
## Проверка витрин
|
||||||
|
|
||||||
Даг `homework_check` запускается руками, расписания у него нет. Задача
|
В Airflow есть даг `homework_check` с отдельной задачей для каждой учебной
|
||||||
витрины красная, пока витрины нет или счет не сходится, и зеленая, когда
|
витрины. Запускайте его вручную: расписания у него нет. Задача завершится
|
||||||
собрано верно. В сводку качества конвейера `dm.dq_summary` он не пишет: это
|
ошибкой, если витрина еще не создана или ее данные расходятся с ожидаемыми.
|
||||||
инструмент лабы, а не история качества стенда.
|
Причину и примеры расхождений ищите в журнале задачи. Проверки еще не
|
||||||
|
выполненных заданий тоже будут красными.
|
||||||
|
|
||||||
Для каждой витрины проверка независимо рассчитывает ожидаемый результат
|
Проверка заново рассчитывает показатели по источнику и сравнивает их с вашей
|
||||||
прямо по ее источнику. У четырех заданий источник
|
витриной. Строки сопоставляются по полям, которые их определяют: например,
|
||||||
эталона тот же, что у витрины, `dds.session_v` или `dds.event_v`; у задания
|
по дню и странице. `FULL OUTER JOIN` позволяет найти и разные значения, и
|
||||||
4 это таблицы брака ODS, потому что в DDS нет сущности брака. Обе стороны
|
строки, которых нет с одной стороны. `INNER JOIN` пропустил бы отсутствующие
|
||||||
соединяются по деловому ключу через `FULL OUTER JOIN`, и считается число
|
строки. Когда расхождений нет, задача становится зеленой.
|
||||||
строк, где стороны разошлись или одной стороны нет. Ноль — зеленый. Полное
|
|
||||||
соединение сохраняет ключи обеих сторон: при внутреннем пропавший день или
|
|
||||||
ключ не попал бы в результат, и потеря осталась бы незамеченной.
|
|
||||||
|
|
||||||
Раздел «Как проверить себя» каждого задания по витрине называет деловой ключ и
|
`homework_check` не сохраняет результаты в сводку качества `dm.dq_summary`.
|
||||||
сравниваемые величины. Тот же прием в стендовом исполнении — [даг
|
|
||||||
`dq_check`](../../dags/dq_check.py) и запросы в [`sql/dq/`](../../sql/dq).
|
|
||||||
|
|
||||||
Запросы проверок лежат в `sql/dq/`. Откройте их после собственного решения:
|
Запросы проверок лежат в [`sql/dq/`](../../sql/dq/). Откройте их после собственного решения:
|
||||||
они заново рассчитывают показатели витрины, поэтому содержат ответ на
|
они заново рассчитывают показатели витрины, поэтому содержат ответ на
|
||||||
существенную часть задания. Сравните свой способ расчета с готовым.
|
существенную часть задания. Сравните свой способ расчета с готовым.
|
||||||
|
|
||||||
## Факультативы
|
## Задания по желанию
|
||||||
|
|
||||||
Эти задания вы берете по желанию. Полный список сюжетов — в разделе
|
Полный список сюжетов — в разделе
|
||||||
[«Факультативы — пять разделов»](../specs/2026-09-01-labs-program.md#4-факультативы--пять-разделов)
|
[«Факультативы — пять разделов»](../specs/2026-09-01-labs-program.md#4-факультативы--пять-разделов)
|
||||||
программы.
|
программы.
|
||||||
|
|
||||||
### Почему выручка не сходится
|
### Почему выручка не сходится
|
||||||
|
|
||||||
Открывается после ступени 1. Расхождения между источниками, повторы
|
Открывается после ступени 1. Четыре расследования качества данных:
|
||||||
покупок и день, после которого отчет можно считать окончательным.
|
|
||||||
|
|
||||||
### Тонкий анализ данных
|
- [Куда пропали покупки](optional/missing-purchases.md): найти заказы без
|
||||||
|
события, выбрать источник витрины и условие проверки полноты.
|
||||||
|
- [Почему повтор покупки завышает выручку](optional/duplicate-purchases.md):
|
||||||
|
рассчитать выручку с одной строкой на заказ и проверить результат на
|
||||||
|
повторных событиях.
|
||||||
|
- [Какая позиция объясняет разницу в сумме](optional/order-amount-difference.md):
|
||||||
|
разобрать товары заказа без потери точности и найти причину разницы в суммах.
|
||||||
|
- [Почему вчерашняя выручка меняется сегодня](optional/revenue-over-time.md):
|
||||||
|
определить, за какие дни пересчитывать выручку и какую историю заказов сохранять.
|
||||||
|
|
||||||
Открывается после ступени 1. Знание о пользователе на дату и обогащение
|
Первые три работы используют готовые данные. Четвертая продвигает мир через
|
||||||
отчета своим словарем.
|
Airflow; для нее вы сохраняете состояния заказов после каждого модельного дня.
|
||||||
|
|
||||||
### Superset и BI
|
### Данные о посетителях
|
||||||
|
|
||||||
|
Открывается после ступени 1.
|
||||||
|
|
||||||
|
- [Как новые заказы меняют отчет о посетителях](optional/identity-over-time.md):
|
||||||
|
сохранить историю связей, сравнить расчеты на прежних сессиях и определить,
|
||||||
|
какие сведения нужны для воспроизведения результата.
|
||||||
|
|
||||||
|
### Отчеты в Superset
|
||||||
|
|
||||||
Открывается после ступени 2. Два самостоятельных задания:
|
Открывается после ступени 2. Два самостоятельных задания:
|
||||||
|
|
||||||
@@ -141,17 +189,15 @@ DM](../architecture/dm.md).
|
|||||||
- [В DBeaver работает, в Superset нет](optional/superset-missing-object.md):
|
- [В DBeaver работает, в Superset нет](optional/superset-missing-object.md):
|
||||||
выяснить, почему клиенты по-разному видят один объект хранилища.
|
выяснить, почему клиенты по-разному видят один объект хранилища.
|
||||||
|
|
||||||
### Инженерия стенда
|
### Загрузка и обновление данных
|
||||||
|
|
||||||
Открывается после ступени 1. Прием и хранение данных, раскладка по шардам,
|
Открывается после ступени 1.
|
||||||
повторная обработка дня и обновление витрин.
|
|
||||||
|
|
||||||
### Что делать, когда стенд сломался
|
- [Как обновлять витрину без полной пересборки](optional/incremental-daily-traffic.md):
|
||||||
|
продолжить работу о связях пользователей, пересчитать затронутые дни
|
||||||
|
и проверить повтор незавершенной загрузки. Повышенная сложность.
|
||||||
|
|
||||||
Открывается после ступени 7. Поломки стенда и инструкции по восстановлению
|
## Удаление временных объектов
|
||||||
по итогам расследований; весь раздел — повышенная сложность.
|
|
||||||
|
|
||||||
## Уборка
|
|
||||||
|
|
||||||
После опытов удаляйте свои временные объекты. Если удалили учебную витрину,
|
После опытов удаляйте свои временные объекты. Если удалили учебную витрину,
|
||||||
ее задача в `homework_check` должна снова завершиться ошибкой: отсутствие
|
ее задача в `homework_check` должна снова завершиться ошибкой: отсутствие
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# Почему повтор покупки завышает выручку
|
||||||
|
|
||||||
|
Несколько событий покупки могут относиться к одному заказу. Вы проверите,
|
||||||
|
как они завышают сумму, и составите расчет, который учитывает каждый заказ
|
||||||
|
один раз.
|
||||||
|
|
||||||
|
Факультатив доступен после ступени 1. Работайте в DBeaver пользователем
|
||||||
|
`analyst` по [инструкции](../../../README.md#подключиться-из-dbeaver).
|
||||||
|
Дождитесь завершения `etl_pipeline`; оставьте `world_live` на паузе и
|
||||||
|
дождитесь уже начатых прогонов, чтобы данные не менялись между запросами.
|
||||||
|
|
||||||
|
## Расчет по событиям покупки
|
||||||
|
|
||||||
|
В ODS уже устранены повторные доставки событий. Достаточно ли этого,
|
||||||
|
чтобы сложить суммы всех `purchase` для витрины заказов? Проверьте на данных.
|
||||||
|
|
||||||
|
Возьмите день, за который уже получены все слепки заказов: его дата должна быть не позднее
|
||||||
|
`max(snapshot_date) - 6` из `ods.order_v`. Во всех запросах к `dds.event_v`
|
||||||
|
отбирайте этот `event_date` и `event_type = 'purchase'`.
|
||||||
|
|
||||||
|
Номер заказа находится в `purchase_id[1]`, заявленная сумма — в
|
||||||
|
`purchase_revenue[1]`. По формату источника у события покупки один номер заказа.
|
||||||
|
|
||||||
|
Сначала посчитайте число событий и сумму всех заявленных сумм. Затем найдите
|
||||||
|
номера заказов, которые встречаются несколько раз. Если таких нет, выберите
|
||||||
|
другой день с полученными слепками.
|
||||||
|
|
||||||
|
Для двух повторившихся покупок выпишите `event_id`, `client_id`, `event_time`,
|
||||||
|
номер заказа и сумму. Сопоставьте число событий и заказов в `dds.order_v`.
|
||||||
|
|
||||||
|
## Расчет по заказам
|
||||||
|
|
||||||
|
Напишите запрос с одной строкой на номер заказа. Сохраните в результате
|
||||||
|
номер, число исходных событий и одну заявленную сумму. До выбора суммы
|
||||||
|
проверьте, что во всех событиях заказа она одинакова. Затем посчитайте итог
|
||||||
|
дня по результату этого запроса и сравните с расчетом по событиям.
|
||||||
|
|
||||||
|
Проверьте, что номера в результате уникальны, а число строк совпадает с числом
|
||||||
|
разных номеров в исходных событиях. Отдельно проверьте свое решение на случае
|
||||||
|
двух разных заказов с одинаковой суммой: сколько денег оно учтет?
|
||||||
|
|
||||||
|
## Заказы с повторными событиями
|
||||||
|
|
||||||
|
Возьмите один повторившийся номер заказа. Сколько денег он добавил в первый
|
||||||
|
расчет и сколько — во второй? Подтвердите разность его событиями.
|
||||||
|
|
||||||
|
Повторите расчет для остальных повторившихся номеров. Сохраните таблицу:
|
||||||
|
номер заказа, число событий и вклад в разность сумм. Проверьте, объясняют ли
|
||||||
|
эти заказы все расхождение за день до копейки.
|
||||||
|
|
||||||
|
Слой ODS убирает повторную доставку одного события по его идентификатору;
|
||||||
|
`dds.event_v` читает уже это состояние. Сравните число разных `event_id`
|
||||||
|
и число разных номеров заказа в выбранных строках. Почему устранение
|
||||||
|
повторов по первому ключу не дало тот же результат, что по второму?
|
||||||
|
Подтвердите ответ конкретными идентификаторами.
|
||||||
|
|
||||||
|
После собственного расчета найдите эти заказы в `dm.purchase_vs_orders_v`.
|
||||||
|
Если у повторившегося заказа класс отличается от `duplicate_event`, проверьте
|
||||||
|
его статус и [порядок присвоения классов](../../architecture/dm.md#dmpurchase_vs_orders_v).
|
||||||
|
Класс витрины не заменяет поиск всех повторов в событиях.
|
||||||
|
|
||||||
|
## Сравнение с выручкой магазина
|
||||||
|
|
||||||
|
Сложите `revenue` из `dm.revenue_daily_v` за тот же `report_date` по всем
|
||||||
|
категориям. Эта витрина считает стоимость позиций оплаченных заказов,
|
||||||
|
без скидки и доставки.
|
||||||
|
|
||||||
|
Сравните результат с суммой по событиям после устранения повторов. Совпадение не требуется:
|
||||||
|
удаление повторов устраняет одну причину расхождения. Найдите хотя бы один
|
||||||
|
заказ, который объясняет, почему деньги трекера и магазина все еще могут
|
||||||
|
различаться. Для этого сопоставьте события, заказ и готовую сверку.
|
||||||
|
|
||||||
|
Рядом с запросом укажите, чему соответствует одна строка результата
|
||||||
|
и какие поля ее определяют. Обоснуйте, где нужна одна строка на событие,
|
||||||
|
а где одна на заказ: следует ли ради этого расчета
|
||||||
|
удалять повторные покупки из `dds.event_v`? Используйте найденные `event_id`.
|
||||||
|
|
||||||
|
## Результат работы
|
||||||
|
|
||||||
|
- [ ] сохранен запрос с одной строкой на заказ, числом событий и суммой;
|
||||||
|
- [ ] уникальность ключа и полнота набора номеров проверены по источнику;
|
||||||
|
- [ ] разность сумм полностью объяснена вкладом лишних событий;
|
||||||
|
- [ ] обоснованы ключ расчета и место устранения повторов;
|
||||||
|
- [ ] найден случай, показывающий границы расчета денег по трекеру.
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
# Как новые заказы меняют отчет о посетителях
|
||||||
|
|
||||||
|
Вы сохраните историю связей посетителей сайта с покупателями магазина.
|
||||||
|
На ее основе проверите, как новые связи меняют старые отчеты и что нужно,
|
||||||
|
чтобы повторить прежний расчет.
|
||||||
|
|
||||||
|
Факультатив доступен после ступени 1. Понадобятся Airflow и DBeaver с
|
||||||
|
подключением `student` по [инструкции](../../../README.md#подключиться-из-dbeaver).
|
||||||
|
Результаты запросов сохраняйте в `default` через одно подключение:
|
||||||
|
ваши таблицы будут доступны только на узле этого подключения.
|
||||||
|
Если предложенное имя таблицы занято, выберите свое.
|
||||||
|
|
||||||
|
В событиях сайта посетитель обозначен `client_id` из cookie браузера,
|
||||||
|
а в заказах покупатель обозначен `user_id`. Сопоставив событие покупки
|
||||||
|
с заказом, можно связать эти идентификаторы. Тогда и к прежним визитам
|
||||||
|
этого `client_id` можно добавить
|
||||||
|
пользователя магазина. Вы проверите, как появление такой связи меняет число
|
||||||
|
известных пользователей в отчете за прошедший день.
|
||||||
|
|
||||||
|
## Связи посетителей с покупателями
|
||||||
|
|
||||||
|
Поставьте `world_live` на паузу, дождитесь завершения начатых прогонов
|
||||||
|
генерации и обработки. Запустите `etl_pipeline` и дождитесь успеха.
|
||||||
|
На время работы мир будете продвигать вручную; новые дни останутся в стенде.
|
||||||
|
|
||||||
|
Напишите запрос по двум источникам:
|
||||||
|
|
||||||
|
| Источник | Нужные поля |
|
||||||
|
|---|---|
|
||||||
|
| `dds.event_v` | `event_type`, `event_time`, `client_id`, `purchase_id` |
|
||||||
|
| `dds.order_v` | `order_id`, `user_id` |
|
||||||
|
|
||||||
|
В результате нужна одна строка на пару `client_id`, `user_id`. Для каждой пары
|
||||||
|
найдите самое раннее время события покупки, которое удалось сопоставить
|
||||||
|
с заказом. Назовите эту колонку `first_purchase_at`.
|
||||||
|
В событии `purchase` массив `purchase_id` содержит один номер заказа.
|
||||||
|
Статус заказа не ограничивайте: отмена покупки не меняет найденную связь
|
||||||
|
посетителя с покупателем.
|
||||||
|
|
||||||
|
Источники распределены по разным ключам. Для соединения используйте
|
||||||
|
`GLOBAL INNER JOIN`, заключив выборки событий и заказов в подзапросы.
|
||||||
|
Устройство источников описано в [карте идентичностей](../../architecture/dds/identity.md).
|
||||||
|
|
||||||
|
Проверьте уникальность пар и совпадение их полного набора с
|
||||||
|
`dds.identity_map_v`: одного равенства количества недостаточно.
|
||||||
|
Для одной пары покажите номера заказов и события, по которым получили
|
||||||
|
`first_purchase_at`.
|
||||||
|
|
||||||
|
## История связей
|
||||||
|
|
||||||
|
Создайте таблицу:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE default.lab_identity_history
|
||||||
|
(
|
||||||
|
observation_day Date,
|
||||||
|
client_id UInt64,
|
||||||
|
user_id UInt64,
|
||||||
|
first_purchase_at DateTime('Europe/Samara')
|
||||||
|
)
|
||||||
|
ENGINE = MergeTree
|
||||||
|
ORDER BY (observation_day, client_id, user_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждая копия списка должна содержать все найденные пары. В колонку
|
||||||
|
`observation_day` запишите последний обработанный день событий:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT max(event_date) AS observation_day FROM dds.event_v;
|
||||||
|
```
|
||||||
|
|
||||||
|
Обозначим полученную дату **H0**. Она показывает, после обработки какого
|
||||||
|
модельного дня сохранен список; календарная дата запуска Airflow здесь
|
||||||
|
не используется. Сохраните полный результат своего запроса через
|
||||||
|
`INSERT INTO ... SELECT`, добавив H0 в `observation_day` каждой строки.
|
||||||
|
Сверьте число записанных строк с результатом исходного запроса и проверьте
|
||||||
|
уникальность пар для этой даты.
|
||||||
|
|
||||||
|
Затем дважды выполните последовательность:
|
||||||
|
|
||||||
|
1. Запустите `world_next_day` с `days = 1` и дождитесь успеха.
|
||||||
|
2. Запустите `etl_pipeline` и дождитесь успеха.
|
||||||
|
3. Получите новую `observation_day`, сохраните полный набор связей и проверьте его.
|
||||||
|
|
||||||
|
Получатся три копии списка: после дней **H0**, **H1** и **H2**.
|
||||||
|
Для каждой даты сохраняйте список один раз: обычный `MergeTree`
|
||||||
|
не устраняет повторы вставки. Сохраните и проверьте список перед тем,
|
||||||
|
как запускать следующий модельный день.
|
||||||
|
|
||||||
|
## Дата покупки и дата появления связи
|
||||||
|
|
||||||
|
Сравните списки за соседние даты: H0 с H1, затем H1 с H2. Найдите пары,
|
||||||
|
которых не было в прежнем списке. Для нескольких таких пар покажите
|
||||||
|
`client_id`, `user_id`, `first_purchase_at` и первую `observation_day`,
|
||||||
|
в которой пара появилась. Найдите пример, где покупка произошла раньше
|
||||||
|
этой даты.
|
||||||
|
|
||||||
|
Для одной такой связи вернитесь к заказу и событию. По данным и
|
||||||
|
[порядку доставки заказов](../../architecture/orders/ingestion.md) объясните,
|
||||||
|
почему пары не было в прежнем списке. Какие данные понадобились для ее
|
||||||
|
появления и когда они стали доступны?
|
||||||
|
|
||||||
|
Для пары, которая присутствовала уже в H0, нельзя определить дату появления:
|
||||||
|
у вас нет более ранних копий списка. Для новой пары можно установить
|
||||||
|
интервал между двумя сохранениями, но не точное время внутри него.
|
||||||
|
|
||||||
|
## Пересчет прежних отчетов
|
||||||
|
|
||||||
|
Возьмите один и тот же набор сессий из `dds.session_v` за даты до H0
|
||||||
|
включительно. Постройте два дневных расчета:
|
||||||
|
|
||||||
|
- со списком связей на H0;
|
||||||
|
- со списком связей на H2.
|
||||||
|
|
||||||
|
Для каждого дня посчитайте посетителей по разным `client_id` и известных
|
||||||
|
пользователей по разным `user_id`. Сессии без связи должны остаться в расчете,
|
||||||
|
но неизвестный пользователь не должен увеличивать число известных.
|
||||||
|
В соединении сессий с вашей таблицей используйте `GLOBAL LEFT JOIN`.
|
||||||
|
В таблице связей заранее отберите одну `observation_day`, чтобы копии
|
||||||
|
списка за разные дни не смешались.
|
||||||
|
|
||||||
|
Найдите день с разным числом известных пользователей. Проследите изменение
|
||||||
|
до конкретных связей и прежних сессий. Проверьте, что число посетителей
|
||||||
|
не изменилось, а число известных пользователей не больше числа посетителей.
|
||||||
|
|
||||||
|
Теперь проверьте, можно ли воспроизвести расчет на H0 без сохраненного
|
||||||
|
списка за эту дату. Возьмите список на H2 и оставьте только пары,
|
||||||
|
у которых `first_purchase_at` приходится на H0 или более ранний день.
|
||||||
|
Сравните полный набор пар с копией на H0. Выведите пары, которые есть
|
||||||
|
только в одном из списков.
|
||||||
|
На примере найденных различий объясните, какие данные нужно сохранять,
|
||||||
|
чтобы повторить прежний расчет.
|
||||||
|
|
||||||
|
## Результат работы
|
||||||
|
|
||||||
|
У вас есть запрос сопоставления посетителей с покупателями, три сохраненных
|
||||||
|
списка связей и сравнение двух расчетов на одном наборе сессий. На конкретных
|
||||||
|
парах вы можете показать разницу между датой покупки и датой появления пары
|
||||||
|
в сохраненном списке, а также объяснить, что нужно для повторения прежнего
|
||||||
|
расчета.
|
||||||
|
|
||||||
|
Сохраните SQL и таблицу `lab_identity_history`: они нужны в продолжении
|
||||||
|
[«Как обновлять витрину без полной пересборки»](incremental-daily-traffic.md).
|
||||||
|
Если продолжать не планируете, удалите свою таблицу через `DROP TABLE`.
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
# Как обновлять витрину без полной пересборки
|
||||||
|
|
||||||
|
Вы настроите обновление витрины только за дни, затронутые новыми данными.
|
||||||
|
Затем проверите, можно ли продолжить загрузку, которая успела записать
|
||||||
|
лишь часть результатов.
|
||||||
|
|
||||||
|
Факультатив повышенной сложности продолжает работу
|
||||||
|
[«Как новые заказы меняют отчет о посетителях»](identity-over-time.md).
|
||||||
|
Понадобятся ее запрос сохранения списка связей и таблица `default.lab_identity_history`.
|
||||||
|
Работайте под `student` через то же подключение DBeaver.
|
||||||
|
|
||||||
|
Стенд пересчитывает `dm.daily_traffic_v` целиком: новая связь может изменить
|
||||||
|
число известных пользователей в старом дне. Вы построите рядом свою витрину,
|
||||||
|
которая пересчитывает только затронутые дни и дает тот же результат.
|
||||||
|
|
||||||
|
При обычном продвижении модельного времени появляются новые дни сессий
|
||||||
|
и новые связи. Уже найденные пары сохраняются, а сессии прежних дней
|
||||||
|
не меняются.
|
||||||
|
Удаление данных, исправления старых сессий и пересоздание мира потребовали бы
|
||||||
|
другого способа обнаружения изменений.
|
||||||
|
|
||||||
|
## Витрина и журнал загрузок
|
||||||
|
|
||||||
|
Создайте две локальные таблицы. Если имена заняты, выберите свои.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE default.lab_daily_traffic
|
||||||
|
(
|
||||||
|
report_date Date,
|
||||||
|
visitors UInt64,
|
||||||
|
known_users UInt64,
|
||||||
|
observation_day Date
|
||||||
|
)
|
||||||
|
ENGINE = ReplacingMergeTree(observation_day)
|
||||||
|
ORDER BY report_date;
|
||||||
|
|
||||||
|
CREATE TABLE default.lab_traffic_runs
|
||||||
|
(
|
||||||
|
observation_day Date
|
||||||
|
)
|
||||||
|
ENGINE = MergeTree
|
||||||
|
ORDER BY observation_day;
|
||||||
|
```
|
||||||
|
|
||||||
|
В витрине одна актуальная строка на день. Для обновления вставляйте полный
|
||||||
|
новый результат дня с новой `observation_day`. Читайте витрину с `FINAL`:
|
||||||
|
для каждого `report_date` останется строка с максимальной `observation_day`.
|
||||||
|
Старые версии могут исчезнуть при слиянии,
|
||||||
|
поэтому для воспроизведения прежнего расчета нужна сохраненная история связей.
|
||||||
|
|
||||||
|
В журнал записывайте `observation_day` списка связей, использованного
|
||||||
|
для расчета. Добавляйте дату только после записи всех выбранных дней
|
||||||
|
и успешной сверки. Максимальная дата в журнале укажет, какой список
|
||||||
|
использовался в последней завершенной загрузке.
|
||||||
|
|
||||||
|
## Первая загрузка
|
||||||
|
|
||||||
|
Убедитесь, что `world_live` стоит на паузе и нет активных прогонов генерации
|
||||||
|
и обработки. Запустите `etl_pipeline` и дождитесь успеха. Получите текущую
|
||||||
|
`observation_day` способом из первой работы. Если списка за эту дату еще нет
|
||||||
|
в вашей истории, сохраните его. Повторно вставлять уже сохраненный список
|
||||||
|
не требуется.
|
||||||
|
|
||||||
|
Напишите `INSERT INTO ... SELECT`, который рассчитывает все даты сессий из
|
||||||
|
`dds.session_v` со списком связей на текущую дату. Правила результата:
|
||||||
|
|
||||||
|
| Колонка | Что считать |
|
||||||
|
|---|---|
|
||||||
|
| `report_date` | `session_date` |
|
||||||
|
| `visitors` | число разных `client_id` |
|
||||||
|
| `known_users` | число разных известных `user_id` |
|
||||||
|
| `observation_day` | дата выбранной копии списка связей |
|
||||||
|
|
||||||
|
Сессии без известного пользователя сохраняйте в расчете. Несколько кук одного
|
||||||
|
пользователя не должны увеличивать `known_users`. Для соединения с вашей
|
||||||
|
таблицей связей используйте `GLOBAL LEFT JOIN`, отобрав одну `observation_day`.
|
||||||
|
|
||||||
|
Построчно сверьте свои `report_date`, `visitors`, `known_users` с
|
||||||
|
`dm.daily_traffic_v`. Сверка должна находить лишние и отсутствующие даты
|
||||||
|
и различия обоих показателей. Учебную витрину читайте с `FINAL`.
|
||||||
|
После совпадения запишите дату наблюдения в журнал один раз.
|
||||||
|
|
||||||
|
## Выбор дней для пересчета
|
||||||
|
|
||||||
|
Продвиньте мир на один день: `world_next_day` с `days = 1`, затем
|
||||||
|
`etl_pipeline`; после каждого запуска дождитесь успеха. Сохраните новый
|
||||||
|
список связей запросом из первой работы.
|
||||||
|
|
||||||
|
Обозначим максимальную `observation_day` в журнале **P**, а дату нового
|
||||||
|
списка связей — **H**. До записи результата составьте запрос, возвращающий
|
||||||
|
даты для пересчета без повторов. Он должен учесть два изменения:
|
||||||
|
|
||||||
|
- в сессиях появились дни, которых еще нет в вашей витрине;
|
||||||
|
- в списке на H появились пары, которых не было в списке на P; у этих `client_id`
|
||||||
|
есть сессии, в том числе за прежние дни.
|
||||||
|
|
||||||
|
Сначала выведите даты и выберите один прежний день: какая новая пара
|
||||||
|
потребовала его пересчета и какие сессии с ней связаны? Рядом посчитайте,
|
||||||
|
сколько дат пересчитала бы полная загрузка. Зафиксируйте оба числа;
|
||||||
|
равенство тоже возможно, если новые связи затронули всю историю.
|
||||||
|
|
||||||
|
Для сравнения выберите из списка на H пары, у которых день покупки
|
||||||
|
по `first_purchase_at` позже P. Совпадет ли результат с новыми парами,
|
||||||
|
найденными сравнением списков? Покажите пару, которую ограничение по дате
|
||||||
|
покупки пропускает.
|
||||||
|
|
||||||
|
## Обновление витрины
|
||||||
|
|
||||||
|
Напишите второй `INSERT INTO ... SELECT`. Для выбранных дат он должен
|
||||||
|
рассчитать все сессии с полным списком связей на H. Прибавление числа новых
|
||||||
|
связей к прежнему `known_users` не заменяет пересчет разных пользователей.
|
||||||
|
|
||||||
|
Вставьте результаты с версией H. Полностью сверьте актуальную учебную витрину
|
||||||
|
со стендовой, включая дни, которые не пересчитывали. Только после совпадения
|
||||||
|
добавьте H в журнал.
|
||||||
|
|
||||||
|
Без продвижения мира снова определите P по журналу и повторите выбор дат.
|
||||||
|
Запрос должен вернуть пустой результат, а витрина — остаться прежней.
|
||||||
|
Повторно записывать H в журнал не требуется.
|
||||||
|
|
||||||
|
## Повтор незавершенной загрузки
|
||||||
|
|
||||||
|
Снова запустите `world_next_day` с `days = 1`, затем `etl_pipeline`,
|
||||||
|
дожидаясь успеха каждого. Сохраните новый список связей с его датой H.
|
||||||
|
Получите даты для пересчета, но запишите результат только за одну из
|
||||||
|
них. Остальные пока не загружайте и дату в журнал не добавляйте.
|
||||||
|
Так вы воспроизведете остановку между записями дней, не ломая стенд.
|
||||||
|
|
||||||
|
Снова определите P по журналу, выполните запрос выбора дат и пересчет
|
||||||
|
этих дней. Проверьте, что результат совпал со стендовой витриной без очистки
|
||||||
|
уже записанных строк. После сверки запишите H в журнал.
|
||||||
|
|
||||||
|
Объясните на этом опыте, почему сохранение нового списка связей еще
|
||||||
|
не означает, что витрина обновлена. Что изменится, если записать H в журнал
|
||||||
|
до обновления витрины?
|
||||||
|
|
||||||
|
## Результат работы
|
||||||
|
|
||||||
|
Сохраните SQL первой загрузки, выбора дат, обновления и сверки.
|
||||||
|
После двух новых модельных дней ваша витрина совпадает со стендовой,
|
||||||
|
повтор без новых данных сохраняет результат, а незавершенную загрузку
|
||||||
|
можно продолжить без полной пересборки.
|
||||||
|
|
||||||
|
Сравните число пересчитанных дней с полной загрузкой и перечислите,
|
||||||
|
какие дополнительные данные пришлось хранить. Так можно оценить объем работы.
|
||||||
|
На небольшом стенде полный пересчет может быть дешевле.
|
||||||
|
В вашей реализации поиск связей пока читает всю историю покупок.
|
||||||
|
По новым данным обновляется только дневная витрина; такое обновление
|
||||||
|
называют инкрементальным.
|
||||||
|
|
||||||
|
После работы удалите свои `lab_daily_traffic`, `lab_traffic_runs` и
|
||||||
|
`lab_identity_history` через `DROP TABLE`, если они больше не нужны.
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# Куда пропали покупки
|
||||||
|
|
||||||
|
Вы сравните покупки из событий сайта с заказами магазина. Найденные
|
||||||
|
расхождения помогут выбрать источник для полной витрины заказов.
|
||||||
|
|
||||||
|
Факультатив доступен после ступени 1. В DBeaver подключитесь пользователем
|
||||||
|
`analyst` по [инструкции](../../../README.md#подключиться-из-dbeaver).
|
||||||
|
Дождитесь завершения `etl_pipeline`; на время расследования оставьте
|
||||||
|
`world_live` на паузе и дождитесь уже начатых прогонов.
|
||||||
|
|
||||||
|
## Выбор дня
|
||||||
|
|
||||||
|
Можно ли построить полную витрину заказов по событиям `purchase`?
|
||||||
|
Проверьте это на одном дне: сначала сопоставьте события сайта, затем сверьте
|
||||||
|
номера заказов с выгрузкой магазина.
|
||||||
|
|
||||||
|
Слепок заказов содержит их состояние на конец модельного дня. Состояние
|
||||||
|
заказа может меняться семь дней, поэтому для окончательного сравнения нужны
|
||||||
|
все слепки за этот период. Возьмите день, для которого уже получен последний
|
||||||
|
из них. Такой день называется закрытым. Запрос показывает самую позднюю
|
||||||
|
подходящую дату:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT max(snapshot_date) - 6 AS last_closed_day
|
||||||
|
FROM ods.order_v;
|
||||||
|
```
|
||||||
|
|
||||||
|
Используйте выбранный день во всех запросах. События отбирайте по
|
||||||
|
`dds.event_v.event_date`, заказы — по `dds.order_v.order_date`.
|
||||||
|
Сравнивайте все заказы дня, включая отмененные: сейчас вопрос в том,
|
||||||
|
зарегистрировал ли магазин заказ, а не в том, был ли он оплачен.
|
||||||
|
|
||||||
|
## События оформления заказа
|
||||||
|
|
||||||
|
При оформлении заказа посетитель видит страницу подтверждения, а трекер
|
||||||
|
отправляет событие покупки. Просмотр страницы и покупка — отдельные события.
|
||||||
|
|
||||||
|
Посчитайте просмотры страницы подтверждения и события покупки:
|
||||||
|
|
||||||
|
- у просмотра `event_type = 'pageview'`, а `url` заканчивается на `/confirmation`;
|
||||||
|
- у покупки `event_type = 'purchase'`.
|
||||||
|
|
||||||
|
Вычтите число событий покупки из числа просмотров подтверждения. Сохраните
|
||||||
|
оба счетчика и разность. Как соотносились бы эти числа, если бы для каждого
|
||||||
|
заказа пришло ровно по одному событию каждого вида? Что можно предположить
|
||||||
|
по вашему результату и чего эти два числа еще не доказывают?
|
||||||
|
|
||||||
|
## Сверка с заказами магазина
|
||||||
|
|
||||||
|
В событии номер заказа находится в `purchase_id[1]`, в заказах — в `order_id`.
|
||||||
|
Соберите список заказов выбранного дня, для которых нет события покупки.
|
||||||
|
Несколько событий с одним номером заказа должны оставаться одной покупкой.
|
||||||
|
Сохраните запрос сверки: на каждый такой заказ он должен возвращать дату,
|
||||||
|
номер и статус. Дату задавайте в одном месте, чтобы запрос можно было
|
||||||
|
повторить для другого дня.
|
||||||
|
|
||||||
|
Проверьте два равенства:
|
||||||
|
|
||||||
|
- число заказов магазина равно числу разных заказов из событий плюс число
|
||||||
|
заказов без события;
|
||||||
|
- разность просмотров подтверждения и событий покупки равна числу заказов
|
||||||
|
без события.
|
||||||
|
|
||||||
|
Первое равенство проверяет полноту сопоставления источников. Второе связывает
|
||||||
|
разность счетчиков с найденными заказами без события. Проверьте, как на него
|
||||||
|
влияют повторные покупки.
|
||||||
|
|
||||||
|
Найдите номер заказа с несколькими событиями `purchase`. Для его `client_id`
|
||||||
|
выведите события рядом с этими покупками по времени: `event_time`,
|
||||||
|
`event_type` и `url`. Найдите просмотры `/confirmation` перед первой
|
||||||
|
и повторной покупкой. Посчитайте, сколько повтор добавил просмотров
|
||||||
|
подтверждения и сколько — покупок. Изменилась ли от этих добавлений разность
|
||||||
|
счетчиков? Подтвердите ответ найденными строками. Второе равенство относится
|
||||||
|
к устройству этого стенда; для другого источника его нужно проверять заново.
|
||||||
|
|
||||||
|
Если в выбранном дне повторов нет, возьмите другой закрытый день.
|
||||||
|
|
||||||
|
## Сравнение с готовой витриной
|
||||||
|
|
||||||
|
Найдите свои заказы без события в `dm.purchase_vs_orders_v` по `order_id`.
|
||||||
|
Сравните их `status` и `mismatch_class`. Витрина присваивает заказу один класс,
|
||||||
|
хотя причин расхождения может быть несколько. По
|
||||||
|
[правилу классификации](../../architecture/dm.md#dmpurchase_vs_orders_v)
|
||||||
|
объясните, почему потеря события не всегда попадает в `lost_event`.
|
||||||
|
|
||||||
|
Возьмите один заказ из своего списка и сохраните два запроса: первый находит
|
||||||
|
его в магазине, второй показывает отсутствие покупки в трекере.
|
||||||
|
|
||||||
|
## Выбор источника для витрины
|
||||||
|
|
||||||
|
По результатам сверки укажите источник для витрины, в которой должен быть
|
||||||
|
каждый зарегистрированный заказ. Обоснуйте выбор найденными номерами.
|
||||||
|
|
||||||
|
Рядом с запросом запишите, когда его результат можно считать окончательным.
|
||||||
|
Что изменится, если запустить его до получения всех слепков выбранного дня?
|
||||||
|
Позволяет ли отсутствие события в `dds.event_v` установить место потери —
|
||||||
|
до приема в хранилище или при обработке? Какие данные нужны, чтобы это выяснить?
|
||||||
|
|
||||||
|
## Результат работы
|
||||||
|
|
||||||
|
- [ ] сохранен запрос сверки по дате, возвращающий заказы без события;
|
||||||
|
- [ ] оба равенства сходятся, влияние повторного просмотра объяснено;
|
||||||
|
- [ ] один случай подтвержден запросами к обоим источникам;
|
||||||
|
- [ ] источник витрины выбран по результатам сверки;
|
||||||
|
- [ ] указаны условие окончательной проверки и границы ее выводов.
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# Какая позиция объясняет разницу в сумме
|
||||||
|
|
||||||
|
Вы сравните товары в событии покупки и заказе магазина, чтобы найти причину
|
||||||
|
разницы в суммах. При разборе вложенных данных понадобится сохранить точность
|
||||||
|
цен до копейки.
|
||||||
|
|
||||||
|
Факультатив доступен после ступени 1. В DBeaver используйте пользователя
|
||||||
|
`analyst` из [инструкции подключения](../../../README.md#подключиться-из-dbeaver).
|
||||||
|
Дождитесь завершения `etl_pipeline`; оставьте `world_live` на паузе и
|
||||||
|
дождитесь уже начатых прогонов.
|
||||||
|
|
||||||
|
## Выбор заказа
|
||||||
|
|
||||||
|
Суммы покупки в трекере и заказа в магазине различаются. Разберите их позиции
|
||||||
|
и проверьте отдельно точность своего запроса и различия между событием и заказом.
|
||||||
|
|
||||||
|
В `dm.purchase_vs_orders_v` выберите один заказ с
|
||||||
|
`mismatch_class = 'amount_delta'`. Возьмите закрытый день: `order_day`
|
||||||
|
не позднее `max(snapshot_date) - 6` из `ods.order_v`. Сохраните `order_id`,
|
||||||
|
`order_day`, `declared_revenue` и `items_total`.
|
||||||
|
|
||||||
|
Найдите этот заказ в `dds.order_v`. Сопоставьте `items_total`, `discount`,
|
||||||
|
`delivery` и `total`. Запишите равенство, связывающее эти четыре значения,
|
||||||
|
и проверьте его на заказе. С какой суммой магазина следует сравнивать
|
||||||
|
`declared_revenue`, если мы ищем расхождение именно в составе корзины?
|
||||||
|
Проверьте свой выбор после разбора позиций.
|
||||||
|
|
||||||
|
## Товарные позиции события и заказа
|
||||||
|
|
||||||
|
Найдите событие покупки в `ods.event_v`: `purchaseID[1]` совпадает с номером
|
||||||
|
заказа, `EventDate` — с выбранным днем, `EventType = 'purchase'`.
|
||||||
|
Откройте поле `ecommerce`. Внутри JSON массив `purchase.products` содержит
|
||||||
|
товары из события покупки с полями `id`, `price` и `quantity`.
|
||||||
|
|
||||||
|
Разверните этот массив в строки и получите артикул, точную цену, количество
|
||||||
|
и стоимость каждой позиции. Для разбора пригодятся `JSONExtractArrayRaw`
|
||||||
|
и `JSONExtract`, для разворачивания массива — `ARRAY JOIN`.
|
||||||
|
Деньги извлекайте как `Decimal` с двумя знаками после запятой.
|
||||||
|
|
||||||
|
С другой стороны используйте массивы `item_sku`, `item_price` и
|
||||||
|
`item_quantity` заказа в `dds.order_v`. Элементы с одним индексом относятся
|
||||||
|
к одной позиции; при разворачивании сохраняйте эту связь.
|
||||||
|
|
||||||
|
Сопоставьте позиции по артикулу так, чтобы остались видны товары,
|
||||||
|
присутствующие только с одной стороны. Для каждой позиции сравните наличие,
|
||||||
|
цену и количество. Что различается и сколько это добавляет к расхождению
|
||||||
|
сумм?
|
||||||
|
Сохраните запрос сверки для выбранных даты и номера заказа; в результате
|
||||||
|
оставьте артикул, значения с обеих сторон и разность стоимостей позиций.
|
||||||
|
|
||||||
|
## Проверка сумм
|
||||||
|
|
||||||
|
Подтвердите три равенства:
|
||||||
|
|
||||||
|
- сумма стоимостей клиентских позиций равна `declared_revenue`;
|
||||||
|
- сумма стоимостей позиций магазина равна `items_total`;
|
||||||
|
- сумма расхождений по позициям равна
|
||||||
|
`declared_revenue - items_total`.
|
||||||
|
|
||||||
|
Затем сравните точные цены из JSON с готовым массивом `productPrice`
|
||||||
|
события. Пересчитайте стоимость клиентской корзины по этому массиву:
|
||||||
|
сойдется ли она с `declared_revenue` до копейки? Найдите цены, на которых
|
||||||
|
результаты различаются, и сверьте наблюдение с
|
||||||
|
[форматом выгрузки](../../formats/clickstream-event.md). Если все цены
|
||||||
|
выбранной корзины целые, возьмите еще один заказ с расхождением и дробной ценой.
|
||||||
|
|
||||||
|
Какие строки подтверждают найденное расхождение? Есть ли в источниках
|
||||||
|
сведения о том, почему оно возникло? Отделите наблюдения от предположений.
|
||||||
|
|
||||||
|
## Поля для точного расчета
|
||||||
|
|
||||||
|
Укажите рядом с запросом, откуда брать цену, каким типом ее читать и по какому
|
||||||
|
ключу сопоставлять позиции. Подтвердите выбор результатами обеих сверок:
|
||||||
|
суммы разобранных позиций с суммой своего источника и позиций двух источников
|
||||||
|
между собой. Какой результат укажет на ошибку вашего разбора, а какой
|
||||||
|
потребует искать причину выше — в данных, которые получил запрос?
|
||||||
|
|
||||||
|
## Результат работы
|
||||||
|
|
||||||
|
- [ ] сохранен запрос сверки позиций, не теряющий товары без пары;
|
||||||
|
- [ ] все три равенства сходятся до копейки;
|
||||||
|
- [ ] найден пример потери точности в готовом массиве цен;
|
||||||
|
- [ ] выбор полей, типа денег и ключа сопоставления подтвержден расчетами;
|
||||||
|
- [ ] расхождение источников сверки отделено от ошибки своего разбора и различия
|
||||||
|
между стоимостью корзины и итогом со скидкой и доставкой.
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
# Почему вчерашняя выручка меняется сегодня
|
||||||
|
|
||||||
|
Вы проследите, как опоздавшие заказы и смена статусов меняют выручку
|
||||||
|
за прошедший день. По этим наблюдениям определите, какие дни пересчитывать
|
||||||
|
и какую историю заказов сохранять.
|
||||||
|
|
||||||
|
Факультатив доступен после ступени 1. Понадобятся Airflow и DBeaver с
|
||||||
|
подключением `student` по [инструкции](../../../README.md#подключиться-из-dbeaver).
|
||||||
|
Пользователь `student` может читать данные стенда и записывать ваши наблюдения
|
||||||
|
в базу `default`.
|
||||||
|
Вы будете наблюдать один день оформления заказов, продвигая мир по одному
|
||||||
|
модельному дню. Проверьте, можно ли при загрузке выручки каждый раз считать
|
||||||
|
только новый день. Новые данные останутся в стенде.
|
||||||
|
|
||||||
|
## Подготовка
|
||||||
|
|
||||||
|
Поставьте `world_live` на паузу. Дождитесь завершения начатых прогонов
|
||||||
|
генерации и обработки, затем запустите `etl_pipeline` и дождитесь успеха.
|
||||||
|
Так исходное наблюдение будет снято после обновления витрин.
|
||||||
|
|
||||||
|
Узнайте последний день событий и дату последнего полученного слепка:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
(SELECT max(event_date) FROM dds.event_v) AS event_day,
|
||||||
|
(SELECT max(snapshot_date) FROM ods.order_v) AS snapshot_day;
|
||||||
|
```
|
||||||
|
|
||||||
|
День `event_day` обозначим **D**. Наблюдайте именно его до конца работы.
|
||||||
|
Дата слепка, по которому сделано очередное наблюдение, будет **S**.
|
||||||
|
Это модельные даты магазина. Календарная дата запуска Airflow день D не задает.
|
||||||
|
|
||||||
|
Слепок содержит состояние заказов на конец модельных суток и доставляется
|
||||||
|
при следующем запуске генератора. Поэтому покупки последнего завершившегося
|
||||||
|
дня уже видны в событиях, а их заказов в
|
||||||
|
хранилище еще нет. Проверьте это для D в `dds.order_v` и
|
||||||
|
`dm.purchase_vs_orders_v`.
|
||||||
|
|
||||||
|
## Таблицы для истории заказов
|
||||||
|
|
||||||
|
Представление `dds.order_v` показывает текущее состояние заказов, а сырые слепки
|
||||||
|
хранятся ограниченное время. Сохраните историю в двух своих таблицах: потом
|
||||||
|
вы сможете сравнивать наблюдения обычными SQL-запросами в том же DBeaver.
|
||||||
|
|
||||||
|
Выполните DDL ниже. Это локальные таблицы на узле вашего подключения;
|
||||||
|
всю работу выполняйте через одно подключение. Если имена уже заняты вашей
|
||||||
|
прошлой работой, выберите другие и используйте их в дальнейших запросах.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE default.lab_order_history
|
||||||
|
(
|
||||||
|
order_date Date,
|
||||||
|
snapshot_date Date,
|
||||||
|
order_id String,
|
||||||
|
status String,
|
||||||
|
items_total Decimal(18, 2)
|
||||||
|
)
|
||||||
|
ENGINE = MergeTree
|
||||||
|
ORDER BY (order_date, snapshot_date, order_id);
|
||||||
|
|
||||||
|
CREATE TABLE default.lab_revenue_history
|
||||||
|
(
|
||||||
|
order_date Date,
|
||||||
|
snapshot_date Date,
|
||||||
|
revenue Decimal(18, 2),
|
||||||
|
awaiting_order_ids Array(String)
|
||||||
|
)
|
||||||
|
ENGINE = MergeTree
|
||||||
|
ORDER BY (order_date, snapshot_date);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Первое наблюдение
|
||||||
|
|
||||||
|
Напишите два `INSERT INTO ... SELECT` с явным списком колонок назначения:
|
||||||
|
|
||||||
|
| Куда записать | Что взять из стенда |
|
||||||
|
|---|---|
|
||||||
|
| `default.lab_order_history` | По строке на заказ дня D из `dds.order_v`: `order_date`, `order_id`, `status`, `items_total`; добавьте дату наблюдения S |
|
||||||
|
| `default.lab_revenue_history` | Одну строку с D, S, суммой `revenue` за `report_date = D` из `dm.revenue_daily_v` и массивом номеров `awaiting_order` за `order_day = D` из `dm.purchase_vs_orders_v` |
|
||||||
|
|
||||||
|
Во втором запросе сумму и массив получите отдельными агрегатами, чтобы
|
||||||
|
заказы не умножили строки выручки при соединении. Для массива пригодится
|
||||||
|
`groupArray(order_id)`. Дату S берите из `max(snapshot_date)` в `ods.order_v`.
|
||||||
|
|
||||||
|
Сначала выполните части `SELECT` отдельно и проверьте результаты, затем
|
||||||
|
сохраните их. До первого слепка заказов дня D еще нет: в таблицу заказов
|
||||||
|
строки не добавятся, но в таблице итогов должна появиться строка с нулевой
|
||||||
|
выручкой. Она зафиксирует, что наблюдение сделано.
|
||||||
|
|
||||||
|
Каждую пару D и S сохраняйте один раз: повторный `INSERT` добавит копии строк.
|
||||||
|
После записи проверьте, что на эту пару приходится одна строка итогов,
|
||||||
|
а число заказов и число разных `order_id` совпадают с `dds.order_v` за D.
|
||||||
|
|
||||||
|
Выручка этой витрины — стоимость позиций оплаченных заказов без скидки и
|
||||||
|
доставки. Для каждого сохраненного наблюдения посчитайте число заказов по
|
||||||
|
статусам и сумму `items_total` только у `paid`. Эта сумма должна сходиться
|
||||||
|
с сохраненной выручкой, сложенной по всем категориям.
|
||||||
|
|
||||||
|
## Наблюдения за следующие дни
|
||||||
|
|
||||||
|
Для каждого следующего наблюдения:
|
||||||
|
|
||||||
|
1. Запустите `world_next_day` с `days = 1` и дождитесь успеха.
|
||||||
|
2. Запустите `etl_pipeline` и дождитесь успеха: `world_next_day` сам витрины
|
||||||
|
не обновляет.
|
||||||
|
3. Повторите два запроса сохранения с новой датой S и проверьте записанные строки.
|
||||||
|
|
||||||
|
Во всех запросах остается прежний день D. За один запуск добавляйте один
|
||||||
|
день, чтобы сохранить промежуточные состояния. Пакетный `world_next_day`
|
||||||
|
отправляет события без пауз; ждать 24 минуты живого дня здесь не нужно.
|
||||||
|
|
||||||
|
Продолжайте до слепка **D + 7** включительно. Заказы дня D могут меняться
|
||||||
|
семь дней, от D до D + 6. Этот период называется окном изменяемости.
|
||||||
|
Последний шаг нужен для сравнения состояния в конце окна и после него.
|
||||||
|
От исходного наблюдения потребуется восемь однодневных запусков.
|
||||||
|
|
||||||
|
## Изменения заказов и выручки
|
||||||
|
|
||||||
|
Для сравнения прочитайте `default.lab_order_history` дважды: в одном
|
||||||
|
подзапросе отберите прежнюю S, в другом — новую; день D остается общим.
|
||||||
|
Соедините состояния по `order_id`, сохранив и заказы без пары. Итоги для этих
|
||||||
|
же D и S возьмите из `default.lab_revenue_history`. Найдите:
|
||||||
|
|
||||||
|
- заказы, отсутствовавшие в первом слепке D, но появившиеся в следующем;
|
||||||
|
- переходы из `created` в `paid`;
|
||||||
|
- отмены заказов, для которых раньше был виден статус `paid`.
|
||||||
|
|
||||||
|
Первый слепок, полученный на следующий день, — обычная доставка заказов.
|
||||||
|
Опоздавшими здесь считаются те, которых не было уже в этом слепке.
|
||||||
|
Покажите на нескольких номерах, как они исчезают из `awaiting_order`.
|
||||||
|
|
||||||
|
Выберите два соседних наблюдения, между которыми хотя бы один заказ перешел
|
||||||
|
из `paid` в `cancelled`. Разложите изменение выручки по заказам.
|
||||||
|
Составьте таблицу: номер заказа, прежний и новый статусы, вклад в выручку
|
||||||
|
до и после изменения. Вклад определите по правилу расчета витрины, указанному
|
||||||
|
выше; учтите и заказы, которых еще не было в первом наблюдении. Сумма
|
||||||
|
изменений вкладов должна совпасть с изменением `revenue` до копейки.
|
||||||
|
|
||||||
|
Покажите на этих наблюдениях, какой результат остался бы в витрине,
|
||||||
|
если бы конвейер после первого расчета обрабатывал только новые дни заказов.
|
||||||
|
Достаточно ли добавлять в старый день только опоздавшие заказы? Проверьте
|
||||||
|
свой ответ на найденных изменениях статусов.
|
||||||
|
|
||||||
|
## Требования к истории заказов
|
||||||
|
|
||||||
|
Возьмите отмененный заказ, у которого в вашей истории был статус `paid`.
|
||||||
|
Укажите два наблюдения, доказывающих, что он был оплачен, а затем отменен.
|
||||||
|
|
||||||
|
Теперь возьмите отмененный заказ, для которого ни в одном вашем наблюдении
|
||||||
|
нет `paid`. Проверьте утверждение «этот заказ никогда не был оплачен»:
|
||||||
|
что известно о его состоянии между двумя слепками? Достаточно ли сохраненных
|
||||||
|
наблюдений для такого вывода?
|
||||||
|
|
||||||
|
Сформулируйте требования к данным для двух задач: восстановить состояние
|
||||||
|
витрины на момент прошлого расчета и узнать, был ли заказ когда-либо оплачен.
|
||||||
|
Какие сведения для каждой задачи должен передавать источник и сохранять
|
||||||
|
хранилище? Проверьте, каких нужных сведений нет в ваших таблицах наблюдений.
|
||||||
|
|
||||||
|
## Правило пересчета витрины
|
||||||
|
|
||||||
|
Сравните сохраненные состояния при S = D + 6 и S = D + 7: состав заказов,
|
||||||
|
их статусы, суммы и выручку. Какой слепок последним мог изменить заказы дня D
|
||||||
|
и каким прогоном он доставлен? Укажите наблюдение, после которого отчет
|
||||||
|
можно считать окончательным. Было бы достаточно совпадения двух соседних
|
||||||
|
сумм, если бы окно еще не закончилось?
|
||||||
|
|
||||||
|
Запишите правило выбора дней для пересчета после очередного слепка.
|
||||||
|
Считайте, что начальная история уже загружена и ежедневные прогоны не
|
||||||
|
пропускались. Укажите, по какой дате определяются границы, какие дни нужно
|
||||||
|
пересчитать и когда день можно перестать включать в расчет. Подтвердите
|
||||||
|
правило своими наблюдениями, включая опоздание и отмену.
|
||||||
|
|
||||||
|
После собственных выводов сверьтесь с описанием
|
||||||
|
[слепка и его доставки](../../architecture/orders/snapshot.md#слепок-и-его-доставка)
|
||||||
|
и [границ истории отмен](../../architecture/orders/fate.md#судьба-заказа).
|
||||||
|
|
||||||
|
## Результат работы
|
||||||
|
|
||||||
|
- [ ] сохранена последовательность наблюдений одного дня D с датами слепков S;
|
||||||
|
- [ ] на опоздавших заказах и изменениях статусов показаны ошибки загрузки
|
||||||
|
только новых дней или только новых заказов;
|
||||||
|
- [ ] изменение выручки между наблюдениями сходится с изменением вкладов заказов;
|
||||||
|
- [ ] для одного отмененного заказа найдены два наблюдения: с оплатой и с отменой;
|
||||||
|
- [ ] сформулированы требования к истории расчетов и истории оплаты;
|
||||||
|
- [ ] правило пересчета и момент закрытия дня обоснованы наблюдениями
|
||||||
|
и правилом доставки слепков.
|
||||||
|
|
||||||
|
После работы оставьте `world_live` на паузе либо верните автоматическое
|
||||||
|
продвижение, если пользовались им до наблюдения. Таблицы истории останутся
|
||||||
|
для повторной проверки выводов. Когда они больше не нужны, удалите только
|
||||||
|
свои `default.lab_order_history` и `default.lab_revenue_history`.
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
# Свой дашборд в Superset
|
# Свой дашборд в Superset
|
||||||
|
|
||||||
**Чему учит:** собрать из своих витрин отчет о поведении посетителей,
|
Из своих витрин вы соберете отчет о поведении посетителей: подберете графики
|
||||||
подобрать графики под вопросы и разместить их на одном понятном экране.
|
и разместите их на одном экране так, чтобы отчет можно было понять
|
||||||
|
без ваших пояснений.
|
||||||
|
|
||||||
Это факультатив раздела «Superset и BI». Его можно пройти после ступени 2,
|
Это задание раздела «Отчеты в Superset». Его можно пройти после ступени 2,
|
||||||
когда готовы витрины ступени 1.
|
когда готовы витрины ступени 1.
|
||||||
|
|
||||||
## На какие вопросы ответит дашборд
|
## Отчет о поведении посетителей
|
||||||
|
|
||||||
Соберите обзор трафика и поведения посетителей. Он должен отвечать на три
|
Соберите обзор трафика и поведения посетителей. Он должен отвечать на три
|
||||||
вопроса: что происходило в магазине, какие страницы, устройства и рекламные
|
вопроса: что происходило в магазине, какие страницы, устройства и рекламные
|
||||||
@@ -17,25 +18,23 @@
|
|||||||
стендового дашборда не нужно: выберите форму, в которой ответы читаются без
|
стендового дашборда не нужно: выберите форму, в которой ответы читаются без
|
||||||
пояснений рядом с экраном.
|
пояснений рядом с экраном.
|
||||||
|
|
||||||
## Свои объекты — отдельно
|
## Отдельный дашборд для задания
|
||||||
|
|
||||||
Не добавляйте графики в стендовый дашборд и не правьте его датасеты. При
|
Не добавляйте графики в стендовый дашборд и не правьте его датасеты. При
|
||||||
инициализации Superset импорт с `--overwrite` заменит ручные правки стендовых
|
инициализации Superset импорт с `--overwrite` заменит ручные правки стендовых
|
||||||
объектов файлами из репозитория. `make up` свой дашборд не стирает; срок жизни
|
объектов файлами из репозитория. `make up` свой дашборд не стирает; срок жизни
|
||||||
таких объектов описан в [доке Superset](../../architecture/superset.md), «Импорт
|
таких объектов описан в [справочнике Superset](../../architecture/superset.md), «Импорт
|
||||||
при каждой инициализации».
|
при каждой инициализации».
|
||||||
|
|
||||||
Используйте свои датасеты и графики, соберите из них свой дашборд.
|
Используйте свои датасеты и графики, соберите из них свой дашборд.
|
||||||
Назовите его, например, «Мои витрины DM». Это то же правило, по которому DDL
|
Назовите его, например, «Мои витрины DM».
|
||||||
домашек лежит в `sql/ddl/70-labs.sql`: стендовые объекты уже есть, свои
|
|
||||||
создаете вы.
|
|
||||||
|
|
||||||
## Датасеты
|
## Датасеты
|
||||||
|
|
||||||
Датасет `session_overview_v`, его метрика доли отказов и график уже готовы
|
Датасет `session_overview_v`, его метрика доли отказов и график уже готовы
|
||||||
после [ступени 2](../2-1-superset-chart.md). Используйте их здесь. Добавьте
|
после [ступени 2](../2-1-superset-chart.md). Используйте их здесь. Добавьте
|
||||||
недостающие датасеты, чтобы в подключении `ClickHouse` были представлены
|
недостающие датасеты, чтобы в подключении `ClickHouse` были представлены
|
||||||
пять публичных представлений схемы `dm`:
|
пять представлений ваших витрин из схемы `dm`:
|
||||||
|
|
||||||
- `events_enriched_v`;
|
- `events_enriched_v`;
|
||||||
- `top_pages_daily_v`;
|
- `top_pages_daily_v`;
|
||||||
@@ -56,7 +55,7 @@
|
|||||||
| `dq_errors_daily_v` | число ошибок по дню загрузки, источнику и классу |
|
| `dq_errors_daily_v` | число ошибок по дню загрузки, источнику и классу |
|
||||||
| `utm_effectiveness_v` | трафик, покупки и заявленную выручку по рекламным каналам |
|
| `utm_effectiveness_v` | трафик, покупки и заявленную выручку по рекламным каналам |
|
||||||
|
|
||||||
Для долей используйте слагаемые из витрин. Например, доля отказов считается
|
Для долей используйте суммы счетчиков из витрин. Например, доля отказов считается
|
||||||
как сумма `bounce_sessions`, деленная на сумму `sessions`. Среднее по готовым
|
как сумма `bounce_sessions`, деленная на сумму `sessions`. Среднее по готовым
|
||||||
долям для такого графика не подходит.
|
долям для такого графика не подходит.
|
||||||
|
|
||||||
@@ -73,7 +72,7 @@
|
|||||||
по модельному времени. Для панели ошибок выберите период загрузки отдельно
|
по модельному времени. Для панели ошибок выберите период загрузки отдельно
|
||||||
и укажите его в подписи. Общий фильтр времени для всего дашборда не требуется.
|
и укажите его в подписи. Общий фильтр времени для всего дашборда не требуется.
|
||||||
|
|
||||||
## Как проверить себя
|
## Проверка результата
|
||||||
|
|
||||||
Автоматической проверки этого задания нет. Числа вы сверите запросами,
|
Автоматической проверки этого задания нет. Числа вы сверите запросами,
|
||||||
а понятность графиков проверите по подписям.
|
а понятность графиков проверите по подписям.
|
||||||
@@ -103,9 +102,9 @@
|
|||||||
Проверьте, можно ли найти эти ответы по одним подписям графиков. Если есть
|
Проверьте, можно ли найти эти ответы по одним подписям графиков. Если есть
|
||||||
возможность, попросите другого человека прочитать экран без ваших пояснений.
|
возможность, попросите другого человека прочитать экран без ваших пояснений.
|
||||||
|
|
||||||
## Что должно получиться
|
## Результат работы
|
||||||
|
|
||||||
- [ ] у вас есть пять датасетов поверх публичных представлений витрин заданий 1–5;
|
- [ ] у вас есть пять датасетов по представлениям витрин заданий 1–5;
|
||||||
- [ ] вы собрали свой дашборд, сохранив стендовые объекты без изменений;
|
- [ ] вы собрали свой дашборд, сохранив стендовые объекты без изменений;
|
||||||
- [ ] на дашборде есть хотя бы один график от каждой витрины;
|
- [ ] на дашборде есть хотя бы один график от каждой витрины;
|
||||||
- [ ] вы проверили, что графики и подписи понятны по вопросам из раздела выше;
|
- [ ] вы проверили, что графики и подписи понятны по вопросам из раздела выше;
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
# В DBeaver работает, в Superset нет
|
# В DBeaver работает, в Superset нет
|
||||||
|
|
||||||
**Чему учит:** найти причину, по которой один клиент читает объект
|
Вы создадите представление, которое читается из DBeaver, но недоступно
|
||||||
хранилища, а другой его не видит.
|
в Superset. Затем найдете причину и исправите ее.
|
||||||
|
|
||||||
Факультатив доступен после [ступени 2](../2-1-superset-chart.md).
|
Факультатив доступен после [ступени 2](../2-1-superset-chart.md).
|
||||||
Свой дашборд для опыта не нужен. Используйте готовую витрину стенда
|
Свой дашборд для опыта не нужен. Используйте готовую витрину стенда
|
||||||
`dm.daily_traffic_v`.
|
`dm.daily_traffic_v`.
|
||||||
|
|
||||||
## Подготовьте опыт
|
## Подготовка
|
||||||
|
|
||||||
В DBeaver подключитесь к первой ноде пользователем `etl`, как описано
|
В DBeaver подключитесь к первому узлу пользователем `etl`, как описано
|
||||||
в разделе [«Как применить свой DDL»](../README.md#как-применить-свой-ddl).
|
в разделе [«Выполнение SQL в DBeaver»](../README.md#выполнение-sql-в-dbeaver).
|
||||||
Для опыта создайте отдельное представление:
|
Для опыта создайте отдельное представление:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
@@ -28,19 +28,19 @@ SELECT count() FROM dm.bi_node_probe_v;
|
|||||||
Повторите запрос в SQL Lab Superset, выбрав подключение `ClickHouse`.
|
Повторите запрос в SQL Lab Superset, выбрав подключение `ClickHouse`.
|
||||||
Сохраните текст ошибки.
|
Сохраните текст ошибки.
|
||||||
|
|
||||||
## Найдите причину
|
## Поиск причины
|
||||||
|
|
||||||
Выясните, к каким серверам обращаются DBeaver и Superset. Адреса подключения
|
Выясните, к каким серверам обращаются DBeaver и Superset. Адреса подключения
|
||||||
показывают настройки клиентов, а имя ответившего сервера можно спросить
|
показывают настройки клиентов, а имя ответившего сервера можно спросить
|
||||||
запросом `SELECT hostName()`.
|
запросом `SELECT hostName()`.
|
||||||
|
|
||||||
Проверьте наличие `dm.bi_node_probe_v` на обеих нодах. Для второй ноды
|
Проверьте наличие `dm.bi_node_probe_v` на обоих узлах. Для второго узла
|
||||||
заведите подключение DBeaver с портом из
|
заведите подключение DBeaver с портом из
|
||||||
[README](../../../README.md#подключиться-из-dbeaver). Запишите, где объект
|
[README](../../../README.md#подключиться-из-dbeaver). Запишите, где объект
|
||||||
есть, и объясните, почему успешного создания в первом подключении оказалось
|
есть, и объясните, почему успешного создания в первом подключении оказалось
|
||||||
недостаточно.
|
недостаточно.
|
||||||
|
|
||||||
Исправьте DDL так, чтобы представление появилось на обеих нодах. Подключение
|
Исправьте DDL так, чтобы представление появилось на обоих узлах. Подключение
|
||||||
Superset должно остаться прежним. Сохраните исправленный DDL и повторите
|
Superset должно остаться прежним. Сохраните исправленный DDL и повторите
|
||||||
исходный запрос в обоих клиентах.
|
исходный запрос в обоих клиентах.
|
||||||
|
|
||||||
@@ -48,23 +48,23 @@ Superset должно остаться прежним. Сохраните исп
|
|||||||
[справочнике Superset](../../architecture/superset.md#устройство-зоны) и
|
[справочнике Superset](../../architecture/superset.md#устройство-зоны) и
|
||||||
[описание распределенного DDL ClickHouse](https://clickhouse.com/docs/reference/statements/distributed-ddl).
|
[описание распределенного DDL ClickHouse](https://clickhouse.com/docs/reference/statements/distributed-ddl).
|
||||||
|
|
||||||
## Как проверить себя
|
## Проверка результата
|
||||||
|
|
||||||
В обоих клиентах запрос к вашему представлению должен вернуть столько же
|
В обоих клиентах запрос к вашему представлению должен вернуть столько же
|
||||||
строк, сколько `SELECT count() FROM dm.daily_traffic_v`. Сравнивайте результаты
|
строк, сколько `SELECT count() FROM dm.daily_traffic_v`. Сравнивайте результаты
|
||||||
при завершенном прогоне конвейера, пока мир не продвигается.
|
при завершенном прогоне конвейера, пока мир не продвигается.
|
||||||
|
|
||||||
Объясните причину ошибки через три факта: куда пришел DDL, куда пришел
|
Объясните причину ошибки через три факта: куда пришел DDL, куда пришел
|
||||||
запрос Superset и на каких нодах существовало представление.
|
запрос Superset и на каких узлах существовало представление.
|
||||||
|
|
||||||
После опыта удалите только `dm.bi_node_probe_v` на обеих нодах. Не включайте
|
После опыта удалите только `dm.bi_node_probe_v` на обоих узлах. Не включайте
|
||||||
его в постоянный DDL домашних витрин: это временный объект расследования.
|
его в постоянный DDL домашних витрин: это временный объект расследования.
|
||||||
|
|
||||||
## Что должно получиться
|
## Результат работы
|
||||||
|
|
||||||
- [ ] у вас сохранены успешный запрос из DBeaver и ошибка из SQL Lab;
|
- [ ] у вас сохранены успешный запрос из DBeaver и ошибка из SQL Lab;
|
||||||
- [ ] вы можете объяснить, к каким серверам подключены клиенты, где было
|
- [ ] вы можете объяснить, к каким серверам подключены клиенты, где было
|
||||||
создано представление и почему Superset его не видел;
|
создано представление и почему Superset его не видел;
|
||||||
- [ ] у вас есть исправленный DDL; оба клиента возвращают столько же строк,
|
- [ ] у вас есть исправленный DDL; оба клиента возвращают столько же строк,
|
||||||
сколько исходная витрина;
|
сколько исходная витрина;
|
||||||
- [ ] после опыта вы удалили учебное представление с обеих нод.
|
- [ ] после опыта вы удалили учебное представление с обоих узлов.
|
||||||
@@ -45,12 +45,16 @@
|
|||||||
Пишите для учащегося: объясняйте, чему он научится, зачем нужны действия
|
Пишите для учащегося: объясняйте, чему он научится, зачем нужны действия
|
||||||
и как понять результат. Историю разработки оставляйте за пределами задания.
|
и как понять результат. Историю разработки оставляйте за пределами задания.
|
||||||
|
|
||||||
«Как проверить себя» описывает действия для проверки и поиска ошибок.
|
Начинайте задание коротким описанием предстоящей работы. Заголовки должны
|
||||||
«Что должно получиться» — результат работы и то, что учащийся теперь может
|
называть содержание разделов: «Подготовка», «Создание витрины», «Проверка
|
||||||
|
результата». Шаблонные вопросы «Чему учит» и «Что сделать» не нужны.
|
||||||
|
|
||||||
|
Раздел проверки описывает действия для проверки и поиска ошибок.
|
||||||
|
Раздел результата перечисляет итог работы и то, что учащийся теперь может
|
||||||
объяснить. Если проверка уже встроена в шаги упражнения, повторять ее
|
объяснить. Если проверка уже встроена в шаги упражнения, повторять ее
|
||||||
отдельным разделом не нужно.
|
отдельным разделом не нужно.
|
||||||
|
|
||||||
Остальные заголовки и структуру выбирайте по содержанию. Образцы — задания
|
Структуру выбирайте по содержанию. Образцы — задания
|
||||||
про [дашборд Superset](../labs/optional/superset-dashboard.md) и
|
про [дашборд Superset](../labs/optional/superset-dashboard.md) и
|
||||||
[расследование подключения](../labs/optional/superset-missing-object.md).
|
[расследование подключения](../labs/optional/superset-missing-object.md).
|
||||||
|
|
||||||
@@ -149,38 +153,85 @@
|
|||||||
|
|
||||||
## 4. Факультативы — пять разделов
|
## 4. Факультативы — пять разделов
|
||||||
|
|
||||||
Ниже перечислены только названия сюжетов и их уроки. Материал, формы и способы
|
Ниже перечислены сюжеты, их уроки и принятые решения по форме заданий. Исходный
|
||||||
проверки остаются в [инвентаризации](../research/2026-08-31-labs-inventory.md).
|
материал и способы проверки — в
|
||||||
|
[инвентаризации](../research/2026-08-31-labs-inventory.md).
|
||||||
|
|
||||||
### Почему выручка не сходится
|
### Почему выручка не сходится
|
||||||
|
|
||||||
Этот раздел рекомендован первым из факультативов. Он открывается после ступени
|
Этот раздел рекомендован первым из факультативов и открывается после ступени
|
||||||
1 основной программы: материал живет на стенде с этапа 6.
|
1 основной программы. Шесть сюжетов собраны в четыре лабораторные: три
|
||||||
|
расследования готовых данных и одно наблюдение во времени. Опоздания,
|
||||||
|
изменение выручки и история отмен используют одну историю заказов, поэтому
|
||||||
|
для них выбрана общая работа вместо трех отдельных.
|
||||||
|
|
||||||
- **Почему трекер недосчитывает покупки (#186).** Разрыв воронки подтверждений
|
Результат расследования — запрос сверки и инженерное решение, подтвержденное
|
||||||
показывает потерянные события и границу доверия к двум источникам.
|
данными: выбор источника, ключа, полей для точного расчета или правила
|
||||||
- **Почему повтор покупки завышает выручку (#187).** Движок убирает технический
|
пересчета и хранения истории. Объяснение суммы служит проверкой решения.
|
||||||
дубль строки, а повтор одной покупки приходится находить по деловому ключу.
|
Вариант закончить работы только объяснением показателей отклонен: он
|
||||||
- **Какая позиция объясняет разницу в сумме.** Расхождение суммы нужно
|
смещал акцент в анализ отчетов. Постройка новых DAG и подключение проверок
|
||||||
разложить до конкретной вычеркнутой позиции и объяснить до копейки.
|
к конвейеру оставлены основной программе, чтобы факультативы не повторяли
|
||||||
- **Был ли отмененный заказ оплачен.** Текущее состояние не заменяет историю, а
|
ее задания.
|
||||||
некоторые различия после истечения срока хранения сырых данных уже не
|
|
||||||
восстановить.
|
- **[Куда пропали покупки](../labs/optional/missing-purchases.md) (#186).**
|
||||||
- **Когда отчет за день можно считать окончательным (#188).** Отчет за день
|
Сверка по номерам заказов обосновывает источник витрины и условие
|
||||||
становится окончательным лишь после того, как источник больше не может
|
окончательной проверки полноты. Счетчики событий помогают обнаружить
|
||||||
изменить этот день.
|
расхождение, но не устанавливают место потери.
|
||||||
- **Почему выручка за день еще меняется.** Внутри окна изменяемости выручка
|
- **[Почему повтор покупки завышает выручку](../labs/optional/duplicate-purchases.md) (#187).**
|
||||||
движется, а после закрытия дня фиксируется.
|
Учащийся собирает расчет с одной строкой на заказ, проверяет ключ и полноту
|
||||||
|
набора номеров, отличает его зерно от зерна исходных событий.
|
||||||
|
- **[Какая позиция объясняет разницу в сумме](../labs/optional/order-amount-difference.md).**
|
||||||
|
Сверка позиций обосновывает выбор исходных полей и типа денег, отделяет
|
||||||
|
ошибку собственного разбора от различия данных события и заказа.
|
||||||
|
- **[Почему вчерашняя выручка меняется сегодня](../labs/optional/revenue-over-time.md) (#188).**
|
||||||
|
Наблюдение опозданий и смены статусов обосновывает правило пересчета при
|
||||||
|
ежедневной загрузке без пропусков. Учащийся определяет момент закрытия дня
|
||||||
|
и требования к истории для воспроизведения расчета и проверки факта оплаты.
|
||||||
|
|
||||||
|
В четвертой лабораторной выбрано собственное наблюдение: учащийся продвигает
|
||||||
|
мир по одному дню через Airflow и сохраняет результаты после обновления витрин
|
||||||
|
в две локальные таблицы ClickHouse под `student`: состояния заказов и итоги
|
||||||
|
наблюдений. Сравнение выполняется SQL-запросами в том же подключении DBeaver.
|
||||||
|
Учащийся может писать только в `default`; источники доступны ему на чтение.
|
||||||
|
Отдельная строка итогов фиксирует и наблюдение без заказов. Экспорт в CSV
|
||||||
|
отклонен как основной способ: он требовал неописанного перехода к другому
|
||||||
|
инструменту или обратному импорту файлов.
|
||||||
|
Готовые слепки и сочетание готового примера с собственным наблюдением
|
||||||
|
отклонены: нужную историю можно получить на стенде и самому увидеть,
|
||||||
|
как новые данные меняют уже рассчитанный отчет.
|
||||||
|
|
||||||
|
День считается закрытым после получения и обработки последнего слепка
|
||||||
|
семидневного окна изменяемости; этот слепок доставляется следующим модельным
|
||||||
|
днем. Одного совпадения сумм в соседних наблюдениях для такого вывода
|
||||||
|
недостаточно. История отмен тоже имеет границу: оплата и отмена между двумя
|
||||||
|
дневными слепками могут остаться невидимыми. Отсутствие наблюдаемой оплаты
|
||||||
|
не доказывает, что оплаты не было.
|
||||||
|
|
||||||
### Тонкий анализ данных
|
### Тонкий анализ данных
|
||||||
|
|
||||||
Открывается после ступени 1 основной программы.
|
Открывается после ступени 1 основной программы.
|
||||||
|
|
||||||
- **Что мы знали о пользователе в тот день (#145).** Текущее знание и знание на
|
- **[Как новые заказы меняют отчет о посетителях](../labs/optional/identity-over-time.md) (#145).**
|
||||||
дату дают разные отчеты; сведения из будущего меняют прошлое.
|
Учащийся собирает связи с подтверждающими покупками и сохраняет три
|
||||||
|
ежедневных наблюдения. На одном наборе прежних сессий сравнивает расчет
|
||||||
|
с прежними и новыми связями, выбирает историю для воспроизведения расчета.
|
||||||
- **Добавь названия регионов в отчет (#143).** Менти собирает свой словарь по
|
- **Добавь названия регионов в отчет (#143).** Менти собирает свой словарь по
|
||||||
образцу каталога и обогащает отчет через `dictGet`.
|
образцу каталога и обогащает отчет через `dictGet`.
|
||||||
|
|
||||||
|
Работа о связях продолжается в инженерном разделе лабораторной про
|
||||||
|
инкрементальный `daily_traffic`. В обеих учащийся продвигает мир через
|
||||||
|
`world_next_day` и `etl_pipeline`, сохраняет наблюдения под `student` в
|
||||||
|
свои локальные таблицы `default` и сравнивает их SQL-запросами.
|
||||||
|
|
||||||
|
Время первой подтверждающей покупки и время обнаружения связи разделены.
|
||||||
|
Минимум времени `purchase` не принят за границу появления новых связей:
|
||||||
|
заказ может поступить позже события. Нужные изменения определяются по
|
||||||
|
разности сохраненных наборов пар. Ежедневное наблюдение ограничивает момент
|
||||||
|
появления связи интервалом; для пар начального наблюдения более ранняя
|
||||||
|
история неизвестна. История сессий в этом опыте растет без исправлений
|
||||||
|
прошлых дней, а связи не отзываются — поэтому сохраненных наборов связей
|
||||||
|
достаточно для сравнения расчетов на прежних сессиях.
|
||||||
|
|
||||||
### Superset и BI
|
### Superset и BI
|
||||||
|
|
||||||
Открывается после ступени 2 основной программы.
|
Открывается после ступени 2 основной программы.
|
||||||
@@ -236,9 +287,28 @@
|
|||||||
- **Что будет, если день заменился не на всех шардах.** Замена дня завершается
|
- **Что будет, если день заменился не на всех шардах.** Замена дня завершается
|
||||||
отдельно на каждом шарде, поэтому целостность результата подтверждает
|
отдельно на каждом шарде, поэтому целостность результата подтверждает
|
||||||
отдельная сверка.
|
отдельная сверка.
|
||||||
- **Как обновлять витрину без полной пересборки (#149) — повышенная
|
- **[Как обновлять витрину без полной пересборки](../labs/optional/incremental-daily-traffic.md)
|
||||||
сложность.** Экономия на пересчете требует хранить историю изменений, которую
|
(#149) — повышенная сложность.** Продолжение работы о связях пользователей:
|
||||||
полная пересборка стирает.
|
учащийся строит свою витрину, выбирает затронутые дни по новым сессиям
|
||||||
|
и связям, сверяет весь результат с `dm.daily_traffic_v`. Журнал успешных
|
||||||
|
загрузок позволяет повторить опыт с частично записанным результатом.
|
||||||
|
|
||||||
|
Инкрементальная работа начинается с полной загрузки текущего состояния,
|
||||||
|
затем охватывает два новых модельных дня. Обновление собственной витрины
|
||||||
|
выполнено вставкой версий в `ReplacingMergeTree`, чтение — с `FINAL`.
|
||||||
|
Это оставляет права `student` прежними и не требует менять конвейер стенда.
|
||||||
|
Версия — дата наблюдения; ключ — день отчета. История связей хранится
|
||||||
|
отдельно от версий витрины. Выбор опирается на
|
||||||
|
[договор движка](https://clickhouse.com/docs/engines/table-engines/mergetree-family/replacingmergetree):
|
||||||
|
максимальная версия по ключу и удаление прежних версий при слияниях.
|
||||||
|
DDL, обновление вставкой и чтение с `FINAL` проверены под `student`
|
||||||
|
на ClickHouse 26.3.17.56.
|
||||||
|
|
||||||
|
Полный пересчет остается эталоном, а экономия оценивается числом выбранных
|
||||||
|
дней. На малом объеме ускорение не обещано. Поиск связей продолжает читать
|
||||||
|
историю источников целиком; инкрементальным становится расчет дневной витрины.
|
||||||
|
Полная замена стендового ETL отклонена: для учебного сравнения достаточно
|
||||||
|
своей витрины и журнала рядом с работающим конвейером.
|
||||||
|
|
||||||
### Что делать, когда стенд сломался
|
### Что делать, когда стенд сломался
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -77,7 +77,7 @@
|
|||||||
и через окружение; имена и примеры собраны в
|
и через окружение; имена и примеры собраны в
|
||||||
[справочнике запуска](../docs/architecture/world.md#как-запустить-генератор).
|
[справочнике запуска](../docs/architecture/world.md#как-запустить-генератор).
|
||||||
Прямой запуск генератора не продвигает сохраненную позицию времени стенда —
|
Прямой запуск генератора не продвигает сохраненную позицию времени стенда —
|
||||||
ей управляют [даги модельного времени](../docs/handbook/airflow-dags.md#даги-мира).
|
ей управляют [даги модельного времени](../docs/handbook/airflow-dags.md#генерация-модельных-дней).
|
||||||
|
|
||||||
Из каталога `generator/` доступны:
|
Из каталога `generator/` доступны:
|
||||||
|
|
||||||
|
|||||||
@@ -420,7 +420,7 @@ COLUMNS: tuple[Column, ...] = (
|
|||||||
numpy_dtype="int64",
|
numpy_dtype="int64",
|
||||||
normalized_name="product_price",
|
normalized_name="product_price",
|
||||||
group=ColumnGroup.ECOMMERCE,
|
group=ColumnGroup.ECOMMERCE,
|
||||||
comment="цена за штуку целым числом: деньги генератор считает целыми",
|
comment="цена за штуку, округленная до целых рублей",
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
name="productQuantity",
|
name="productQuantity",
|
||||||
|
|||||||
@@ -61,6 +61,11 @@
|
|||||||
<query>GRANT SELECT ON system.settings</query>
|
<query>GRANT SELECT ON system.settings</query>
|
||||||
</grants>
|
</grants>
|
||||||
</analyst_reader>
|
</analyst_reader>
|
||||||
|
<student_writer>
|
||||||
|
<grants>
|
||||||
|
<query>GRANT SELECT, INSERT, CREATE TABLE, DROP TABLE ON default.*</query>
|
||||||
|
</grants>
|
||||||
|
</student_writer>
|
||||||
<monitoring_reader>
|
<monitoring_reader>
|
||||||
<grants>
|
<grants>
|
||||||
<query>GRANT SELECT ON stg.*</query>
|
<query>GRANT SELECT ON stg.*</query>
|
||||||
@@ -113,6 +118,14 @@
|
|||||||
<query>GRANT analyst_reader</query>
|
<query>GRANT analyst_reader</query>
|
||||||
</grants>
|
</grants>
|
||||||
</analyst>
|
</analyst>
|
||||||
|
<student>
|
||||||
|
<password from_env="CLICKHOUSE_STUDENT_PASSWORD"/>
|
||||||
|
<quota>default</quota>
|
||||||
|
<grants>
|
||||||
|
<query>GRANT analyst_reader</query>
|
||||||
|
<query>GRANT student_writer</query>
|
||||||
|
</grants>
|
||||||
|
</student>
|
||||||
<grafana>
|
<grafana>
|
||||||
<password from_env="CLICKHOUSE_GRAFANA_PASSWORD"/>
|
<password from_env="CLICKHOUSE_GRAFANA_PASSWORD"/>
|
||||||
<quota>default</quota>
|
<quota>default</quota>
|
||||||
|
|||||||
Reference in new issue
Block a user