Привет, Хабр! На связи Илья Амосов из команды поддержки Lakehouse‑платформы данных Data Ocean Nova вендора Data Sapience. В сегодняшней публикации я раскрою тему влияния динамического сокрытия чувствительных данных на производительность SQL‑запросов. Мы сравним различные методики маскирования, узнаем, как работает оптимизатор и движок со скрытыми полями, происходит ли деградация пропускной способности платформы, как влияет на производительность выбранный метод сокрытия чувствительных данных в случаях, если вы используете компонент на базе Apache Ranger.

В статье я отвечу на четыре практических вопроса:

  • Дает ли динамическое маскирование заметный прирост времени выполнения?

  • Как ведет себя Ranger и SQL‑движок при конкурентной нагрузке при работе со скрытыми данными?

  • Что происходит, если маскируемое поле участвует в фильтрации, агрегации или JOIN?

  • Какой тип сокрытия оказывается наиболее пригодным для аналитических сценариев?

Вступление

Любая платформа данных enterprise‑класса должна отвечать современным требованиям информационной безопасности, в числе которых есть и ограничение доступа определенных ролей и групп пользователей к чувствительным данным. В зависимости от конечных требований и класса допуска системы инструментами скрытия могут быть либо шифрование, либо маскирование. В данной статье речь пойдет именно о маскировании.

Среди обращений пользователей и клиентов нашей платформы данных часто встречается следующий вопрос: «Мы бы хотели включить встроенный функционал маскирования, но не понимаем степень деградации производительности, которую мы должны учесть при планировании запаса вычислительных мощностей». Действительно, при проектировании аналитических платформ почти всегда возникает вопрос: «Можно ли включить динамическое сокрытие данных и не получить заметную деградацию производительности?»

На бумаге все выглядит достаточно просто: в SQL‑движках платформы политики динамического маскирования применяются через плагин ролевого доступа Apache Ranger, и при выполнении запроса пользователь получает уже не исходные значения чувствительных полей, а их преобразованную версию.

Это может быть:

  • полное скрытие значения;

  • показ последних 4 символов или только 4 первых;

  • хэширование;

  • подстановка NULL.

Для задач безопасности это удобно, а насколько это жизнеспособно в условиях промышленной эксплуатации? Особенно если речь идет не о единичных запросах, а о конкурентной нагрузке, аналитических выборках, JOIN‑операциях и фильтрации по большим таблицам.

Мы решили измерить поведение нашей реализации Apache Ranger на практике.

Цель тестирования была простой: понять, как разные типы динамического маскирования влияют на время выполнения SQL‑запросов, стабильность работы под нагрузкой и корректность аналитических запросов.

Зачем вообще это тестировать

Важно понять, что именно происходит с запросом в момент выполнения.Одно дело, если система подменяет значение в самом конце, когда результат уже почти готов к выдаче пользователю. И совсем другое, если преобразование применяется раньше, еще во время обработки данных. Тогда это влияет не только на то, что увидит пользователь, но и на то, как вообще будет выполняться запрос.

А это особенно важно для решения аналитических задач. В простом просмотре данных поле можно скрыть, и на этом задача решена, но что, если скрытые поля участвуют в операциях WHERE, JOIN, GROUP BY? В такой ситуации пользователи хотят заранее понимать: маскирование просто скроет значение в выдаче или начнет заметно влиять на результат запроса.

Давайте перейдем к практическим экспериментам.

Методика и результаты

Чтобы не смешивать все в один большой эксперимент, мы разбили тестирование на несколько отдельных сценариев. Нам было важно понять, в каких именно случаях маскирование почти не влияет на запросы, а в каких его влияние становится заметным.
Как и обычно, мы придерживались принципа — никаких тестирований на одном узле! Конфигурация любого испытания должна быть приближена хотя бы к начальному сценарию промышленной эксплуатации.

