Введение
В конце прошлого года мы завершили один из самых интересных и необычных проектов, которыми нам приходилось заниматься.
Наш клиент - 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, мы провели краткий сравнительный анализ, который приведен ниже:
postgresql_anonymizer
+ Самое популярное по количеству звезд на github решение из имеющихся
+ Очень много функций для разных типов данных с разными стратегиями, которые можно применять точечно для выбранных полей (https://postgresql-anonymizer.readthedocs.io/en/latest/masking_functions/)
+ Можно выгружать сразу в *.sql дамп
- Нужно устанавливать как расширение рядом с бд
- Для каждого поля нужно прописывать security labels с масками
- Маски работают только с одной схемой
- Нет данных по производительности
Это решение мы не рассматривали из-за того, что оно должно устанавливаться как расширение, запихивать, что-то в production - так себе идея.
triki
+ Заявлена высокая скорость работы (1.4гб mysql dump 17sec)
+ Много типов данных + можно определить свои (форк + правки т.к. кристал руби-френдли язык)
+ Можно выгрузить конфигурацию всей схемы для дальнейшей настройки обфускатора (таблицы-поля)
+ Не требуется никаких правок в исходной бд, только настроить коннект
+ Выгружает в *.sql дамп
Выглядело неплохим вариантом, удобный деплой, понятный нам и заказчику язык, очевидный недостаток - не понятно как сохраняется распределение данных.
pgdump-obfuscator (форк)
+ есть возможность задавать параметры полей через cli (только в форке)
- нет информации о производительности
- обновлялся 8 лет назад
clickhouse-obfuscator
+ Заявлена высокая скорость работы (в докладе говорилось, что 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 postgres
docker 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 /tmp
git clone git@github.com:devrimgunduz/pagila.git
docker exec -i db psql -U postgres pagila < /tmp/pagila/pagila-schema.sql
docker 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 Boat
RICHARD | PENN | 7 | AIRPLANE SIERRA | A Touching Saga of a Hunter And a Butler who must Discover a Butler in A Jet Boat
OPRAH | KILMER | 7 | AIRPLANE SIERRA | A Touching Saga of a Hunter And a Butler who must Discover a Butler in A Jet Boat
MENA | HOPPER | 7 | AIRPLANE SIERRA | A Touching Saga of a Hunter And a Butler who must Discover a Butler in A Jet Boat
MICHAEL | 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/config
docker 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 tables
I, [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: text
not_null: true
obfuscator_data_type: String
fake_data:
type: pattern
value: "%{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 password
ruby 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 password
bundle exec ruby pg_obfuscator.rb --obfuscate
ruby 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 Tank
RURA | 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 Tank
BER | 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 Tank
CA | 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 Tank
MER | 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 за отличный продукт!