From 535b03783a914093ad068005436ba397eb17a768 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Wed, 26 Aug 2026 05:32:03 -0400 Subject: [PATCH] =?UTF-8?q?docs(dds):=20=D1=83=D0=B1=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=BF=D0=BE=D0=B2=D1=82=D0=BE=D1=80=D1=8B=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=8A=D1=8F=D1=81=D0=BD=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B2?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8?= =?UTF-8?q?=D1=8F=D1=85=20=D0=B7=D0=B0=D0=BA=D0=B0=D0=B7=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Зачем: - Полный разбор урока про идемпотентность стоял дважды, предупреждение про окно K — трижды; внимание менти конечно, и каждый пересказ списывается с этого счёта. Что: - Докстринг dds_transform сокращён до одной фразы с указателями: полный разбор остался в шапке DDL, механика подмены — в order_replace.sql. - Комментарий про ORDER_WINDOW_DAYS в даге сведён к указателю на compose, где число объявлено и где записан риск расхождения. - Прорежен повтор одного риторического приёма «это не X» — оставлен там, где различие и есть суть. Проверка: - make lint, make config-test - Прогон dds_transform зелёный, слой сходится с ods.order_v: 1917 заказов, 8 дней. Co-Authored-By: Claude Opus 5 (1M context) --- compose.yaml | 4 ++-- dags/dds_transform.py | 23 +++++++---------------- sql/ddl/45-dds-tables.sql | 4 ++-- sql/dds/order_rebuild.sql | 4 ++-- sql/dds/order_scope.sql | 4 ++-- 5 files changed, 15 insertions(+), 24 deletions(-) diff --git a/compose.yaml b/compose.yaml index 70f2129..5a65ec0 100644 --- a/compose.yaml +++ b/compose.yaml @@ -54,8 +54,8 @@ x-world-starting-days: &world-starting-days "8" # генератора значило бы разрешить менять физику мира снаружи, а опись мира # держится на её неизменности. Стенду копия нужна затем, что по этому же числу # трансформация DDS решает, какие дни пересобирать. Разойдутся — пересборка -# промахнётся по краю окна, и заметит это не проверка, а менти со вчерашней -# выручкой. +# промахнётся по краю окна. Сверять копии некому, и это осознанно: слепые пятна +# конвейера собраны в docs/architecture/etl.md и адресованы контуру DQ. x-order-window-days: &order-window-days "7" x-airflow-common: &airflow-common diff --git a/dags/dds_transform.py b/dags/dds_transform.py index 39b854a..bae503f 100644 --- a/dags/dds_transform.py +++ b/dags/dds_transform.py @@ -8,17 +8,10 @@ дневного конвейера, с кнопки менти и после падения (docs/architecture/etl.md, «Объём прогона»). -Идемпотентность здесь устроена не так, как в приёме заказов, и в этом весь -урок сущности. Приём гасит повтор колонкой версии: ReplacingMergeTree -оставляет позднюю строку. Трансформация версий не знает — она собирает день -заново в таблицу-двойник и атомарно подменяет им партицию цели. Поэтому -повтор прогона не меняет делового содержимого слоя, а строки пересобранных -дней получают новый _load_id: видно, каким запуском собран каждый день. - -Замена партиции идёт по локальным таблицам с ON CLUSTER: по Distributed -операции с партициями отвечают кодом 48 (docs/architecture/storage.md, «Что -проверено»). Форма двойника и цена ошибки в ключе шардирования разобраны в -sql/ddl/45-dds-tables.sql. +Повтор прогона гасит не колонка версии, а замена партиции целиком: день +собирается в таблицу-двойник и подменяет собой партицию цели. Почему слой +устроен так — sql/ddl/45-dds-tables.sql; механика самой подмены — +sql/dds/order_replace.sql. """ from __future__ import annotations @@ -38,11 +31,9 @@ START_DATE = datetime.datetime(2026, 1, 1, tzinfo=datetime.UTC) SQL_ROOT = Path("/opt/airflow/sql") CLICKHOUSE_CONNECTION = "clickhouse_default" -# Окно изменяемости заказа, модельные дни. Число мира, а не настройка стенда: -# владелец — generator/world.py (ORDER_WINDOW_DAYS), который этим окном -# собирает слепок. Здесь оно читается, чтобы хранилище знало, какие дни ещё -# могут измениться. Разойдутся два объявления — пересборка промахнётся по краю -# окна, поэтому оба названы в compose.yaml рядом и с этой оговоркой. +# Окно изменяемости заказа, модельные дни: по нему трансформация решает, какие +# дни ещё могут измениться. Число принадлежит миру, владелец — генератор; +# почему у стенда лежит копия и чем она рискует — compose.yaml. ORDER_WINDOW_DAYS = int(os.environ["ORDER_WINDOW_DAYS"]) diff --git a/sql/ddl/45-dds-tables.sql b/sql/ddl/45-dds-tables.sql index edc19ee..9e22762 100644 --- a/sql/ddl/45-dds-tables.sql +++ b/sql/ddl/45-dds-tables.sql @@ -86,8 +86,8 @@ ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/{database}/{table}', '{ PARTITION BY order_date ORDER BY order_id; --- Двойник шардируется тем же ключом, что цель, и это не украшение: REPLACE --- PARTITION идёт по локальным таблицам, шард за шардом. Ляг строка заказа в +-- Двойник шардируется тем же ключом, что цель. REPLACE PARTITION идёт по +-- локальным таблицам, шард за шардом. Ляг строка заказа в -- доноре на другой шард, чем её место в цели, — подмена перенесла бы её не -- туда, и заказ пропал бы с одного шарда и задвоился на другом. CREATE TABLE IF NOT EXISTS dds.order_stage_dist ON CLUSTER clickstream_cluster diff --git a/sql/dds/order_rebuild.sql b/sql/dds/order_rebuild.sql index f886371..a43a053 100644 --- a/sql/dds/order_rebuild.sql +++ b/sql/dds/order_rebuild.sql @@ -10,8 +10,8 @@ -- раз на весь отрезок. Кто раскладывает строки по шардам — вставка через -- Distributed, ключ cityHash64(order_id) — разобрано в DDL двойника. -- --- Чистит двойник DROP PARTITION ALL, а не TRUNCATE, и это не вкусовщина: --- TRUNCATE — отдельное право, роли etl оно не выдано, а снятие всех партиций +-- Чистит двойник DROP PARTITION ALL, а не TRUNCATE: последнее — отдельное +-- право, роли etl оно не выдано, а снятие всех партиций -- входит в ALTER, который у неё есть (ADR 0007). Замерено: TRUNCATE отвечает -- ролью etl кодом 497, DROP PARTITION ALL проходит. Расширять права ради -- операции, для которой уже есть разрешённая форма, значило бы платить diff --git a/sql/dds/order_scope.sql b/sql/dds/order_scope.sql index 23fffa9..48a9db1 100644 --- a/sql/dds/order_scope.sql +++ b/sql/dds/order_scope.sql @@ -21,8 +21,8 @@ -- задачами и падают порознь, поэтому упавший день оставляет дыру, пока -- соседние дни собираются дальше. Считай мы прогресс по максимуму собранного -- дня, дыра позади окна не заросла бы никогда, а прогоны продолжали бы --- зеленеть над щербатым слоем. Цена честного ответа — проход по дню заказа в --- ODS; она того стоит. +-- зеленеть над щербатым слоем. Цена честного ответа — проход по дню заказа +-- в ODS. -- -- Край окна берётся из делового поля, а не из ярлыков доставки: время -- создания заказа одинаково у всех его версий, а `snapshot_date` и `_load_id`