В нашем случае стенд состоял из:

  • 4 compute‑узлов, развернутых в Kubernetes;

  • 24 vCores CPU на узел;

  • 220 ГБ оперативной памяти на узел;

  • объектного хранилища на базе MinIO.

В качестве эталона использовался вариант без сокрытия, с которым сравнивались остальные подходы. Для разных типов маскирования использовался одинаковый по объему и составу (за исключением скрытых полей) датасет, который состоял из двух таблиц: таблицы клиентов (75 млн записей) и таблицы счетов (150 млн записей).

Далее мы шли от простого к сложному.

1. Базовый сценарий — обычный SELECT

Сначала мы проверили самый простой сценарий — обычный SELECT, где маскируемые поля возвращаются в результирующем множестве, а фильтрация идет по открытому полю. Такой тест нужен был как отправная точка, чтобы посмотреть, как само маскирование влияет на запрос, если не добавлять ничего лишнего.

В обычных запросах заметной просадки не было. В режиме без маскирования среднее время составило 1801 мс. Для режима полного скрытия получили 1935 мс, то есть +7,4%. Хэширование дало 1870 мс, то есть +3,8%. Режим показа последних 4 символов оказался чуть быстрее режима без маскирования — 1709 мс, а подстановка NULL показала наиболее заметное снижение времени — 1111 мс, что соответствует −38,3%. 

Тип маскирования

Средний размер возвращаемых данных, byte

Среднее время выполнения, мс

Отклонение от базового режима,%

Без маскирования

2307

1801

0,0%

Полное скрытие значения

2307

1935

+7,4%

Показ последних 4 символов

2299

1709

−5,1%

Хэширование

3518

1870

+3,8%

Подстановка NULL

1118

1111

−38,3%

В таблице «размер возвращаемых данных» определяется по сумме всей строки, возвращаемой в запросе. Во многом он определяет скорость возврата суммарного датасета на клиентскую часть и объем потребляемой движком оперативной памяти. В данном случае мы видим прямую корреляцию между тем, сколько данных движку надо прочитать и вывести на клиентскую часть, и временем работы. Запомним, что разные режимы сокрытия по‑разному влияют на объем возвращаемых данных. Это требует разных ресурсов SQL‑движка и влияет на время выполнения.

Если смотреть на общую картину, в обычном SELECT, где маскируемые поля возвращаются в результате, а не участвуют в вычислительной логике, заметной просадки не произошло. Иными словами, сам по себе факт сокрытия еще не означает, что запросы резко начнут выполняться дольше.

Тестовый запрос

SELECT
     contractor_id_int,
     contractor_id_char,
     last_name,
     first_name,
     middle_name,
     birth_date,
     contractor_type_id_int,
     contractor_type_id_char
FROM
     contractor
WHERE
      birth_date BETWEEN '@start_period_date' AND '@end_period_date';

2. Конкурентная нагрузка

Следующим шагом мы проверили более жизненный кейс: несколько запросов выполняются параллельно. В реальной системе пользователи не работают по одному, поэтому было важно понять, не появляется ли из‑за маскирования нестабильность или неожиданный рост времени отклика при одновременной нагрузке. Разумеется, деградация может во многом зависеть и от типа SQL‑движка, выбранного в качестве исполнителя среди доступных в Data Ocean Nova: StarRocks, Impala или Trino, ведь все они по‑разному толерантны к конкурентной нагрузке. Данный эксперимент проводился в Impala.

Во всех потоках использовался один и тот же запрос с фиксированными параметрами. При этом структура запросов оставалась неизменной, чтобы различия в результатах были связаны именно с маскированием. Такие условия мы сделали намеренно, чтобы исключить влияние различий в выборке на результаты тестирования, ведь мы хотели проверить именно Ranger, а не сам движок. О сравнении производительности самих MPP‑движков в конкурентной нагрузке мы пишем регулярно, и ознакомиться с материалами можно тут, и тут, и тут, и еще вот тут.

