Семантический слой и Governance
Порядок и права доступа
Содержание
Сто таблиц, триста пользователей и ни одной внятной записи, кому что можно видеть. Доступы раздавали «по кнопке» и «на всякий случай», так что теперь даже вы не уверены, кто из аналитиков дотягивается до зарплат, а у какого отдела своя «единственно верная» выручка.
Так выглядит компания без Data Governance. И дело не в том, что кто-то злой, — просто раздавать права руками на сотне таблиц невозможно в принципе. Нужен механизм, который сделает это за вас и не забудет.
Этот механизм — семантический слой. Он не только хранит метрики, но и следит, чтобы каждый видел ровно свои данные: чужая строка или чужая колонка физически не попадали ему в руки. Так как я уже несколько лет работаю с Cube, то решил показать примеры реализации на этом стеке.
Кто владелец метрики? Или почему у нас три разные выручки
Главная проблема любой растущей компании — метрический хаос. Маркетинг считает выручку по отгруженным товарам, финансы — по поступившим деньгам, а продажи — по подписанным контрактам. В итоге на планёрке каждый защищает свою версию реальности, и начинается самая настоящая драма.
Data Governance отвечает на вопрос: кто владеет метрикой? Кто тот человек, который может сказать: «Да, вот эта формула расчёта LTV — единственно верная, а всё остальное — галлюцинации»? Это называется Data Stewardship.
Но назначить ответственного мало. Нужно заставить всех пользоваться его определениями. Тут на сцену выходит семантический слой. Он выступает в роли единой точки истины. Вместо того чтобы каждый аналитик писал свой «грязный» SQL-запрос в BI-инструменте, все обращаются к семантической модели.
Семантический слой жёстко фиксирует: «Вот эта метрика называется Выручка, считается вот по этому алгоритму, и владелец у неё — финансовый директор». Если вы хотите получить отчёт, вы берёте эту метрику из слоя. Это исключает ситуацию, когда два отдела приходят на встречу с разными цифрами и начинают обвинять друг друга в некомпетентности.
Безопасность доступа: семантический слой как контролёр
Теперь поговорим о правах доступа. Когда у вас сотни таблиц и тысячи пользователей, раздавать права напрямую в базе данных или хранилище — это как выдать ключи от всех номеров отеля всем гостям. Рано или поздно кто-то зайдёт не в ту комнату и увидит то, что видеть не должен.
Семантический слой — это контролёр на входе. Да, он может слегка замедлить аналитика, которому хочется быстро запихнуть сырой SQL-запрос прямо в дашборд, минуя все правила. Но зато он спасает от крайне неприятных последствий: финансовых потерь, утечек персональных данных и репутационного ущерба.
Вся магия безопасности в семантическом слое реализуется через два механизма: RLS и CLS. Главное преимущество в том, что вам не нужно прописывать эти правила в каждой витрине данных или BI-отчёте. Вы настраиваете их один раз на уровне семантической модели.
flowchart LR
A[Пользователь] --> B[Семантический слой]
B --> C{RLS<br/>фильтр строк}
B --> D{CLS<br/>скрытие колонок}
C --> E[(Хранилище)]
D --> E
E --> F[Только свои данные]Row-Level Security (RLS)
Row-Level Security, или безопасность на уровне строк, работает как аккуратный официант: он приносит вам ровно то блюдо, что вы заказали, и ни крошки чужого.
Например, у вас есть таблица с продажами по всей стране. Региональный менеджер по Уралу заходит в BI-систему и тянет общую метрику «Продажи». Благодаря RLS, настроенному в семантическом слое, система незаметно подставляет фильтр WHERE region = 'Ural'. Менеджер счастлив — он видит свои данные, и ему не нужно знать, как дела у соседней области или сколько продала Москва. Семантический слой сам фильтрует строки на лету, основываясь на роли или атрибутах пользователя.
Как это делается в Cube
В Cube для этого есть декларативные политики доступа (access_policy): описываете правила один раз в модели данных — и слой применяет их ко всем исходящим запросам. Никакого дублирования фильтров в каждом дашборде, никаких «а ты не забыл дописать WHERE».
Политика — это двухмерная матрица прав. member_level решает, какие метрики и измерения видит пользователь (это та самая CLS, до неё доберёмся чуть ниже), а row_level — какие строки ему вообще разрешено увидеть. Строки фильтруются прямо в SQL на лету, из атрибутов пользователя (userAttributes), которые слой получает при аутентификации. Вот политика для куба сделок:
cube(`deals`, { sql: `SELECT * FROM public.deals`, // Политики доступа (Data Access Policies) access_policy: [ { // Обычные менеджеры по продажам group: `sales`, member_level: { includes: `*`, // Доступ ко всем метрикам и измерениям куба }, row_level: { filters: [ { member: `salesPersonId`, operator: `equals`, values: [`{userAttributes.userId}`], // Только свои сделки }, ], }, }, { // Региональные руководители group: `sales_manager`, member_level: { includes: `*`, }, row_level: { filters: [ { member: `region`, operator: `equals`, values: [`{userAttributes.region}`], // Все сделки своего региона }, ], }, }, ], measures: { count: { type: `count` }, }, dimensions: { salesPersonId: { sql: `sales_person_id`, type: `string` }, region: { sql: `region`, type: `string` }, },})
Обычный менеджер видит только свои сделки, руководитель региона — весь регион, а пользователь, которого нет ни в одной группе, — вообще ничего: политики работают по принципу default deny. Если человек состоит сразу в нескольких группах, Cube пересекает их правила — доступ остаётся только к пересечению разрешённых областей. Так что «случайно вытащить чужую строку через хитрый отчёт» больше не получится: слой просто не даст.
Column-Level Security (CLS)
Column-Level Security, или безопасность на уровне колонок, — это как витрина магазина: вы видите общий ассортимент, но в подсобку, где хранятся самые чувствительные товары, без особого пропуска не пройти.
Допустим, аналитику нужно посчитать среднюю зарплату по департаментам. Ему не обязательно видеть конкретные суммы каждого сотрудника. В семантическом слое вы можете скрыть колонку salary от всех, кроме HR-директора. Аналитик сможет использовать метку «Есть данные о зарплате» для группировки, но физически достать сами цифры из колонки не сможет. BI-инструмент даже не узнает, что эта колонка существует для данного пользователя.
Как это делается в Cube
Если RLS отвечает на вопрос «какие строки видит пользователь», то CLS — «какие метрики и измерения для него вообще существуют». В терминах Cube это Member-Level Security, тот самый member_level из политики доступа выше. В классических СУБД права на колонки — это простыни GRANT-ов на каждую таблицу. Здесь — декларативное правило в коде модели, которое одинаково работает и для API, и для BI.
Есть нюанс, который стоит держать в голове: по умолчанию все кубы и их члены публичны. Но как только вы определили хоть одну политику, Cube переключается на default deny — видно только то, что явно разрешено. Метрику, которую забыли выдать группе, невозможно запросить: для этой группы её просто не существует.
Видимость настраивается через includes (белый список) и excludes (чёрный список) внутри member_level. Тот же сценарий с зарплатами, но на витрине заказов: менеджерам скрываем абсолютный показатель count, наблюдателям — ещё и оперативную детализацию за 7 дней, а гостям оставляем одну агрегированную метрику.
view(`orders_view`, { // Подключаем нужные элементы из базового куба cubes: [ { join_path: `orders`, includes: [`status`, `created_at`, `count`, `count_7d`, `count_30d`], }, ], // Политики доступа на уровне членов (Column-Level Security) access_policy: [ { // Менеджеры видят всё, кроме абсолютного показателя `count` group: `manager`, member_level: { excludes: [`count`], }, }, { // Наблюдатели не видят `count` и детализацию за 7 дней group: `observer`, member_level: { excludes: [`count`, `count_7d`], }, }, { // Гости — строго одна агрегированная метрика (белый список) group: `guest`, member_level: { includes: [`count_30d`], }, }, ],})
Гость попробовал запросить count — и получил отказ ещё до генерации SQL. Слой даже не дошёл до хранилища: этого члена для его группы не существует.
Маскирование: третий инструмент
RLS прячет строки, CLS — колонки. Но есть ещё один сценарий: пользователь законно видит колонку, только значения внутри неё должны быть «испорчены». Номер телефона — +7-9**-***-**-**, зарплата — диапазон 100–150 тыс. ₽ вместо точной цифры.
| Механизм | Что прячет | Пример |
|---|---|---|
| RLS | Целые строки | Менеджер видит только свой регион |
| CLS | Колонки | Аналитик не видит salary |
| Маскирование | Значения внутри колонки | Телефон +7-9**-***-**-** |
Чем маскирование отличается от CLS
CLS убирает колонку целиком: поле просто исчезает из схемы в BI-инструменте. Маскирование работает мягче — пользователь видит колонку, строит по ней дашборды и агрегации, но вместо реальных значений получает обезличенные: ***, NULL или -1. Отчёты не «ломаются» из-за пропавших полей, а персональные данные (PII) остаются под замком — что прямым текстом закрывает вопросы GDPR и ФЗ-152.
Семантические слои маскированием «из коробки» обычно не занимаются — это работа хранилища (Dynamic Data Masking в Snowflake, policy tags в BigQuery) или производных полей в модели: создаёте измерение salary_masked, которое возвращает диапазон, и выдаёте его вместо настоящего. Хорошая новость: когда RLS и CLS уже настроены, маскирование превращается в точечную задачу для пары полей, а не в проект на тысячу таблиц.
Как это делается в Cube
В Cube маскирование собирается из двух деталей: параметр mask на уровне измерения или меры задаёт правило подмены (статическая строка или SQL-выражение), а member_masking внутри политики доступа говорит, к каким полям и для какой группы применять маску.
Самая вкусная фишка — условное маскирование в связке с RLS. Если политика даёт полный доступ к полю, но ограничивает его правилом row_level, Cube генерирует SQL вида CASE WHEN {условие_RLS} THEN {реальное_значение} ELSE {маска} END. Менеджер видит реальный email только в своих строках, а чужие — замаскированными.
cube(`customers`, { sql: `SELECT * FROM public.customers`, dimensions: { email: { sql: `email`, type: `string`, // Динамическое маскирование: первые 3 символа и домен mask: { sql: `CONCAT(LEFT(${CUBE}.email, 3), '***@', SPLIT_PART(${CUBE}.email, '@', 2))`, }, }, phone: { sql: `phone`, type: `string`, mask: `***-***-**-**`, // Статическое маскирование для всех случаев }, }, measures: { count: { type: `count` }, }, access_policy: [ { // Аналитики видят реальные email, но телефон всегда замаскирован group: `analyst`, member_level: { includes: [`email`, `count`], }, member_masking: { includes: [`phone`], // Поле доступно, но значения подменяются маской }, }, { // Менеджеры видят реальные данные только по своим клиентам group: `manager`, member_level: { includes: [`email`, `phone`, `count`], }, row_level: { filters: [ { member: `assigned_manager_id`, operator: `equals`, values: [`{userAttributes.userId}`], }, ], }, member_masking: { includes: `*`, // По умолчанию маскируем всё }, // В строках, где assigned_manager_id совпадает с userId, // менеджер увидит реальные email и phone. В остальных — маску. }, ],})
Такой подход превращает семантический слой в единую точку применения политик безопасности:
- Для инженеров — не нужно дублировать логику маскирования в десятках SQL-представлений или настраивать её отдельно в каждом BI-инструменте.
- Для безопасности — правила неотделимы от метаданных: через API, SQL-интерфейс или дашборд запросил пользователь данные, маска сработает всегда, потому что встроена в переписывание запросов на уровне движка.
- Для пользователей — интерфейс остаётся бесшовным: привычные столбцы и агрегации на месте, а конфиденциальные значения надёжно спрятаны на уровне отдельных строк.
Как внедрить и не сойти с ума
Внедрение Data Governance через семантический слой — процесс болезненный, как переход на здоровое питание. Но вот пошаговый план, как пережить эту перестройку:
- Проведите инвентаризацию. Выпишите все метрики, которые используются в компании. Найдите дубликаты и противоречия.
- Назначьте стюардов (Data Stewards). Для каждого домена (финансы, маркетинг, продажи) назначьте ответственного, который будет утверждать определения метрик.
- Опишите семантическую модель. Используйте инструменты вроде dbt Semantic Layer, Cube, AtScale или встроенные семантические слои в современных BI. Опишите физические таблицы, связи, измерения и меры.
- Настройте матрицу доступов. Определите, кто какие роли имеет. Интегрируйте семантический слой с корпоративной системой аутентификации (Active Directory, Okta), чтобы права подтягивались автоматически.
- Запретите прямой доступ. Отрежьте аналитикам возможность писать сырые SQL-запросы к витринам в обход семантического слоя. Да, они будут ныть первые две недели, но потом привыкнут и полюбят вас за то, что им больше не нужно помнить структуру всех таблиц.
Резюме
Data Governance без семантического слоя — это просто набор красивых презентаций и скучных регламентов, которые никто не читает. Семантический слой без Data Governance — это быстрый способ построить красивую витрину, которая всё равно показывает три разных версии выручки.
Только вместе они создают экосистему, где данные безопасны, метрики едины, а бизнес перестаёт тратить время на споры о том, чья цифра правильнее, и начинает наконец зарабатывать деньги. Так что наведите порядок в своих данных — и пусть аналитика будет управляемой и приносящей удовольствие.


