Привет, Хабр! На связи Илья Амосов из команды поддержки 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_charFROM contractorWHERE 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% |
|
Хэширование |
113083 |
+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, чтобы следить за новыми публикациями.
ссылка на оригинал статьи https://habr.com/ru/articles/1033038/