В каждом тесте запускалось 50 параллельных потоков, каждый из которых выполнял по 50 запросов. Общее число запросов за итерацию тестирования — 2500 запросов. В протоколе фиксировалось среднее время выполнения.

Тип маскирования

Среднее время выполнения, мс

Отклонение от базового режима,%

Без маскирования

1999

0,0%

Полное скрытие значения

1999

0,0%

Показ последних 4 символов

1089

−45,5%

Хэширование

1995

−0,2%

Подстановка NULL

1398

−30,1%

И здесь система повела себя вполне предсказуемо. В режиме без маскирования среднее время составило 1999 мс, режим полного скрытия значения дал те же 1999 мс, хэширование — 1995 мс, то есть разница практически отсутствует. Показ последних 4 символов и подстановка NULL оказались быстрее режима без маскирования — 1089 мс и 1398 мс соответственно (помним выводы из предыдущего теста про объем возвращаемых данных и его влияние на время запроса).

Если смотреть именно на конкурентную нагрузку, ничего неожиданного здесь не произошло. Система отработала ровно, без неприятных сюрпризов и без заметной деградации, только из‑за самого факта маскирования. Главный вывод: Ranger легко переварил обработку правил и передал эстафету непосредственно движку‑исполнителю.

3. Отдельная проверка влияния самого маскирования

После этого мы вынесли в отдельный сценарий оценку того, как влияет на производительность сам механизм сокрытия. Здесь задача была максимально простой: убрать побочные эффекты и посмотреть, насколько меняется время выполнения в ситуации, где сокрытие чувствительных данных не вмешивается в более сложную логику запроса.

На старте казалось, что само преобразование значений должно заметно утяжелять запрос. Но в простом сценарии этого не произошло. Пока замаскированное поле не участвует в WHERE, JOIN или агрегации, влияние самого механизма сокрытия остается небольшим.

Тип маскирования

Среднее время выполнения, мс

Отклонение от базового режима,%

Без маскирования

229

0,0%

Полное скрытие значения

223

−2,6%

Показ последних 4 символов

212

−7,4%

Хэширование

234

+2,2%

Подстановка NULL

186

−18,8%

Хэширование добавило всего +2,2% к режиму без маски: 234 мс против 229 мс. Режим полного скрытия показал 223 мс, показ последних 4 символов — 212 мс, подстановка NULL — 186 мс.

4. Смешанная нагрузка

Дальше мы проверили сценарий, в котором masked и unmasked запросы выполняются одновременно. Для эксплуатации это важный момент: в боевой системе такие режимы почти всегда существуют параллельно.

Для режимов полного скрытия значения, показа последних 4 символов и хэширования разница по времени относительно режима без маскирования была минимальной — от −0,9% до +0,3%. Подстановка NULL снова показала ускорение в этой паре.

Иными словами, само по себе сосуществование разных режимов доступа не стало отдельной проблемой. Система может одновременно обрабатывать запросы без явной деградации из‑за смешения таких режимов.

Тип маскирования

Средний размер возвращаемых данных, byte

Среднее время выполнения, мс

Отклонение от базового режима,%

1

Без маскирования

2307

1859

0,0%

Полное скрытие значения

2307

1857

−0,1%

2

Без маскирования

2288

1790

0,0%

Показ последних 4 символов

2288

1774

−0,9%

3

Без маскирования

2307

1972

0,0%

Хэширование

3518

1977

+0,3%

4

Без маскирования

2294

1427

0,0%

Подстановка NULL

1118

1122

−21,4%

Каждая пара строк представляет сравнение режима без маскирования и соответствующего режима маскирования в условиях смешанной нагрузки.

5. Аналитические сценарии

Самые интересные отличия проявились в аналитических сценариях. Пока замаскированное поле просто выводится в SELECT, все выглядит довольно спокойно. Но, как только это поле начинает участвовать в логике запроса, картина меняется.

5.1. Фильтрация по маскируемому полю

