Как мы автоматизировали обезличивание на большой БД. Петрович-Тех
Как мы автоматизировали обезличивание на большой БД. Петрович-Тех

Привет! Меня зовут Дима Левин, сейчас я работаю системным аналитиком в Петрович-Техе. Так уж сложилось, что в своей работе в нескольких проектах я плотно работал с персональными данными. Сегодня хочу рассказать о проекте по автоматизации обезличивания большой БД (30 000+ таблиц размером до 10 миллиардов строк). Надеюсь, мой опыт будет полезен.

С каждым годом появляется все больше и больше требований в работе с персональными данными (ПДн). Списки данных, подпадающих под статус ПДн, регулярно расширяются, а ответственность за утечку ПДн повышается.

Работать в разработке и тестировании ПО в таких условиях становится всё сложнее, особенно когда по разным причинам необходимо привлечь внешних специалистов или подрядную организацию, а подключаемая к ПО база данных содержит ПДн.

Конечно, можно сгенерировать данные для любой БД, исключив ПДн, но не во всех ситуациях этот вариант подходит, т.к. в таком случае невозможно учесть все нюансы и особенности данных. Существует достаточно много инструментов и для генерации, и для анонимизации данных, но в наших нынешних реалиях и условиях техстека компаний далеко не всё можно применить. Кроме того, какой бы подход не был выбран, разбираться в его тонкостях обезличивания всё равно придётся.

Подробно об утверждённых методах обезличивания можно ознакомиться в Приказе Федеральной службы по надзору в сфере связи, информационных технологий и массовых коммуникаций от 19.06.2025 № 140 "Об утверждении требований к обезличиванию персональных данных и методов обезличивания персональных данных " http://publication.pravo.gov.ru/document/0001202508010002?index=9

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

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

Датасет – ускоряем процесс, бережём ресурсы

Если ваша база содержит десятки тысяч таблиц (в нашем проекте их было больше 30 000), соответственно, сотни тысяч полей, а некоторые таблицы могут быть немалого размера (в нашем случае до 10 млрд строк). Как переварить всю эту массу данных в обозримый промежуток времени? Кроме того, не забываем, что база регулярно обновляется и процесс необходимо проводить на регулярной основе. Если начать процесс профилирования (определения наличия и типизирования ПДн) данных каждого отдельного поля в такой базе, то о разработке можно забыть на очень долгое время, и, скорее всего. неприемлемое для проекта.

Решением оказался подход, позволяющий многократно снизить нагрузку на сервер и ускорить процесс в сотни раз – разделение процесса профилирования на 3 этапа.

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

Датасет формируется в 2 этапа и содержит 2 таблицы:

1. Таблица метаданных, позволяющая определить принадлежность данных конкретной таблице и полю, а также содержащая полезные в определении профиля данных свойства полей.

поле

описание

id

Id таблицы и поля

table_name

имя таблицы

column_name

имя колонки

max_row

максимальное кол-во строк в таблице

data_type

тип поля

is_nullable

допустим ли null

col_precision

макс. длина значения поля, не пустого

create_date

дата формирования датасета

2. Сами данные в едином строковом формате.

поле

описание

tab_col_id

id таблицы и поля к которым относятся данные

exam_data

примеры данных

count_unic_row

количество уникальных значений в выборке

Подавляющая часть ПДн хранится в полях, имеющих строковый тип и тип «дата». На этапе формирования датасета сразу исключаем поля, в которых крайне маловероятно наличие ПДн.

Например, можно смело исключать типы: 'tinyint', 'binary','bigint', 'int', 'money', 'bit', 'smallint', 'float', 'numeric'. Данные типы обычно не используются для хранения ПДн (т.к. просто неудобны для этих целей).

Также исключаем сложные и объёмные типы данных: 'image', 'binary', 'varbinary', 'xml', 'text'. Данные таких типов обычно не влияют на функционал приложения. Кроме того, в них крайне сложно выявлять ПДн и маскировать (в нашем проекте такие данные затирались полностью). Но если задача этого требует, механизм обезличивания можно попытаться расширить для выявления ПДн в xml и json форматах.

Кроме того, сразу исключаем поля, имеющие только NULL и пустые значения. Все оставшиеся данные для удобства приводим к единому строковому типу.

Конечно, в реальном проекте, после нескольких итераций, появились ещё несколько фильтров, но это уже зависит от особенностей конкретного кейса.

