Введение
В конце прошлого года мы завершили один из самых интересных и необычных проектов, которыми нам приходилось заниматься.
Наш клиент - klara.com - коммуникационная телемедицинская платформа, упрощающая взаимодействие пациентов с врачами в США, столкнулась со стремительным ростом на волне пандемии 2020 года. Одним из вызовов на которые пришлось отвечать инженерам klara.com в это непростое время стало автоматизированное нагрузочное тестирование, способное обнаружить проблемы с производительностью до того как они проявят себя в production среде.
Задача построения пайплайна нагрузочного тестирования выглядела достаточно изолированной для того, чтобы ее можно было выделить в отдельный проект и отдать на реализацию нашей команде.
Постановка задачи выглядела так: спроектировать и реализовать пайплайн нагрузочного тестирования таким образом, чтобы его можно было легко поддерживать силами QA-инженеров, использовать как часть CI/CD процессов компании, расширять по необходимости и иметь возможность реализовать различные сценарии нагрузочного тестирования. Критерием успешности проекта стала точная реализация одного из кейса по производительности из production в среде нагрузочного тестирования с помощью инструментов, которые мы разработали.
Технологическая платформа
Основные языки программирования в команде - Ruby, JS. В качестве основного хранилища klara.com использует Postgres. Безопасность персональных данных пациентов (PHI - Protected Health Information) является ключевым аспектом бизнеса klara.com. Для обеспечения надежного хранения и обработки данных платформа использует HIPPA совместимую SaaS платформу - Aptible. Aptible покупает ресурсы у AWS, поэтому для дальнейшего описания будет достаточно считать Aptible сильно урезанной и зарегулированной версией AWS.
Для максимально корректной реализации нагрузочного тестирования нам нужна среда максимально похожая на production среду. Идеально, если идентичными будут: серверный парк, структура и объем данных, версии кода, структура и объем трафика. Очевидно также, что сделать все перечисленное абсолютно идентичным за разумное время и бюджет не реально и всегда приходится принимать некие допущения.
В этой статье я расскажу как мы готовили данные для нагрузочного тестирования.
Чтобы воспроизвести похожее поведение приложения во время тестов нам нужно иметь максимально похожую на production базу данных с точки зрения объема данных и их распределения. Из-за того, что klara хранит в том числе персональные данные, нам понадобится обфускация базы. Дополнительное условие - скорость работы обфускатора, хотелось бы быстро.
Обзор решений
Сейчас существует несколько инструментов для решения этой задачи в postgres, мы провели краткий сравнительный анализ, который приведен ниже:
+ Самое популярное по количеству звезд на github решение из имеющихся
+ Очень много функций для разных типов данных с разными стратегиями, которые можно применять точечно для выбранных полей (http://personeltest.ru/aways/postgresql-anonymizer.readthedocs.io/en/latest/masking_functions/)
+ Можно выгружать сразу в *.sql дамп
- Нужно устанавливать как расширение рядом с бд
- Для каждого поля нужно прописывать security labels с масками
- Маски работают только с одной схемой
- Нет данных по производительности
Это решение мы не рассматривали из-за того, что оно должно устанавливаться как расширение, запихивать, что-то в production - так себе идея.
+ Заявлена высокая скорость работы (1.4гб mysql dump 17sec)
+ Много типов данных + можно определить свои (форк + правки т.к. кристал руби-френдли язык)
+ Можно выгрузить конфигурацию всей схемы для дальнейшей настройки обфускатора (таблицы-поля)
+ Не требуется никаких правок в исходной бд, только настроить коннект
+ Выгружает в *.sql дамп
Выглядело неплохим вариантом, удобный деплой, понятный нам и заказчику язык, очевидный недостаток - не понятно как сохраняется распределение данных.
pgdump-obfuscator (форк)
+ есть возможность задавать параметры полей через cli (только в форке)
- нет информации о производительности
- обновлялся 8 лет назад
+ Заявлена высокая скорость работы (в докладе говорилось, что 1тб данных обфусцирован за 1.5 дня)
- Написан на C++ (нашей команде сложно вносить изменения)
- Принимает только csv-формат
- Нет внятной документации (только статья на Хабре)
Высокая скорость, сохранение распределения данных после обфускации, поддержка большой компанией и комьюнити - по этим причинам мы выбрали обфускатор от clickhouse.
pg_obfuscator
К сожалению для нашей задачи, clickhouse obfuscator не поддерживал прямое подключение к postgresql. Поэтому нам пришлось написать утилиту, которая решает задачу обфускации postgresql с учетом всей специфики работы именно с этой базой. Исходный код доступен по адресу.
Утилита представляет собой wrapper над psql client и clickhouse obfuscator и реализует следующую функциональность
-
выгрузка схемы базы с сохранением внешних ключей и проверок ссылочной целостности
-
исключение из обфускации таблиц, полей таблиц
-
использование предопределенных шаблонов для генерации фейковых значений для полей
-
маппинг типов данных postgres на типы данных clickhouse
-
генерацию конфигурации со значениями по умолчанию
Из-за специфики работы clickhouse-obfuscator утилита требует дискового пространства для работы равного двойному размеру базы данных. Поставляется в виде docker image и доступна по адресу.
В настоящий момент есть ограничения, которые следует иметь в виду:
-
утилита поддерживает только базовые типы postgres и не поддерживает вложенные: hstore, json, jsonb
-
несмотря на автоматическую генерацию конфига, для первого запуска он нуждается в правках
-
объем docker image составляет почти 700Mb
В рамках поставленной задачи мы наблюдали следующие скоростные характеристики: тестовая база 10Гб обфусцировалась за 40 минут, продуктовая в 50 Гб - 6..8 часов. Чем обусловлена нелинейность работы мы не выясняли.
Ниже я продемонстрирую работу c pg_obfuscator на примере работы с devrimgunduz/pagila: PostgreSQL Sample Database.
Демо
Развернем контейнер с postgres и создадим в нем 2 базы для демонстрации:
docker run --rm --name=db -e POSTGRES_PASSWORD=password -p5432:5432 postgresdocker exec -i db psql -U postgres postgres -c 'create database pagila;'docker exec -i db psql -U postgres postgres -c 'create database pagila_o;'
Посмотрим IP-адрес базы - он понадобится для работы обфускатора:
docker inspect db | grep IPAdd
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.2"
И зальем в контейнер скрипты из проекта pagila:
cd /tmpgit clone git@github.com:devrimgunduz/pagila.gitdocker exec -i db psql -U postgres pagila < /tmp/pagila/pagila-schema.sqldocker exec -i db psql -U postgres pagila < /tmp/pagila/pagila-data.sql
Убедимся, что там появились данные:
docker exec -i db psql -U postgres pagila -c 'select
a.first_name, a.last_name, f.film_id, f.title, f.description from
film f join film_actor fa on f.film_id = fa.film_id join actor a on
a.actor_id=fa.actor_id where f.film_id = 7;'
first_name | last_name | film_id | title | description------------+-----------+---------+-----------------+-----------------------------------------------------------------------------------JIM | MOSTEL | 7 | AIRPLANE SIERRA | A Touching Saga of a Hunter And a Butler who must Discover a Butler in A Jet BoatRICHARD | PENN | 7 | AIRPLANE SIERRA | A Touching Saga of a Hunter And a Butler who must Discover a Butler in A Jet BoatOPRAH | KILMER | 7 | AIRPLANE SIERRA | A Touching Saga of a Hunter And a Butler who must Discover a Butler in A Jet BoatMENA | HOPPER | 7 | AIRPLANE SIERRA | A Touching Saga of a Hunter And a Butler who must Discover a Butler in A Jet BoatMICHAEL | BOLGER | 7 | AIRPLANE SIERRA | A Touching Saga of a Hunter And a Butler who must Discover a Butler in A Jet Boat(5 rows)
База для экспериментов готова. Теперь расчехлим pg_obfuscator, принципиальным моментом является монтирования тома для конфига, чтобы иметь возможность его потом поправить.
mkdir /tmp/configdocker run -it --rm -v /tmp/config:/opt/pg_obfuscator/config pg_obfuscator sh
Дальше команды выполняются в шелле обфускатора, если не сказано другого:
bundle exec ruby pg_obfuscator.rb --configure --source-db-host 172.17.0.2 --source-db-port 5432 --source-db-name pagila --source-db-user postgres --source-db-password password.......I, [2021-04-02T08:47:54.682868 #9] INFO -- : Processed 20 tablesI, [2021-04-02T08:47:54.683243 #9] INFO -- : Check config before run export tables and obfuscation!I, [2021-04-02T08:47:54.696328 #9] INFO -- : Config saved to: config/config.yml
Обфускатор говорит, что нужно проверить конфиг и внести необходимые изменения. Конфиг для 20 таблиц получился около 400 строк, секции, которые нуждаются в правках отмечены - need_fix: true. Для того, чтобы вам было легче повторить я выложил исправленный конфиг сюда.
Для демонстрации генерации фейковых данных посмотрим на секцию в таблице actor:
last_name:db_data_type: textnot_null: trueobfuscator_data_type: Stringfake_data:type: patternvalue: "%{first_name}SON"
В качестве фамилии мы используем имя и постфикс SON.
Выполним последовательно экспорт схемы, данных, обфускацию и заливку полученных данных в базу pagila_o
ruby pg_obfuscator.rb --export-schema --source-db-host 172.17.0.2 --source-db-port 5432 --source-db-name pagila --source-db-user postgres --source-db-password passwordruby pg_obfuscator.rb --export-tables --source-db-host 172.17.0.2 --source-db-port 5432 --source-db-name pagila --source-db-user postgres --source-db-password passwordbundle exec ruby pg_obfuscator.rb --obfuscateruby pg_obfuscator.rb --import --target-db-host 172.17.0.2 --target-db-port 5432 --target-db-name pagila_o --target-db-user postgres --target-db-password password
и выйдя из контейнера с обфускатором посмотрим на результат
docker exec -i db psql -U postgres pagila_o -c 'select a.first_name, a.last_name, f.film_id, f.title, f.description from film f join film_actor fa on f.film_id = fa.film_id join actor a on a.actor_id=fa.actor_id where f.film_id = 7;'
first_name | last_name | film_id | title | description------------+-----------+---------+-----------+------------------------------------------------------------------------------------------------------------------------SA | SASON | 7 | ROON SUIT | A Amazing Display of a Database Administ And a Dog And a Database a Pastry Chef And a Car And a Manned Mine Shark TankRURA | RURASON | 7 | ROON SUIT | A Amazing Display of a Database Administ And a Dog And a Database a Pastry Chef And a Car And a Manned Mine Shark TankBER | BERSON | 7 | ROON SUIT | A Amazing Display of a Database Administ And a Dog And a Database a Pastry Chef And a Car And a Manned Mine Shark TankCA | CASON | 7 | ROON SUIT | A Amazing Display of a Database Administ And a Dog And a Database a Pastry Chef And a Car And a Manned Mine Shark TankMER | MERSON | 7 | ROON SUIT | A Amazing Display of a Database Administ And a Dog And a Database a Pastry Chef And a Car And a Manned Mine Shark Tank(5 rows)
Видим, что last_name сформирован фейкером, описание обфусцировано, ссылочная целостность сохранена.
На этом все, мы надеемся, что утилита будет полезна сообществу, будем рады видеть пулреквесты.
Благодарим команду clickhouse obfuscator за отличный продукт!