Сначала мы проверили сценарий, где скрытое поле участвует в WHERE. Это один из самых показательных кейсов, потому что здесь важно понять: сравнение выполняется по исходному значению или уже по его преобразованной версии.

В режиме без маскирования время составило 643 мс. Для режима полного скрытия получили уже 5527 мс, то есть +759,6%, для показа последних 4 символов — 5344 мс, то есть +731,1%. Хэш дал 823 мс, что соответствует +28%, а подстановка NULL — всего 43 мс, то есть −93,3%.

Именно здесь становится видно, что маскирование — это уже не просто «скрыли колонку от пользователя». Как только поле участвует в WHERE и одновременно скрывается, система работает уже не с исходным значением, а с его преобразованной версией.

Наиболее заметно это проявилось в режимах полного скрытия значения и показа только последних 4 символов: для нестроковых типов фактически выполняется дополнительное преобразование для каждой строки, и именно поэтому запрос становится заметно дольше.

Тип маскирования

Средний размер возвращаемых данных, byte

Среднее время выполнения, мс

Отклонение от базового режима,%

Без маскирования

2304

643

0,0%

Полное скрытие значения

128

5527

+759,6%

Показ последних 4 символов

128

5344

+731,1%

Хэширование

128

823

+28,0%

Подстановка NULL

128

43

−93,3%

5.2. Агрегация по скрытым данным

Во втором аналитическом сценарии JOIN выполнялся по немаскированному полю, а агрегация шла уже по данным, к которым применена подстановка NULL. Здесь нас интересовало не только время, но и то, как меняется сам смысл результата.

Время выполнения оказалось ниже, чем в режиме без маскирования: 2969 мс против 4210 мс, то есть −29,5%. Связано с тем, что объем данных в результате уменьшается.

Но здесь важнее не скорость, а логика запроса. Агрегация выполнялась уже не по исходным значениям, а по преобразованным данным. Формально запрос работал корректно, ошибок не было, результат возвращался, но интерпретировать этот результат нужно уже иначе: он отражает не исходные данные, а их маскированное представление.

Это как раз тот случай, когда запрос не ломается, но начинает считать уже не то, что считалось бы без маскирования.

Тип маскирования

Средний размер возвращаемых данных, byte

Среднее время выполнения, мс

Отклонение от базового режима,%

Без маскирования

450

4210

0,0%

Подстановка NULL

212

2969

−29,5%

5.3. JOIN по полю с NULL

Дальше мы проверили JOIN по замаскированному полю в режиме подстановки NULL. Здесь результат предсказуем, но от этого не менее важен.

Если поле в условии соединения приводится к подстановке NULL, совпадений не возникает, JOIN не находит пары строк и фактически разваливается. Время выполнения в этом сценарии составило 3076 мс против 5832 мс у базового, то есть −47,3%.

Но это как раз тот случай, когда быстрее не значит лучше. Запрос выполняется быстрее потому, что полноценного сопоставления строк больше не происходит. С точки зрения аналитики такой режим для ключей соединения фактически бесполезен.

Тип маскирования

Средний размер возвращаемых данных, byte

Среднее время выполнения, мс

Отклонение от базового режима,%

Без маскирования

451

5832

0,0%

Подстановка NULL

52

3076

−47,3% 

5.4. JOIN по полю с хэш

И, наконец, самый интересный сценарий — JOIN по полю, замаскированному через хэш.

Логика здесь понятна: одинаковые исходные значения дают одинаковый хэш, а значит, теоретически данные можно сопоставлять и без раскрытия оригинала. И действительно, в этом сценарии JOIN отработал корректно, строки возвращались, агрегация и группировка тоже выполнялись. Но задержка оказалась очень высокой: 113 083 мс против 8857 мс у режима без маскирования, то есть +1176,8%.

Тип маскирования

Среднее время выполнения, мс

Отклонение от базового режима,%

Без маскирования

8857

0,0%

Хэширование

113 083

+1176,8%