Ну и самое главное, для того чтобы радикально ускорить получение равномерной выборки уникальных значений из больших таблиц (> 1 млрд строк), используем следующую схему:

  • Выбираем 100 000 случайных значений, используя функцию TABLESAMPLE(100000 ROWS) (для MS SQL; приблизительная выборка значений, ускоряет процесс на несколько порядков);

  • Удаляем дублирующиеся значения;

  • Делаем повторную выборку из 1000 значений, используя функцию NEWID().

Средний размер таблиц был порядка 1 000 000 строк. Каждое поле содержит один тип данных, предварительное исследование показало, что в полях в среднем попадается не более 3% нетипичных для поля данных (несоответствующих шаблону поля). Двойная случайная выборка позволяла сохранить процент наличия неконсистентных значений на первоначальном уровне с очень большим запасом для достоверности определения профиля данных.

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

 Пример кода скрипта формирующего датасет:

create table #all_data (tab_col_id bigint, exam_data varchar(4000), count_unic_row bigint);
declare @count_row nvarchar(6) = '1000', -- максимальное кол-во уникальных строк каждого поля в итоговой таблице
@sql_2 nvarchar(max) = '';
	
--Верхнеуровневая фильтрация таблиц на основании метаданных
drop table if exists #CTE_tables;
create table #CTE_tables (id bigint, table_name varchar(128), column_name varchar(128), data_type varchar(128));
insert into #CTE_tables(id, table_name, column_name, data_type)
select  id, table_name, column_name, data_type 
from all_tab_col_data c with(nolock)--таблица с уже собранными мета данными
where c.max_row > 0 -- не пустые таблицы (имеющие хотя бы одну строку)	 
	and c.data_type not in 
('image', 'binary', 'varbinary', 'xml', 'text', 'tinyint', 'BINARY','bigint', 'int', 'money', 'bit', 'SMALLINT',  'float', 'numeric') --исключенные типы данных

