<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Medallion architecture - DataTalks.RU. Data Engineering / DWH / Data Pipeline</title>
	<atom:link href="https://datatalks.ru/tag/medallion-architecture/feed/" rel="self" type="application/rss+xml" />
	<link>https://datatalks.ru/tag/medallion-architecture/</link>
	<description>RoadMap для инженера данных. Дорожная карта по инструментам Data Engineer</description>
	<lastBuildDate>Tue, 21 Oct 2025 06:31:58 +0000</lastBuildDate>
	<language>ru-RU</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.3</generator>

<image>
	<url>https://datatalks.ru/wp-content/uploads/2024/12/cropped-logo_datatalks-32x32.png</url>
	<title>Medallion architecture - DataTalks.RU. Data Engineering / DWH / Data Pipeline</title>
	<link>https://datatalks.ru/tag/medallion-architecture/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Построение архитектуры Medallion для данных Bluesky в формате JSON с помощью ClickHouse</title>
		<link>https://datatalks.ru/building-a-medallion-architecture-for-bluesky-json-data-with-clickhouse/</link>
					<comments>https://datatalks.ru/building-a-medallion-architecture-for-bluesky-json-data-with-clickhouse/#respond</comments>
		
		<dc:creator><![CDATA[Data Engineer (Admin)]]></dc:creator>
		<pubDate>Mon, 20 Oct 2025 20:32:50 +0000</pubDate>
				<category><![CDATA[ClickHouse]]></category>
		<category><![CDATA[Bronze layer]]></category>
		<category><![CDATA[ClickPipes]]></category>
		<category><![CDATA[Gold layer]]></category>
		<category><![CDATA[Medallion architecture]]></category>
		<category><![CDATA[Silver layer]]></category>
		<category><![CDATA[sql.clickhouse.com]]></category>
		<category><![CDATA[движок ReplacingMergeTree]]></category>
		<category><![CDATA[движок S3Queue]]></category>
		<guid isPermaLink="false">https://datatalks.ru/?p=2203</guid>

					<description><![CDATA[<p>Ниже — перевод статьи “Building a Medallion architecture for Bluesky JSON data with ClickHouse” с сайта ClickHouse. Построение архитектуры Medallion для данных Bluesky в формате JSON с помощью ClickHouse Мы так же взволнованы, как и вся остальная дата-сообщество, из-за недавнего всплеска популярности социальной сети BlueSky и её API, который позволяет получать доступ к потоку публикуемого [&#8230;]</p>
<p>Сообщение <a href="https://datatalks.ru/building-a-medallion-architecture-for-bluesky-json-data-with-clickhouse/">Построение архитектуры Medallion для данных Bluesky в формате JSON с помощью ClickHouse</a> появились сначала на <a href="https://datatalks.ru">DataTalks.RU. Data Engineering / DWH / Data Pipeline</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Ниже — перевод статьи <a href="https://clickhouse.com/blog/building-a-medallion-architecture-for-bluesky-json-data-with-clickhouse" target="_blank" rel="noopener">“Building a Medallion architecture for Bluesky JSON data with ClickHouse”</a> с сайта ClickHouse.</p>
<h1>Построение архитектуры Medallion для данных Bluesky в формате JSON с помощью ClickHouse</h1>
<p>Мы так же взволнованы, как и вся остальная дата-сообщество, из-за недавнего всплеска популярности социальной сети <strong>BlueSky</strong> и её <strong>API</strong>, который позволяет получать доступ к потоку публикуемого контента.</p>
<p>Этот набор данных содержит поток с высокой пропускной способностью — тысячи <strong>JSON-событий</strong> в секунду, и мы подумали, что будет интересно сделать эти данные доступными для сообщества, чтобы каждый мог выполнять по ним запросы.</p>
<p>Во время исследования данных мы обнаружили, что во многих событиях присутствуют некорректные или повреждённые временные метки. Набор данных также содержит частые дубликаты. Поэтому мы не можем просто импортировать данные и на этом закончить — потребуется некоторая очистка.</p>
<p>Это идеальная возможность попробовать <strong>архитектуру Medallion</strong>, о которой мы недавно <a href="https://datatalks.ru/medallion-architecture-with-clickhouse/" target="_blank" rel="noopener">писали в блоге</a>. В этом посте мы оживим эти концепции на практическом примере.</p>
<p>Мы создадим рабочий процесс, который решает эти задачи, <strong>организуя набор данных в три отдельные уровня: бронзовый, серебряный и золотой</strong>. Мы будем придерживаться принципов архитектуры <strong>Medallion</strong> и активно использовать недавно представленный <strong>тип данных JSON</strong>.</p>
<p>Каждый уровень будет доступен для публичных запросов в нашей демо-среде на <a href="https://sql.clickhouse.com/" target="_blank" rel="noopener">sql.clickhouse.com</a>, где читатели смогут самостоятельно изучить и взаимодействовать с результатами. Мы даже подготовили несколько примерных аналитических запросов, чтобы вам было проще начать!</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_01.png"><img fetchpriority="high" decoding="async" class="aligncenter size-full wp-image-2214" src="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_01.png" alt="" width="2012" height="704" srcset="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_01.png 2012w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_01-300x105.png 300w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_01-1024x358.png 1024w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_01-768x269.png 768w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_01-1536x537.png 1536w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_01-450x157.png 450w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_01-780x273.png 780w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_01-1600x560.png 1600w" sizes="(max-width: 2012px) 100vw, 2012px" /></a></p>
<h1>Что такое Bluesky?</h1>
<p>Для тех, кто не так активен в социальных сетях, вы могли пропустить недавний взлёт популярности <strong>Bluesky</strong>, которая в настоящее время набирает почти миллион пользователей в день. <strong>Bluesky</strong> — это социальная сеть, похожая на X (бывший Twitter), но, в отличие от него, она полностью открыта и децентрализована!</p>
<p><strong>Bluesky</strong>, построенная на <strong>AT Protocol (ATProto)</strong>, представляет собой децентрализованную платформу социальных сетей, которая позволяет пользователям самостоятельно размещать свой контент. По умолчанию данные хранятся на <strong>Bluesky Personal Data Server (PDS)</strong>, но пользователи могут выбирать — размещать эти серверы (и свой контент) у себя. Такой подход отражает возврат к принципам раннего Интернета, когда пользователи имели контроль над своим контентом и связями, вместо того чтобы зависеть от централизованных платформ, которые доминируют и владеют пользовательскими данными.</p>
<p>Данные каждого пользователя управляются в лёгкой, открытой программной среде, где одна база данных SQLite используется для хранения. Такая структура обеспечивает взаимодействие между системами (interoperability) и гарантирует, что право собственности на контент остаётся за пользователем, даже если центральная платформа выйдет из строя или изменит свою политику.</p>
<p>И самое главное для нас: как и старый Twitter, Bluesky предоставляет бесплатный способ получать события — например, посты — в реальном времени, что открывает потенциально огромный набор данных для аналитики, по мере того как сеть набирает популярность.</p>
<h1>Чтение данных Bluesky</h1>
<p>Чтобы загрузить данные из Bluesky, мы используем недавно выпущенный <strong>Jetstream API</strong>, который упрощает потребление событий Bluesky, предоставляя потоки, закодированные в формате JSON. В отличие от оригинального <strong>firehose</strong>, который требует обработки <strong>бинарных данных CBOR</strong> и <strong>файлов CAR</strong>, Jetstream снижает сложность, делая процесс доступным для разработчиков, работающих с приложениями в реальном времени. Этот API идеально соответствует нашему случаю использования, позволяя фильтровать и обрабатывать тысячи событий в секунду из постов Bluesky, одновременно решая распространённые проблемы, такие как повреждённые данные и высокий уровень дублирования.</p>
<p>В нашей реализации мы подключаемся к публичному экземпляру <strong>Jetstream</strong>, потребляя непрерывный поток событий в формате JSON для загрузки. Для этого используется простой <strong>bash-скрипт</strong>, который обрабатывает <strong>поток JSON-событий</strong> в реальном времени из <strong>Jetstream</strong>.</p>
<p><a href="https://github.com/ClickHouse/sql.clickhouse.com/blob/main/load_scripts/bluesky/ingest.sh" target="_blank" rel="noopener">Ссылка на полный bash-скрипт.</a></p>
<p>Вкратце, он выполняет следующее:</p>
<ul>
<li>Проверяет <strong>GCS bucket</strong> на наличие самого последнего файла <code>.csv.gz</code>, извлекает его временную метку (используемую как курсор) и применяет её для возобновления подписки <strong>Jetstream</strong> с нужной позиции. Это обеспечивает непрерывность данных и минимизирует дублирование.</li>
<li>Инструмент <code><strong>websocat</strong></code> используется для подключения к <strong>Jetstream API</strong>, подписки на события и передачи <strong>JSON-потока</strong> для обработки. Параметр <code>wantedCollections</code> фильтрует нужные события, а <code>cursor</code> обеспечивает пошаговое (инкрементальное) получение данных, например:</li>
</ul>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">websocat -Un --max-messages-rev $MAX_MESSAGES "$WS_URL/subscribe?wantedCollections=app.*&amp;cursor=$cursor" &gt; "$OUTPUT_FILE"</pre><p></p>
<ul>
<li>Входящие данные <strong>JSON</strong> разбиваются на фрагменты по <strong>500 000 строк</strong>, при этом каждый фрагмент представляет собой файл, где последняя временная метка используется в качестве идентификатора файла. Мы используем <code>clickhouse-local</code> для преобразования файла в CSV, затем сжимаем его в <code>.gz</code> и загружаем в <strong>GCS bucket</strong> с помощью <code>gsutil</code>.</li>
<li>Скрипт выполняется внутри <strong>Docker-контейнера</strong> ClickHouse, который запускается каждые 3 минуты с помощью <code>Google Cloud Run Job</code>.</li>
</ul>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_02.jpg"><img decoding="async" class="aligncenter size-full wp-image-2215" src="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_02.jpg" alt="" width="1129" height="748" srcset="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_02.jpg 1129w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_02-300x199.jpg 300w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_02-1024x678.jpg 1024w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_02-768x509.jpg 768w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_02-450x298.jpg 450w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_02-780x517.jpg 780w" sizes="(max-width: 1129px) 100vw, 1129px" /></a></p>
<p>Обратите внимание, что файлы естественным образом упорядочены по своим именам, основанным на временной метке последнего события. Это критически важно для последующего эффективного инкрементального чтения из <strong>GCS bucket</strong>. Однако скрипт не гарантирует, что будут зафиксированы все события Bluesky.</p>
<h1>Выборка (sampling) данных</h1>
<p>На момент написания этого поста мы зафиксировали почти <strong>1,5 миллиарда событийных строк</strong>, собранных примерно за 21 день. Мы можем использовать функцию <strong>gcs</strong> в <strong>ClickHouse</strong>, чтобы выполнить запрос к данным напрямую и определить общее количество необработанных строк.</p><pre class="urvanov-syntax-highlighter-plain-tag">clickhouse-cloud :) SELECT count()
FROM gcs('https://storage.googleapis.com/pme-internal/bluesky/*.gz', '', '', 'CSVWithNames')

┌────count()─┐
│ 1484500000 │ -- 1.48 billion
└────────────┘

1 row in set. Elapsed: 72.396 sec. Processed 1.48 billion rows, 205.07 GB (20.51 million rows/s., 2.83 GB/s.)
Peak memory usage: 4.85 GiB.</pre><p>Мы можем взять выборку данных, используя ту же функцию, преобразовав каждую строку в тип <strong>JSON</strong> и применив формат <code>PrettyJSONEachRow</code>, чтобы получить читаемый результат.</p><pre class="urvanov-syntax-highlighter-plain-tag">SET allow_experimental_json_type = 1

SELECT data::'JSON' AS event
FROM gcs('https://storage.googleapis.com/pme-internal/bluesky/*.gz', '', '', 'CSVWithNames')
LIMIT 1
FORMAT PrettyJSONEachRow

{
  "account": {
    "active": true,
    "did": "did:plc:kjealuouxn3l6v4byxh2fhff",
    "seq": "706717212",
    "time": "2024-11-27T18:00:02.429Z"
  },
  "did": "did:plc:kjealuouxn3l6v4byxh2fhff",
  "kind": "account",
  "time_us": "1732730402720719"
}

1 row in set. Elapsed: 0.233 sec.</pre><p>Хотя приведённый выше пример даёт некоторое представление о структуре событий, он не полностью отражает сложность, изменчивость и непоследовательность данных. Столбец <code>kind</code> в значительной степени определяет последующую структуру, при этом <strong>API</strong> передаёт три типа событий: <code>commit</code>, <code>identity</code> и <code>account</code>.</p>
<p><strong>Краткое описание типов событий:</strong></p>
<ul>
<li><code>commit</code> — событие фиксации (commit) указывает на создание, обновление или удаление записи. Этот тип представляет большинство событий и включает посты, лайки и подписки.</li>
<li><code>identity</code> — обновление идентичности учётной записи.</li>
<li><code>account</code> — обновление состояния учётной записи.</li>
</ul>
<p>Мы подробнее исследуем эти данные после их загрузки в бронзовый слой (<strong>Bronze layer</strong>).</p>
<h1>Проблемы с данными Bluesky</h1>
<p>Данные Bluesky, как они поступают через JetStream API, имеют ряд проблем, включая следующее:</p>
<ul>
<li><strong>Повреждённый JSON (Malformed JSON)</strong> — время от времени встречаются некорректно сформированные JSON-события. Хотя они редки, такие записи могут нарушить обработку файла. Мы исключаем их с помощью функции <code>isValidJSON</code>, ограничивая загрузку в бронзовый слой (Bronze layer) только теми строками, для которых функция возвращает значение 1.</li>
<li><strong>Непоследовательная структура (Inconsistent structure)</strong> — хотя временная метка сбора данных (поле time_us) присутствует во всех событиях, путь JSON, содержащий время, когда событие произошло, зависит от типа события. Наш рабочий процесс должен извлекать единую согласованную временную метку, основываясь на этих условиях. Простой анализ показывает, что:
<ul>
<li><code>commit.record.createdAt</code> можно использовать для событий типа commit;</li>
<li><code>identity.time</code> — для событий identity;</li>
<li><code>account.time</code> — для событий account.</li>
</ul>
</li>
<li><strong>Будущие или некорректные временные метки (Future or invalid timestamps)</strong> — некоторые события имеют временные метки из будущего. Например, при выборке событий на момент написания поста 42 тысячи commit-событий имели будущие значения времени. Ещё 4 миллиона commit-событий имели метки времени, относящиеся к периоду до запуска Bluesky как сервиса.</li>
<li><strong>Повторяющиеся структуры (Repeated structures)</strong> — встречаются случаи, когда JSON содержит глубоко рекурсивные структуры. Это приводит к появлению более 1800 уникальных JSON-путей, большинство из которых, вероятно, не имеют существенной ценности для анализа содержимого.</li>
<li><strong>Дубликаты (Duplicates)</strong> — несмотря на использование курсора для поддержания последовательности данных, JetStream API создаёт дубликаты (где содержимое идентично, за исключением временной метки сбора). Удивительно, но такие дубликаты могут появляться в широком диапазоне времени — в некоторых случаях с разницей до 24 часов. Важно отметить, что большинство дубликатов встречаются в интервале около 20 минут.</li>
</ul>
<p>Приведённые выше пункты не представляют собой исчерпывающий список проблем с качеством данных — мы продолжаем находить новые сложности! Однако, в целях наглядности и сжатости примера, в нашем демонстрационном <strong>Medallion workflow</strong> мы сосредоточимся именно на перечисленных проблемах.</p>
<h1>Тип данных JSON в ClickHouse</h1>
<p><strong>JSON</strong> играет ключевую роль в реализации архитектуры <strong>Medallion</strong> для данных Bluesky, позволяя системе хранить высокодинамичную и полуструктурированную информацию в бронзовом слое (<strong>Bronze layer</strong>). Новый тип данных JSON в ClickHouse, представленный в версии 24.8, решил ключевые проблемы, с которыми сталкивались предыдущие реализации.</p>
<p>В отличие от традиционных подходов, которые предполагают единственный тип для каждого JSON-пути (что часто приводит к принудительному приведению типов или их преобразованию), <strong>JSON-тип в ClickHouse</strong> хранит значения каждого уникального пути и типа в отдельных подколонках (sub-columns).</p>
<p>Такой подход обеспечивает эффективное хранение, сводит к минимуму лишние операции ввода-вывода (I/O) и избегает затрат на приведение типов во время выполнения запроса.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_03-scaled.png"><img decoding="async" class="aligncenter size-full wp-image-2216" src="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_03-scaled.png" alt="" width="2560" height="1065" srcset="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_03-scaled.png 2560w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_03-300x125.png 300w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_03-1024x426.png 1024w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_03-768x319.png 768w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_03-1536x639.png 1536w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_03-2048x852.png 2048w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_03-450x187.png 450w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_03-780x324.png 780w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_03-1600x665.png 1600w" sizes="(max-width: 2560px) 100vw, 2560px" /></a></p>
<p>Например, когда в таблицу вставляются два JSON-пути с разными типами данных, ClickHouse сохраняет значения каждого типа в отдельных подколонках. Эти подколонки могут быть запрошены независимо, что снижает ненужные операции ввода-вывода.<br />
При этом, если запросить колонку, содержащую несколько типов данных, её значения всё равно возвращаются как единый столбец в ответе.</p>
<p>Кроме того, благодаря использованию смещений (<strong>offsets</strong>), ClickHouse гарантирует, что подколонки остаются плотными (<strong>dense</strong>) — то есть не хранят значения по умолчанию для отсутствующих JSON-путей. Такой подход максимизирует степень сжатия и дополнительно снижает нагрузку на I/O.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_04-scaled.png"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2217" src="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_04-scaled.png" alt="" width="2560" height="1396" srcset="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_04-scaled.png 2560w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_04-300x164.png 300w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_04-1024x558.png 1024w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_04-768x419.png 768w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_04-1536x838.png 1536w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_04-2048x1117.png 2048w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_04-450x245.png 450w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_04-780x425.png 780w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_04-1600x873.png 1600w" sizes="(max-width: 2560px) 100vw, 2560px" /></a></p>
<p>Также данный тип данных не страдает от проблемы “взрыва подколонок” (sub-column explosion), возникающей при большом количестве уникальных JSON-путей.<br />
Это особенно важно для данных Bluesky, где при отсутствии фильтрации встречается более 1800 уникальных путей.<br />
При этом это не мешает хранению всех этих путей — новые пути просто сохраняются в общей колонке данных, если превышен лимит (при этом статистика ускоряет выполнение запросов).</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_05-scaled.png"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2219" src="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_05-scaled.png" alt="" width="2560" height="1346" srcset="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_05-scaled.png 2560w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_05-300x158.png 300w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_05-1024x539.png 1024w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_05-768x404.png 768w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_05-1536x808.png 1536w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_05-2048x1077.png 2048w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_05-450x237.png 450w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_05-780x410.png 780w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_05-1600x841.png 1600w" sizes="(max-width: 2560px) 100vw, 2560px" /></a></p>
<p>Такое оптимизированное обращение с JSON обеспечивает эффективное хранение сложных, полуструктурированных наборов данных, таких как данные Bluesky, в бронзовом слое архитектуры.<br />
Для пользователей, заинтересованных в технических деталях реализации этого нового типа колонок, рекомендуется ознакомиться с подробным постом в нашем блоге (ссылка предоставлена в оригинале).</p>
<h1>Бронзовый уровень для необработанных данных (Bronze, сырые данные)</h1>
<p>Хотя исходное описание <strong>бронзового слоя (Bronze layer)</strong> не предполагает фильтрацию или преобразование данных, мы относимся к этому менее догматично и считаем, что минимальная фильтрация и недеструктивные преобразования данных могут быть полезны для исследования проблем и возможности воспроизведения данных в будущем.</p>
<p>Для преобразований мы рекомендуем ограничиться теми, которые можно реализовать с помощью <strong>материализованных колонок (Materialized columns)</strong>, как показано ниже в нашей схеме бронзового слоя:</p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE bluesky.bluesky_raw
(
  `data` JSON(SKIP `commit.record.reply.root.record`, SKIP `commit.record.value.value`),
  `_file` LowCardinality(String),
  `kind` LowCardinality(String) MATERIALIZED getSubcolumn(data, 'kind'),
  `scrape_ts` DateTime64(6) MATERIALIZED fromUnixTimestamp64Micro(CAST(getSubcolumn(data, 'time_us'), 'UInt64')),
  `bluesky_ts` DateTime64(6) MATERIALIZED multiIf(getSubcolumn(data, 'kind') = 'commit', parseDateTime64BestEffortOrZero(CAST(getSubcolumn(data, 'commit.record.createdAt'), 'String')), getSubcolumn(data, 'kind') = 'identity', parseDateTime64BestEffortOrZero(CAST(getSubcolumn(data, 'identity.time'), 'String')), getSubcolumn(data, 'kind') = 'account', parseDateTime64BestEffortOrZero(CAST(getSubcolumn(data, 'account.time'), 'String')), toDateTime64(0, 6)),
  `dedup_hash` String MATERIALIZED cityHash64(arrayFilter(p -&gt; ((p.1) != 'time_us'), JSONExtractKeysAndValues(CAST(data, 'String'), 'String')))
)
ENGINE = ReplacingMergeTree
PRIMARY KEY (kind, bluesky_ts)
ORDER BY (kind, bluesky_ts, dedup_hash)</pre><p>Некоторые важные замечания по этой схеме:</p>
<ul>
<li><strong>Тип JSON</strong> — колонка data использует новый тип данных JSON и содержит всё событие целиком.<br />
Мы применяем оператор <strong>SKIP</strong>, чтобы исключить определённые пути JSON, которые, как показал анализ, были ответственны за повторяющиеся структуры, отмеченные ранее.</li>
<li><strong>Сохранение метаданных</strong> — колонка <code>_file</code> содержит ссылку на файл, из которого была загружена строка.</li>
<li><strong>Материализованные колонки (Materialized columns)</strong> — остальные колонки являются материализованными и вычисляются из колонки data во время вставки:
<ul>
<li><code>scrape_ts</code> — время, когда событие было доставлено; извлекается из поля JSON time_us.</li>
<li><code>kind</code> — тип события, как упоминалось ранее.</li>
<li><code>bluesky_ts</code> — выполняет условную логику, извлекая временную метку события на основе значения kind; это решает проблему непоследовательной структуры и обеспечивает единый формат временных меток для всех событий.</li>
<li><code>dedup_hash</code> — содержит хеш события.</li>
</ul>
</li>
<li>Для вычисления хеша создаётся массив всех <strong>JSON-путей</strong> и их значений, за исключением <code>time_us</code> (так как это поле отличается у дубликатов), с помощью функции <code>JSONExtractKeysAndValues</code>.<br />
Затем функция <code>cityHash64</code> обрабатывает этот массив, создавая уникальный хеш события.</li>
<li><strong>ReplacingMergeTree</strong> — используется движок <code>ReplacingMergeTree</code>, который позволяет устранять дубликаты записей, имеющих одинаковые значения ключей сортировки (<strong>ORDER BY</strong>).<br />
<strong>Дедупликация выполняется асинхронно</strong> во время фоновых слияний, которые происходят в неопределённое время и не могут быть напрямую контролируемы — то есть дедупликация осуществляется постепенно (<code>eventual deduplication</code>).</li>
</ul>
<p>В нашей схеме ключ <strong>ORDER BY</strong> включает <code>kind</code> и <code>bluesky_ts</code>, что:</p>
<ul>
<li>обеспечивает эффективное чтение;</li>
<li>гарантирует высокую степень сжатия, группируя строки с похожими атрибутами.</li>
</ul>
<p>Мы также добавляем <code>dedup_hash</code>, чтобы уникально идентифицировать строки для дедупликации, но не включаем его в <strong>PRIMARY KEY</strong>.<br />
Это оптимизация, которая предотвращает загрузку индекса по <code>dedup_hash</code> в память — разумное решение, так как мы не выполняем прямые запросы по хешу.</p>
<p>Наш бронзовый слой выполняет минимальные преобразования данных с помощью материализованных колонок, но при этом обеспечивает возможность дедупликации.<br />
Важно отметить, что использование <strong>ReplacingMergeTree</strong> здесь не является обязательным и не влияет на будущие слои.</p>
<p>Пользователи могут предпочесть обычный <strong>MergeTree</strong>, если хотят анализировать дубликаты напрямую.</p>
<p>Наш выбор обусловлен главным образом желанием минимизировать объём хранимых данных.</p>
<h2>Загрузка данных из объектного хранилища (s3, object storage)</h2>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_06.png"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2221" src="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_06.png" alt="" width="977" height="616" srcset="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_06.png 977w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_06-300x189.png 300w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_06-768x484.png 768w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_06-450x284.png 450w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_06-780x492.png 780w" sizes="(max-width: 977px) 100vw, 977px" /></a></p>
<p>Как описано выше, наш конвейер загрузки данных (<strong>ingestion pipeline</strong>) использует инструмент <strong>websocat</strong> для потоковой передачи данных из <strong>JetStream API</strong>, сохраняя события в виде файлов <code>.csv.gz</code> в <strong>Google Cloud Storage (GCS)</strong>.</p>
<p>Этот промежуточный шаг предоставляет несколько преимуществ:</p>
<ul>
<li>он позволяет воспроизводить данные (<strong>data replay</strong>),</li>
<li>сохраняет оригинальную копию необработанных данных (<strong>raw data</strong>)</li>
<li>и имитирует подход, который многие пользователи используют для загрузки данных из объектного хранилища.</li>
</ul>
<p>Чтобы считать эти файлы из <strong>GCS</strong> в нашу таблицу бронзового слоя <strong>bluesky_raw</strong>, мы используем движок таблицы <strong>S3Queue (S3Queue table engine)</strong>. Этот движок считывает данные из объектного хранилища, совместимого с <strong>S3</strong>, автоматически обрабатывает новые файлы по мере их добавления в бакет и вставляет их в указанную таблицу через <strong>материализованное представление (materialized view)</strong>.</p>
<p>Создание этой таблицы требует небольшой <strong>DDL-команды</strong>:</p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE bluesky.bluesky_queue
(
  `data` Nullable(String)
)
ENGINE = S3Queue('https://storage.googleapis.com/pme-internal/bluesky/*.gz', '', '', 'CSVWithNames')
SETTINGS mode = 'ordered', s3queue_buckets = 30, s3queue_processing_threads_num = 10;</pre><p>Обратите внимание, что мы указываем <strong>GCS-бакет</strong>, содержащий <strong>сжатые (gzipped) файлы</strong>,<br />
и определяем каждую строку как тип String с помощью объявления схемы.</p>
<p>Важно, что мы включаем <strong>&#171;ordered mode&#187;</strong> через настройку <code>mode = 'ordered'</code>. Это заставляет файлы обрабатываться в лексикографическом порядке, обеспечивая последовательную загрузку данных.</p>
<p>Хотя это означает, что файлы, добавленные с более ранним порядком сортировки, игнорируются, такая конфигурация поддерживает эффективную и инкрементальную обработку, и устраняет необходимость выполнять масштабные операции сравнения множеств, если файлы не имеют естественного порядка.</p>
<p>Наше раннее использование временных меток (<strong>timestamps</strong>) для имен файлов гарантирует, что данные обрабатываются в правильной последовательности, а движок <strong>S3Queue</strong> быстро распознаёт новые файлы, которые нужно загрузить.</p>
<p>Наша среда <a href="https://sql.clickhouse.com/" target="_blank" rel="noopener">sql.clickhouse.com</a>, в которую мы загружаем данные, состоит из трёх узлов, каждый из которых имеет 60 виртуальных процессорных ядер (<strong>vCPUs</strong>).</p>
<p>Параметр <code>s3queue_processing_threads_num</code> задаёт количество потоков для обработки файлов на каждом сервере.</p>
<p>Кроме того, при использовании ordered mode вводится дополнительная настройка &#8212; <code>s3queue_buckets</code>. Как рекомендуется, мы устанавливаем её как произведение количества реплик (3) на количество потоков обработки (10).</p>
<p>Чтобы потреблять строки из этой очереди, необходимо присоединить <strong>инкрементальное материализованное представление (Incremental Materialized View)</strong>. Это представление читает данные из очереди, выполняет SELECT-запрос над строками, а результат отправляется в таблицу бронзового слоя <strong>bluesky_raw</strong>.</p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE MATERIALIZED VIEW bluesky.bluesky_mv TO bluesky.bluesky_raw
(
  `data` Nullable(String)
)
AS SELECT
  data,
  _file
FROM bluesky.bluesky_queue
WHERE isValidJSON(data) = 1</pre><p>Обратите внимание, что мы выполняем базовую фильтрацию уже на этом уровне:<br />
в таблицу бронзового слоя передаются только строки, где <code>isValidJSON(data) = 1</code>, то есть содержащие валидный <strong>JSON</strong>.</p>
<p>Также мы добавляем метаданные — колонку <code>_file</code>, чтобы иметь запись о том, из какого <strong>gzip-файла</strong> была загружена каждая строка.</p>
<h2>Потоковая передача Bluesky напрямую в ClickHouse (Streaming)</h2>
<p>Обратите внимание, что <strong>ClickHouse</strong> <strong>может напрямую выполнять потоковую загрузку данных</strong> с использованием <strong>JSON-форматов</strong> ввода, как недавно продемонстрировал наш технический директор (CTO) Алексей Миловидов.</p>
<p>Это можно реализовать, объединив тип данных JSON и формат ввода JSON.</p>
<p>Например:</p><pre class="urvanov-syntax-highlighter-plain-tag">websocat -n "wss://jetstream1.us-east.bsky.network/subscribe?wantedCollections=app.*" | pv -l | split -l 1000 --filter='clickhouse-client --host sql-clickhouse.clickhouse.com --secure --password "" --query "INSERT INTO bluesky.bluesky_raw (data) FORMAT JSONAsObject"'</pre><p></p>
<h2>ClickPipes в ClickHouse Cloud</h2>
<p>Хотя механизм таблиц <strong>S3Queue</strong> позволяет нам выполнять потоковую передачу данных из объектного хранилища в ClickHouse, у него есть определённые ограничения. Помимо того, что он поддерживает только <strong>S3-совместимые хранилища</strong>, он обеспечивает <strong>семантику “по крайней мере один раз” (at-least-once)</strong>.</p>
<p>Пользователи ClickHouse Cloud могут предпочесть использовать <strong>ClickPipes</strong> — управляемое решение для загрузки данных, которое обеспечивает семантику <strong>“ровно один раз” (exactly-once)</strong>, поддерживает больше источников (например, <strong>Kafka</strong>) и разделяет ресурсы загрузки и ресурсы кластера.</p>
<p>Эта технология может быть использована для замены <strong>S3Queue</strong> в описанной выше архитектуре с минимальной настройкой через пошаговый мастер (<strong>guided wizard</strong>).</p>
<h2>Запросы к бронзовому уровню</h2>
<p>Хотя мы не рекомендуем предоставлять доступ к вашей таблице уровня <strong>Bronze</strong> конечным пользователям (<strong>downstream consumers</strong>), выбранный нами ключ сортировки (<strong>ordering key</strong>) позволяет эффективно исследовать данные, выявлять дополнительные проблемы с их качеством или, при необходимости, повторно воспроизводить данные через последующие уровни архитектуры.</p>
<p>Мы отмечали, что во время слияния (merge) <strong>движок ReplacingMergeTree</strong> определяет дубликаты строк, используя значения столбцов, указанных в <strong>ORDER BY</strong>, как уникальный идентификатор, и сохраняет только последнюю версию записи. Однако это обеспечивает лишь постепенную (<strong>eventual</strong>) корректность — то есть не гарантирует, что все дубликаты будут удалены, поэтому полагаться на это не стоит.</p>
<p>Чтобы гарантировать корректные результаты, пользователям необходимо дополнять <strong>фоновое объединение (background merges)</strong> операцией удаления дубликатов во время выполнения запроса, что можно сделать с помощью оператора <strong>FINAL</strong>.<br />
Однако это создаёт дополнительную нагрузку на ресурсы и негативно влияет на производительность запросов, что является ещё одной причиной, по которой мы не советуем предоставлять доступ к Bronze-таблицам потребителям данных.</p>
<p>В приведённых выше примерах запросов мы опускаем оператор <strong>FINAL</strong>, принимая небольшой уровень дублирования, поскольку это допустимо для разведочного анализа данных.</p>
<p>Большинство данных представляют собой <strong>commit-события (commit events)</strong>:</p><pre class="urvanov-syntax-highlighter-plain-tag">SELECT kind, formatReadableQuantity(count()) AS c
FROM bluesky_raw
GROUP BY kind
FORMAT PrettyCompactMonoBlock
┌─kind─────┬─c──────────────┐
│ commit   │ 614.55 million │
│ account  │ 1.72 million   │
│ identity │ 1.70 million   │
└──────────┴────────────────┘

3 rows in set. Elapsed: 0.124 sec. Processed 617.97 million rows, 617.97 MB (5.00 billion rows/s., 5.00 GB/s.)
Peak memory usage: 139.03 MiB.</pre><p>Внутри этих <strong>commit-событий</strong> можно исследовать типы событий с помощью синтаксиса пути <strong>JSON (JSON path syntax)</strong>:</p><pre class="urvanov-syntax-highlighter-plain-tag">SELECT
  data.commit.collection AS collection,
  count() AS c,
  uniq(data.did) AS users
FROM bluesky_raw
WHERE kind = 'commit'
GROUP BY ALL
ORDER BY c DESC
LIMIT 10
FORMAT PrettyCompactMonoBlock

┌─collection───────────────┬─────────c─┬───users─┐
│ app.bsky.feed.like       │ 705468149 │ 7106516 │
│ app.bsky.graph.follow    │ 406406091 │ 8629730 │
│ app.bsky.feed.post       │ 137946245 │ 4323265 │
│ app.bsky.feed.repost     │  90847077 │ 2811398 │
│ app.bsky.graph.block     │  25277808 │ 1523621 │
│ app.bsky.graph.listitem  │   8464006 │  166002 │
│ app.bsky.actor.profile   │   8168943 │ 4083558 │
│ app.bsky.graph.listblock │  643292   │  216695 │
│ app.bsky.feed.threadgate │  559504   │   94202 │
│ app.bsky.feed.postgate   │  275675   │   38790 │
└──────────────────────────┴───────────┴─────────┘

10 rows in set. Elapsed: 19.923 sec. Processed 1.38 billion rows, 122.00 GB (69.50 million rows/s., 6.12 GB/s.)
Peak memory usage: 1003.91 MiB.</pre><p>Мы видим, что основная часть событий — это <strong>“лайки”</strong> и <strong>“подписки (follows)”</strong>, что вполне ожидаемо.</p>
<h1>Серебряный уровень для очищенных данных (Silver Layer)</h1>
<p><strong>Слой Silver (Серебряный)</strong> представляет собой следующий этап в архитектуре <strong>Medallion</strong>, преобразуя сырые данные из слоя <strong>Bronze (Бронзового)</strong> в более согласованную и структурированную форму.</p>
<p><strong>Этот слой решает проблемы качества данных:</strong> выполняет дополнительную фильтрацию, стандартизирует схемы, производит преобразования и обеспечивает полное удаление дубликатов.<br />
В ClickHouse обычно наблюдается прямая связь между таблицами Bronze и их эквивалентами в Silver.</p>
<p>Мы знаем, что дубликаты событий имеют одинаковые значения <code>bluesky_ts</code> (и других столбцов), различаясь лишь по <code>scrape_ts</code>, причём последнее значение может быть значительно позже.<br />
Однако ранее мы установили, что большинство дубликатов появляются в пределах 20 минут.<br />
Чтобы гарантировать, что в <strong>золотой слой (Gold)</strong> не попадут дубликаты, мы вводим понятие конечного <strong>окна дедупликации (finite duplication window)</strong> в слое <strong>Silver</strong>.</p>
<p>События будут распределяться по этим окнам дедупликации, которые смещены относительно текущего времени на основе значения <code>bluesky_ts</code>.<br />
Эти «окна» периодически сбрасываются (<strong>flushed</strong>) в слой Gold, с гарантией, что в каждое окно попадёт только одна копия события.</p>
<p><strong>Использование окон дедупликации избавляет нас от необходимости проводить дедупликацию за бесконечный период времени, что существенно снижает нагрузку на систему и делает задачу более управляемой.</strong></p>
<p>Как мы покажем далее, это можно эффективно реализовать в ClickHouse.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_07.png"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2227" src="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_07.png" alt="" width="998" height="711" srcset="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_07.png 998w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_07-300x214.png 300w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_07-768x547.png 768w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_07-450x321.png 450w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_07-780x556.png 780w" sizes="(max-width: 998px) 100vw, 998px" /></a></p>
<p>Назначение событий в окна дедупликации, которые синхронизируются с реальным временем и периодически сбрасываются, предполагает, что данные доставляются без значительных задержек.</p>
<p><strong>Анализ таблицы Bronze показывает, что:</strong></p>
<ul>
<li>90% событий имеют значение bluesky_ts, отличающееся от времени их поступления (извлечённого из имени файла в GCS) не более чем на 20 минут.<br />
Это возможно, если:</li>
<li>Обработка 1 миллиона сообщений за один раз не вызывает значительных задержек;</li>
<li>Время чтения и обработки через S3Queue также незначительно (это можно проверить через системные таблицы);</li>
<li>Время, извлечённое из имени файла, близко к реальному времени загрузки, что подтверждается запросами к GCS.</li>
</ul>
<p>Кроме того, более 94% событий имеют разницу между <code>scrape_ts</code> и <code>bluesky_ts</code> меньше 20 минут (в 90% случаев — даже менее 10 секунд).<br />
Это означает, что значение <code>scrape_ts</code> также не отстаёт от времени поступления данных.</p>
<p>Понимая, что события обычно доставляются в течение 20 минут после их <code>bluesky_ts</code>, мы можем надёжно формировать окна дедупликации в слое Silver.<br />
Для этого мы создаём <strong>раздел (partition)</strong> в ClickHouse для каждого 20-минутного интервала — таким образом, раздел фактически соответствует одному окну.</p>
<p>События распределяются по разделам в зависимости от того, в какой интервал они попадают, с помощью функции:</p><pre class="urvanov-syntax-highlighter-plain-tag">toStartOfInterval(bluesky_ts, toIntervalMinute(20))</pre><p><strong>Итоговая схема таблицы Silver выглядит следующим образом:</strong></p>
<ul>
<li>Мы используем ReplacingMergeTree, но выполняем дедупликацию только внутри каждого раздела, то есть слияние происходит только в пределах окна.</li>
<li>Для управления объёмом данных применяется TTL, который удаляет строки, старше 1440 секунд (24 часа).</li>
</ul>
<p>Параметр <code>ttl_only_drop_parts = 1</code> гарантирует, что части удаляются только тогда, когда все строки в них устарели.</p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE bluesky.bluesky_dedup
(
  `data` JSON(SKIP `commit.record.reply.root.record`, SKIP `commit.record.value.value`),
  `kind` LowCardinality(String),
  `scrape_ts` DateTime64(6),
  `bluesky_ts` DateTime64(6),
  `dedup_hash` String
)
ENGINE = ReplacingMergeTree
PARTITION BY toStartOfInterval(bluesky_ts, toIntervalMinute(20))
ORDER BY dedup_hash
TTL toStartOfMinute(bluesky_ts) + toIntervalMinute(1440) SETTINGS ttl_only_drop_parts=1</pre><p>Так как слишком большое количество разделов может привести к проблемам производительности и ошибкам вроде <strong>“Too many parts”</strong>, мы ограничиваем таблицу Silver только одними сутками данных (всего 72 окна по 20 минут). Старые данные автоматически удаляются с помощью правил TTL, сохраняя эффективность и стабильность системы.</p>
<h2>Инкрементные материализованные представления для фильтрации</h2>
<p>При применении фильтрации и правил дедупликации к данным <strong>уровня Bronze</strong>, пользователи часто сохраняют «негативные совпадения» (то есть записи, не прошедшие фильтры) в отдельной таблице — так называемой <strong>Dead-Letter таблице</strong> — для последующего анализа.</p>
<p>Так как мы планируем периодически отправлять свежие партиции из слоя Silver в слой Gold, нам нежелательно, чтобы события поступали слишком поздно.<br />
По этой причине, а также чтобы продемонстрировать <strong>принцип “dead letter queue”</strong>, мы будем отправлять все события из слоя Bronze, у которых разница между <code>scrape_ts</code> и <code>bluesky_ts</code> превышает 20 минут, в <strong>очередь “dead letter”</strong>.</p>
<p>События же, у которых задержка меньше 20 минут, будут вставляться в соответствующую партицию таблицы Silver, показанную ранее.</p>
<p>Для реализации этого подхода мы используем две <strong>инкрементные материализованные представления (incremental materialized views)</strong>.<br />
Каждое из них выполняет SELECT-запрос к строкам, вставленным в таблицу уровня Bronze (<strong>bluesky_raw</strong>), и отправляет результаты либо:</p>
<ul>
<li>в таблицу <strong>dead letter queue</strong>,</li>
<li>либо в таблицу Silver (<strong>bluesky_dedup</strong>).</li>
</ul>
<p>Основное различие между этими двумя представлениями заключается в их фильтрующих условиях.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_08.png"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2229" src="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_08.png" alt="" width="981" height="637" srcset="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_08.png 981w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_08-300x195.png 300w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_08-768x499.png 768w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_08-450x292.png 450w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_08-780x506.png 780w" sizes="(max-width: 981px) 100vw, 981px" /></a></p>
<p>Представление для отправки строк в <strong>таблицу Silver</strong>:</p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE MATERIALIZED VIEW bluesky.bluesky_dedup_mv TO bluesky.bluesky_dedup
(
	`data` JSON,
	`kind` LowCardinality(String),
	`scrape_ts` DateTime64(6),
	`bluesky_ts` DateTime64(6),
	`dedup_hash` String
)
AS SELECT
	data,
	kind,
	scrape_ts,
	bluesky_ts,
	dedup_hash
FROM bluesky.bluesky_raw
WHERE abs(timeDiff(scrape_ts, bluesky_ts)) &lt; 1200</pre><p>Схема таблицы <strong>Dead-Letter Queue</strong> и связанное с ней материализованное представление:</p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE bluesky.bluesky_dlq
(
	`data` JSON(SKIP `commit.record.reply.root.record`, SKIP `commit.record.value.value`),
	`kind` LowCardinality(String),
	`scrape_ts` DateTime64(6),
	`bluesky_ts` DateTime64(6),
	`dedup_hash` String
)
ENGINE = MergeTree
ORDER BY (kind, scrape_ts)

CREATE MATERIALIZED VIEW bluesky.bluesky_dlq_mv TO bluesky.bluesky_dlq
(
	`data` JSON,
	`kind` LowCardinality(String),
	`scrape_ts` DateTime64(6),
	`bluesky_ts` DateTime64(6),
	`dedup_hash` String
)
AS SELECT
	data,
	kind,
	scrape_ts,
	bluesky_ts,
	dedup_hash
FROM bluesky.bluesky_raw
WHERE abs(timeDiff(scrape_ts, bluesky_ts)) &gt;= 1200</pre><p>Обратите внимание, что для <strong>очереди “dead letter”</strong> используется обычный движок <strong>MergeTree</strong>,<br />
так как дедупликация здесь не требуется — эти данные предназначены для анализа проблем и диагностики, а не для основной аналитики.</p>
<h2>Отправка данных на золотой уровень (Gold Layer)</h2>
<p>Описанный выше процесс оставляет <strong>разделы (partitions)</strong>, заполненные на уровне <strong>Silver</strong>.<br />
Периодически нам необходимо переносить данные из этих разделов в уровень <strong>Gold</strong>, гарантируя, что все события были полностью дедуплицированы, и при этом делать это достаточно оперативно, чтобы обеспечить наличие свежих данных в <strong>слое Gold</strong> для аналитики.</p>
<p>Мы реализуем этот периодический <strong>перенос (flushing)</strong> с помощью <strong>Refreshable Materialized View</strong>.<br />
Такие представления выполняются периодически по таблицам уровня Silver и позволяют выполнять сложные преобразования, включая денормализацию данных перед их записью в таблицы уровня Gold.</p>
<p>В нашем случае нам нужно просто периодически вставлять данные из последнего раздела, который больше не получает новых данных, в таблицу <strong>Gold</strong>.<br />
Запрос при этом должен выполняться с использованием оператора FINAL, чтобы гарантировать, что все события дедуплицированы.</p>
<p>Хотя такой запрос обычно более затратен вычислительно, здесь мы можем использовать два преимущества:</p>
<ul>
<li>Запрос выполняется периодически — в нашем случае каждые 20 минут, что смещает нагрузку с пользовательских запросов на уровень загрузки данных.</li>
<li>Мы обрабатываем только один раздел за одно выполнение. Можно ограничить дедупликацию во время выполнения только этим разделом, установив параметр <code>do_not_merge_across_partitions_select_final=1</code>, что дополнительно оптимизирует запрос и снижает нагрузку.</li>
</ul>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_09.png"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2265" src="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_09.png" alt="" width="1112" height="840" srcset="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_09.png 1112w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_09-300x227.png 300w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_09-1024x774.png 1024w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_09-768x580.png 768w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_09-450x340.png 450w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_09-780x589.png 780w" sizes="(max-width: 1112px) 100vw, 1112px" /></a></p>
<p>Для этого требуется определить, какой именно раздел нужно перенести в <strong>Gold</strong> при каждом выполнении.<br />
Эта логика показана на диаграмме выше, а в кратком виде выглядит так:</p>
<ul>
<li>Мы определяем последний раздел в таблице Silver <strong>bluesky_dedup</strong> с помощью служебного поля <code>_partition_id</code>.<br />
Из этого значения вычитаем 40 минут, получая раздел, который был создан два окна назад (X &#8212; 2) — называем его <code>current_partition</code>.</li>
<li>В целевой таблице уровня Gold bluesky есть столбец <code>_rmt_partition_id</code>,<br />
заполняемый <strong>refreshable materialized view</strong>, где хранится, из какого раздела уровня Silver поступило каждое событие.<br />
Мы используем это поле, чтобы определить последний успешно перенесённый раздел, прибавляем 20 минут,<br />
получая раздел, который нужно обработать следующим — <code>next_to_process</code>.</li>
</ul>
<p>Если <code>next_to_process = 1200</code>, это значит, что таблица bluesky пуста<br />
(0 + 1200 секунд = 1200), и ещё не было ни одной передачи данных.<br />
В этом случае мы используем <code>current_partition</code> и вставляем все события, где <code>_partition_id = current_partition</code>.</p>
<p>Если <code>next_to_process &gt; 1200</code>, значит, переносы уже выполнялись.<br />
Если <code>current_partition &gt;= next_to_process</code>, то мы отстаём не менее чем на 40 минут (2 окна),<br />
и используем значение <code>next_to_process</code>, вставляя все события, где <code>_partition_id = next_to_process</code>.<br />
Если же <code>current_partition &lt; next_to_process</code>, выполняется <code>noop</code> (ничего не происходит) — данные не переносятся.</p>
<p>Эта логика устойчива к сбоям, таким как пропуски выполнения каждые 20 минут, повторные запуски или задержки выполнения. В результате формируется <code>Refreshable Materialized View</code>, в SELECT-запросе которого инкапсулирована описанная выше логика.</p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE MATERIALIZED VIEW bluesky.blue_sky_dedupe_rmv
REFRESH EVERY 20 MINUTE APPEND TO bluesky.bluesky
(
  `data` JSON(SKIP `commit.record.reply.root.record`, SKIP `commit.record.value.value`),
  `kind` LowCardinality(String),
  `bluesky_ts` DateTime64(6),
  `_rmt_partition_id` LowCardinality(String)
)
AS WITH
  (
          --step 1
        SELECT toUnixTimestamp(subtractMinutes(CAST(_partition_id, 'DateTime'), 40))
        FROM bluesky.bluesky_dedup
        GROUP BY _partition_id
        ORDER BY _partition_id DESC
        LIMIT 1
  ) AS current_partition,
  (
          --step 2
        SELECT toUnixTimestamp(addMinutes(CAST(max(partition_id), 'DateTime'), 20))
        FROM bluesky.latest_partition
  ) AS next_to_process
SELECT
  data,
  kind,
  bluesky_ts,
  _partition_id AS _rmt_partition_id
FROM bluesky.bluesky_dedup
FINAL
--step 3 &amp; 4
WHERE _partition_id = CAST(if(next_to_process = 1200, current_partition, if(current_partition &gt;= next_to_process, next_to_process, 0)), 'String')
SETTINGS do_not_merge_across_partitions_select_final = 1</pre><p>Это представление выполняется каждые 20 минут, передавая очищенные и дедуплицированные данные в <strong>уровень Gold</strong>. Следует отметить, что данные появляются в <strong>Gold</strong> с задержкой около 40 минут, хотя при необходимости пользователи могут выполнять запросы к уровню <strong>Silver</strong> для получения более свежих данных.</p>
<p>Внимательный читатель заметит, что в шаге 2 и на диаграмме выше наш запрос использует таблицу <code>latest_partition</code>, а не обращается напрямую к <code>_rmt_partition_id</code> в таблице <strong>bluesky</strong> уровня Gold.<br />
Эта таблица создаётся с помощью инкрементного материализованного представления (incremental materialized view) и служит оптимизацией, которая ускоряет определение следующего раздела для обработки.</p>
<p>Это представление отслеживает последний вставленный раздел в таблицу <strong>Gold</strong> и выглядит следующим образом.</p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE MATERIALIZED VIEW bluesky.latest_partition_mv TO bluesky.latest_partition
(
	`partition_id` UInt32
)
AS SELECT max(CAST(_rmt_partition_id, 'UInt32')) AS partition_id
FROM bluesky.bluesky

CREATE TABLE bluesky.latest_partition
(
	`partition_id` SimpleAggregateFunction(max, UInt32)
)
ENGINE = AggregatingMergeTree
ORDER BY tuple()</pre><p></p>
<h1>Золотой уровень для анализа данных (Gold Layer для аналитики)</h1>
<p>Указанное выше <strong>refreshable materialized view</strong> периодически отправляет данные в таблицу уровня Gold — bluesky.</p>
<p>Схема этой таблицы показана ниже:</p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE bluesky.bluesky
(
	`data` JSON(SKIP `commit.record.reply.root.record`, SKIP `commit.record.value.value`),
	`kind` LowCardinality(String),
	`bluesky_ts` DateTime64(6),
	`_rmt_partition_id` LowCardinality(String)
)
ENGINE = MergeTree
PARTITION BY toStartOfInterval(bluesky_ts, toIntervalMonth(1))
ORDER BY (kind, bluesky_ts)</pre><p>Поскольку данные полностью дедуплицированы до момента вставки, мы можем использовать стандартный <strong>MergeTree</strong>.</p>
<p><strong>Ключ сортировки (ORDER BY)</strong> выбирается исключительно на основе шаблонов доступа потребителей данных и с целью оптимизации сжатия.</p>
<p><strong>Таблица разделена по месяцам (partitioned by month)</strong> &#8212; в первую очередь для удобства управления данными, а также потому, что ожидается, что большинство запросов будут обращаться к самым последним данным.</p>
<p>Обратите внимание: хотя мы по-прежнему используем тип данных <strong>JSON</strong> на этом уровне,<br />
возможно выполнение дополнительных трансформаций данных на этапе предыдущего<br />
<strong>refreshable materialized view</strong> — например:</p>
<ul>
<li>извлечение часто используемых полей в корень таблицы,</li>
<li>использование столбцов типа <strong>ALIAS</strong>, чтобы упростить синтаксис запросов и повысить удобство анализа.</li>
</ul>
<h2>Материализованные представления для общих запросов (Для часто запрашиваемых метрик)</h2>
<p>Этот <strong>слой gold</strong> должен быть полностью оптимизирован для выполнения запросов со стороны прикладных систем и потребителей данных. Хотя наш ключ сортировки направлен на то, чтобы облегчить этот процесс, не все шаблоны доступа будут одинаковыми. До настоящего времени наиболее распространённым применением инкрементных материализованных представлений было выполнение фильтрации и вставки данных между слоями. Однако наше более раннее использование представления для вычисления следующего раздела (partition) намекало на то, как ещё можно оптимизировать другие запросы.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_10.png"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2280" src="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_10.png" alt="" width="1370" height="611" srcset="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_10.png 1370w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_10-300x134.png 300w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_10-1024x457.png 1024w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_10-768x343.png 768w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_10-450x201.png 450w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_10-780x348.png 780w" sizes="(max-width: 1370px) 100vw, 1370px" /></a></p>
<p>Помимо фильтрации и отправки подмножеств данных в целевую таблицу с другими ключами сортировки (оптимизированными под иные шаблоны доступа), материализованные представления могут использоваться для предварительного вычисления агрегатов во время вставки данных в таблицу gold.</p>
<p>Результаты таких агрегатов будут представлять собой уменьшенную форму исходных данных (частичный набросок, если речь идёт об агрегации). Это не только упрощает последующие запросы к целевой таблице, но и обеспечивает более высокую скорость выполнения, поскольку вычисления переносятся с момента запроса на момент вставки, тем самым снижая время отклика при запросе.</p>
<p>Полное руководство по материализованным представлениям можно найти здесь.</p>
<p>В качестве примера рассмотрим наш предыдущий запрос, который вычисляет наиболее распространённые типы <strong>commit-событий</strong>:</p><pre class="urvanov-syntax-highlighter-plain-tag">SELECT data.commit.collection AS collection, count() AS c, uniq(data.did) AS users
FROM bluesky
WHERE kind = 'commit'
GROUP BY ALL
ORDER BY c DESC
LIMIT 10

┌─collection───────────────┬─────────c─┬───users─┐
│ app.bsky.feed.like       │ 269979403 │ 5270604 │
│ app.bsky.graph.follow    │ 150891706 │ 5631987 │
│ app.bsky.feed.post       │  46886207 │ 3083647 │
│ app.bsky.feed.repost     │  33249341 │ 1956986 │
│ app.bsky.graph.block     │   9789707 │  993578 │
│ app.bsky.graph.listitem  │   3231676 │  102020 │
│ app.bsky.actor.profile   │   1731669 │ 1280895 │
│ app.bsky.graph.listblock │  263667   │  105310 │
│ app.bsky.feed.threadgate │  215715   │   49871 │
│ app.bsky.feed.postgate   │   99625   │   19960 │
└──────────────────────────┴───────────┴─────────┘

10 rows in set. Elapsed: 6.445 sec. Processed 516.53 million rows, 45.50 GB (80.15 million rows/s., 7.06 GB/s.)
Peak memory usage: 986.51 MiB.</pre><p>Для 500 миллионов событий выполнение этого запроса занимает около 6 секунд.<br />
Чтобы преобразовать его в инкрементное материализованное представление, необходимо подготовить таблицу, которая будет получать результаты инкрементной агрегации:</p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE bluesky.top_post_types
(
  `collection` LowCardinality(String),
  `posts` SimpleAggregateFunction(sum, UInt64),
  `users` AggregateFunction(uniq, String)
)
ENGINE = AggregatingMergeTree
ORDER BY collection</pre><p>Обратите внимание, что нам необходимо использовать <strong>AggregatingMergeTree</strong> и указать ключ сортировки как ключ группировки — результаты агрегации с одинаковыми значениями этого столбца будут объединяться.</p>
<p>Инкрементные результаты должны храниться в специальных типах столбцов <strong>SimpleAggregateFunction</strong> и <strong>AggregateFunction</strong> — для этого необходимо указать саму функцию и связанный с ней тип данных.</p>
<p>Ниже показано соответствующее материализованное представление, которое заполняет эту таблицу при вставке строк в таблицу <strong>gold</strong>. Обратите внимание, что используется суффикс &#8212; <code>State</code>, чтобы явно сгенерировать состояние агрегации:</p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE MATERIALIZED VIEW top_post_types_mv TO top_posts_types
AS
SELECT data.commit.collection AS collection, count() AS posts,
  uniqState(CAST(data.did, 'String')) AS users
FROM bluesky
WHERE kind = 'commit'
GROUP BY ALL

When querying this table, we use the -Merge suffix to merge aggregation states.


SELECT collection,
       sum(posts) AS posts,
       uniqMerge(users) AS users
FROM top_post_types
GROUP BY collection
ORDER BY posts DESC
LIMIT 10

10 rows in set. Elapsed: 0.042 sec.</pre><p>Производительность запроса улучшилась более чем в 150 раз!</p>
<p>Ниже приведена финальная диаграмма архитектуры, показывающая все наши уровни:</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_11.png"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2281" src="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_11.png" alt="" width="1795" height="883" srcset="https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_11.png 1795w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_11-300x148.png 300w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_11-1024x504.png 1024w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_11-768x378.png 768w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_11-1536x756.png 1536w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_11-450x221.png 450w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_11-780x384.png 780w, https://datatalks.ru/wp-content/uploads/2025/10/Medallion_with_Bluesky_11-1600x787.png 1600w" sizes="(max-width: 1795px) 100vw, 1795px" /></a></p>
<h2>Примеры запросов и визуализации на sql.clickhouse.com</h2>
<p>Приведённый выше пример представляет собой очень простую демонстрацию. Эти данные доступны на сайте <a href="https://sql.clickhouse.com/" target="_blank" rel="noopener">sql.clickhouse.com</a>, где описанный выше рабочий процесс <strong>Medallion</strong> выполняется непрерывно. Мы также предоставили дополнительные материализованные представления в качестве примеров для эффективного выполнения запросов.</p>
<p>Например, чтобы определить, в какое время суток пользователи чаще всего ставят лайки, публикуют и репостят в Bluesky, можно выполнить следующий запрос:</p><pre class="urvanov-syntax-highlighter-plain-tag">SELECT event, hour_of_day, sum(count) as count
FROM bluesky.events_per_hour_of_day
WHERE event in ['post', 'repost', 'like']
GROUP BY event, hour_of_day
ORDER BY hour_of_day;

72 rows in set. Elapsed: 0.007 sec.</pre><p>Запрос выполняется за 7 миллисекунд.</p>
<p>Вы можете запустить этот запрос в нашем playground, чтобы отобразить результат в виде графика.</p>
<p>Ниже приведено соответствующее материализованное представление и целевая таблица, которая заполняется по мере вставки строк в gold-таблицу:</p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE bluesky.events_per_hour_of_day
(
    event LowCardinality(String),
    hour_of_day UInt8,
    count SimpleAggregateFunction(sum, UInt64)
)
ENGINE = AggregatingMergeTree
ORDER BY (event, hour_of_day);


CREATE MATERIALIZED VIEW bluesky.events_per_hour_of_day_mv TO bluesky.events_per_hour_of_day
AS SELECT
    extract(data.commit.collection, '\\.([^.]+)</pre><p>Полный список запросов и соответствующих им представлений можно посмотреть здесь.<br />
Кроме того, вы можете напрямую выполнять запросы к <strong>gold</strong> или <strong>silver таблицам</strong>!</p>
<p><strong>Некоторые примеры, с которых можно начать:</strong></p>
<ul>
<li><a href="https://sql.clickhouse.com/?query_id=P2VKEOGYQVHFPA8F5IFZ2P&amp;run_query=true&amp;tab=results" target="_blank" rel="noopener">Общее количество событий (Total events)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=51KVUJ5FGJUQV9XU13JKL3&amp;run_query=true&amp;tab=charts" target="_blank" rel="noopener">Когда пользователи чаще всего используют Bluesky (When do people use BlueSky)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=9WMMTPMMP7TAIWO5ZGWZZE&amp;run_query=true&amp;tab=results" target="_blank" rel="noopener">Самые популярные типы событий (Top event types)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=RJR6SMBYEKJSSWWUXFHP1U&amp;run_query=true&amp;tab=charts" target="_blank" rel="noopener">Топ типов событий по количеству (Top event types by count)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=5C6SW7OHKEVFLRDMED2WNB&amp;run_query=true&amp;tab=charts" target="_blank" rel="noopener">Топ типов событий по уникальным пользователям (Top event types by unique users)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=8YAFPZQXXCGD75842UKE2W&amp;run_query=true&amp;tab=results" target="_blank" rel="noopener">Самые залайканные посты (Most liked posts)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=7ICWGHB7HIIEWCMYFAWIAE&amp;run_query=true&amp;tab=results" target="_blank" rel="noopener">Самые залайканные посты о ClickHouse (Most liked posts about ClickHouse)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=BP4SSVSKXB4EJCMFHU8B6D&amp;run_query=true&amp;tab=results" target="_blank" rel="noopener">Самые часто reposted посты (Most reposted posts)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=ATT83TXCQE8DUDCM84GB6E&amp;run_query=true&amp;tab=charts" target="_blank" rel="noopener">Самые используемые языки (Most used languages)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=H2CYZJDRCXCFYLPXMJVRCR&amp;run_query=true&amp;tab=results" target="_blank" rel="noopener">Самые залайканные пользователи (Most liked users)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=SH7GXXK4BYPHELXM5AHWDJ&amp;run_query=true&amp;tab=results">Самые часто reposted пользователи (Most reposted users)</a></li>
</ul>
<h1>Заключительные мысли</h1>
<p>В этом блоге мы продемонстрировали полностью реализованную архитектуру Medallion, построенную исключительно на ClickHouse, показав, как его мощные возможности позволяют преобразовывать “сырые”, полуструктурированные данные в качественные, готовые к запросам наборы данных.</p>
<p><strong>Через уровни Bronze, Silver и Gold</strong> мы решили типичные проблемы, такие как повреждённые данные (malformed data), несогласованность структуры и значительное количество дубликатов.<br />
Благодаря использованию типа данных JSON в ClickHouse, нам удалось эффективно обрабатывать по своей природе полуструктурированные и динамичные данные, при этом сохраняя высокую производительность.</p>
<p>Хотя эта архитектура обеспечивает надёжный и гибкий рабочий процесс, она всё же вносит определённые задержки по мере перемещения данных между слоями.<br />
В нашем решении <strong>“окна дедупликации” (deduplication windows)</strong> помогли минимизировать эти задержки, однако остаётся компромисс между скоростью доставки данных в реальном времени и качеством данных.<br />
Поэтому <strong>архитектура Medallion</strong> особенно хорошо подходит для наборов данных с высокой степенью дублирования и менее критичными требованиями к мгновенной доступности данных.</p><pre class="urvanov-syntax-highlighter-plain-tag">) AS event,
    toHour(bluesky_ts) as hour_of_day,
    count() AS count
FROM bluesky.bluesky
WHERE (kind = 'commit')
GROUP BY event, hour_of_day;</pre><p>Полный список запросов и соответствующих им представлений можно посмотреть здесь.<br />
Кроме того, вы можете напрямую выполнять запросы к <strong>gold</strong> или <strong>silver таблицам</strong>!</p>
<p><strong>Некоторые примеры, с которых можно начать:</strong></p>
<ul>
<li><a href="https://sql.clickhouse.com/?query_id=P2VKEOGYQVHFPA8F5IFZ2P&amp;run_query=true&amp;tab=results" target="_blank" rel="noopener">Общее количество событий (Total events)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=51KVUJ5FGJUQV9XU13JKL3&amp;run_query=true&amp;tab=charts" target="_blank" rel="noopener">Когда пользователи чаще всего используют Bluesky (When do people use BlueSky)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=9WMMTPMMP7TAIWO5ZGWZZE&amp;run_query=true&amp;tab=results" target="_blank" rel="noopener">Самые популярные типы событий (Top event types)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=RJR6SMBYEKJSSWWUXFHP1U&amp;run_query=true&amp;tab=charts" target="_blank" rel="noopener">Топ типов событий по количеству (Top event types by count)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=5C6SW7OHKEVFLRDMED2WNB&amp;run_query=true&amp;tab=charts" target="_blank" rel="noopener">Топ типов событий по уникальным пользователям (Top event types by unique users)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=8YAFPZQXXCGD75842UKE2W&amp;run_query=true&amp;tab=results" target="_blank" rel="noopener">Самые залайканные посты (Most liked posts)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=7ICWGHB7HIIEWCMYFAWIAE&amp;run_query=true&amp;tab=results" target="_blank" rel="noopener">Самые залайканные посты о ClickHouse (Most liked posts about ClickHouse)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=BP4SSVSKXB4EJCMFHU8B6D&amp;run_query=true&amp;tab=results" target="_blank" rel="noopener">Самые часто reposted посты (Most reposted posts)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=ATT83TXCQE8DUDCM84GB6E&amp;run_query=true&amp;tab=charts" target="_blank" rel="noopener">Самые используемые языки (Most used languages)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=H2CYZJDRCXCFYLPXMJVRCR&amp;run_query=true&amp;tab=results" target="_blank" rel="noopener">Самые залайканные пользователи (Most liked users)</a></li>
<li><a href="https://sql.clickhouse.com/?query_id=SH7GXXK4BYPHELXM5AHWDJ&amp;run_query=true&amp;tab=results">Самые часто reposted пользователи (Most reposted users)</a></li>
</ul>
<h1>Заключительные мысли</h1>
<p>В этом блоге мы продемонстрировали полностью реализованную архитектуру Medallion, построенную исключительно на ClickHouse, показав, как его мощные возможности позволяют преобразовывать “сырые”, полуструктурированные данные в качественные, готовые к запросам наборы данных.</p>
<p>Через уровни Bronze, Silver и Gold мы решили типичные проблемы, такие как <strong>повреждённые данные (malformed data)</strong>, несогласованность структуры и значительное количество дубликатов.<br />
Благодаря использованию типа данных JSON в ClickHouse, нам удалось эффективно обрабатывать по своей природе полуструктурированные и динамичные данные, при этом сохраняя высокую производительность.</p>
<p>Хотя эта архитектура обеспечивает надёжный и гибкий рабочий процесс, она всё же вносит определённые задержки по мере перемещения данных между слоями.</p>
<p>В нашем решении <strong>“окна дедупликации” (deduplication windows)</strong> помогли минимизировать эти задержки, однако остаётся компромисс между скоростью доставки данных в реальном времени и качеством данных.</p>
<p>Поэтому <strong>архитектура Medallion</strong> особенно хорошо подходит для наборов данных с высокой степенью дублирования и менее критичными требованиями к мгновенной доступности данных.</p>
<p>Сообщение <a href="https://datatalks.ru/building-a-medallion-architecture-for-bluesky-json-data-with-clickhouse/">Построение архитектуры Medallion для данных Bluesky в формате JSON с помощью ClickHouse</a> появились сначала на <a href="https://datatalks.ru">DataTalks.RU. Data Engineering / DWH / Data Pipeline</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://datatalks.ru/building-a-medallion-architecture-for-bluesky-json-data-with-clickhouse/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Создание архитектуры Medallion с помощью ClickHouse</title>
		<link>https://datatalks.ru/medallion-architecture-with-clickhouse/</link>
					<comments>https://datatalks.ru/medallion-architecture-with-clickhouse/#respond</comments>
		
		<dc:creator><![CDATA[Data Engineer (Admin)]]></dc:creator>
		<pubDate>Wed, 01 Jan 2025 18:34:23 +0000</pubDate>
				<category><![CDATA[ClickHouse]]></category>
		<category><![CDATA[Data Architecture / Data Modeling]]></category>
		<category><![CDATA[Bronze layer]]></category>
		<category><![CDATA[ClickPipes]]></category>
		<category><![CDATA[Gold layer]]></category>
		<category><![CDATA[Medallion architecture]]></category>
		<category><![CDATA[S3Queue]]></category>
		<category><![CDATA[Silver layer]]></category>
		<category><![CDATA[архитектура Medallion]]></category>
		<guid isPermaLink="false">https://datatalks.ru/?p=527</guid>

					<description><![CDATA[<p>Перевод статьи: Building a Medallion architecture with ClickHouse Построение архитектуры Medallion с использованием ClickHouse Крупномасштабная обработка данных требует эффективной структуризации, трансформации и анализа наборов данных. Архитектура Medallion — это шаблон проектирования рабочего процесса данных для организации и повышения их качества посредством поэтапных преобразований, который широко используется для управления сложными наборами данных. Обычно она реализуется с [&#8230;]</p>
<p>Сообщение <a href="https://datatalks.ru/medallion-architecture-with-clickhouse/">Создание архитектуры Medallion с помощью ClickHouse</a> появились сначала на <a href="https://datatalks.ru">DataTalks.RU. Data Engineering / DWH / Data Pipeline</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><strong>Перевод статьи:</strong> <a href="https://clickhouse.com/blog/building-a-medallion-architecture-with-clickhouse" target="_blank" rel="noopener">Building a Medallion architecture with ClickHouse</a></p>
<h1>Построение архитектуры Medallion с использованием ClickHouse</h1>
<p>Крупномасштабная обработка данных требует эффективной структуризации, трансформации и анализа наборов данных. <strong>Архитектура Medallion</strong> — это шаблон проектирования рабочего процесса данных для организации и повышения их качества посредством поэтапных преобразований, который широко используется для управления сложными наборами данных. Обычно она реализуется с помощью инструментов, таких как Spark и Delta Lake, и позволяет систематически очищать «сырой», неструктурированный набор данных до состояния, пригодного для анализа и прикладных приложений.</p>
<p>В этом посте мы исследуем, как <strong>архитектура Medallion</strong> может быть полностью реализована с использованием нативных конструкций ClickHouse, что устраняет необходимость во внешних фреймворках или инструментах. Благодаря высокой производительности запросов, поддержке широкого спектра форматов данных и встроенным функциям для управления и преобразования данных, ClickHouse можно эффективно использовать на каждом этапе этой архитектуры.</p>
<p><span style="color: #ff6600;"><strong>Цель этого поста</strong> </span>— показать, как три этапа архитектуры Medallion могут быть теоретически реализованы с помощью ClickHouse.</p>
<p><em>В следующем посте мы продемонстрируем это на практике, используя поток данных из набора Bluesky.</em> Этот набор данных включает множество типичных проблем, таких как некорректные события, высокая степень дублирования и несоответствия временных меток, что делает его подходящим для демонстрации описанных процессов.</p>
<h2>Что такое архитектура Medallion?</h2>
<p><strong>Архитектура Medallion</strong> — это широко используемый рабочий процесс обработки данных, который организует данные в многоуровневую структуру, где качество данных постепенно улучшается по мере их прохождения через этапы. Хотя она широко применяется в озёрах данных (data lake houses), эту архитектуру также можно использовать в режиме реального времени в хранилищах данных для обеспечения эффективного управления и трансформации данных.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/01/what_is_medallion_architecture.jpeg" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="aligncenter wp-image-537 size-full" src="https://datatalks.ru/wp-content/uploads/2025/01/what_is_medallion_architecture.jpeg" alt="" width="1639" height="644" srcset="https://datatalks.ru/wp-content/uploads/2025/01/what_is_medallion_architecture.jpeg 1639w, https://datatalks.ru/wp-content/uploads/2025/01/what_is_medallion_architecture-300x118.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/01/what_is_medallion_architecture-1024x402.jpeg 1024w, https://datatalks.ru/wp-content/uploads/2025/01/what_is_medallion_architecture-768x302.jpeg 768w, https://datatalks.ru/wp-content/uploads/2025/01/what_is_medallion_architecture-1536x604.jpeg 1536w, https://datatalks.ru/wp-content/uploads/2025/01/what_is_medallion_architecture-450x177.jpeg 450w, https://datatalks.ru/wp-content/uploads/2025/01/what_is_medallion_architecture-780x306.jpeg 780w, https://datatalks.ru/wp-content/uploads/2025/01/what_is_medallion_architecture-1600x629.jpeg 1600w" sizes="(max-width: 1639px) 100vw, 1639px" /></a></p>
<p>Архитектура включает три слоя (или этапа), каждый из которых выполняет определённые задачи в процессе обработки данных:</p>
<h3><strong>Bronze слой</strong></h3>
<p>Этот слой служит зоной приёма для сырых, необработанных данных непосредственно из исходной системы — своего рода «зона промежуточного хранения». Данные хранятся в их оригинальной структуре с минимальными преобразованиями и дополнительными метаданными. Этот слой оптимизирован для быстрой загрузки данных и может служить историческим архивом исходных данных, которые всегда доступны для повторной обработки или отладки.</p>
<p><strong>Следует ли хранить все данные в бронзовом слое — вопрос спорный.</strong> Некоторые пользователи предпочитают фильтровать данные и применять преобразования, например, упрощение JSON, переименование полей или отсеивание некорректных данных. Мы не занимаем жёсткую позицию, но рекомендуем оптимизировать хранение для использования только серебряным слоем, а не другими потребителями.</p>
<h3><strong>Silver слой</strong></h3>
<p><strong>На этом этапе данные очищаются, дублирующиеся записи удаляются, и они приводятся к единой схеме.</strong> Сырые данные из бронзового слоя обогащаются и преобразуются для получения более точного и согласованного представления. Данные на этом этапе становятся пригодными для использования в масштабах предприятия, например, для задач машинного обучения и аналитики. Модель данных должна формироваться на этом уровне с акцентом на согласованность первичных и внешних ключей для упрощения последующих соединений.</p>
<p>Хотя это не является стандартом, приложения и конечные потребители могут обращаться к этому слою. Обычно это бизнес-приложения, которым требуется весь очищенный набор данных, например, для рабочих процессов машинного обучения. Важно отметить, что качество данных не улучшится после этого этапа, только удобство их эффективного запроса.</p>
<h3><strong>Gold слой</strong></h3>
<p><strong>Этот слой содержит полностью подготовленные, бизнес-ориентированные и проектно-специфические наборы данных, которые делают данные более доступными (и производительными) для конечных пользователей.</strong> Эти наборы данных часто денормализуются или предварительно агрегируются для оптимальной производительности при чтении и могут быть составлены из нескольких таблиц предыдущего серебряного слоя. Здесь внимание сосредоточено на применении окончательных преобразований и обеспечении высочайшего качества данных для потребления конечными пользователями или приложениями, такими как отчётность и пользовательские дашборды.</p>
<p>Этот многоуровневый подход к обработке данных нацелен на эффективное решение таких проблем, как качество данных, дублирование и несоответствия в схемах. Постепенно преобразуя необработанные данные, архитектура Medallion обеспечивает чёткую родословную данных и их последовательное улучшение, чтобы они были готовы к анализу или операционному использованию.</p>
<p>Хотя мы считаем, что название &#171;архитектура медальонов&#187; могло бы лучше отражать содержание её уровней, полезные процессы и дисциплина, которую она способствует, делают её ценной.</p>
<h2>Архитектура Medallion с использованием ClickHouse</h2>
<p>В этом разделе мы предлагаем, как каждый уровень архитектуры Medallion может быть реализован с помощью ClickHouse, и как встроенные функции системы могут быть использованы для передачи данных между уровнями. Этот подход является гибким и развивается на основе нашего внутреннего опыта и отзывов пользователей. Мы будем рады предложениям, которые помогут улучшить эти практики.</p>
<h3><strong>Bronze слой с использованием ClickHouse</strong></h3>
<p>Bronze слой служит точкой входа для сырых, необработанных данных, оптимизированных для высокоскоростной загрузки с использованием гибких и производительных конструкций ClickHouse. Этот слой может также выполнять функцию исторического архива, сохраняя необработанные данные для их родословной, отладки или повторной обработки без необходимости в полном очищении или удалении дубликатов на начальном этапе. Такой акцент на производительность и гибкость создаёт надёжную основу для дальнейшей обработки и преобразования данных на следующих этапах.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_bronze_layer.jpeg" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="aligncenter wp-image-539 size-full" src="https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_bronze_layer.jpeg" alt="" width="754" height="777" srcset="https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_bronze_layer.jpeg 754w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_bronze_layer-291x300.jpeg 291w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_bronze_layer-450x464.jpeg 450w" sizes="(max-width: 754px) 100vw, 754px" /></a></p>
<p>Основные характеристики Bronze слоя при использовании ClickHouse.</p>
<h4><strong>Загрузка данных из источников</strong></h4>
<p>Данные могут загружаться в этот слой напрямую через клиенты, ELT-инструменты, такие как Fivetran, или потоки из Kafka с использованием ClickPipes или коннектора Kafka для ClickHouse. В ClickHouse Cloud доступны дополнительные возможности для построчного чтения данных из S3-хранилищ через S3Queue и ClickPipes, поддерживающих более 70 форматов данных (включая сжатые), таких как Parquet и форматы для озёр данных, например Iceberg. Подход с использованием S3 как зоны промежуточного хранения особенно часто применяется при обработке больших полуструктурированных данных с менее согласованной схемой.</p>
<h4><strong>Оптимизация для быстрой вставки</strong></h4>
<p>Bronze слой, как правило, реализуется с использованием MergeTree, который разработан для эффективной обработки быстрых вставок. Схема таблицы и ключ упорядочивания настраиваются для оптимизации операций вставки, а также для обеспечения высокой производительности чтения в случае необходимости повторной обработки данных (для Silver слоя) или их анализа на предмет проблем с качеством. Учитывая, что этот слой не предназначен для конечных потребителей, мы рекомендуем оптимизировать ключ упорядочивания для эффективного полного сканирования данных, например, использовать ключ, соответствующий порядку чтения — обычно по времени.</p>
<h4><strong>Поддержка полуструктурированных данных в формате JSON</strong></h4>
<p>Новый тип JSON в ClickHouse является ключевой функцией для обработки полуструктурированных данных в Bronze слое. Этот тип позволяет загружать данные с динамическими и непредсказуемыми схемами без необходимости строгого соблюдения структуры на этапе загрузки, что особенно ценно для наборов данных с несогласованными или изменяющимися структурами. Благодаря поддержке динамического JSON Bronze слой становится эффективной зоной приёма необработанных данных, способной обрабатывать сценарии, где согласованность схемы не гарантирована. Обратите внимание, что тип JSON позволяет применять фильтры с контролем, какие пути объектов сохраняются. Пользователи также могут отфильтровывать некорректные JSON перед сохранением в этом слое и, при необходимости, упрощать сложные структуры.</p>
<p>Использование этого типа данных не ограничивается Bronze слоем, так как он может быть полезен и на других уровнях, например, для колонок с динамическими схемами, таких как пользовательские теги.</p>
<h4><strong>Материализованные колонки для базовой обработки</strong></h4>
<p>Материализованные колонки предоставляют мощный механизм для извлечения и преобразования определённых полей во время загрузки данных. Хотя их возможности ограничены, они позволяют эффективно обрабатывать JSON-данные, создавая производные колонки для часто запрашиваемых атрибутов. Такой подход особенно полезен, если JSON-данные включают нерегулярные пути или структуры, что позволяет выполнять базовую предварительную обработку без необходимости полного соблюдения схемы. Эти извлечённые колонки также часто используются для последующей фильтрации.</p>
<h4><strong>Партиционирование и управление хранением данных</strong></h4>
<p>Таблицы Bronze слоя могут быть разделены на партиции для оптимизации производительности запросов и обеспечения эффективного управления данными. Рекомендуется использовать правила TTL для автоматического удаления устаревших данных, которые больше не нужны, что способствует соответствию требованиям и эффективному использованию хранилища.</p>
<h3><strong>Silver слой с использованием ClickHouse</strong></h3>
<p>Silver слой представляет следующий этап в рабочем процессе Medallion, преобразуя сырые данные из Bronze слоя в более согласованную и структурированную форму. Этот уровень решает проблемы качества данных, такие как фильтрация некорректных строк, стандартизация схем и выполнение преобразований. В ClickHouse обычно используется прямое сопоставление таблиц Bronze слоя с их эквивалентами в Silver, но с очищенным и обогащённым набором данных, который служит основой для дальнейшей обработки в Gold слое.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_silver_layer.jpeg" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="aligncenter wp-image-541 size-full" src="https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_silver_layer.jpeg" alt="" width="1387" height="797" srcset="https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_silver_layer.jpeg 1387w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_silver_layer-300x172.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_silver_layer-1024x588.jpeg 1024w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_silver_layer-768x441.jpeg 768w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_silver_layer-450x259.jpeg 450w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_silver_layer-780x448.jpeg 780w" sizes="(max-width: 1387px) 100vw, 1387px" /></a></p>
<h4><strong>Инкрементальные материализованные представления</strong></h4>
<p>Silver слой, как правило, заполняется с использованием инкрементальных материализованных представлений, привязанных к таблицам Bronze слоя. Эти представления выполняют запросы на новых блоках данных, добавленных в Bronze слой, применяя фильтрацию, преобразования и нормализацию схемы перед записью результатов в таблицы Silver слоя для сохранения. Такие представления позволяют эффективно и непрерывно преобразовывать данные, а при использовании нескольких представлений пользователи могут создавать разные версии данных, каждая из которых предназначена для определённых таблиц Silver слоя с учётом конкретных задач. Кроме того, некорректные или необрабатываемые строки могут быть перенаправлены в очередь &#171;мертвых писем&#187; (dead letter queue) через отдельные материализованные представления, что позволяет их проверить и, возможно, восстановить, не засоряя основной набор данных.</p>
<h4><strong>Обработка дублирования и CDC</strong></h4>
<p>Для сценариев, требующих удаления дубликатов или обработки потоков <strong>Change Data Capture (CDC)</strong>, можно использовать движок таблиц <code>ReplacingMergeTree</code>. Ключ упорядочивания этого движка используется для выполнения удаления дубликатов (с уникальными наборами значений, идентифицирующими строку), причём обновления обрабатываются как версионные вставки — это особенно полезно в сценариях CDC. Обратите внимание, что <code>ReplacingMergeTree</code> выполняет удаление дубликатов во время слияния данных, поэтому он обеспечивает только возможную согласованность, требуя использования оператора FINAL при запросах, чтобы гарантировать отсутствие дубликатов в результатах. В общем случае мы рекомендуем конечным приложениям осторожно обращаться к этому слою, так как использование FINAL может значительно увеличить время выполнения запросов.</p>
<h4><strong>Партиционирование и управление хранением данных</strong></h4>
<p>Аналогично Bronze слою, таблицы <strong>Silver слоя</strong> могут быть разделены на партиции для оптимизации производительности запросов и управления данными с использованием TTL. Партиционирование также может улучшить производительность при чтении из таблиц <code>ReplacingMergeTree</code> с использованием <code>FINAL</code>. Мы рекомендуем следовать лучшим практикам, таким как оптимизация операций слияния, чтобы поддерживать высокую производительность. Так как этот слой не является долгосрочным архивом и может не использоваться в качестве источника для последующих этапов, данные могут храниться в этом слое более короткий период, чем в <strong>Bronze</strong> и <strong>Gold слоях</strong>, с более короткими интервалами партиционирования.</p>
<h3><strong>Gold слой с использованием ClickHouse</strong></h3>
<p><strong>Gold слой</strong> представляет заключительный этап <strong>архитектуры Medallion</strong>, где данные преобразуются в полностью денормализованные, готовые для бизнеса наборы данных, оптимизированные для использования конечными приложениями и аналитикой. Этот слой заполняется из <strong>Silver слоя</strong> с использованием обновляемых материализованных представлений, выполняющих сложные преобразования, включая соединения и агрегации. Это обеспечивает максимально удобную для использования форму данных, сводя к минимуму необходимость в объединениях и даже агрегациях на этапе выполнения запросов. Таблицы <strong>Gold слоя</strong> разрабатываются для обеспечения высокой производительности, поддерживая конечные приложения с минимальной задержкой и максимальной эффективностью.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_gold_layer.jpeg" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="aligncenter wp-image-543 size-full" src="https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_gold_layer.jpeg" alt="" width="1845" height="654" srcset="https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_gold_layer.jpeg 1845w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_gold_layer-300x106.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_gold_layer-1024x363.jpeg 1024w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_gold_layer-768x272.jpeg 768w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_gold_layer-1536x544.jpeg 1536w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_gold_layer-450x160.jpeg 450w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_gold_layer-780x276.jpeg 780w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_gold_layer-1600x567.jpeg 1600w" sizes="(max-width: 1845px) 100vw, 1845px" /></a></p>
<h4><strong>Обновляемые материализованные представления</strong></h4>
<p>В отличие от инкрементальных материализованных представлений, используемых для заполнения Silver слоя, Gold слой заполняется с помощью обновляемых материализованных представлений. Эти представления выполняются периодически для таблиц Silver слоя и позволяют выполнять продвинутые преобразования, такие как сложные объединения, денормализуя данные перед их записью в таблицы Gold слоя. При использовании данных из таблиц Silver слоя с движком ReplacingMergeTree, эти представления могут выполнять запросы с оператором FINAL, чтобы гарантировать вставку полностью очищенных от дубликатов данных в Gold слой. Такой подход обеспечивает наивысшее качество данных, сохраняя при этом гибкость для выполнения сложных запросов.</p>
<h4><strong>Проектирование таблиц для конечных приложений</strong></h4>
<p>Таблицы в Gold слое обычно реализуются с использованием стандартных таблиц <code>MergeTree</code>, где ключи упорядочивания оптимизируются специально под шаблоны доступа конечных приложений. Эти денормализованные наборы данных структурированы таким образом, чтобы минимизировать необходимость в дополнительных объединениях, что позволяет приложениям выполнять быстрые и эффективные запросы. Адаптация схемы и ключей к требованиям пользовательских запросов обеспечивает бесшовную интеграцию с инструментами отчётности, аналитическими панелями и интерактивными интерфейсами.</p>
<h4><strong>Инкрементальные материализованные представления для предвычисленных агрегаций</strong></h4>
<p>Помимо хранения денормализованных наборов данных, Gold слой часто включает инкрементальные материализованные представления для предвычисления агрегаций. Эти представления выполняют запросы с использованием <code>GROUP BY</code> для новых вставок в таблицы Gold слоя, записывая промежуточные результаты агрегации в целевые таблицы с использованием движка <code>AggregatingMergeTree</code>. Перенос вычислений с этапа выполнения запроса на этап вставки значительно снижает задержки запросов. Конечные запросы, в свою очередь, нуждаются только в объединении меньших промежуточных состояний, что делает их высокопроизводительными и подходящими для поддержки пользовательских приложений с богатыми возможностями фильтрации и агрегации.</p>
<p>Наше демонстрационное приложение <code>ClickPy</code> активно использует материализованные представления для предоставления возможностей визуализации и фильтрации данных по набору Python PYPI, содержащему триллион строк. Подробности доступны в репозитории ClickPy на GitHub.</p>
<h2>Полная архитектура Medallion с использованием ClickHouse</h2>
<p>Объединив все описанные выше этапы, мы получаем полную <strong>архитектуру Medallion</strong>, реализованную с помощью <strong>ClickHouse</strong>:</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_full_schema.jpeg" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="aligncenter wp-image-544 size-full" src="https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_full_schema.jpeg" alt="" width="1719" height="614" srcset="https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_full_schema.jpeg 1719w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_full_schema-300x107.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_full_schema-1024x366.jpeg 1024w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_full_schema-768x274.jpeg 768w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_full_schema-1536x549.jpeg 1536w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_full_schema-450x161.jpeg 450w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_full_schema-780x279.jpeg 780w, https://datatalks.ru/wp-content/uploads/2025/01/clickhouse_medallion_architecture_full_schema-1600x571.jpeg 1600w" sizes="(max-width: 1719px) 100vw, 1719px" /></a></p>
<p><strong>Эта архитектура Medallion</strong>, реализованная на основе <strong>ClickHouse</strong>, предлагает структурированный подход к управлению конвейерами данных через каскадные преобразования. Благодаря поддержке более чем 70 форматов файлов, сырые данные могут быть непосредственно загружены в <strong>Bronze слой</strong> с использованием функций <code>s3Queue</code> или <code>ClickPipe</code> (в ClickHouse Cloud), после чего они инкрементально очищаются и обогащаются в Silver слое, а затем подготавливаются в <strong>Gold слое</strong> для оптимального использования в приложениях и аналитике. Используя таблицы <code>MergeTree</code> <strong>ClickHouse</strong>, материализованные представления (инкрементальные/обновляемые) и поддержку различных форматов файлов, эта архитектура позволяет организовать процесс загрузки, преобразования и доставки данных без необходимости использования сторонних инструментов.</p>
<p>Обратите внимание, что пользователи не обязаны развертывать все три этапа этой архитектуры и могут исключить любой из слоев. Например, можно пропустить <strong>Silver слой</strong>, если данные уже предоставляются с минимальными проблемами качества и без дубликатов.</p>
<p>Хотя преимущества здесь весьма убедительны, с чёткой методологией и набором инструментов для предоставления чистых, оптимизированных данных конечным пользователям и приложениям, у данной архитектуры есть и некоторые недостатки.</p>
<p>Таблицы <strong>Gold слоя</strong> по своей природе представляют одни и те же данные в разных формах, каждая из которых оптимизирована для потребляющего её приложения. Это приводит к необходимости дублирования данных. Однако связанные с этим затраты на репликацию можно снизить, используя объектное хранилище для таблиц <code>MergeTree</code> с разделением хранения и вычислений.</p>
<p>Кроме того, архитектура требует управления несколькими уровнями, что добавляет сложности в конвейеры данных. Это требует мониторинга, который можно осуществить с помощью системных таблиц <strong>ClickHouse</strong>, предоставляющих видимость состояния материализованных представлений и процессов миграции данных. Также инструменты, такие как <strong>Grafana</strong>, могут уведомлять о проблемах, таких как сбои представлений или несоответствия данных, благодаря источнику данных <strong>ClickHouse</strong>.</p>
<p>Наиболее сложной для устранения является присущая данной архитектуре задержка в доступности данных из-за необходимости последовательного перемещения данных через каждый слой. В результате эту архитектуру сложнее оптимизировать для использования в реальном времени, где критична оперативная доступность данных.</p>
<h1>Заключительные мысли и вывод</h1>
<p><strong>Архитектура Medallion с использованием ClickHouse</strong> демонстрирует мощный, автономный подход к управлению рабочими процессами данных, обеспечивая загрузку, преобразование и потребление данных. Используя встроенные возможности <strong>ClickHouse</strong>, организации могут строить эффективные, масштабируемые конвейеры, которые предоставляют чистые и оптимизированные наборы данных для аналитики и приложений.</p>
<p>Отличительной особенностью реализации на основе <strong>ClickHouse</strong> является её автономный подход. Весь процесс — от загрузки данных до их преобразования и потребления — происходит нативно в ClickHouse без необходимости использования сторонних инструментов.</p>
<p>В нашем следующем блоге мы покажем шаги для практического развертывания архитектуры Medallion для данных Bluesky, размещённых на sql.clickhouse.com, где вы сможете исследовать и запрашивать каждый уровень архитектуры напрямую.</p>
<p>Сообщение <a href="https://datatalks.ru/medallion-architecture-with-clickhouse/">Создание архитектуры Medallion с помощью ClickHouse</a> появились сначала на <a href="https://datatalks.ru">DataTalks.RU. Data Engineering / DWH / Data Pipeline</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://datatalks.ru/medallion-architecture-with-clickhouse/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Перевод 2 главы &#171;Моделирование данных для аналитики (dbt)&#187;</title>
		<link>https://datatalks.ru/dbt-data-modeling-for-analytics/</link>
					<comments>https://datatalks.ru/dbt-data-modeling-for-analytics/#respond</comments>
		
		<dc:creator><![CDATA[Data Engineer (Admin)]]></dc:creator>
		<pubDate>Wed, 01 Jan 2025 10:34:00 +0000</pubDate>
				<category><![CDATA[Data Architecture / Data Modeling]]></category>
		<category><![CDATA[dbt (Data Build Tool)]]></category>
		<category><![CDATA[Bronze layer]]></category>
		<category><![CDATA[Data Vault]]></category>
		<category><![CDATA[dbt]]></category>
		<category><![CDATA[Dimensional Data Modeling]]></category>
		<category><![CDATA[ERD]]></category>
		<category><![CDATA[Gold layer]]></category>
		<category><![CDATA[Medallion architecture]]></category>
		<category><![CDATA[Silver layer]]></category>
		<category><![CDATA[Звёздная схема]]></category>
		<category><![CDATA[Инмон]]></category>
		<category><![CDATA[кардинальность]]></category>
		<category><![CDATA[Кимболл]]></category>
		<category><![CDATA[Снежинка]]></category>
		<guid isPermaLink="false">https://datatalks.ru/?p=308</guid>

					<description><![CDATA[<p>Глава 2 &#171;Моделирование данных для аналитики (dbt)&#187; В современном мире, ориентированном на данные, организации всё больше полагаются на аналитику данных для получения ценных инсайтов и принятия обоснованных решений. Моделирование данных играет важнейшую роль в этом процессе, обеспечивая прочную основу для структурирования и организации данных, что поддерживает эффективный анализ. Кроме того, понимание концепций моделирования данных и [&#8230;]</p>
<p>Сообщение <a href="https://datatalks.ru/dbt-data-modeling-for-analytics/">Перевод 2 главы &#171;Моделирование данных для аналитики (dbt)&#187;</a> появились сначала на <a href="https://datatalks.ru">DataTalks.RU. Data Engineering / DWH / Data Pipeline</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h1><strong>Глава 2 &#171;Моделирование данных для аналитики (dbt)&#187;</strong></h1>
<p>В современном мире, ориентированном на данные, организации всё больше полагаются на аналитику данных для получения ценных инсайтов и принятия обоснованных решений. Моделирование данных играет важнейшую роль в этом процессе, обеспечивая прочную основу для структурирования и организации данных, что поддерживает эффективный анализ. Кроме того, понимание концепций моделирования данных и нормализации имеет ключевое значение для полного раскрытия потенциала аналитики и получения действенных инсайтов из сложных наборов данных.</p>
<p><strong>Моделирование данных</strong> заключается в определении структуры, взаимосвязей и атрибутов сущностей данных в системе. Важным аспектом моделирования данных является нормализация данных.</p>
<p><strong>Нормализация данных</strong> — это техника устранения избыточности данных и улучшения их целостности. Она включает разделение данных на логические единицы и организацию их в отдельные таблицы, что уменьшает дублирование данных и повышает общую эффективность базы данных. Нормализация обеспечивает хранение данных в структурированном и последовательном виде, что критически важно для точного анализа и надёжных результатов.</p>
<p>В контексте аналитики моделирование данных предоставляет прочную основу для создания аналитических моделей. Аналитики могут проектировать эффективные модели, которые фиксируют релевантную информацию и поддерживают поставленные аналитические цели, понимая взаимосвязи между сущностями и структурами данных. Другими словами, хорошо спроектированная модель данных позволяет аналитикам выполнять сложные запросы, объединять таблицы и агрегировать данные для получения значимых инсайтов.</p>
<p><span style="color: #ff6600;"><strong>Понимание моделирования данных и нормализации критически важно для эффективного анализа данных.</strong></span> Без подходящей модели данных аналитикам может быть трудно получить доступ к данным и правильно их интерпретировать, что может привести к ошибочным выводам и неэффективным решениям. Кроме того, отсутствие нормализации может привести к аномалиям данных, несоответствиям и затруднениям в их агрегировании, что препятствует процессу анализа.</p>
<p>В этой книге мы выделяем SQL и dbt как две основные технологии для поддержания эффективного проекта по аналитической инженерии, что также применимо к проектированию и реализации эффективной модели данных. Причина этого в том, что SQL предоставляет пользователям возможность определять таблицы, манипулировать данными и извлекать информацию благодаря своим мощным возможностям запросов. Его непревзойдённая гибкость и универсальность делают его отличным инструментом для построения и поддержки моделей данных, позволяя пользователям формулировать сложные взаимосвязи и с лёгкостью получать доступ к конкретным подмножествам данных.</p>
<p><strong>В дополнение к SQL, dbt играет центральную роль в этой истории, поднимая искусство моделирования данных на новый уровень.</strong> Он выступает в роли всеобъемлющей платформы для построения и оркестрации сложных конвейеров данных. В рамках этой платформы пользователи могут определять <strong>логику преобразования, применять важные бизнес-правила и создавать многократно используемые модульные компоненты кода, называемые моделями.</strong> Стоит отметить, что dbt выходит за рамки автономной функциональности: он легко <strong><span style="color: #ff6600;">интегрируется с системами контроля версий,</span></strong> упрощая совместную работу и обеспечивая последовательность, возможность аудита и лёгкую воспроизводимость моделей данных.</p>
<p>Ещё одним важным аспектом SQL и dbt в моделировании данных является их <span style="color: #ff6600;"><strong>акцент на тестировании и документации</strong></span>, хотя с определёнными различиями, которые стоит уточнить. В контексте моделирования данных тестирование включает проверку точности, надёжности и соответствия модели данных бизнес-правилам. Хотя важно отметить, что возможности тестирования в dbt отличаются от традиционного юнит-тестирования в разработке программного обеспечения, они выполняют схожую цель. Вместо традиционных юнит-тестов dbt предлагает запросы валидации, которые сопоставимы с теми, что аналитики обычно запускают. Эти запросы проверки обеспечивают качество данных, их целостность и соответствие определённым правилам, создавая уверенность в результатах модели. Более того, dbt превосходит в документации, являясь ценным ресурсом как для аналитиков, так и для заинтересованных сторон. Эта документация упрощает понимание логики и предположений, лежащих в основе модели данных, что повышает прозрачность и способствует эффективному сотрудничеству.</p>
<p>SQL и dbt совместно дают профессионалам в области данных возможность создавать надёжные, масштабируемые и поддерживаемые модели данных, которые способствуют получению аналитических инсайтов и обоснованных решений. Используя эти инструменты, организации могут раскрыть весь потенциал своих данных, стимулируя инновации и получая конкурентное преимущество в современном мире, ориентированном на данные. Комбинирование обоих инструментов в рамках одной архитектуры и стратегии данных приносит значительные преимущества для моделирования данных.</p>
<h1>Краткое введение в моделирование данных</h1>
<p>В мире проектирования баз данных создание структурированной и организованной среды имеет важное значение для эффективного хранения, обработки и использования данных. Моделирование баз данных играет важную роль в достижении этой цели, предоставляя чертёж для представления конкретной реальности или бизнеса и поддержки его процессов и правил.</p>
<p>Однако прежде чем углубляться в создание этого чертежа, следует сосредоточиться на понимании нюансов бизнеса. <span style="color: #003366;"><strong>Понимание операций, терминологии и процессов бизнеса необходимо для создания точных и значимых моделей данных.</strong></span> Путём сбора требований через интервью, анализ документов и изучение процессов мы получаем представление о потребностях бизнеса и требованиях к данным.</p>
<p>Во время этого процесса сбора необходимо сосредоточиться на естественной коммуникации — письменном языке. Формулируя факты о бизнесе в однозначных предложениях, мы обеспечиваем точное отражение бизнеса, свободное от интерпретации. Разбиение сложных предложений на простые структуры с подлежащими, сказуемыми и прямыми дополнениями помогает лаконично зафиксировать реальности бизнеса.</p>
<p>Помимо этих ключевых практик, стоит отметить, что эксперты в данной области, такие как Лоуренс Корр в своей популярной книге <strong>Agile Data Warehouse Design (DecisionOne Press)</strong>, рекомендуют использовать дополнительные техники, такие как визуализация на доске и обсуждение, на начальном этапе проектирования моделей данных. Эти стратегии могут привнести тонкость в процесс, позволяя более глубоко изучить требования бизнеса и гарантировать, что итоговые модели данных будут безупречно соответствовать целям и особенностям бизнеса.</p>
<p>После завершения этапа понимания мы переходим к <strong>трём базовым этапам моделирования базы данных:</strong></p>
<ul>
<li>Концептуальная фаза</li>
<li>Логическая фаза</li>
<li>Физическая фаза</li>
</ul>
<p>Эти этапы представляют собой путь к созданию надёжной и хорошо организованной структуры базы данных.</p>
<h2>Концептуальная фаза моделирования</h2>
<p>Концептуальная фаза моделирования базы данных требует выполнения нескольких важных шагов.</p>
<p>Во-первых, необходимо определить цель и задачи базы данных, а также уточнить конкретные проблемы или требования, которые она должна решать.</p>
<p>Следующим шагом является сбор требований посредством интервьюирования заинтересованных сторон и экспертов предметной области для полного понимания необходимых элементов данных, взаимосвязей и ограничений.</p>
<p>Затем проводится анализ и определение сущностей, что включает в себя идентификацию ключевых объектов или концепций, которые будут представлены в базе данных, и определение их атрибутов и отношений.</p>
<p><strong>На начальном этапе проектирования создаются лёгкие схемы нормализации, которые обеспечивают целостность между определёнными сущностями и их взаимосвязями, а также минимизируют избыточность путём организации сущностей и атрибутов вокруг семантически связанных структур.</strong> Идентификация ключей, включая первичные и внешние ключи, имеет критическое значение для поддержания уникальности и установления связей между таблицами.</p>
<p>Эти проекты базы данных часто создаются с помощью диаграмм, текстовых описаний или других методов, которые фиксируют и эффективно передают дизайн и концепцию базы данных. Одним из самых распространённых инструментов для визуального представления концепций базы данных является <span style="color: #003366;"><strong>диаграмма сущность-связь (ERD)</strong></span>. Визуальные модели, созданные с использованием ERD, служат диаграммным представлением, которое эффективно описывает сущности для моделирования, их взаимосвязи и кардинальность этих взаимосвязей. Используя модель ERD, мы можем визуально описать структуру базы данных, включая сущности как основные компоненты, связи или ассоциации между сущностями, а также количество или степень этих взаимосвязей.</p>
<p>Давайте сделаем очень простой концептуальный дизайн базы данных. Представьте, что O’Reilly хочет отслеживать книги и авторов, которые были ранее опубликованы, а также даты выхода новых книг, которые ещё не изданы. Мы проводим серию интервью с менеджерами издательства и начинаем понимать, какие именно данные необходимо хранить в базе данных. <span style="color: #003366;"><strong>Основная цель — определить задействованные сущности, их связи и атрибуты каждой сущности.</strong></span> Имейте в виду, что это упражнение является иллюстративным и намеренно упрощено.</p>
<p>Мы выделяем три разные сущности в этой подсистеме управления книгами:</p>
<h3><strong>Книга (Book)</strong></h3>
<p>Эта сущность представляет книгу, опубликованную O’Reilly. Атрибуты могут включать:</p>
<ul>
<li>book_id</li>
<li>title</li>
<li>publication_date</li>
<li>ISBN</li>
<li>price</li>
<li>конкретную категорию.</li>
</ul>
<p>Собеседники отметили, что в этой модели каждая книга может относиться только к одной категории.</p>
<h3><strong>Автор (Author)</strong></h3>
<p>Эта сущность представляет автора, который написал книги для O’Reilly.</p>
<p>Атрибуты могут включать:</p>
<ul>
<li>author_id</li>
<li>author_name</li>
<li>email</li>
<li>bio.</li>
</ul>
<h3><strong>Категория (Category)</strong></h3>
<p>Эта сущность представляет категорию книги и может содержать такие атрибуты, как:</p>
<ul>
<li>category_id (уникальный идентификатор)</li>
<li>category_name.</li>
</ul>
<p>Следующим шагом будет определение связей между сущностями. В проектировании баз данных могут существовать различные типы связей между сущностями, и <span style="color: #003366;"><strong>тип связи называют кардинальностью.</strong></span></p>
<p><strong>Например:</strong></p>
<ul>
<li><strong>В отношении &#171;один-к-одному&#187; (<span style="color: #ff6600;">one-to-one</span>)</strong> можно рассматривать связь между сущностями &#171;Книга&#187; и &#171;Автор&#187;, где каждая книга связана только с одним автором, и наоборот.</li>
<li><strong>В отношении &#171;один-ко-многим&#187; (<span style="color: #ff6600;">one-to-many</span>)</strong> можно рассматривать связь между сущностью &#171;Категория&#187; и &#171;Книга&#187;, где каждая книга может относиться только к одной категории, но каждая категория может содержать несколько книг.</li>
<li><strong>В отношении &#171;многие-к-одному&#187; (<span style="color: #ff6600;">many-to-one</span>)</strong> можно рассматривать связь между сущностью &#171;Издатель&#187; и &#171;Книга&#187;, где один издатель издаёт множество книг.</li>
<li><strong>В отношении &#171;многие-ко-многим&#187; (<span style="color: #ff6600;">many-to-many</span>)</strong> можно рассматривать связь между сущностью &#171;Книга&#187; и &#171;Читатель&#187;, где множество читателей могут владеть множеством книг.</li>
</ul>
<p>Продолжая наше упражнение, мы также определили две чёткие связи.</p>
<h3><strong>Связь &#171;Книга-Категория&#187;</strong></h3>
<p>Устанавливает связь между книгами и категориями. Одна книга может относиться к одной категории, а одна категория может включать несколько книг. Эта связь представлена как отношение &#171;один-ко-многим&#187;.</p>
<h3><strong>Связь &#171;Книга-Автор&#187;</strong></h3>
<p>Устанавливает связь между книгами и авторами. Одна книга может иметь нескольких авторов, а один автор может писать несколько книг. Эта связь представлена как отношение &#171;многие-ко-многим&#187;. Именно в рамках этой связи происходит публикация конкретной книги.</p>
<p>При определении связей часто используют названия, которые отражают реальное взаимодействие между сущностями. Например, вместо названия &#171;Книга-Категория&#187; можно использовать <span style="color: #003366;"><strong>&#171;Классифицирует&#187; (Classifies)</strong></span>, так как категория классифицирует книгу, а вместо &#171;Книга-Автор&#187; можно использовать <strong><span style="color: #003366;">&#171;Публикует&#187; (Publishes)</span></strong>, так как автор публикует книги.</p>
<p>Теперь, когда у нас есть представление о сущностях, атрибутах и связях, у нас есть всё необходимое для проектирования базы данных с использованием диаграммы &#171;сущность-связь&#187; (ERD). Таким образом, мы можем визуально представить сущности, связи и их кардинальность, как показано на Рисунке 2-1.</p>
<p><strong>Рисунок 2-1. Пример ERD для базы данных книг</strong></p>
<p><a href="https://datatalks.ru/wp-content/uploads/2024/12/2_1_ERD_example_for_the_books_database.jpeg" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="aligncenter wp-image-478 size-full" src="https://datatalks.ru/wp-content/uploads/2024/12/2_1_ERD_example_for_the_books_database.jpeg" alt="" width="968" height="679" srcset="https://datatalks.ru/wp-content/uploads/2024/12/2_1_ERD_example_for_the_books_database.jpeg 968w, https://datatalks.ru/wp-content/uploads/2024/12/2_1_ERD_example_for_the_books_database-300x210.jpeg 300w, https://datatalks.ru/wp-content/uploads/2024/12/2_1_ERD_example_for_the_books_database-768x539.jpeg 768w, https://datatalks.ru/wp-content/uploads/2024/12/2_1_ERD_example_for_the_books_database-450x316.jpeg 450w, https://datatalks.ru/wp-content/uploads/2024/12/2_1_ERD_example_for_the_books_database-780x547.jpeg 780w" sizes="(max-width: 968px) 100vw, 968px" /></a></p>
<p>Как мы можем наблюдать, <span style="color: #003366;"><strong>сущности</strong></span> представлены в виде белых прямоугольных блоков и обозначают реальные объекты или концепции, такие как &#171;Книга&#187; или &#171;Автор&#187;.</p>
<p><span style="color: #003366;"><strong>Связи</strong></span> представлены в виде ромбов и показывают, как сущности связаны друг с другом.</p>
<p><span style="color: #003366;"><strong>Атрибуты</strong></span> представлены в виде затенённых блоков и описывают свойства или характеристики сущности. Примером могут быть &#171;Имя&#187; или &#171;Дата публикации&#187;. Кроме того, атрибуты можно классифицировать как <span style="color: #003366;"><strong>ключевые атрибуты</strong></span> (подчёркнутые затенённые блоки), которые уникально идентифицируют сущность, или как <span style="color: #003366;"><strong>неключевые атрибуты</strong></span> (неподчёркнутые затенённые блоки), которые предоставляют дополнительную информацию о сущности. Существуют и другие типы атрибутов при проектировании таких диаграмм, но мы ограничимся основами.</p>
<p>Другие компоненты в ERD включают <strong><span style="color: #003366;">кардинальность</span></strong> и <span style="color: #003366;"><strong>ограничения участия</strong></span>. Кардинальность определяет количество экземпляров в связи, обычно обозначается символами, такими как 1, M или N, чтобы указать, является ли связь &#171;один-к-одному&#187; или &#171;один-ко-многим&#187;. (N указывает на неопределённое количество связей.)</p>
<h2>Логическая фаза моделирования</h2>
<p><span style="color: #003366;"><strong>На логической фазе моделирования</strong></span> основное внимание уделяется нормализации данных для устранения избыточности, повышения целостности данных и оптимизации производительности запросов. Результатом является нормализованная логическая модель, которая точно отражает отношения и зависимости между сущностями.</p>
<p>Эта фаза может быть разделена на два шага.</p>
<p><span style="color: #003366;"><strong>Сначала реструктуризация схемы &#171;сущность-связь&#187; фокусируется на оптимизации схемы на основе определённых критериев.</strong></span> Этот шаг не привязан к какой-либо конкретной логической модели.</p>
<p><span style="color: #003366;"><strong>На втором этапе оптимизированная ERD переводится в конкретную логическую модель.</strong></span></p>
<p>Предположим, мы решили сопоставить ERD с реляционной моделью базы данных (что будет нашим случаем), в отличие от документационной или графовой базы данных. В этом случае каждая сущность из концептуального упражнения ERD представлена в виде таблицы. Атрибуты каждой сущности становятся столбцами соответствующей таблицы. Ограничение первичного ключа указывается для столбцов, являющихся первичными ключами каждой таблицы. Кроме того, связи &#171;многие-ко-многим&#187; представлены отдельными промежуточными таблицами, которые содержат внешние ключи, ссылающиеся на соответствующие сущности.</p>
<p><strong>Переводя концептуальное упражнение ERD в логическую схему с использованием реляционной модели, мы создаём структурированное представление сущностей, их атрибутов и их связей.</strong> Эта логическая схема может стать основой для реализации базы данных в конкретной системе управления базами данных (DBMS), оставаясь независимой от какой-либо конкретной системы.</p>
<p>Чтобы выполнить этот перевод эффективно, применяются все шаги нормализации, но мы хотим поделиться эффективным алгоритмом:</p>
<ol>
<li>Сущность E преобразуется в таблицу T.</li>
<li>Имя E становится именем T.</li>
<li>Первичный ключ E становится первичным ключом T.</li>
<li>Простые атрибуты E становятся простыми атрибутами T.</li>
</ol>
<p><strong>Что касается связей, можно выделить несколько шагов:</strong></p>
<p><span style="color: #003366;"><strong>Связи N:1</strong></span></p>
<p>В таблице T1 определяется внешний ключ, который ссылается на первичный ключ таблицы T2. Это устанавливает связь между двумя таблицами, указывая на отношение N:1. Атрибуты (Attrs), связанные с этой связью, отображаются и включаются в таблицу T1.</p>
<p><span style="color: #003366;"><strong>Связи N:N</strong></span></p>
<p>Создаётся специальная таблица перекрёстных ссылок для представления связи REL. Первичный ключ REL определяется как комбинация первичных ключей обеих таблиц T1 и T2, которые выступают в роли внешних ключей в таблице перекрёстных ссылок. Атрибуты (Attrs), связанные с этой связью, отображаются и включаются в таблицу перекрёстных ссылок.</p>
<p>Теперь давайте применим эти правила к нашему предыдущему концептуальному модели; см. Рисунок 2-2.</p>
<p><strong>Рисунок 2-2. Пример логической ERD для базы данных книг</strong></p>
<p><a href="https://datatalks.ru/wp-content/uploads/2024/12/2_2_Logical_ERD_example_for_the_books_database.jpeg" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="aligncenter wp-image-479 size-full" src="https://datatalks.ru/wp-content/uploads/2024/12/2_2_Logical_ERD_example_for_the_books_database.jpeg" alt="" width="744" height="621" srcset="https://datatalks.ru/wp-content/uploads/2024/12/2_2_Logical_ERD_example_for_the_books_database.jpeg 744w, https://datatalks.ru/wp-content/uploads/2024/12/2_2_Logical_ERD_example_for_the_books_database-300x250.jpeg 300w, https://datatalks.ru/wp-content/uploads/2024/12/2_2_Logical_ERD_example_for_the_books_database-450x376.jpeg 450w" sizes="(max-width: 744px) 100vw, 744px" /></a></p>
<p>В нашем примере мы имеем несколько сущностей, которые, как предполагает наш алгоритм, напрямую сопоставляются с таблицами. Это относится к &#171;Авторам&#187;, &#171;Книгам&#187; и &#171;Категории&#187;.<br />
Мы определили отношение 1:N между &#171;Книгами&#187; и &#171;Категорией&#187;, где одна книга принадлежит одной категории, но одна категория может содержать несколько книг. Для отображения этого отношения мы создаём внешний ключ в таблице &#171;Книги&#187;, чтобы он ссылался на соответствующую категорию.</p>
<p>У нас также есть отношение N:N. В этом случае мы должны создать новую таблицу (таблицу перекрёстных ссылок), которая хранит это отношение. В нашем случае мы создаём таблицу &#171;Publishes&#187; (Публикации), для которой первичный ключ становится составным, объединяющим связанные сущности (ID книги и ID автора). Одновременно атрибуты связи становятся атрибутами этой таблицы перекрёстных ссылок.</p>
<h2>Физическая фаза моделирования</h2>
<p>Теперь мы готовы <span style="color: #003366;"><strong>преобразовать нормализованную логическую модель в физический дизайн базы данных</strong></span> на этапе, называемом <span style="color: #ff6600;"><strong>физической фазой или созданием физической модели.</strong></span></p>
<p><strong>Этот этап определяет структуры хранения, стратегии индексирования и типы данных для обеспечения эффективного хранения и извлечения данных.</strong> В то время как логическая модель сосредоточена на концептуальном представлении, физическая модель учитывает детали реализации, необходимые для оптимального управления данными.</p>
<p>В нашем случае мы продолжаем с предыдущей логической модели и предполагаем, что будем работать с MySQL. Пример 2-1 демонстрирует физическую модель базы данных для книг.</p>
<p><strong>Пример 2-1. База данных для книг в физической модели</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE category (
	category_id INT PRIMARY KEY,
	category_name VARCHAR(255)
);

CREATE TABLE books (
	book_id INT PRIMARY KEY,
	ISBN VARCHAR(13),
	title VARCHAR(50),
	summary VARCHAR(255)
	FOREIGN KEY (category_id) REFERENCES category(category_id)
);

CREATE TABLE authors (
author_id INT PRIMARY KEY,
author_name VARCHAR(255),
date_birth DATETIME
);

CREATE TABLE publishes (
	book_id INT,
	author_id INT,
	publish_date DATE,
	planned_publish_date DATE
	FOREIGN KEY (book_id) REFERENCES books(book_id),
	FOREIGN KEY (author_id) REFERENCES author(author_id)
);</pre><p>В Примере 2-1 мы создали четыре таблицы:</p>
<ol>
<li><strong>category</strong> (категории),</li>
<li><strong>books</strong> (книги),</li>
<li><strong>authors</strong> (авторы) и</li>
<li><strong>publishes</strong> (публикации).</li>
</ol>
<p>Аспект физического дизайна уточняет структуру таблиц, типы данных и ограничения, чтобы соответствовать системе базы данных MySQL.</p>
<p>Например, в таблице <code>category</code> мы можем указать тип данных для столбца <code>category_id</code> как <code>INT</code>, что обеспечивает его пригодность для хранения целочисленных значений. Этот столбец также определяется как первичный ключ, так как он идентифицирует уникальные записи в таблице. Аналогично, столбец <code>category_name</code> может быть определён как <code>VARCHAR(255)</code> для размещения строк с переменной длиной, представляющих названия категорий.</p>
<p>В таблице <code>books</code> можно назначить соответствующие типы данных и длины для таких столбцов, как <code>book_id (INT)</code>, <code>ISBN (VARCHAR(13))</code>, <code>title (VARCHAR(50))</code> и <code>summary (VARCHAR(255))</code>. Кроме того, столбец <code>category_id</code> может быть настроен как внешний ключ, ссылающийся на столбец <code>category_id</code> в таблице <code>category</code>. Обратите внимание, что каждый <strong>ISBN-код</strong> состоит из строк длиной 13 символов, поэтому для хранения большего количества символов нет необходимости.</p>
<p>Аналогично, в таблице <code>authors</code> можно определить типы данных для столбцов <code>author_id (INT)</code>, <code>author_name (VARCHAR(255))</code> и <code>date_birth (DATETIME)</code>, все они соответствуют ожидаемым типам значений.</p>
<p>В таблице <code>publishes</code> мы отмечаем, что установили ограничения внешнего ключа для создания связей между столбцом <code>book_id</code> в таблице <code>books</code> и столбцом <code>author_id</code> в таблице <code>authors</code>. При этом внешний ключ состоит из первичных ключей двух таблиц, которые он связывает.</p>
<p>После всех этих этапов мы успешно прошли путь от требований к концепции, затем к логической реляционной модели и завершили практическую реализацию модели в <strong>MySQL</strong>, таким образом построив нашу базу данных.</p>
<h2>Процесс нормализации данных</h2>
<p><strong>Техника нормализации данных состоит из нескольких шагов, каждый из которых направлен на организацию данных в логические и эффективные структуры.</strong> Пример 2-2 иллюстрирует таблицу books, содержащую несколько соответствующих атрибутов.</p>
<p><strong>Пример 2-2. Таблица books для нормализации</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE books (
    book_id INT PRIMARY KEY,
    title VARCHAR(100),
    author VARCHAR(100),
    publication_year INT,
    genre VARCHAR(50)
);</pre><p><strong>Первым шагом нормализации, известным как <span style="color: #ff6600;">первая нормальная форма (1NF)</span>, является устранение повторяющихся групп путём разбиения данных на более мелкие атомарные единицы.</strong> Мы создаём таблицу <code>authors</code>, включающую идентификатор автора и его имя. Таблица <code>books</code> теперь ссылается на идентификатор автора вместо того, чтобы многократно хранить полное имя, как показано в Примере 2-3.</p>
<p><strong>Пример 2-3. Таблица books в 1NF</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">-- Table Authors
CREATE TABLE authors (
    author_id INT PRIMARY KEY,
    author_name VARCHAR(100)
);

-- Table Books
CREATE TABLE books (
    book_id INT PRIMARY KEY,
    title VARCHAR(100),
    publication_year INT,
    genre VARCHAR(50),
    author_id INT,
    FOREIGN KEY (author_id) REFERENCES authors(author_id)
);</pre><p><strong>Переходя ко <span style="color: #ff6600;">второй нормальной форме (2NF)</span>, мы изучаем зависимости внутри данных.</strong> Мы замечаем, что год публикации функционально зависит от идентификатора книги, а жанр — от идентификатора автора.</p>
<p><strong>Чтобы соответствовать 2NF, мы разделяем таблицу books на три таблицы:</strong></p>
<ul>
<li><strong>books,</strong> содержащую идентификатор книги и название.</li>
<li><strong>authors,</strong> содержащую идентификатор автора и его имя.</li>
<li><strong>bookDetails,</strong> хранящую идентификатор книги, год публикации и жанр.</li>
</ul>
<p>Это гарантирует, что каждый столбец зависит исключительно от первичного ключа, как показано в Примере 2-4.</p>
<p><strong>Пример 2-4. Таблица books во 2NF</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">-- Table Authors
CREATE TABLE authors (
    author_id INT PRIMARY KEY,
    author_name VARCHAR(100)
);

-- Table Books
CREATE TABLE books (
    book_id INT PRIMARY KEY,
    title VARCHAR(100)
);

-- Table book details
CREATE TABLE bookDetails (
    book_id INT PRIMARY KEY,
    author_id INT,
    genre VARCHAR(50),
    publication_year INT,
    FOREIGN KEY (author_id) REFERENCES authors(author_id)
);</pre><p><strong><span style="color: #ff6600;">Третья нормальная форма (3NF)</span> сосредотачивается на устранении транзитивных зависимостей.</strong> Мы осознаём, что жанр можно вывести из идентификатора книги через таблицу <code>bookDetails</code>. Чтобы решить эту проблему, мы создаём новую таблицу <code>genres</code> с идентификатором жанра и его названием. Таблица <code>bookDetails</code> теперь ссылается на идентификатор жанра вместо того, чтобы напрямую хранить название жанра (Пример 2-5).</p>
<p><strong>Пример 2-5. Таблица books в 3NF</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE authors (
    author_id INT PRIMARY KEY,
    author_name VARCHAR(100)
);

CREATE TABLE books (
    book_id INT PRIMARY KEY,
    title VARCHAR(100)
);

CREATE TABLE genres (
    genre_id INT PRIMARY KEY,
    genre_name VARCHAR(50)
);

CREATE TABLE bookDetails (
    book_id INT PRIMARY KEY,
    author_id INT,
    genre_id INT,
    publication_year INT,
    FOREIGN KEY (author_id) REFERENCES authors(author_id),
    FOREIGN KEY (genre_id) REFERENCES genres(genre_id)
);</pre><p>Эти результирующие нормализованные структуры (3NF) часто используются в операционных системах, также известных как <strong>системы обработки транзакций в режиме онлайн (OLTP)</strong>, которые разработаны для эффективной обработки и хранения транзакций, а также для извлечения данных о транзакциях, таких как заказы клиентов, банковские транзакции или расчёты заработной платы.</p>
<blockquote><p><strong>Важно отметить, что при необходимости можно применять дополнительные шаги нормализации, такие как <span style="color: #ff6600;">четвёртая нормальная форма (4NF)</span> и <span style="color: #ff6600;">пятая нормальная форма (5NF)</span>, <span style="color: #339966;">чтобы устранить сложные зависимости данных и обеспечить ещё более высокий уровень целостности данных.</span></strong></p></blockquote>
<p>Нормализация данных имеет решающее значение для достижения эффективной обработки и хранения отдельных транзакций в системе OLTP. В процессе нормализации данные делятся на более мелкие, менее избыточные части, чтобы достичь этой цели, что приносит несколько преимуществ для OLTP-систем.</p>
<p><strong>Нормализация данных известна своим акцентом на уменьшении избыточности данных и улучшении целостности данных,</strong> так как данные организованы в несколько таблиц, каждая из которых выполняет определённую функцию. Эти таблицы связаны первичными и внешними ключами для установления их взаимосвязей, что гарантирует уникальность записей в каждой таблице и исключает повторение одинаковых полей в нескольких таблицах, за исключением ключевых или системных полей, таких как идентификаторы или временные метки создания.</p>
<p><strong><span style="color: #003366;">Ещё одной причиной актуальности нормализации данных является повышение производительности.</span></strong> Такие нормализованные базы данных разрабатываются для эффективной обработки быстрых операций чтения и записи за счёт минимизации избыточности данных и установления чётко определённых взаимосвязей между таблицами, чтобы база данных могла справляться с большим количеством транзакций с молниеносной скоростью. Это особенно важно для транзакционных систем, где своевременное выполнение операций критически важно.</p>
<p>И, наконец, нормализованная база данных фокусируется на хранении только актуальных данных, чтобы база данных представляла наиболее актуальную информацию. В таблице, которая хранит информацию о клиентах, каждая запись всегда отражает текущие данные о клиенте, такие как имя, номер телефона и другая релевантная информация, что гарантирует точное представление текущего состояния дел.</p>
<p>Однако парадигма несколько отличается, когда речь идёт о проекте или системе аналитики. Часто пользователи хотят иметь возможность получать необходимые данные без необходимости выполнения множества соединений, что является естественным следствием процесса нормализации. <strong>В то время как система OLTP оптимизирована для операций записи</strong>, чтобы избежать увеличения задержки в таких живых системах, как веб-приложения, <strong>пользователи аналитических систем хотят оптимизации операций чтения</strong>, чтобы как можно быстрее получать аналитические данные.</p>
<p>В отличие от нормализованных транзакционных баз данных, которые хранят живые данные, аналитические базы данных, как ожидается, содержат как данные в реальном времени, так и не в реальном времени, а также выступают в качестве исторического архива для прошлых данных. Часто аналитическая база данных ожидает интеграции данных из нескольких OLTP-систем для предоставления объединённого представления бизнес-процессов.</p>
<p>Эти различия действительно критически важны для понимания, так как они лежат в основе различных требований к организации, хранению и использованию данных. Однако важно уточнить, что то, что мы только что изучили, относится главным образом к области нормализации для оптимизации производительности и соблюдения лучших практик в проектировании баз данных OLTP. Хотя эта основа ценна, она представляет собой лишь одну грань более широкой области аналитической инженерии.</p>
<p>Чтобы предоставить более чёткий план, давайте определим, что наше путешествие начинается с изучения базового типа моделирования данных, который составляет основу систем OLTP. После этого мы перейдём к обсуждению подходов к моделированию данных, оптимизированных для среды OLAP. Разграничивая эти аспекты, мы стремимся обеспечить всестороннее понимание обеих сторон моделирования данных, что подготовит нас к более глубокому изучению методов аналитической инженерии и их применения в последующих разделах.</p>
<h1>Моделирование данных в разрезе измерений (Dimensional Data Modeling)</h1>
<p><strong>Моделирование данных</strong> — это основополагающий аспект проектирования и организации баз данных для эффективного хранения и управления данными. Как мы уже обсуждали, оно включает в себя определение структуры, взаимосвязей и атрибутов сущностей данных в рамках системы.</p>
<p>Одним из популярных подходов к моделированию данных является моделирование в разрезе измерений, которое ориентировано на создание моделей данных, поддерживающих аналитические и отчётные потребности. <strong>Этот подход особенно хорошо подходит для хранилищ данных и приложений бизнес-аналитики (BI).</strong> Он акцентирует внимание на создании моделей, состоящих из таблиц фактов, представляющих измеримые данные, и таблиц измерений, предоставляющих описательный контекст. Используя техники моделирования данных в разрезе измерений, такие как <strong><span style="color: #003366;">звёздная схема и схема снежинки</span></strong>, данные можно организовать так, чтобы упростить сложные запросы и обеспечить эффективный анализ данных.</p>
<p>Связь между моделированием данных и моделированием в разрезе измерений заключается в их взаимодополняющем характере. Моделирование данных предоставляет основу для захвата и структурирования данных, тогда как моделирование в разрезе измерений предлагает специализированный метод для поддержки аналитических и отчётных нужд. Вместе эти подходы позволяют организациям проектировать надёжные и гибкие базы данных, которые поддерживают как транзакционную обработку, так и углублённый анализ данных.</p>
<p>Для понимания моделирования данных в разрезе измерений стоит отдать должное двум личностям, считающимся отцами хранилищ данных и этого подхода: Биллу Инмону и Ральфу Кимболлу. Они признаны пионерами в области корпоративного сбора информации, управления и аналитики для поддержки принятия решений.</p>
<h2>Инмон против Кимболла: подходы к хранилищам данных</h2>
<p>Они внесли значительный вклад в дискуссию на тему хранилищ данных, каждый отстаивая свои философии и подходы.</p>
<p><strong>Инмон</strong> предлагает создание централизованного хранилища данных, охватывающего всё предприятие, с целью создания комплексной BI-системы.</p>
<p>В то время как <strong>Кимболл</strong> предлагает создание множества небольших витрин данных (data marts), ориентированных на конкретные отделы, что позволяет проводить анализ и составлять отчёты на уровне департаментов.</p>
<p><strong>Их противоположные точки зрения приводят к различным техникам проектирования и стратегиям внедрения хранилищ данных.</strong></p>
<p>Помимо различных подходов, Инмон и Кимболл предлагают отличающиеся методы структурирования данных в контексте хранилищ данных. Инмон выступает за использование реляционной модели (ERD), в частности, третьей нормальной формы (3NF), для корпоративного хранилища данных. В то время как подход Кимболла использует многомерную модель в измерительном хранилище данных с применением звёздных и снежиноковых схем.</p>
<p><span style="color: #003366;"><strong>Инмон утверждает, что структурирование данных в реляционной модели обеспечивает согласованность на уровне предприятия.</strong> </span>Эта согласованность упрощает создание витрин данных на основе измерительной модели. С другой стороны, <span style="color: #003366;"><strong>Кимболл считает, что организация данных в dimensional модели облегчает работу с информационной шиной, позволяя пользователям лучше понимать, анализировать, агрегировать данные и исследовать несоответствия.</strong></span> Более того, подход Кимболла позволяет аналитическим системам напрямую обращаться к данным. В отличие от этого, подход Инмона ограничивает аналитические системы в доступе к данным только через корпоративное хранилище данных, требуя взаимодействия с витринами данных для их извлечения.</p>
<p><span style="color: #ff6600;"><strong>Витрина данных</strong></span> — это конкретная часть хранилища данных, предназначенная для удовлетворения уникальных потребностей определённого отдела или бизнес-единицы.</p>
<h3>Звёздная схема, снежинка и Data Vault</h3>
<p>В следующих разделах мы углубимся в три техники моделирования: звёздную схему, снежинку и новую модель Data Vault. Data Vault, представленная Дэном Линстедтом в 2000 году, набирает популярность в последние годы. Эта модель следует более нормализованной структуре, которая не полностью совпадает с подходом Инмона, но является похожей.</p>
<h2>Моделирование с использованием звёздной схемы</h2>
<p><strong>Звёздная схема</strong> является широко используемым подходом к моделированию в реляционных хранилищах данных, особенно для аналитики и составления отчётов. Этот подход включает классификацию таблиц как <strong>таблиц измерений (dimension tables)</strong> или <strong>таблиц фактов (fact tables)</strong> для эффективной организации и представления бизнес-единиц и связанных наблюдений или событий.</p>
<p><strong>Таблицы измерений используются для описания бизнес-сущностей, которые моделируются.</strong> Эти сущности могут включать различные аспекты, такие как продукты, люди, места и концепции, включая время. В звёздной схеме обычно присутствует таблица измерения времени, которая предоставляет полный набор дат для анализа. Таблица измерений обычно состоит из одного или нескольких ключевых столбцов, которые служат уникальными идентификаторами для каждой сущности, а также дополнительных описательных столбцов, предоставляющих больше информации о сущностях.</p>
<p><strong>Таблицы фактов, напротив, хранят наблюдения или события, происходящие в бизнесе.</strong> Это могут быть заказы на продажу, уровни запасов, валютные курсы, температуры и другие измеряемые данные. Таблица фактов содержит ключевые столбцы измерений, которые ссылаются на таблицы измерений, а также числовые столбцы измерений. Ключевые столбцы измерений определяют размерность таблицы фактов и указывают, какие измерения включены в анализ. Например, таблица фактов, которая хранит данные о целях продаж, может содержать ключевые столбцы измерений для <code>Date</code> и <code>ProductKey</code>, что означает, что анализ включает измерения, связанные с временем и продуктами.</p>
<p><strong>Гранулярность таблицы фактов</strong> определяется значениями в её ключевых столбцах измерений. Если, например, столбец <code>Date</code> в таблице фактов для целей продаж содержит значения, представляющие первый день каждого месяца, то гранулярность таблицы находится на уровне <strong>Месяц/Продукт</strong>. Это означает, что таблица фактов фиксирует данные о целях продаж на ежемесячном уровне, специфичном для каждого продукта.</p>
<p>Структурируя данные в звёздной схеме с таблицами измерений, представляющими бизнес-единицы, и таблицами фактов, фиксирующими наблюдения или события, компании могут эффективно выполнять сложный анализ и получать значимые инсайты. <strong>Звёздная схема обеспечивает понятную и интуитивно понятную структуру для выполнения запросов и агрегирования данных</strong>, что упрощает анализ и понимание взаимосвязей между измерениями и фактами в наборе данных.</p>
<p>Возвращаясь к нашей таблице книг, мы следуем шагам моделирования для разработки простой модели звёздной схемы. Первый шаг — это идентификация таблиц измерений. Для начала вспомним нашу базовую таблицу в Примере 2-6.</p>
<p><strong>Пример 2-6. Базовая таблица для звёздной схемы</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">-- This is our base table
CREATE TABLE books (
    book_id INT PRIMARY KEY,
    title VARCHAR(100),
    author VARCHAR(100),
    publication_year INT,
    genre VARCHAR(50)
);</pre><p>Мы должны определить все индивидуальные измерения (атрибуты, связанные с конкретной бизнес-сущностью) в таблице книг и создать отдельные таблицы измерений для каждого из них. В нашем примере, как и на этапе нормализации, мы определяем три сущности: книги, авторы и жанры. Посмотрим на физическую модель в Примере 2-7.</p>
<p><strong>Пример 2-7. Таблицы измерений для звёздной схемы</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">-- Create the dimension tables
CREATE TABLE dimBooks (
    book_id INT PRIMARY KEY,
    title VARCHAR(100)
);

CREATE TABLE dimAuthors (
    author_id INT PRIMARY KEY,
    author VARCHAR(100)
);

CREATE TABLE dimGenres (
    genre_id INT PRIMARY KEY,
    genre VARCHAR(50)
);</pre><p><strong>Когда речь идёт о наименовании таблиц измерений, рекомендуется использовать описательные и интуитивно понятные названия, которые отражают представляемые сущности.</strong> Например, если у нас есть таблица измерений, представляющая книги, её можно назвать <code>dimBook</code> или просто <code>books</code>. Аналогично, для таблиц измерений, представляющих авторов, жанры или другие сущности, можно использовать такие названия, как <code>dimAuthor</code> или <code>dimGenre</code>.</p>
<p><strong>Для таблиц фактов рекомендуется использовать названия, указывающие на измерения или события, которые фиксируются.</strong> Например, если у нас есть таблица фактов, записывающая данные о продажах книг, её можно назвать <code>factBookSales</code> или <code>salesFact</code>. Эти названия показывают, что таблица содержит данные, связанные с продажами книг.</p>
<p>Мы можем создать таблицу фактов под названием <code>factBookPublish</code>, как показано в Примере 2-8, для фиксации данных о публикации.</p>
<p><strong>Пример 2-8. Таблица фактов для звёздной схемы</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">-- Create the fact table
CREATE TABLE factBookPublish (
    book_id INT,
    author_id INT,
    genre_id INT,
    publication_year INT,
    FOREIGN KEY (book_id) REFERENCES dimBooks (book_id),
    FOREIGN KEY (author_id) REFERENCES dimAuthors (author_id),
    FOREIGN KEY (genre_id) REFERENCES dimGenres (genre_id)
);</pre><p>Этот код создаёт новую таблицу фактов <code>factBookPublish</code> с колонками, представляющими измерения или события, связанные с измерениями. В данном случае это только год публикации. Ограничения внешнего ключа устанавливают связи между таблицей фактов и таблицами измерений.</p>
<p>С моделью звёздной схемы, представляющей набор данных о книгах, у нас теперь есть прочная основа для проведения различных аналитических операций и получения ценных инсайтов. Размерная структура звёздной схемы позволяет эффективно и интуитивно выполнять запросы, исследуя данные с разных точек зрения. После завершения процесса моделирования мы должны получить модель, аналогичную Рисунку 2-3, которая напоминает звезду, отсюда и название — звёздная схема.</p>
<p><strong>Рисунок 2-3. Модель звёздной схемы</strong></p>
<p><a href="https://datatalks.ru/wp-content/uploads/2024/12/2_3_Star_schema_model.jpeg" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="aligncenter wp-image-481 size-full" src="https://datatalks.ru/wp-content/uploads/2024/12/2_3_Star_schema_model.jpeg" alt="" width="726" height="337" srcset="https://datatalks.ru/wp-content/uploads/2024/12/2_3_Star_schema_model.jpeg 726w, https://datatalks.ru/wp-content/uploads/2024/12/2_3_Star_schema_model-300x139.jpeg 300w, https://datatalks.ru/wp-content/uploads/2024/12/2_3_Star_schema_model-450x209.jpeg 450w" sizes="(max-width: 726px) 100vw, 726px" /></a></p>
<h3><strong>Использование этой модели</strong></h3>
<p>Теперь мы можем легко анализировать публикации книг, применяя фильтры, такие как жанр, автор или год публикации. Например, можно быстро получить общее количество публикаций для определённого жанра. Соединяя таблицы измерений с таблицей фактов, как показано в Примере 2-9, мы можем без труда получать информацию о взаимосвязях между книгами, авторами, жанрами и продажами.</p>
<p><strong>Пример 2-9. Извлечение данных из звёздной схемы</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">-- Example for retrieving the total publications for a specific genre.
SELECT 
    COALESCE(dg.genre, 'Not Available'), -- Or '-1'
    COUNT(*) AS total_publications
FROM factBookPublish bp
LEFT JOIN dimGenres dg ON dg.genre_id = bp.genre_id
GROUP BY g.genre;</pre><p>Как видно, при объединении таблицы фактов с таблицей измерений был использован <code>LEFT JOIN</code>. Это достаточно распространённый подход, так как он гарантирует, что все записи из таблицы фактов будут включены в результат, независимо от того, существует ли соответствующая запись в таблице измерений.</p>
<p>Этот подход важен, поскольку учитывает возможность отсутствия соответствующих записей в каждой таблице измерений для каждой записи таблицы фактов. Используя <code>LEFT JOIN</code>, вы сохраняете все данные из таблицы фактов, одновременно дополняя их соответствующими атрибутами из таблицы измерений. Это позволяет выполнять анализ и агрегации на основе различных измерений и исследовать данные с разных точек зрения. Однако необходимо учитывать отсутствие соответствий, для чего часто используется оператор <code>COALESCE</code>, позволяющий установить значение по умолчанию, например, <code>-1</code> или <code>Not available</code>.</p>
<p>Кроме того, <code>LEFT JOIN</code> позволяет обновлять таблицы измерений инкрементально. Если в таблицу измерений добавляются новые записи, <code>LEFT JOIN</code> по-прежнему включает существующие записи таблицы фактов, связывая их с доступными данными из таблицы измерений. Такая гибкость обеспечивает стабильность анализа и отчётности, даже если данные таблиц измерений изменяются со временем.</p>
<h3>Преимущества звёздной схемы</h3>
<p><strong>В целом, простота и денормализованная структура звёздной схемы делают её удобной для выполнения агрегаций и обобщений.</strong> С её помощью можно создавать различные отчёты, такие как анализ трендов продаж за период, самые популярные жанры или доходы по авторам. Также звёздная схема поддерживает <span style="color: #003366;"><strong>операции детализации (drill-down)</strong></span> и <strong><span style="color: #003366;">свёртки (roll-up)</span></strong>, что позволяет переходить к более детализированной информации или, наоборот, к более обобщённым данным для комплексного анализа.</p>
<p>Эта техника моделирования также отлично интегрируется с инструментами визуализации данных и платформами BI. Подключив модель к инструментам, таким как Tableau, Power BI или Looker, можно создавать наглядные дашборды и интерактивные отчёты. Такие ресурсы позволяют заинтересованным сторонам быстро извлекать инсайты и принимать решения на основе данных.</p>
<h3><strong>Уточнение модели</strong></h3>
<p>Однако стоит отметить, что предыдущий пример не полностью демонстрирует денормализацию, характерную для звёздных схем. Например, если ваш набор данных строго придерживается правила &#171;один жанр на книгу&#187;, можно упростить модель, объединив информацию о жанрах непосредственно в единую таблицу <code>dimBooks</code>, что повысит уровень денормализации и упростит доступ к данным.</p>
<h2>Моделирование с использованием снежинки</h2>
<p><strong>В схеме &#171;Снежинка&#187; (Snowflake Data Model)</strong> &#8212; модель данных более нормализована по сравнению со звёздной схемой. <strong>Она включает дополнительные уровни нормализации, разделяя таблицы измерений на несколько связанных таблиц.</strong> Это обеспечивает лучшую целостность данных и уменьшает избыточность.</p>
<p>Например, рассмотрим <strong>схему Снежинка</strong> для базы данных электронной коммерции. У нас есть таблица измерений <code>customers</code>, которая содержит информацию о клиентах, такую как идентификатор, имя и адрес. В схеме Снежинка эту таблицу можно разделить на несколько связанных таблиц.</p>
<p><strong>Таблица customers</strong> может быть разделена на таблицу <code>customers</code> и отдельную таблицу <code>addresses</code>. Таблица <span style="color: #003366;"><strong>customers</strong></span> будет содержать атрибуты, специфичные для клиента, такие как идентификатор и имя клиента. В то же время таблица <span style="color: #003366;"><strong>addresses</strong></span> будет содержать информацию, связанную с адресами, например, идентификатор, улицу, город и почтовый индекс. Если несколько клиентов имеют один и тот же адрес, информацию об этом адресе нужно хранить только один раз в таблице addresses и связывать её с соответствующими клиентами.</p>
<p>Для извлечения данных из <strong>схемы Snowflake</strong> обычно требуется выполнить несколько соединений между связанными таблицами. Например, если нужно запросить имя клиента и адрес, необходимо объединить таблицы <strong>customers</strong> и <strong>addresses</strong> по идентификатору. Хотя Snowflake схема обеспечивает лучшую целостность данных, она также требует более сложных запросов из-за дополнительных связей.</p>
<h3><strong>Сравнение Star и Snowflake схем</strong></h3>
<p>И звёздная, и снежинка схемы являются распространёнными подходами к проектированию хранилищ данных. <strong>В звёздной схеме таблицы измерений денормализованы, то есть содержат избыточные данные.</strong> Это даёт преимущества в виде более простой реализации, эффективных запросов (из-за меньшего количества операций JOIN), но может потребовать больше места для хранения и усложнить обновление и устранение неполадок.</p>
<p>С другой стороны, <strong>схема Снежинка обеспечивает лучшую нормализацию и гибкость управления данными</strong>, что может быть полезно при работе с большими наборами данных и сложными отношениями.</p>
<p>В зависимости от ваших потребностей можно использовать гибридные модели, в которых звёздная схема сочетается с нормализованными элементами для оптимизации. Например, для представления местоположения клиентов по всему миру в звёздной схеме можно создать единую таблицу измерений <code>dimLocation</code>, включающую все иерархии местоположений в денормализованной форме (Пример 2-10).</p>
<p><strong>Пример 2-10. Измерение местоположения в звёздной схеме</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE dimLocation (
    locationID INT PRIMARY KEY,
    country VARCHAR(50),
    city VARCHAR(50),
    State VARCHAR(50)
);</pre><p>А в snowflake схеме иерархия местоположений будет разделена на несколько таблиц (Пример 2-11).</p>
<p><strong>Пример 2-11. Измерение местоположения в схеме Снежинка</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE dimLocation (
    locationID INT PRIMARY KEY,
    locationName VARCHAR(50),
    cityID INT
);

CREATE TABLE dimCity (
    cityID INT PRIMARY KEY,
    city VARCHAR(50),
    stateID INT
);

CREATE TABLE dimState (
    stateID INT PRIMARY KEY,
    state VARCHAR(50),
    countryID INT
);

CREATE TABLE dimCountry (
    countryID INT PRIMARY KEY,
    country VARCHAR(50)
);</pre><p></p>
<h3><strong>В примере схемы Снежинка</strong></h3>
<p>Измерение местоположения разделено на четыре таблицы:</p>
<ul>
<li>dimLocation,</li>
<li>dimCity,</li>
<li>dimState и</li>
<li>dimCountry.</li>
</ul>
<p>Эти таблицы связаны между собой с использованием первичных и внешних ключей для установления взаимосвязей между уровнями иерархии.</p>
<p>Одним из ключевых моментов является то, что, несмотря на наличие четырёх таблиц для представления измерения местоположения, только таблица с наивысшим уровнем иерархии соединяется с таблицей фактов (или таблицами фактов) через свой первичный ключ. Все остальные уровни иерархии следуют цепочке от самого высокого уровня до наименьшей детализации.</p>
<p><strong>На Рисунке 2-4 показана эта схема.</strong></p>
<p><a href="https://datatalks.ru/wp-content/uploads/2024/12/2_4_Snowflake_schema_model.jpeg" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="aligncenter wp-image-482 size-full" src="https://datatalks.ru/wp-content/uploads/2024/12/2_4_Snowflake_schema_model.jpeg" alt="" width="824" height="609" srcset="https://datatalks.ru/wp-content/uploads/2024/12/2_4_Snowflake_schema_model.jpeg 824w, https://datatalks.ru/wp-content/uploads/2024/12/2_4_Snowflake_schema_model-300x222.jpeg 300w, https://datatalks.ru/wp-content/uploads/2024/12/2_4_Snowflake_schema_model-768x568.jpeg 768w, https://datatalks.ru/wp-content/uploads/2024/12/2_4_Snowflake_schema_model-450x333.jpeg 450w, https://datatalks.ru/wp-content/uploads/2024/12/2_4_Snowflake_schema_model-780x576.jpeg 780w" sizes="(max-width: 824px) 100vw, 824px" /></a></p>
<h2>Моделирование с использованием Data Vault</h2>
<p><strong>Data Vault 2.0</strong> — это подход к моделированию, который не относится к измерительному моделированию, но заслуживает упоминания. <strong>Его методология сочетает элементы трёхзвенной нормальной формы (3NF) и dimensional modeling для создания логической корпоративной базы данных (EDW).</strong> Data Vault разработан для работы с различными типами данных, включая структурированные, полуструктурированные и неструктурированные, предоставляя гибкие и масштабируемые шаблоны.</p>
<p><span style="color: #003366;"><strong>Одной из ключевых характеристик Data Vault является модульный и инкрементальный подход, который интегрирует исходные данные на основе бизнес-ключей.</strong></span> Такой подход обеспечивает способность хранилища данных адаптироваться к изменениям в бизнес-требованиях и эволюции наборов данных.</p>
<p>В деталях эта техника моделирования предоставляет масштабируемое и гибкое решение для хранилищ данных и аналитики. Data Vault разработан для обработки больших объёмов данных, частых изменений в бизнес-требованиях и эволюционирующих источников данных.</p>
<p>Модель Data Vault состоит из трёх основных компонентов:</p>
<ul>
<li>хабы (hubs),</li>
<li>связи (links) и</li>
<li>спутники (satellites).</li>
</ul>
<h3><strong>Хабы (Hubs)</strong></h3>
<p><span style="color: #003366;"><strong>Хабы представляют бизнес-сущности и служат центральной точкой для хранения уникальных идентификаторов, называемых бизнес-ключами.</strong></span> Каждый хаб соответствует конкретной сущности, такой как клиенты, продукты или местоположения. Таблица хаба содержит колонку бизнес-ключа вместе с любыми описательными атрибутами, связанными с этой сущностью.</p>
<p>Отделение бизнес-ключа от описательных атрибутов позволяет легко отслеживать изменения в описательной информации, не затрагивая целостность бизнес-ключа.</p>
<h3><strong>Связи (Links)</strong></h3>
<p><span style="color: #003366;"><strong>Связи фиксируют взаимосвязи между бизнес-сущностями.</strong></span> Они создаются для представления отношений &#171;многие ко многим&#187; или сложных ассоциаций. Таблица связей содержит внешние ключи из участвующих хабов, формируя мост между связанными сущностями. Такой подход позволяет моделировать сложные отношения, не создавая дублирование данных или избыточную сложность.</p>
<h3><strong>Спутники (Satellites)</strong></h3>
<p><span style="color: #003366;"><strong>Спутники хранят контекстные атрибуты, связанные с хабами и связями.</strong> </span>Они содержат дополнительную описательную информацию, которая не является частью бизнес-ключа, но предоставляет важный контекст о сущностях. Спутники связаны с соответствующими хабами или связями через внешние ключи, что позволяет хранить изменяющиеся данные и сохранять исторические записи.</p>
<p>Для одного хаба или связи может быть связано несколько спутников, каждый из которых фиксирует определённые атрибуты для различных моментов времени или с разных точек зрения.</p>
<p><span style="color: #ff6600;"><strong>Архитектура Data Vault способствует прослеживаемости, масштабируемости и аудируемости, предоставляя надёжную основу для интеграции данных, аналитики и управления данными.</strong></span> Используя хабы, связи и спутники, организации могут создавать Data Vault, который поддерживает их аналитические потребности, адаптируется к изменяющимся бизнес-требованиям и сохраняет надёжную историческую запись изменений данных.</p>
<h3><strong>Пример моделирования таблицы книг</strong></h3>
<p>Рассмотрим таблицу <code>books</code> и следуем трём этапам моделирования, чтобы разработать простую модель <strong>Data Vault</strong>.</p>
<p><strong><span style="color: #ff6600;">Первый шаг</span> — это идентификация бизнес-ключей и создание соответствующих хабов и спутников. </strong></p>
<p>В данном случае у нас есть только одна бизнес-сущность, поэтому связи использоваться не будут. Пример 2-12 демонстрирует моделирование таблицы книг с использованием Data Vault 2.0.</p>
<p><strong>Пример 2-12. Моделирование таблицы книг с использованием Data Vault 2.0</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">-- This is our base table
CREATE TABLE books (
    book_id INT PRIMARY KEY,
    title VARCHAR(100),
    author VARCHAR(100),
    publication_year INT,
    genre VARCHAR(50)
);</pre><p>В моделировании Data Vault мы начинаем с идентификации бизнес-ключей, которые являются уникальными идентификаторами для каждой сущности. В данном случае первичный ключ таблицы <code>books</code>, <code>book_id</code>, служит бизнес-ключом.</p>
<p>Теперь мы можем приступить к моделированию и созданию нашей первой таблицы: таблицы хаба, которая хранит уникальные бизнес-ключи и соответствующие им хэш-ключи для обеспечения стабильности. Пример 2-13 показывает создание таблицы хаба.</p>
<p><strong>Пример 2-13. Создание хаба</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE hubBooks (
    bookKey INT PRIMARY KEY,
    bookHashKey VARCHAR(50),
    Title VARCHAR(100)
);</pre><p>В таблице хаба мы храним уникальный идентификатор для каждой книги в виде <strong>первичного ключа (<code>bookKey</code>)</strong> и <strong>хэш-ключа (<code>bookHashKey</code>)</strong> для обеспечения стабильности. Колонка <code><strong>Title</strong></code> содержит описательную информацию о книге.</p>
<p><strong>Далее создаётся спутниковая таблица,</strong> представленная в Примере 2-14, которая фиксирует дополнительные детали о книгах и сохраняет историю изменений.</p>
<p><strong>Пример 2-14. Создание спутника</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">CREATE TABLE satBooks (
    bookKey INT,
    loadDate DATETIME,
    author VARCHAR(100),
    publicationYear INT,
    genre VARCHAR(50),
    PRIMARY KEY (bookKey, loaddate),
    FOREIGN KEY (bookKey) REFERENCES hubBooks(bookKey)
);</pre><p>Разделяя основную информацию о книге в таблице хаба и сохраняя исторические данные в спутниковой таблице, мы обеспечиваем возможность фиксировать изменения атрибутов, таких как автор, год публикации или жанр, без модификации существующих записей.</p>
<p>В модели Data Vault могут быть и другие таблицы, такие как таблицы связей для представления отношений между сущностями или дополнительные спутники для фиксации исторических изменений в отдельных атрибутах.</p>
<h1>Моделирование данных в монолитной архитектуре</h1>
<p>До недавнего времени преобладающим подходом к моделированию данных было создание обширных SQL-скриптов. В этом традиционном методе весь процесс моделирования данных заключался в одном SQL-файле, который часто превышал тысячи строк. Для более сложных рабочих процессов разработчики могли разделять файл на несколько SQL-скриптов или хранимых процедур, которые затем выполнялись последовательно с помощью Python-скриптов.</p>
<p>Чтобы усложнить процесс, эти скрипты обычно оставались практически неизвестными внутри организации. В результате, даже если кто-то другой хотел бы повторить процесс моделирования данных аналогичным образом, он неизбежно начинал бы с нуля, упуская возможность повторного использования существующей работы.</p>
<p>Этот подход можно охарактеризовать как монолитный или традиционный подход к моделированию данных, при котором каждый пользователь данных заново выстраивал свои преобразования данных, начиная с исходных данных. Этот подход сопровождался рядом значительных проблем, таких как:</p>
<ul>
<li>отсутствие контроля версий для скриптов;</li>
<li>сложность управления зависимостями между представлениями (views);</li>
<li>практика создания новых представлений или таблиц от исходных данных до финальных отчетов, что затрудняет повторное использование;</li>
<li>несоответственное применение принципа идемпотентности для крупных таблиц, что иногда приводило к избыточности данных;</li>
<li>сложные и трудоёмкие процессы обратной загрузки (backfills).</li>
</ul>
<p>В современном быстро развивающемся мире инженеров данных монолитные модели данных, особенно в контексте SQL-преобразований, представляют собой значительную проблему.</p>
<p><strong>Рассмотрим следующий сценарий:</strong> вы обнаруживаете неисправность в производственной системе, но, как выясняется, даже простое изменение запускает цепную реакцию ошибок, которая затрагивает всю инфраструктуру. Этот пугающий сценарий, характеризующийся сильно взаимосвязанными системами, где незначительное изменение провоцирует каскадный эффект, знаком многим профессионалам в области данных.</p>
<p>Риски, связанные с монолитными моделями данных, — это именно то, чего мы стараемся избежать при проектировании модели данных. Последнее, что нужно, — это тесно связанные модели данных, которые делают отладку и внесение изменений сложной задачей, так как каждое изменение потенциально может нарушить всю цепочку обработки данных. Отсутствие модульности затрудняет достижение гибкости, масштабируемости и поддерживаемости, которые необходимы в современном мире, ориентированном на данные.</p>
<h3><strong>Сложности монолитных моделей</strong></h3>
<p>В монолитной модели данных все компоненты тесно взаимосвязаны, что делает идентификацию и изоляцию проблем крайне сложной задачей. По сути, этот традиционный подход к проектированию систем данных объединяет всю систему в единую, хотя и не всегда целостную, структуру.</p>
<p>Такая взаимосвязанность модели приводит к тому, что на первый взгляд несвязанные изменения могут иметь непредвиденные последствия, затрагивающие всю систему. Эта сложность не только усложняет поиск и устранение ошибок, но и увеличивает риск внесения ошибок или упущения критически важных зависимостей.</p>
<p><strong>Кроме того, недостаток модульности в моделях данных ограничивает способность адаптироваться к изменяющимся бизнес-требованиям.</strong> В динамичной среде потребностей в данных, где источники постоянно меняются, монолитная модель становится узким местом для прогресса. Интеграция новых источников данных, масштабирование инфраструктуры или внедрение новых технологий и фреймворков становится всё более сложной задачей.</p>
<p>Также поддержка и обновление монолитной модели данных становятся времязатратными и ресурсоёмкими процессами. Каждое изменение несёт в себе больше рисков из-за сложных зависимостей внутри системы. Страх случайного нарушения критически важных компонентов приводит к чрезмерно осторожному подходу, замедляющему циклы разработки и тормозящему инновации.</p>
<h3><strong>Преимущества модульного подхода</strong></h3>
<p><span style="color: #ff6600;"><strong>Проблемы, создаваемые монолитными моделями данных, делают переход к модульным моделям необходимостью.</strong> </span>Принятие модульного подхода позволяет инженерам данных добиться большей гибкости, надёжности и адаптивности инфраструктуры данных, чтобы управлять сложностью быстро развивающейся экосистемы данных.</p>
<p>Отход от монолитных структур позволяет организациям максимально эффективно использовать свои данные, стимулировать инновации и получать конкурентные преимущества в современном мире, основанном на данных.</p>
<p><strong>Инструмент dbt играет ключевую роль в реализации модульного подхода и преодолении проблем монолитных моделей.</strong> Он позволяет улучшить поддерживаемость, гибкость и масштабируемость, разделяя единую модель данных на отдельные модули, каждый из которых имеет свой собственный SQL-код и зависимости. Такая модульная структура позволяет работать с отдельными модулями независимо, что упрощает разработку, тестирование и отладку конкретных частей модели данных.</p>
<p>Эта структура устраняет риск непреднамеренных изменений, влияющих на всю систему, и делает процесс внесения изменений и обновлений безопаснее. Тема модульности с использованием dbt будет более подробно рассмотрена в следующих подразделах, а в Главе 4 будет дано её детальное исследование.</p>
<h1>Построение модульных моделей данных</h1>
<p>Приведённый выше пример подчёркивает, насколько важны dbt и модульный подход к моделированию данных для улучшения процесса разработки. Однако почему это до сих пор не стало повсеместной практикой среди инженеров и учёных данных?</p>
<p>Дело в том, что за последние несколько десятилетий в мире разработки программного обеспечения инженеры и архитекторы внедрили модульный подход, чтобы упростить процесс кодирования. Вместо того чтобы обрабатывать единый большой фрагмент кода, модульный подход разделяет процесс разработки на несколько этапов. Этот метод имеет ряд преимуществ по сравнению с альтернативными стратегиями.</p>
<h3><strong>Преимущества модульного подхода</strong></h3>
<p><strong><span style="color: #003366;">Улучшение управляемости</span></strong></p>
<p>Одна из ключевых выгод модульного подхода заключается в том, что он упрощает управление процессом. Разработка крупной программы может быть сложной задачей, но её разбивка на отдельные задачи делает процесс более управляемым. Это помогает разработчикам сохранять концентрацию и избегать чувства перегруженности масштабом проекта.</p>
<p><span style="color: #003366;"><strong>Поддержка командной работы</strong></span></p>
<p>Вместо того чтобы поручать масштабную задачу одному программисту, её можно разделить между членами команды. Каждый разработчик получает свои задачи в рамках общего проекта. Затем результаты их работы объединяются в финальную программу. Этот подход ускоряет процесс разработки и позволяет команде сосредоточиться на своих специализациях.</p>
<p><strong><span style="color: #003366;">Повышение качества кода</span></strong></p>
<p>Разделение кода на небольшие части и закрепление ответственности за каждым разработчиком помогает повысить качество каждой секции. Сфокусировавшись только на своём фрагменте, программист может добиться его безупречности. Как результат, общая программа с меньшей вероятностью будет содержать ошибки после интеграции всех частей.</p>
<p><strong><span style="color: #003366;">Повторное использование кода</span></strong></p>
<p>Модульный подход позволяет многократно использовать уже проверенные модули. Разделяя программу на модули, мы упрощаем её основные аспекты. Если какой-то фрагмент кода хорошо справляется с конкретной задачей, нет необходимости разрабатывать его заново. Вместо этого его можно использовать повторно, экономя время и усилия программистов. Этот процесс можно применять во всей программе, каждый раз, когда требуются схожие функции, что упрощает разработку.</p>
<p><strong><span style="color: #003366;">Организация и читаемость</span></strong></p>
<p>Модульный код отличается высокой организованностью, что улучшает его читаемость. Разделяя код по задачам, программисты могут легко находить и ссылаться на конкретные секции в соответствии с организационной схемой. Это облегчает взаимодействие между разработчиками, так как они могут следовать общей схеме и быстрее понимать структуру кода.</p>
<p><span style="color: #003366;"><strong>Результат:</strong> </span>надёжность и масштабируемость</p>
<p>Все преимущества модульного подхода в конечном итоге ведут к повышению надёжности. Код, который проще читать, отлаживать, поддерживать и совместно использовать, содержит меньше ошибок. Это особенно важно в крупных проектах, где множество разработчиков делятся кодом или взаимодействуют с ним. Модульность позволяет создавать сложные программы более надёжно.</p>
<p>Хотя модульный подход является стандартом в мире разработки программного обеспечения, в сфере данных он долгое время оставался в стороне и начал активно применяться только в последние годы. Причина этого кроется в разрыве между архитектурой данных и программной инженерией. Однако в последние годы индустрия эволюционировала, объединив эти две области, так как упомянутые преимущества одинаково применимы и к аналитике, и к обработке данных.</p>
<h3><strong>Модульность в моделировании данных</strong></h3>
<p><strong>Подобно тому, как модульный подход упрощает процесс разработки кода, он также может оптимизировать проектирование и разработку моделей данных.</strong> Разбивая сложные структуры данных на модульные компоненты, инженеры данных могут лучше управлять ими на различных уровнях детализации. Такой подход позволяет эффективно интегрировать данные, масштабировать архитектуру и повышать её гибкость, облегчая обновления, поддержку и улучшение всей системы.</p>
<p>Кроме того, модульный подход способствует повторному использованию компонентов данных, обеспечивая согласованность и точность моделей, а также снижая избыточность. В целом, принципы модульности обеспечивают надёжную основу для эффективного моделирования и обработки данных, улучшая их организацию, доступность и надёжность.</p>
<p><strong><span style="color: #ff6600;">Модульное моделирование данных</span> </strong>— это мощный метод проектирования эффективных и масштабируемых систем данных. Разработчики могут создавать более надёжные и легко поддерживаемые системы, разбивая сложные структуры данных на небольшие, повторно используемые компоненты. Это позволяет достигать высокой эффективности, а такие инструменты, как dbt и SQL, предоставляют эффективные средства для реализации данного подхода.</p>
<p>В итоге основные принципы модульного моделирования данных можно сформулировать следующим образом:</p>
<p><span style="color: #003366;"><strong>Декомпозиция</strong></span></p>
<p>Разделение модели данных на меньшие, более управляемые компоненты.</p>
<p><span style="color: #003366;"><strong>Абстракция</strong></span></p>
<p>Сокрытие деталей реализации модели данных за интерфейсами.</p>
<p><strong><span style="color: #003366;">Повторное использование</span></strong></p>
<p>Создание компонентов, которые можно использовать повторно в разных частях системы.</p>
<p>Этот тип моделирования данных может быть реализован с использованием таких методов, как нормализация, создание хранилищ данных и виртуализация данных. Например, с помощью нормализации данные разделяются на таблицы на основе их характеристик и связей, что приводит к созданию модульной модели данных.</p>
<p>Другой вариант — использование dbt, который автоматизирует процесс создания модульной модели данных, предоставляя множество функций, поддерживающих принципы модульности.</p>
<ul>
<li><strong>Декомпозиция:</strong> dbt позволяет разбивать модель данных на меньшие, повторно используемые компоненты, создавая макросы и модульные файлы моделей.</li>
<li><strong>Абстракция:</strong> dbt предоставляет простой и единообразный интерфейс для работы с источниками данных, скрывая детали реализации модели.</li>
<li><strong>Повторное использование:</strong> dbt позволяет определять и использовать общий код в различных моделях.</li>
<li><strong>Удобство сопровождения:</strong> dbt предоставляет возможности для тестирования и документирования моделей данных.</li>
<li><strong>Оптимизация производительности:</strong> dbt поддерживает тестирование различных стратегий материализации, позволяя настраивать производительность отдельных компонентов модели данных.</li>
</ul>
<h3>Возможные недостатки модульности</h3>
<p><strong>Несмотря на очевидные преимущества, модульность может иметь свои риски и недостатки:</strong></p>
<ul>
<li><strong><span style="color: #003366;">Оптимизация интегрированных систем:</span> </strong>Интегрированные системы часто можно оптимизировать лучше, чем модульные, благодаря минимизации перемещения данных, снижению использования памяти и возможностям оптимизатора базы данных улучшать SQL на уровне базы данных. Например, создание таблиц из представлений может приводить к неоптимальным моделям.</li>
<li><span style="color: #003366;"><strong>Увеличение количества объектов:</strong></span> Модульность создаёт больше файлов, что означает больше объектов для управления, сопровождения и, возможно, вывода из эксплуатации. Без зрелой стратегии управления данными это может привести к хаосу, связанному с множеством модульных, но неуправляемых таблиц, которые становится сложно поддерживать при возникновении проблем.</li>
</ul>
<h2>Реализация модульных моделей данных с помощью dbt</h2>
<p>Как уже отмечалось, создание модульных моделей данных является важным аспектом разработки надёжной и поддерживаемой инфраструктуры данных. Однако с ростом проекта управление и координация таких моделей могут становиться сложными.</p>
<p>Здесь на помощь приходит мощный инструмент трансформации данных, такой как <strong>dbt</strong>. Совмещая принципы модульного моделирования с возможностями <strong>dbt</strong>, можно достичь нового уровня эффективности и масштабируемости в управлении данными.</p>
<p>Принятие модульного подхода даёт возможность каждому участнику — будь то производитель или потребитель данных — использовать результаты работы других, устраняя необходимость каждый раз начинать с исходных данных.</p>
<p>Интеграция dbt в процесс моделирования данных трансформирует взгляд на модели данных: они перестают быть монолитными структурами и становятся отдельными компонентами. Каждый участник проекта начинает выявлять преобразования, которые можно использовать в различных моделях данных. Эти общие преобразования извлекаются и организуются в базовые модели, что позволяет эффективно ссылаться на них в разных контекстах.</p>
<p>Как показано на Рисунке 2-5, использование базовых моделей данных в различных сценариях, вместо того чтобы каждый раз начинать с нуля, упрощает визуализацию DAG (ориентированного ациклического графа) при моделировании данных. Модульная многоуровневая структура делает понятной логику построения уровней моделей данных и их зависимости.</p>
<p>Однако важно понимать, что простое использование dbt или любого другого фреймворка для моделирования данных не гарантирует автоматическое создание модульных моделей или интуитивно понятного DAG.</p>
<p><strong>Рисунок 2-5. Модульность в dbt</strong></p>
<p><a href="https://datatalks.ru/wp-content/uploads/2024/12/2_5_dbt_modularity.jpeg" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="aligncenter wp-image-483 size-full" src="https://datatalks.ru/wp-content/uploads/2024/12/2_5_dbt_modularity.jpeg" alt="" width="751" height="199" srcset="https://datatalks.ru/wp-content/uploads/2024/12/2_5_dbt_modularity.jpeg 751w, https://datatalks.ru/wp-content/uploads/2024/12/2_5_dbt_modularity-300x79.jpeg 300w, https://datatalks.ru/wp-content/uploads/2024/12/2_5_dbt_modularity-450x119.jpeg 450w" sizes="(max-width: 751px) 100vw, 751px" /></a></p>
<p>Структура вашего DAG зависит от подходов вашей команды к моделированию данных, их идей и последовательности их выражения. Для достижения модульного моделирования данных учитывайте такие принципы, как соглашения об именах, читаемость, а также лёгкость отладки и оптимизации.</p>
<p>Эти принципы можно применять к различным моделям в dbt, включая <strong>модели подготовки (staging models)</strong>, <strong>промежуточные модели (intermediate models)</strong> и <strong>модели для визуализации данных (mart models)</strong>, чтобы улучшить модульность и поддерживать хорошо структурированный DAG.</p>
<p>Начнём путь к использованию dbt для создания модульных моделей данных с понимания того, как dbt обеспечивает повторное использование моделей через синтаксис <strong>Jinja</strong> с использованием ссылки на модели: <code>{{ ref() }}</code>.</p>
<h3><strong>Ссылки на модели данных</strong></h3>
<p>Используя функции dbt, такие как <strong>ссылки на модели (model referencing)</strong> и <strong>синтаксис Jinja</strong>, инженеры данных и аналитики могут устанавливать чёткие зависимости между моделями, повышать повторное использование кода и обеспечивать согласованность и точность своих конвейеров обработки данных.</p>
<p><strong><span style="color: #ff6600;">Jinja</span> в данном контексте — это язык шаблонов, который позволяет выполнять динамические и программируемые преобразования в SQL-коде, предоставляя мощный инструмент для настройки и автоматизации преобразований данных.</strong></p>
<p>Эта мощная комбинация модульности и возможностей dbt позволяет командам создавать гибкие и удобные для сопровождения модели данных, ускоряя процесс разработки и обеспечивая беспрепятственное взаимодействие между заинтересованными сторонами.</p>
<p>Для того чтобы максимально использовать возможности dbt и обеспечить точное построение моделей, важно применять ссылки на модели с помощью синтаксиса <code>{{ ref() }}</code>. Используя этот способ, dbt может автоматически определять и устанавливать зависимости между моделями на основе вышестоящих таблиц. Это обеспечивает плавное и надёжное выполнение конвейера преобразования данных.</p>
<p>С другой стороны, синтаксис Jinja <code>{{ source() }}</code> следует использовать экономно, обычно ограничиваясь первоначальным выбором сырых данных из базы данных. Важно избегать прямых ссылок на таблицы, созданные не с помощью dbt, так как это может затруднить гибкость и модульность рабочего процесса dbt.</p>
<p>Вместо этого внимание должно быть сосредоточено на установлении связей между моделями с использованием синтаксиса Jinja <code>{{ ref() }}</code>, чтобы изменения в вышестоящих таблицах правильно распространялись вниз по потоку, поддерживая чёткий и последовательный процесс преобразования данных. Придерживаясь этих передовых практик, dbt позволяет эффективно управлять моделями и способствует масштабируемости и удобству сопровождения в аналитических рабочих процессах.</p>
<h3><strong>Пример: работа с ссылками на модели</strong></h3>
<p>Допустим, у нас есть две модели: <code>orders</code> и <code>customers</code>, где таблица <code>orders</code> содержит информацию о заказах клиентов, а таблица <code>customers</code> — данные о клиентах. Мы хотим выполнить объединение этих двух таблиц, чтобы обогатить данные заказов информацией о клиентах (Пример 2-15).</p>
<p><strong>Пример 2-15. Ссылки на модели</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">-- In the orders.sql file
SELECT
    o.order_id,
    o.order_date,
    o.order_amount,
    c.customer_name,
    c.customer_email
FROM
    {{ ref('orders') }} AS o
JOIN
    {{ ref('customers') }} AS c
ON
    o.customer_id = c.customer_id


-- In the customers.sql file
-- customers.sql
SELECT
    customer_id,
    customer_name,
    customer_email
FROM
    raw_customers</pre><p>Этот пример демонстрирует, как ссылаться на модели в SQL-запросе с использованием функции <strong>ref()</strong>.</p>
<p>Сценарий включает два файла моделей: <code>orders.sql</code> и <code>customers.sql</code>.</p>
<p>В файле <strong>orders.sql</strong> записан оператор SELECT, чтобы извлечь информацию о заказах из модели orders. Выражение <code>{{ ref('orders') }}</code> создаёт ссылку на модель <strong>orders</strong>, позволяя запросу использовать данные, определённые в этой модели.</p>
<p>Запрос объединяет модель <strong>orders</strong> с моделью <strong>customers</strong> по столбцу <code>customer_id</code>, извлекая дополнительную информацию о клиентах, такую как имя и электронная почта.</p>
<h3><strong>В файле customers.sql</strong></h3>
<p>Оператор <code>SELECT</code> используется для извлечения информации о клиентах из таблицы <code>raw_customers</code>. Эта модель представляет необработанные данные о клиентах до выполнения каких-либо преобразований.</p>
<p>Этот механизм ссылок в <strong>dbt</strong> позволяет создавать модульные и взаимосвязанные модели, которые основываются друг на друге для получения значимых аналитических выводов и отчётов.</p>
<p>Чтобы показать необходимость такого подхода, представим практический пример: вы работаете со сложным набором данных, таким как еженедельные заказы продуктов. Без структурированного подхода управление такими данными быстро становится хаотичным. Вы можете столкнуться с запутанной сетью SQL-запросов, что затруднит отслеживание зависимостей, сопровождение кода и обеспечение точности данных.</p>
<h3><strong>Преимущества структурированного подхода</strong></h3>
<p>Организация процесса преобразования данных в чёткие слои — от исходных данных до<strong> таблиц визуализации (mart tables)</strong> — приносит множество преимуществ. Это упрощает конвейер обработки данных, делая его более понятным и управляемым.</p>
<p>Кроме того, это позволяет вносить поэтапные улучшения, так как каждый слой сосредоточен на конкретной задаче преобразования. Такой подход улучшает сотрудничество между инженерами данных и аналитиками, снижает количество ошибок и, в конечном итоге, приводит к созданию более надёжных и информативных отчётов.</p>
<h3><strong>Слой промежуточных данных (Staging Data Models)</strong></h3>
<p><strong>Слой промежуточных данных (или стейджинг)</strong> играет ключевую роль в моделировании данных, поскольку служит основой для модульного построения более сложных моделей данных. Каждая staging модель данных соответствует исходной таблице с отношением 1:1 к оригинальному источнику данных.</p>
<p>В этом слое важно сохранять простоту моделей подготовки и минимизировать преобразования.</p>
<p><strong>Допустимыми являются такие преобразования, как:</strong></p>
<ul>
<li>Преобразование типов данных,</li>
<li>Переименование столбцов,</li>
<li>Простые вычисления (например, преобразование единиц измерения),</li>
<li>Классификация с использованием условных операторов, таких как <code>CASE WHEN</code>.</li>
</ul>
<p><strong>Staging модели</strong> подготовки обычно материализуются как представления (views), чтобы сохранять актуальность данных и оптимизировать затраты на хранение. Этот подход позволяет промежуточным или итоговым моделям, ссылающимся на слой подготовки, получать доступ к актуальным данным, экономя при этом место и затраты.</p>
<h3><strong>Избежание сложных операций</strong></h3>
<p><strong><span style="color: #003366;">Соединения (Joins):</span> </strong>избегайте их на уровне staging, чтобы предотвратить избыточные или дублирующие вычисления. Соединения лучше выполнять на последующих слоях, где устанавливаются более сложные отношения.</p>
<p><span style="color: #003366;"><strong>Агрегации:</strong> </span>их также следует избегать, так как они могут группировать данные и ограничивать доступ к ценным исходным данным.</p>
<p><span style="color: #003366;"><strong>Основная цель staging слоя</strong> </span>— создать базовые строительные блоки для последующих моделей данных, предоставляя гибкость и масштабируемость для дальнейших преобразований.</p>
<h3><strong>Использование принципа DRY</strong></h3>
<p><strong>Применение моделей staging данных в dbt позволяет использовать принцип &#171;Don’t Repeat Yourself&#187; (DRY) в коде.</strong> Следуя модульной и повторно используемой структуре dbt, мы стараемся переносить все преобразования, которые часто требуются для определённой модели, как можно ближе к источнику.</p>
<p>Это помогает избежать дублирования кода, снижая его сложность и вычислительную нагрузку. Например, если нам часто нужно преобразовывать денежные значения из целых чисел в центах в числа с плавающей точкой в долларах, то выполнение такого преобразования на этапе подготовки данных будет более эффективным. Это позволяет ссылаться на преобразованные значения на следующих этапах, не повторяя те же преобразования несколько раз.</p>
<h3><strong>Пример промежуточной модели данных (staging data model)</strong></h3>
<p>Предположим, у нас есть исходная таблица под названием <code>raw_books</code>, содержащая необработанные данные о книгах. Мы хотим создать staging модель данных под названием <code>stg_books</code>, чтобы преобразовать и подготовить данные для дальнейшей обработки.</p>
<p>В нашем проекте dbt можно создать новый файл модели dbt под названием <strong>stg_books.sql</strong> и определить логику для создания модели подготовки, как показано в Примере 2-16.</p>
<p><strong>Пример 2-16. Модель промежуточных данных (Staging model)</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">/* This should be file stg_books.sql, and it queries the raw table to create
the new model */
SELECT
    book_id,
    title,
    author,
    publication_year,
    genre
FROM
    raw_books</pre><p>Модель промежуточных данных, например <code>stg_books</code> в данном примере, выбирает нужные столбцы из таблицы <code>raw_books</code>. Она может включать базовые преобразования, такие как переименование столбцов или преобразование типов данных.</p>
<p>Создание staging модели позволяет отделить начальное преобразование данных от последующей обработки. Это обеспечивает качество данных, их согласованность и соответствие стандартам перед дальнейшим использованием. Staging модели служат основой для создания более сложных моделей данных в промежуточных и итоговых слоях конвейера обработки данных. Они упрощают преобразования, сохраняют целостность данных и повышают повторяемость и модульность вашего проекта dbt.</p>
<h3><strong>Базовые модели данных (Base Data Models)</strong></h3>
<p><strong>В dbt базовые модели</strong> часто служат staging моделями, но они также могут включать дополнительные этапы преобразования в зависимости от конкретных потребностей проекта. Эти модели обычно разрабатываются для прямой работы с необработанными данными, загружаемыми в ваш хранилище данных, и играют ключевую роль в процессе преобразования данных.</p>
<p>После создания staging моделей или базовых моделей другие модели в вашем проекте dbt могут ссылаться на них.</p>
<p>Изменение термина с base на staging модели в документации dbt отражает стремление не ограничиваться названием &#171;base&#187;, которое подразумевает первый шаг в создании модели данных. Новая терминология обеспечивает большую гибкость в описании роли и цели этих моделей в рамках dbt.</p>
<h3><strong>Промежуточные/средние модели данных (Intermediate Data Models)</strong></h3>
<p><strong>Промежуточный слой (intermediate layer)</strong> играет важную роль в моделировании данных, объединяя атомарные строительные блоки из слоя подготовки для создания более сложных и значимых моделей.</p>
<p>Эти промежуточные модели представляют собой конструкции, имеющие значение для бизнеса, но обычно не показываемые напрямую конечным пользователям через панели управления или приложения.</p>
<p>Чтобы сохранить разделение и оптимизировать производительность, рекомендуется хранить промежуточные модели в виде <strong>эфемерных моделей (ephemeral models)</strong>. Эфемерные модели не создаются напрямую в базе данных или наборе данных, а их код вставляется в модели, которые ссылаются на них, как <strong>общее выражение таблицы (CTE &#8212; Common Table Expressions)</strong>.</p>
<p>Иногда предпочтительнее материализовать их как <strong>представления (views)</strong>. Однако стоит учитывать, что эфемерные модели невозможно выбрать напрямую, что затрудняет отладку. Кроме того, макросы, вызываемые через <strong>run-operation</strong>, не могут ссылаться на эфемерные модели.</p>
<p>Выбор между <strong>эфемерной материализацией и представлением</strong> зависит от конкретного случая использования, но начальная настройка в виде эфемерной модели рекомендуется.</p>
<h3><strong>Организация промежуточных моделей</strong></h3>
<p>Если вы решаете материализовать Intermediate модели как <strong>представления</strong>, полезно размещать их в пользовательской схеме за пределами основной схемы, указанной в вашем профиле <strong>dbt</strong>. Это помогает эффективно организовать модели и управлять правами доступа.</p>
<p><strong>Основная цель Intermediate слоя</strong> — объединить различные сущности и упростить сложность итоговых моделей. Эти модели улучшают читаемость и гибкость общей структуры модели данных.</p>
<p>Важно учитывать частоту ссылок на<strong> Intermediate модель</strong> в других моделях. Если несколько моделей ссылаются на одну и ту же промежуточную модель, это может указывать на проблему в дизайне. В таких случаях преобразование промежуточной модели в макрос может стать подходящим решением для повышения модульности и сохранения более чистого дизайна.</p>
<h3><strong>Эффективное использование промежуточного слоя</strong></h3>
<p>При грамотном использовании промежуточного слоя модели данных становятся более модульными и управляемыми, обеспечивая поглощение сложности при сохранении читаемости и гибкости компонентов.</p>
<p>Предположим, у нас есть две модели подготовки данных, stg_books и stg_authors, представляющие данные о книгах и авторах соответственно. Теперь мы хотим создать промежуточную модель под названием int_book_authors, которая объединяет соответствующую информацию из обеих подготовительных моделей.</p>
<p>В нашем проекте dbt мы можем создать новый файл модели dbt под названием int_book_authors.sql, как показано в Примере 2-17, и определить логику для создания промежуточной модели.</p>
<p><strong>Пример 2-17. Intermediate model</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">-- This should be file int_book_authors.sql
-- Reference the staging models
WITH
books AS (
    SELECT *
    FROM {{ ref('stg_books') }}
),
authors AS (
    SELECT *
    FROM {{ ref('stg_authors') }}
)
-- Combine the relevant information
SELECT
    b.book_id,
    b.title,
    a.author_id,
    a.author_name
FROM
    books b
JOIN
    authors a ON b.author_id = a.author_id</pre><p>В Примере 2-17 модель <code>int_book_authors</code> ссылается на модели подготовки данных <code>stg_books</code> и <code>stg_authors</code>, используя синтаксис <code>{{ ref() }}</code> <strong>Jinja</strong>. Это гарантирует, что <strong>dbt</strong> сможет правильно определить зависимости модели и построить промежуточную модель на основе вышестоящих таблиц.</p>
<h3><strong>Модели витрин данных (Mart Models)</strong></h3>
<p><strong>Верхний слой конвейера</strong> обработки данных состоит из <strong>моделей витрин</strong>, которые отвечают за интеграцию и предоставление бизнес-определённых сущностей конечным пользователям через панели управления или приложения. Эти модели объединяют все релевантные данные из нескольких источников и преобразуют их в целостное представление.</p>
<p><strong>Для обеспечения оптимальной производительности модели витрин обычно материализуются как таблицы.</strong> Материализация моделей позволяет ускорить выполнение запросов и улучшить скорость получения результатов для конечных пользователей. Если время создания или стоимость материализации таблицы становятся проблемой, можно рассмотреть настройку модели как инкрементальной, что позволяет эффективно обновлять данные при добавлении новых записей.</p>
<p>Простота — ключ к моделям витрин, и следует избегать чрезмерного использования соединений (<code>joins</code>). Если в модели витрин требуется множество соединений, стоит пересмотреть дизайн и подумать о переструктурировании промежуточного слоя. Поддерживая относительную простоту моделей витрин, вы обеспечиваете эффективное выполнение запросов и сохраняете общую производительность конвейера обработки данных.</p>
<p>Рассмотрим пример витрины данных для анализа публикаций книг. У нас есть промежуточная модель <code>int_book_authors</code>, которая содержит необработанные данные о книгах, включая информацию об авторах каждой книги (Пример 2-18).</p>
<p><strong>Пример 2-18. Модель витрины данных</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">-- This should be file mart_book_authors.sql
{{
    config(
        materialized='table',
        unique_key='author_id',
        sort='author_id'
    )
}}
WITH book_counts AS (
    SELECT
        author_id,
        COUNT(*) AS total_books
    FROM {{ ref('int_book_authors') }}
    GROUP BY author_id
)
SELECT
    author_id,
    total_books
FROM book_counts</pre><p>Мы начинаем с настройки конфигурации для модели, указывая, что она должна материализоваться как таблица. Уникальный ключ задан как <code>author_id</code> для обеспечения уникальности, а сортировка выполняется по этому же ключу.</p>
<p>Далее мы используем <strong>CTE</strong> под названием <code>book_counts</code> для агрегации данных о книгах. Мы выбираем столбец <code>author_id</code> и подсчитываем количество книг, связанных с каждым автором, из модели подготовки данных <code>stg_books</code>. В заключение, оператор <code>SELECT</code> извлекает агрегированные данные из <strong>CTE</strong> <code>book_counts</code>, возвращая <code>author_id</code> и соответствующее количество книг для каждого автора. Поскольку это материализованная таблица, модель может обновляться при необходимости, чтобы отразить изменения в исходных данных.</p>
<h2>Тестирование моделей данных</h2>
<p><strong>Тестирование в dbt</strong> — важный аспект обеспечения точности и надёжности моделей данных и источников данных. <strong>dbt</strong> предоставляет комплексную структуру тестирования, позволяющую определять и выполнять тесты с использованием <strong>SQL-запросов</strong>. Эти тесты предназначены для выявления строк или записей, не соответствующих заданным критериям утверждений, а не для проверки корректности конкретных условий.</p>
<p>В dbt существует два основных типа тестов: <strong>единичные (singular)</strong> и <strong>общие (generic)</strong>. Единичные тесты представляют собой специфические, целенаправленные тесты, написанные в виде SQL-запросов и хранящиеся в отдельных файлах SQL. Они позволяют проверять определённые аспекты ваших данных, такие как отсутствие <code>NULL</code>-значений в таблице фактов или валидацию определённых преобразований данных. Используя единичные тесты, можно задействовать мощь <strong>Jinja</strong> для динамического определения утверждений на основе данных и бизнес-требований.</p>
<p>Рассмотрим единичный тест в dbt, анализируя Пример 2-19.</p>
<p><strong>Пример 2-19. Пример единичного теста в dbt</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">version: 2

models:
  - name: my_model
  tests:
    - not_null_columns:
      columns:
        - column1
        - column2</pre><p>В этом примере мы определяем единичный тест под названием <code>not_null_columns</code> для модели dbt <code>my_model</code>. Этот тест проверяет, содержат ли определённые столбцы модели значения <code>NULL</code>. Параметр <code>columns</code> указывает столбцы для проверки на наличие <strong>NULL-значений</strong>. В данном случае указаны <code>column1</code> и <code>column2</code>. Если какой-либо из этих столбцов содержит значения <code>NULL</code>, тест завершается с ошибкой.</p>
<p>С другой стороны, <strong>общие тесты (generic tests)</strong> более универсальны и могут быть применены к нескольким моделям или источникам данных. Они определяются в файлах проекта <strong>dbt</strong> с использованием специального синтаксиса. Эти тесты позволяют задавать более комплексные критерии проверки данных, такие как проверка консистентности данных между таблицами или обеспечение целостности определённых столбцов. Они обеспечивают гибкий и повторно используемый способ определения утверждений, которые могут быть применены ко всем моделям dbt. <strong>Общие тесты пишутся и хранятся в файлах YAML (.yml)</strong>, что позволяет параметризовать запросы и легко использовать их в различных контекстах.</p>
<p>Параметризация запросов в общих тестах позволяет быстро адаптировать тесты для множества сценариев. Например, вы можете указать разные имена столбцов или условия при применении общего теста к различным моделям или наборам данных.</p>
<p>Рассмотрим один из таких общих тестов в Примере 2-20.</p>
<p><strong>Пример 2-20. Пример общего теста в dbt</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">version: 2

tests:
  - name: non_negative_values
    severity: warn
    description: Check for non-negative values in specific columns
    columns:
      - column_name: amount
        assert_non_negative: {}
      - column_name: quantity
        assert_non_negative: {}</pre><p>В этом примере общий тест определён под названием <code>non_negative_values</code>. Здесь указаны столбцы для проверки и критерии утверждения для каждого столбца. Тест проверяет, являются ли значения в столбцах <code>amount</code> и <code>quantity</code> неотрицательными. Общие тесты позволяют написать повторно используемую логику тестирования, которая может быть применена ко многим моделям в вашем проекте dbt.</p>
<p>Чтобы повторно использовать общий тест в нескольких моделях, мы можем ссылаться на него в разделе tests каждого отдельного <strong>YAML-файла модели</strong>, как показано в Примере 2-21.</p>
<p><strong>Пример 2-21. Повторное использование общего теста</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">version: 2

models:
  - name: my_model
    columns:
      - column_name: amount
        tests: ["my_project.non_negative_values"]
      - column_name: quantity
        tests: ["my_project.non_negative_values"]</pre><p>В этом примере определена модель <code>my_model</code>, а столбцы <code>amount</code> и <code>quantity</code> указаны с соответствующими тестами. Тесты ссылаются на общий тест <code>non_negative_values</code> из пространства имён <code>my_project</code> (предполагается, что <strong>my_project</strong> — это имя вашего проекта dbt).</p>
<p>Указывая общий тест в разделе <strong>tests</strong> каждой модели, вы можете повторно использовать одну и ту же логику тестирования в нескольких моделях. Такой подход обеспечивает согласованность проверки данных и позволяет легко применять общий тест к определённым столбцам в разных моделях без дублирования логики теста.</p>
<p>Обратите внимание, что <strong>файл YAML</strong> для общего теста должен находиться в правильной директории в структуре вашего <strong>проекта dbt</strong>, и может понадобиться изменить ссылку на тест, чтобы она соответствовала пространству имён и структуре папок вашего проекта.</p>
<h2>Генерация документации данных</h2>
<p>Ещё одним важным компонентом правильного моделирования данных является документация. В частности, обеспечение того, чтобы все в вашей организации, включая бизнес-пользователей, могли легко понимать и получать доступ к метрикам, таким как <strong>ARR (ежегодный повторяющийся доход)</strong>, <strong>NPS (индекс потребительской лояльности)</strong> или <strong>MAU (ежемесячное количество активных пользователей)</strong>, является ключевым для принятия решений на основе данных.</p>
<p>Используя возможности dbt, мы можем документировать, как определяются такие метрики и на каких исходных данных они основаны. Эта документация становится ценным ресурсом, доступным для всех, способствуя прозрачности и упрощая самостоятельное исследование данных.</p>
<p>Устраняя эти семантические барьеры и предоставляя доступную документацию, dbt позволяет пользователям с любым уровнем технической подготовки ориентироваться в наборах данных, обеспечивая, что ценные инсайты доступны широкой аудитории.</p>
<p>Предположим, у нас есть проект dbt с моделью <code>nps_metrics.sql</code>, которая вычисляет индекс потребительской лояльности. Мы можем легко задокументировать эту метрику, используя комментарии внутри SQL-файла, дополненные синтаксисом Markdown, как показано в Примере 2-22.</p><pre class="urvanov-syntax-highlighter-plain-tag">/* nps_metrics.sql

-- This model calculates the Net Promoter Score (NPS)
for our product based on customer feedback.

Dependencies:
- This model relies on the "customer_feedback"
table in the "feedback" schema, which stores customer feedback data.
- It also depends on the "customer" table in the "users"
schema, containing customer information.

Calculation:
-- The NPS is calculated by categorizing customer
feedback from Promoters, Passives, and Detractors
based on their ratings.
-- Promoters: Customers with ratings of 9 or 10.
-- Passives: Customers with ratings of 7 or 8.
-- Detractors: Customers with ratings of 0 to 6.
-- The NPS is then derived by subtracting the percentage
of Detractors from the percentage of Promoters.
*/

-- SQL Query:
WITH feedback_summary AS (
    SELECT
        CASE
            WHEN feedback_rating &gt;= 9 THEN 'Promoter'
            WHEN feedback_rating &gt;= 7 THEN 'Passive'
            ELSE 'Detractor'
        END AS feedback_category
    FROM
        feedback.customer_feedback
    JOIN
        users.customer
    ON customer_feedback.customer_id = customer.customer_id
)
SELECT
    (COUNT(*) FILTER (WHERE feedback_category = 'Promoter')
    - COUNT(*) FILTER (WHERE feedback_category = 'Detractor')) AS nps
FROM
    feedback_summary;</pre><p>В этом примере комментарии предоставляют важную информацию о метрике NPS. Они указывают зависимости модели <code>nps_metrics</code>, объясняют процесс расчёта и упоминают соответствующие таблицы, используемые в запросе.</p>
<p>После документирования модели мы можем сгенерировать документацию для нашего проекта dbt, используя <strong>интерфейс командной строки dbt (CLI)</strong> и выполнив следующую команду (Пример 2-23).</p>
<p><strong>Пример 2-23. Генерация документации</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">dbt docs generate</pre><p>Выполнение этой команды создаёт <strong>HTML-документацию</strong> для <strong>всего проекта dbt</strong>, включая задокументированную метрику NPS. Полученная документация может быть размещена и сделана доступной для пользователей в вашей организации, что позволит легко находить и понимать метрику NPS.</p>
<h2>Отладка и оптимизация моделей данных</h2>
<p>Ценное предложение по оптимизации производительности dbt заключается в тщательном анализе и оптимизации самих запросов. Один из подходов — использовать возможности планировщика запросов, например, планировщика PostgreSQL (Postgres). Понимание работы планировщика запросов помогает выявить возможные узкие места и неэффективности в выполнении запросов.</p>
<p>Другим эффективным методом оптимизации является деконструкция сложных запросов, разбивая их на более мелкие компоненты, такие как CTE (общие табличные выражения). В зависимости от сложности и природы операций, эти CTE затем могут быть преобразованы либо в представления (views), либо в таблицы (tables). Для каждой операции можно задать желаемый способ материализации с помощью <strong>блока config</strong> в <strong>dbt</strong>.</p>
<p>Значительные улучшения производительности могут быть достигнуты за счёт выбора подходящей техники материализации. Это позволяет ускорить выполнение запросов, сократить задержки обработки и повысить общую эффективность моделирования данных. В частности, использование материализации в виде таблиц часто показывает впечатляющий прирост производительности, что может значительно ускорить обработку данных в зависимости от сценария.</p>
<p>Реализация этих рекомендаций по оптимизации позволяет создать более эффективный и рациональный рабочий процесс в dbt. Оптимизация запросов и выбор подходящих стратегий материализации повышают производительность моделей dbt, улучшая обработку данных и обеспечивая более эффективные преобразования.</p>
<p><strong>Рассмотрим сложный запрос в Примере 2-24.</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">SELECT 
    column1, 
    column2, 
    SUM(column3) AS total_sum
FROM table1
INNER JOIN table2 ON table1.id = table2.id
WHERE column4 = 'some_value'
GROUP BY column1, column2
HAVING total_sum &gt; 1000</pre><p>Этот запрос включает объединение таблиц, применение фильтров и выполнение агрегаций. Деконструируем его на несколько <strong>CTE</strong> перед созданием финальной модели (Пример 2-25).</p>
<p><strong>Пример 2-25. Деконструкция сложного запроса 1</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">-- Deconstructing a complex query using CTEs for optimization
-- CTE 1: Joining required data
WITH join_query AS (
    SELECT table1.column1, table1.column2, table2.column3
    FROM table1
    INNER JOIN table2 ON table1.id = table2.id
)
-- CTE 2: Filtering rows
, filter_query AS (
    SELECT column1, column2, column3
    FROM join_query
    WHERE column4 = 'some_value'
)
-- CTE 3: Aggregating and filtering results
, aggregate_query AS (
    SELECT column1, column2, SUM(column3) AS total_sum
    FROM filter_query
    GROUP BY column1, column2
    HAVING total_sum &gt; 1000
)
-- Final query to retrieve the optimized results, and this will be our model
SELECT *
FROM aggregate_query;</pre><p><strong>CTE</strong> <code>join_query</code> фокусируется на объединении необходимых таблиц, в то время как <code>filter_query</code> применяет условия фильтрации для сужения выборки строк. <code>aggregate_query</code> выполняет агрегацию и применяет финальное условие фильтрации.</p>
<p>Разбив сложный запрос на отдельные CTE, вы можете упростить и организовать логику для оптимизации выполнения. Такой подход улучшает читаемость, сопровождаемость и потенциально повышает производительность, так как СУБД может оптимизировать план выполнения для каждого CTE. Финальный запрос извлекает оптимизированные результаты, выбирая столбцы из CTE <code>aggregate_query</code>.</p>
<h3><strong>Процесс отладки материализованных моделей в dbt</strong></h3>
<p>Отладка материализованных моделей может быть сложной задачей, так как требует тщательной проверки. Важным аспектом является обеспечение того, чтобы модель данных соответствовала ожиданиям, а значения совпадали с нематериализованной версией.</p>
<p>Для упрощения отладки и проверки может потребоваться полное обновление всей таблицы, чтобы обработать её как нематериализованную. Это можно сделать с помощью команды <code>dbt run --full-refresh</code>, которая обновляет таблицу и запускает модель как при её первом выполнении.</p>
<p>В некоторых случаях полезно выполнять полное обновление модели и её инкрементальной версии параллельно в течение первых нескольких дней. Этот подход позволяет проверить согласованность между двумя версиями и минимизировать риск будущих расхождений данных. Этот метод особенно эффективен для устоявшихся, надёжных моделей данных, работающих в продакшене, так как он укрепляет уверенность в сделанных изменениях. Сравнивая обновлённую и инкрементальную модели, можно гарантировать точность изменений и уменьшить риск проблем, связанных с данными.</p>
<p>Рассмотрим пример: у нас есть материализованная модель dbt, которая рассчитывает ежемесячную выручку на основе данных транзакций. Чтобы отладить и проверить эту модель, мы подозреваем, что значения, генерируемые материализованной моделью, могут не соответствовать ожидаемым результатам. Для устранения этой проблемы мы решаем полностью обновить таблицу, как если бы она не была инкрементальной. Используя команду <code>dbt run --full-refresh</code>, мы запускаем процесс, обновляющий всю таблицу и выполняющий модель с нуля.</p>
<p>В первые дни мы также запускаем параллельный процесс для обновления материализованной и инкрементальной моделей. Это позволяет сравнить результаты между двумя версиями и убедиться, что они совпадают. Проверяя согласованность обновлённой и инкрементальной моделей, мы укрепляем уверенность в точности внесённых изменений.</p>
<p>Например, если у нас есть устоявшаяся модель выручки, которая используется в продакшене и считается надёжной, сравнение обновлённой и инкрементальной моделей становится ещё более значимым. Таким образом, мы можем убедиться, что изменения в модели не вызвали непреднамеренных расхождений в расчётах выручки. Кроме того, важно проводить комплексное тестирование для обеспечения точности и надёжности ваших моделей данных. Тесты, реализованные на всех этапах рабочего процесса, помогают выявлять проблемы на ранних стадиях и предоставляют ценные сведения о производительности SQL-запросов.</p>
<p>Все эти функциональные возможности dbt, от создания моделей до тестирования и документирования, будут рассмотрены и закреплены в главах 4 и 5.</p>
<h2>Архитектура медальонов (Medallion Architecture Pattern)</h2>
<p>Хранилища данных имеют богатую историю использования в системах поддержки принятия решений и бизнес-аналитике (BI), но ограничены в работе с неструктурированными, полуструктурированными и высокоразнообразными данными. В то же время озёра данных стали хранилищами для хранения данных в различных форматах, но им не хватает таких важных функций, как поддержка транзакций, обеспечение качества данных и консистентность.</p>
<p>Эти недостатки ограничивают их возможности и приводят к потере преимуществ, присущих хранилищам данных. Для удовлетворения современных потребностей компаний требуется гибкая и высокопроизводительная система, которая поддерживает разнообразные приложения для работы с данными, такие как аналитика SQL, мониторинг в реальном времени, анализ данных и машинное обучение. При этом современные достижения в области ИИ делают акцент на обработке широкого диапазона типов данных, включая полуструктурированные и неструктурированные данные, для которых традиционные хранилища данных не оптимизированы.</p>
<p>В результате организации часто используют несколько систем, включая озёра данных, хранилища данных и специализированные базы данных. Это создаёт сложность и задержки из-за перемещения и копирования данных между системами. Чтобы объединить эти традиционные системы и удовлетворить новые требования рынка, появился новый тип систем — <strong>lakehouse</strong>.</p>
<h3><strong>Lakehouse</strong></h3>
<p><strong>Lakehouse</strong> сочетает преимущества озёр данных и хранилищ данных, реализуя структуры данных и функции управления, характерные для хранилищ, непосредственно на экономически эффективном облачном хранилище в открытых форматах, таких как <strong>Apache Delta Lake</strong>, <strong>Iceberg</strong> или <strong>Apache Hudi</strong>. Эти форматы имеют ряд преимуществ перед традиционными форматами файлов, такими как <strong>CSV</strong> и <strong>JSON</strong>.</p>
<ul>
<li><strong>CSV:</strong> не поддерживает типизацию столбцов.</li>
<li><strong>JSON:</strong> предлагает гибкость структуры, но отличается непоследовательной типизацией.</li>
<li><strong>Parquet, Apache Avro, ORC:</strong> улучшают ситуацию, будучи колоночными и более строго типизированными, но не всегда соответствуют требованиям ACID (атомарность, согласованность, изолированность, долговечность).</li>
</ul>
<p><strong>Delta Lake, Iceberg и Hudi добавляют поддержку ACID и возможность служить двусторонними хранилищами данных,</strong> обеспечивая высокую пропускную способность модификаций и поддерживая большие объёмы аналитических запросов. Эти форматы особенно хорошо подходят для современных облачных систем обработки данных в отличие от <strong>Parquet</strong>, изначально разработанного для локальных систем на базе <strong>Hadoop</strong>.</p>
<h3><strong>Преимущества Lakehouse</strong></h3>
<ol>
<li><strong>Поддержка транзакций:</strong> одновременное чтение и запись данных.</li>
<li><strong>Применение схем и управление:</strong> обеспечение согласованности данных.</li>
<li>Прямая поддержка BI-инструментов.</li>
<li><strong>Разделение хранения и вычислений:</strong> масштабируемость.</li>
<li><strong>Открытость:</strong> стандартизированные форматы хранения и API для эффективного доступа.</li>
<li><strong>Поддержка различных типов данных:</strong> от структурированных до неструктурированных.</li>
<li><strong>Совместимость с разными нагрузками:</strong> машинное обучение, аналитика SQL, наука о данных.</li>
<li><strong>Возможности потоковой обработки:</strong> исключение необходимости в отдельных системах для приложений реального времени.</li>
</ol>
<p><strong>Современные lakehouse-системы уровня предприятия включают такие функции, как безопасность, контроль доступа, управление данными, инструменты для их поиска и соответствие нормам конфиденциальности.</strong> Реализация lakehouse позволяет организациям консолидировать эти функции в одной системе, которая используется инженерами данных, аналитиками, учёными, а также инженерами машинного обучения, что способствует созданию новых продуктов на основе данных.</p>
<h2>Архитектура медальонов</h2>
<p>На фоне lakehouse и новых открытых форматов возникает архитектура медальонов. Это парадигма моделирования данных, стратегически структурирующая данные в среде lakehouse с целью улучшения их качества на каждом этапе обработки.</p>
<p><strong>Архитектура обычно включает три уровня:</strong></p>
<ul>
<li>бронзовый,</li>
<li>серебряный и</li>
<li>золотой,</li>
</ul>
<p>каждый из которых символизирует возрастание степени обработки данных.</p>
<h3><strong>Бронзовый уровень (Bronze layer)</strong></h3>
<p><span style="color: #ff6600;"><strong>Бронзовый уровень</strong> </span>служит начальной точкой для данных, поступающих из внешних систем-источников. Таблицы на этом уровне повторяют структуру таблиц системы-источника в их исходном виде, включая дополнительные столбцы метаданных для фиксации информации, такой как дата/время загрузки и идентификатор процесса. Этот уровень фокусируется на эффективном захвате изменений данных (CDC), обеспечении исторического архива исходных данных, сохранении происхождения данных, проведении аудитов и упрощении повторной обработки без необходимости повторного чтения данных из системы-источника.</p>
<h3><strong>Серебряный уровень (Silver layer)</strong></h3>
<p><span style="color: #ff6600;"><strong>Серебряный уровень</strong></span> в архитектуре озера данных выполняет ключевую функцию консолидации и очистки данных, поступающих с бронзового уровня. Он формирует целостное представление, охватывающее ключевые бизнес-сущности, концепции и транзакции, через такие процессы, как сопоставление, слияние, стандартизация и очистка. Примеры включают мастер-данные о клиентах, магазинах, уникальных транзакциях и перекрёстных таблицах.</p>
<p>Этот уровень служит универсальным источником для самостоятельной аналитики, предоставляя пользователям возможности для создания отчётов, проведения продвинутой аналитики и машинного обучения. Серебряный уровень часто принимает вид моделей данных в формате 3NF, звёздной схемы, Data Vault или снежинки. Как и в традиционных хранилищах данных, этот уровень является ценным ресурсом для всех, кто использует данные для реализации проектов и анализа, направленных на решение бизнес-задач.</p>
<h3><strong>Золотой уровень (Gold layer)</strong></h3>
<p><span style="color: #ff6600;"><strong>Золотой уровень</strong></span> предоставляет ценную информацию, необходимую для решения бизнес-задач. Он агрегирует данные из серебряного уровня и подготавливает их для инструментов BI, построения отчётов и приложений машинного обучения. Этот уровень обеспечивает надёжность, высокую производительность и поддержку транзакций ACID для озёр данных, объединяя потоковые и пакетные транзакции поверх облачных хранилищ данных.</p>
<p><strong>Рисунок 2-6. Представление архитектуры медальонов и её связь с dbt</strong></p>
<p><a href="https://datatalks.ru/wp-content/uploads/2024/12/2_6_Representation_of_medallion_architecture_and_how_it_relates_to_dbt.jpeg" target="_blank" rel="noopener"><img loading="lazy" decoding="async" class="aligncenter wp-image-484 size-full" src="https://datatalks.ru/wp-content/uploads/2024/12/2_6_Representation_of_medallion_architecture_and_how_it_relates_to_dbt.jpeg" alt="" width="1275" height="532" srcset="https://datatalks.ru/wp-content/uploads/2024/12/2_6_Representation_of_medallion_architecture_and_how_it_relates_to_dbt.jpeg 1275w, https://datatalks.ru/wp-content/uploads/2024/12/2_6_Representation_of_medallion_architecture_and_how_it_relates_to_dbt-300x125.jpeg 300w, https://datatalks.ru/wp-content/uploads/2024/12/2_6_Representation_of_medallion_architecture_and_how_it_relates_to_dbt-1024x427.jpeg 1024w, https://datatalks.ru/wp-content/uploads/2024/12/2_6_Representation_of_medallion_architecture_and_how_it_relates_to_dbt-768x320.jpeg 768w, https://datatalks.ru/wp-content/uploads/2024/12/2_6_Representation_of_medallion_architecture_and_how_it_relates_to_dbt-450x188.jpeg 450w, https://datatalks.ru/wp-content/uploads/2024/12/2_6_Representation_of_medallion_architecture_and_how_it_relates_to_dbt-780x325.jpeg 780w" sizes="(max-width: 1275px) 100vw, 1275px" /></a></p>
<p><strong>В процессе перехода от бронзового к золотому уровням данные проходят несколько этапов обработки, таких как загрузка, очистка, обогащение и агрегация, что приводит к созданию ценных бизнес-инсайтов.</strong> Этот подход является значительным шагом вперёд по сравнению с традиционными архитектурами данных, такими как хранилища данных с уровнями подготовки и измерений, или даже с озёрами данных, где акцент делается на организации файлов, а не на создании семантических слоёв.</p>
<h3><strong>Дополнительные замечания</strong></h3>
<p>Архитектура медальонов не заменяет другие техники моделирования, такие как звёздная схема или снежинка. Структура схем и таблиц на каждом уровне может варьироваться в зависимости от частоты и типа обновления данных, а также от их предполагаемого использования. Вместо этого она задаёт принципы организации данных через три уровня, чтобы обеспечить более модульный подход к моделированию данных.</p>
<h3><strong>Роль аналитиков-инженеров</strong></h3>
<p>Понимание основ архитектуры медальонов и концепции озёр данных важно для аналитиков-инженеров, так как в некоторых случаях именно здесь они будут проводить значительное количество времени. Это может включать проектирование структур для одного из уровней архитектуры, использование интерфейсов, предоставляемых открытыми форматами, или написание скриптов трансформации (например, с помощью инструментов вроде dbt), чтобы обеспечить продвижение данных через уровни архитектуры.</p>
<p>Однако значение открытых форматов и озёр данных может варьироваться в зависимости от используемой архитектуры данных. Например, в архитектурах вроде Snowflake данные преимущественно загружаются в собственные таблицы, а не в открытые форматы вроде Iceberg. В таких случаях понимание архитектуры озёр данных будет скорее полезным дополнением, чем критической необходимостью для аналитической инженерии.</p>
<h1>Итог</h1>
<p>Моделирование данных значительно эволюционировало в области аналитики для удовлетворения разнообразных бизнес-запросов и требований к отчётности.</p>
<ul>
<li><strong>Звёздная схема</strong> обеспечивает простой способ выполнения запросов, располагая центральную таблицу фактов вокруг таблиц измерений.</li>
<li><strong>Снежинка</strong> позволяет добиться более глубокой детализации, разделяя измерения.</li>
<li><strong>Data Vault</strong> акцентируется на гибкости, чтобы справляться с часто меняющимися источниками данных.</li>
</ul>
<p>Новая архитектура медальонов объединяет все эти модели, формируя полный план для различных аналитических нужд.</p>
<p>Эти усовершенствования направлены на решение определённых аналитических задач, будь то повышение производительности звёздной и снежинки, или гибкость Data Vault. По мере усложнения требований аналитики становится всё более важным выбирать правильный подход к моделированию данных, чтобы не только сделать их доступными, но и обеспечить их осмысленность и полезность для анализа.</p>
<p>Аналитики-инженеры используют такие структуры, как звёздная схема, снежинка, Data Vault или медальоны, чтобы создавать и поддерживать устойчивые, масштабируемые и эффективные структуры данных. Их работа обеспечивает оптимальную организацию данных, делая их легко доступными и полезными для аналитиков и учёных. Создавая когерентные наборы данных из огромных потоков информации, аналитики-инженеры закладывают основу для точных инсайтов и обоснованного принятия решений.</p>
<p>Сообщение <a href="https://datatalks.ru/dbt-data-modeling-for-analytics/">Перевод 2 главы &#171;Моделирование данных для аналитики (dbt)&#187;</a> появились сначала на <a href="https://datatalks.ru">DataTalks.RU. Data Engineering / DWH / Data Pipeline</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://datatalks.ru/dbt-data-modeling-for-analytics/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