И вот это, пожалуй, один из самых важных результатов всей работы. Хэширование действительно сохраняет аналитическую связность данных, но в сложных сценариях за это приходится серьезно платить производительностью.

Но давайте разберемся в причине деградации на детальном текстовом профиле запроса. Сперва сравним суммарную статистику по операциям.

Агрегация плана исполнения без хэширования
Агрегация плана исполнения без хэширования
Агрегация плана исполнения с хэшированием
Агрегация плана исполнения с хэшированием

По агрегированному плану видно, что значительно изменилось время сканирования. Проверим детальный текстовый план.

Детальный фрагмент плана запроса без хэширования
Детальный фрагмент плана запроса без хэширования
Детальный фрагмент плана запроса с хэшированием
Детальный фрагмент плана запроса с хэшированием

В узле HASH JOIN оптимизатор знает о связи между ключом соединения в разных таблицах как pk/fk, но само соединение выполняется по функции mask_hash(pk/fk). При формировании фрагмента JOIN‑движок создает блум‑фильтр, который в случае с немаскированными данными успешно применяется фрагментом сканирования и читает из всего множества только 16,2 миллиона записей, благодаря min/max‑фильтрации на основе storage‑индекса в файлах Parquet. В случае сокрытия данных узлу сканирования приходится читать уже полное множество в 150 миллионов записей, что, собственно, и является главной причиной такого замедления.

Выводы

Первый — динамическое маскирование само по себе не выглядит проблемой для обычных запросов. Если скрытое поле просто выводится в результате, а не участвует в вычислительной логике, то влияние на производительность обычно невелико. Это касается не только обычного чтения, но и конкурентной нагрузки, и смешанных режимов доступа. Apache Ranger хорошо справляется с интенсивной нагрузкой при запросе правил сокрытия данных от SQL‑движка, так как основная нагрузка лежит на плечах движка‑исполнителя.

Второй — реальные сложности начинаются не в момент, когда поле скрывают от пользователя, а в момент, когда над этим полем работают операторы WHERE, GROUP BY или JOIN. Именно там маскирование перестает быть просто визуальным и начинает влиять на саму логику выполнения запроса.

Третий — разные типы маскирования подходят для разных задач. Подстановка NULL хорошо скрывает данные, но полностью убивает аналитику по таким полям, если они участвуют в соединениях или расчетах. Применимость этого метода можно определить как «скрытие доступа к конечным данным», например, к рассчитанной витрине. Режимы частичного маскирования с отображением только 4 символов удобны для отображения, но в отдельных сценариях фильтрации могут резко утяжелять запрос. Применимость скрытия по маске на практике возможна даже и для решения некоторых аналитических задач, для которых подходит сценарий «4 первых» или «4 последних». Хэширование остается самым интересным компромиссом: позволяет сохранить сопоставимость значений и детерминированность результата при операциях JOIN или группировке, но в сложных аналитических запросах это может сказаться на производительности.

И, пожалуй, главный вывод — динамическое маскирование в Lakehouse‑платформе данных Data Ocean Nova — полезная встроенная функция, позволяющая избежать «дедовского метода» создания слоев представлений для каждой роли с ограниченным доступом к данным.

В настоящее время мы проводим нагрузочные испытания на промышленном контуре при работе с общим числом ролей в Ranger от 50 до 100 тысяч. Следите за обновлениями.

Подписывайтесь на хаб Data Sapience, чтобы следить за новыми публикациями.

Комментарии (1)


  1. THSBridge
    08.05.2026 11:59

    Отличная статья. Особенно ценно, что показали не только "что работает быстрее", а как меняется логика запроса при разных типах маскирования. Ваш вывод про хэширование как компромисс — самый важный: аналитику сохраняет, но за скорость приходится платить. У нас в T‑HS Bridge похожая дилемма с безопасностью: рутина без подтверждения, опасное — с запросом. Выбор типа защиты — это всегда компромисс между скоростью и контролем.