--Формирование кода запросов для сбора данных по каждой таблице и полю 	
SELECT					
@sql_2 = @sql_2 + ' 
	insert into #all_data(tab_col_id, exam_data, count_unic_row)		
		select ' + cast(id as varchar(12)) + ' as tab_col_id, exam_data, count_unic_row 
		from (
			select top ' + @count_row +' 
				case --переводим все данные дата сета в строковый тип
					when ''' + data_type + ''' in (''datetime'', ''smalldatetime'') then convert(varchar(max), exam_data, 120)
					when ''' + data_type + ''' in (''time'') then convert(varchar(max), exam_data, 108)	
					when ''' + data_type + ''' in (''date'') then convert(varchar(max), exam_data, 121)
					when ''' + data_type + ''' in (''ntext'', ''text'',''varchar'',''char'',''nvarchar'',''nchar'') then left(exam_data, 4000)
					else try_cast(exam_data as varchar(max))				
				end as exam_data,
			count(*) over() as count_unic_row 
			from(
				select distinct 
					[' + COLUMN_NAME + '] as exam_data
				from scheme_name.' + table_NAME + 'tablesample (100000 rows)) tmp order by newid()
			) as tmp_3
				where UPPER(trim(exam_data)) NOT IN (''NULL'', '''')
				order by newid()		
	'			
FROM #CTE_tables as tmp;

--Запуск динамического запроса
SET @sql_2 = @sql_2 +  ' select ''''as name, ''''as data_column , '''' as count_unic_row;'
exec sp_executesql @sql_2;

Профилирование данных и приоритизация

Персональные данные — это любая информация, которая относится прямо или косвенно к определённому или определяемому физическому лицу (субъекту персональных данных). Статья 3 Федерального закона от 27 июля 2006 года №152-ФЗ «О персональных данных».

Для того чтобы начать процесс обезличивания, в первую очередь необходимо определить типы персональных данных, которые необходимо изменить. Этот список может достаточно сильно отличаться в зависимости от доменной области разрабатываемого ПО. Кто-то, счастливый, обременен лишь заботой о сохранности ФИО и контактных данных, а для кого-то список ПДн насчитывает десятки наименований.

Вот несколько примеров:

Подтип

Тип

Приоритет

Описание

Пример

name

full_name

1

ФИО полностью

Иванов Иван Иванович

name

short_ full_name

2

ФИО с инициалами

Иванов И. И.

name

full_name_translit

1

ФИО полностью в транслитерации

Ivanov Ivan Ivanovich

name

first_name

2

Имя

Иван

 ...

...

... 

date_of_birth

full_dob

1

дата рождения полностью

15-10-1995

date_of_birth

year

2

год рождения

1995

 

… 

passport

full_pass_number

1

Серия и номер паспорта

2356 256358

passport

ser_ pass

2

Серия паспорта

2356

passport

num_ pass

2

Номер паспорта

256358

personal_ids

inn

1

инн

390637232323

personal_ids

snils

1

снилс

129-436-436 82

account_ids

account

1

номер счета

40817810055760515502

account_ids

card_number

1

номер карты

2001010101010101

  …

Очень важно также определить приоритизацию профилей (подробно о приоритетах и принципах их распределения - чуть дальше).

После того как определили и согласовали со всеми заинтересованными лицами профили ПДн и собрали датасет, можно переходить к следующему этапу – профилирование самих данных.

На этой развилке можно пойти как минимум двумя путями: фильтрация на основе шаблонов регулярных выражений или ML модель (мы тестировали бинарную классификацию на основе logistic regression + TF-IDF).  В этой статье мы подробно рассмотрим только первый вариант - профилирование на основе регулярных выражений.

Несколько примеров фильтров на основе регулярных выражений:

-- профиль PHONENUMBER (Пр: +7(909) 45645600)
CASE WHEN (((REGEXP_LIKE(trim(atd.EXAM_DATA), 
	'((.*[^0-9])|^)[\s\+\.]?[78\(]\s?\s?[\-\s\(]?[0-9]{3}\-?\)?\s?\s?[0-9]\-?\s?[0-9]\-?\s?[0-9]\-?\s?[0-9]\-?\s?[0-9]\-?\s?[0-9]?\-?\s?[0-9]?\-?\s?[0-9]?(([^0-9].*?)|$)')) OR REGEXP_LIKE(trim(atd.EXAM_DATA),'^[0-9]{10}$') )
	AND LENGTH(trim(atd.EXAM_DATA)) < 20)
	THEN 1 ELSE 0 --или '\d{10}'
END AS PHONENUMBER

-- профиль E-MAIL (Любые символы + '@' + любые символы + '.' + любые символы)
CASE WHEN (REGEXP_LIKE(trim(atd.EXAM_DATA), 
	'.*@.*\..*'))
	THEN 1 ELSE 0 
END AS E-MAIL

-- профиль FIO_SHORT (Пр: Иванов И. И.. Любое кол-во любых символов + одна заглавная буква + одна или несколько букв + пробел или '|'+ одна заглавная буква + '.' + любое кол-во любых символов)	
CASE WHEN (REGEXP_LIKE(trim(atd.EXAM_DATA), 
	'.*[АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ][А-Яа-яЁё]+\s[АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ]\..*'))
	THEN 1 ELSE 0 
END AS FIO_SHORT

-- профиль FIO_NAME (Пр: Сергей. Длина не более 14 символов, начало строки или любые символы и пробел + 1 буква кириллицы в верхнем регистре + 1 или несколько букв кириллицы + конец строки или пробел и любые символы)
-- (также исключаем подстроку в любом регистре (ов|ова|ев|ева|ина|кий|ая|ко|як|ович|евич|овна|евна|ична|ой + конец строки или пробел и любые символы)
CASE WHEN (REGEXP_LIKE(trim(atd.EXAM_DATA), 
	'(^|.*\s)[АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ][А-Яа-яЁё]+(\s.*|$)')
		AND NOT REGEXP_LIKE(trim(atd.EXAM_DATA), 			'(ов|ова|ев|ева|ина|кий|ая|ко|як|ович|евич|овна|евна|ична|ой|ОВ|ОВА|ЕВ|ЕВА|ИНА|КИЙ|АЯ|КО|ЯК|ОВИЧ|ЕВИЧ|ОВНА|ЕВНА|ИЧНА|ОЙ)(\s.*|$)')
	AND LENGTH(trim(atd.EXAM_DATA)) < 15)
	THEN 1 ELSE 0 
END AS FIO_NAME

В результате обработки подобными фильтрами получаем таблицу профилирования:

таблица

поле

данные

full_name

short_ full_name

first_name

last_name

middle_name

account

main_table

cus_nm

Иванов Иван Иванович

1

0

1

1

1

 

main_table

cus_nm

Петронко Сергей Альбертович

1

0

1

1

1

 

main_table

cus_nm

Самитова Магрипа Харипулаевна

1

0

1

1

1

 

main_table

cus_nm

Молотова К. Р.

0

0

1

0

0

 

main_table

cus_nm

Уткин Сергей

0

0

1

1

0

 

main_table

cus_nm

Морозов Владимир Иванович

1

0

1

1

0

 

 ….

 ….

….

…. 

…. 

 ….

 ….

…. 

…. 

main_table

cus_nm_short

Иванов И. И.

0

1

0

1

0

0

 ….

 ….

….

 ….

…. 

…. 

 ….

…. 

…. 

main_table

cus_sn

Иванов

0

0

0

1

0

0

 ….

 ….

……

 ….

…. 

…. 

…. 

 ….

…. 

main_table

cus_acc

40817810055760515502

0

0

0

0

0

1

В нашем случае получился всего 41 различный профиль и ещё 4 сложных профиля, совмещающих в себе несколько типов данных.

На этом этапе нам и пригодится приоритизация профилей. Записи содержащие полное ФИО (например, “Иванов Иван Иванович”) получили сразу 4 различных профиля. Мы понимаем, что причины в том, что части записей действительно соответствуют условиям присвоения профилей “Имени”, “Фамилии”, “Отчества”, а вся запись в целом соответствует профилю full_name. Соответственно, в итоге для этих записей необходимо учитывать только принадлежность к профилю full_name. Именно для этого на этапе разработки профилей ПДн необходимо продумать приоритизацию профилей.

В разрезе одного конкретного поля мы не получим 100% совпадение приоритетов просто потому, что есть редкие, сложно определяемые ФИО, ошибки в заполнении и данные, заполненные не по стандартному шаблону. Подавляющее большинство строк будет иметь единый профиль (в данном случае не рассматриваются варианты, в которых возможны смешанные типы, например, телефон и е-mail на выбор в одном поле. Такие сценарии требуют дополнительной логики профилирования и дальнейшей обработки).

В результате каждое поле таблиц с ПДн имеет определённый профиль данных. На этом этапе мы можем получить аналитику по ПДн в БД и использовать эту информацию в смежных задачах - защита, политика доступов, обфускация данных, маскирование – всё это направления, в которых такая информация крайне важна.

Правила маскирования, простые и не очень

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

Углубимся в понимание терминологии. ПДн – это сочетание данных, позволяющее идентифицировать персону. Отдельно ФИО, номер телефона и даже номер паспорта являются лишь упорядоченными наборами символов и не позволяют точно идентифицировать персону.

Соответственно, цель анонимизации не состоит в том, чтобы изменить до неузнаваемости абсолютно все данные, сочетание которых определяется как ПДн. Цель - изменить часть этого сочетания данных, сделав невозможным определение персоны.  Если в таблице содержатся поля “Фамилия”, “Имя”, “Отчество”, “серия паспорта”, “номер паспорта”, то достаточно изменить лишь некоторые из этих полей для того чтобы данные перестали относится к ПДн.

Например, изменив поля “Фамилия” и “номер паспорта” данные практически на 100% процентов потеряют свойства ПДн, т.к. идентифицировать персону по полям “Имя”, “Отчество”, “серия паспорта” практически невозможно, ну, если только это не человек с крайне редким именем или отчеством. Но для таких случаев, если они применимы в ваших проектах, просто необходимы дополнительные сценарии маскирования.

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

Сами правила маскирования разрабатываются для каждого профиля индивидуально. Также учитываем, что правила будут работать в высоконагруженных процессах (если, конечно, вы не маскируете базу из 10 таблиц). По этой причине необходимо выбирать максимально экономичные методы маскирования. Это относится и к выбору полей из комбинации ПДн - стараемся исключать поля, маскирование которых самое трудозатратное.

Вот несколько примеров правил маскирования для различных профилей ПДн:

профиль / правило

описание

пример

комментарий

name

Имя берётся из справочника подмены

Иван => Сергей

С учетом принадлежности пола

phone

+7 или 8, а также первые 3 цифры для мобильных номеров, и 5 цифр для стационарных номеров остаются неизменными, остальные изменяются на случайно сгенерированный набор цифр с сохранением исходной длины номера

+7 (909) 4567852 => +7(909) 7852389
8(5486) 526859 => 8(5486) 596389

Сохраняем принадлежность оператору связи и региону стационарного телефона

pass_ser

Не изменяется

4502 => 4502

Первые 2 цифры номер региона по ОКАТО. 3-я и 4–я является годом выдачи паспорта, без явной необходимости, тоже можно не маскировать

pass_num

Заменяются на 6 случайно сгенерированных цифр от 0 до 9

425689 => 789652

 

curd_num

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

4570 5624 6978 6793 => 4570 5678 2786 4430

Маскирование по алгоритму Луна с сохранением функциональности номера и принадлежности банку.

Справочники для подмены ФИО, адресов, ОКАТО и т.д. формируются из реальных данных, они должны быть актуальны и учитывать особенности базы.

Например, если база сформирована на данных жителей конкретного региона, а справочник ФИО - на основе общероссийского списка имен, то после маскирования процент нетипичных для региона ФИО в базе значительно вырастет. Если подобные изменения в статистике базы важны, этот момент стоит учесть, как и прочие нюансы подменных данных.

Для некоторых профилей может быть несколько правил, и применяются они при удовлетворении определенных условий.

Например, для единого профиля ФИО (и для полной формы, и с инициалами, если поле заполнено разнотипно) может быть единый профиль, но разные правила маскирования, отличные для каждого типа ФИО.

Связанные ПДн - от простого к сложному

Процесс маскирования ФИО на самом деле является одной из самых сложных задач.

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

Во-вторых, если в ПО не весь функционал работает через ID данных, то необходимо сохранить согласованность данных в рамках отдельной таблицы, а если потребуется - и всей базы.

Например, в таблице было 10 записей пользователя с ФИО «Иванов Иван Иванович», после маскирования без согласованности эти 10 записей будут иметь 10 различных ФИО.

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

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

Потоки маскирования - сами с усами или на всё готовое

Теперь, когда все элементы механизма разработаны, осталось собрать его в пайплайны и запустить процесс. Учитывая огромное количество таблиц с ПДн (в нашем случае ПДн содержались в 2500 таблицах), организовать процесс анонимизации без автоматизации с генерацией потоков маскирования практически невозможно. 

Существует достаточно много инструментов, позволяющих автоматизировать процесс, по крайней мере выполнить основной объём рутиной работы. Informatica TDM, Perforce Delphix, K2tdm – вот несколько продуктов, способных помочь в решении вопроса обезличивания. Однако, учитывая опыт в работе с Informatica TDM и объём ручной работы, проведенный по доработке пайплайнов, сгенерированных инструментом, выбор в пользу самостоятельной разработки такого инструмента становится вполне обоснованным. Добавлю, что для Informatica TDM примерно 8% (~200 потоков) сгенерированных потоков пришлось полностью перерабатывать.

Какой бы вариант не был выбран, использовать готовые решения или создавать свой инструмент, формирующий пайплайны (DAG), к примеру, в Airflow в любом случае для получения хорошего результата придется пройти все вышеописанные этапы.

ПДн несуществующих персон - матрица в действии

В итоге, несмотря на все сложности согласования и технические подводные камни, все-таки был выстроен процесс анонимизации и получена БД с замаскированными данными. Оставался лишь один важный этап – проверить, а действительно ли в данных нет ПДн. Вернее, они, конечно, там есть, но только «вымышленных персонажей», и ни один реальный Иванов Иван Иванович от обезличивания не пострадает!

Персональные данные несуществующих персон - матрица в действии. Петрович-Тех
Персональные данные несуществующих персон - матрица в действии. Петрович-Тех

Для этого проверяем, все ли данные полей, участвующие в процессе, замаскировались и не совпадают с исходными. Если все этапы проведены правильно, профили и правила маскирования детально продуманы, то результат удовлетворит службу информационной безопасности (конечно, по традиции не с первого подхода).

Всего было замаскировано 5500 полей из 2500 таблиц (8,33%). В первой версии проекта процесс получился полуавтоматическим, так как на этапе формирования датасета и профилирования часть процессов необходимо было запускать вручную. Основной процесс - маскирование данных - проходил в автоматическом режиме. А после нескольких итераций оптимизации удалось уложить все расчеты в 60 часов.

В итоге наша собственная матрица в рамках конкретной БД явилась миру! Миру разработчиков и тестировщиков целевого ПО, которые, хоть и было сделано всё хорошо, сказали не только «спасибо», но и что нужно подправить. Но это уже совсем другая история….


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

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


  1. gazkom
    23.07.2026 08:01

    Есть фирма. Торгует хозяйственными товарами. И у нее в БД 5500 полей с ПДН. Слова DWH, ETL и т.д. я знаю, но хотелось бы понять, а зачем столько?


    1. Lightxaoc Автор
      23.07.2026 08:01

      Это из предыдущего проекта. В общем-то, в наше время сложно найти проект с большой БД но без ПДн.