Право на удаление данных в LLM-контуре без хаоса
Право на удаление данных в LLM-контуре: как найти и убрать ПДн из логов, векторной базы и обучающих выборок после запроса пользователя.

Почему задача быстро разрастается
Запрос на удаление часто выглядит простым: пользователь просит стереть его данные, команда открывает тикет и ждет быстрого ответа. Но в LLM-контуре удаление почти никогда не сводится к одной записи в базе. Один и тот же фрагмент текста проходит через несколько сервисов и оставляет следы в разных местах.
Типичный пример: клиент отправил в чат имя, телефон и номер договора. Приложение сохранило диалог, LLM-шлюз записал технический лог, система наблюдаемости добавила трассировку, а RAG-контур разбил текст на чанки и положил их в векторную базу. Если потом удалить только исходное сообщение, часть данных все равно останется и всплывет позже.
Проблема еще и в том, что персональные данные живут не только в самом тексте. Они часто прячутся в метаданных: user_id, session_id, имени файла, названии документа, тегах, времени запроса, комментариях оператора. По отдельности эти поля могут казаться безобидными. Вместе они снова указывают на конкретного человека.
Обычно копии появляются сразу в нескольких слоях: в логах запросов и ответов, в кэше, в векторной базе, в снапшотах и бэкапах, в служебных таблицах и аудит-записях. Чем дольше система работает в продакшене, тем больше таких следов.
Это особенно заметно там, где LLM уже встроен в рабочие процессы. Даже если команда просто сменила base_url на OpenAI-совместимый эндпоинт и не трогала код, данные не стали жить в одном месте. Они по-прежнему проходят через приложение, шлюз, сервисы безопасности, хранилища логов и иногда через контур оценки качества.
Из-за этого команды часто чистят не те места. Они удаляют запись из основной базы, закрывают задачу, а потом тот же номер телефона находят в embeddings, старом снапшоте или трассировке инцидента. Без карты потока данных почти всегда получается частичное удаление. А частичное удаление - это повторные запросы, ручная работа и лишние риски.
Где искать персональные данные
Обычно данные проходят через несколько слоев: сам запрос, ответ модели, лог приложения, трассировку, кэш и хранилища для поиска по документам. Поэтому искать их нужно не в одной таблице, а по всей цепочке.
Первый след почти всегда лежит в промптах. Пользователь может сам вставить ФИО, телефон, номер договора или адрес. Но данные попадают и в системные сообщения, шаблоны промптов и историю диалога, если приложение автоматически подмешивает прошлые сообщения или данные из CRM.
Ответы модели тоже проверяют отдельно. Если модель пересказала исходный текст, вынесла данные в резюме или вернула их в JSON, у вас уже несколько копий одной и той же информации.
Основные места
После промптов смотрите журналы приложения. Разработчики часто логируют тело запроса целиком, чтобы разбирать ошибки. К этому добавляются access logs, APM, трассировки и audit trail. Если у вас есть LLM-шлюз или reverse proxy, копии могут лежать и там, и в сервисе, который отправляет запрос дальше.
Векторная база требует отдельной проверки. Персональные данные могут лежать не только в тексте чанка, но и в метаданных: user_id, номер клиента, имя файла, e-mail автора, теги, названия папок. Частая ошибка - удалить сам чанк и забыть про embeddings, индексы и служебные поля для фильтрации.
Слои, которые часто пропускают
Дальше проверьте наборы данных для обучения, eval и fine-tune. Туда часто попадают выгрузки саппорта, диалоги операторов, размеченные тикеты и старые JSONL-файлы, про которые уже никто не помнит. Отдельно ищите локальные копии на ноутбуках, в бакетах и в папках с экспериментами.
Еще один частый пропуск - очереди, кэш и временные файлы. Сообщение могло пройти через брокер, остаться в Redis, сохраниться во временном файле при пакетной обработке или попасть в снапшот после сбоя.
Чтобы быстро понять масштаб, обычно хватает пяти вопросов: где запрос появился, где его логировали, где индексировали, где на нем учили или тестировали модель, и где могли остаться временные копии. После такого разбора карта данных почти всегда выглядит честнее.
Что считать удалением на практике
Удаление в LLM-контуре - это не кнопка "скрыть" в админке. Если запись исчезла из интерфейса, но осталась в логах, индексе, кэше или выгрузке для дообучения, данные никуда не делись.
Говорить пользователю "данные удалены" можно только тогда, когда система больше не может подтянуть их в ответ, в поиск, в отладку или в новый обучающий набор. Это более жесткий подход, зато он ближе к реальности.
На практике удаление почти всегда включает несколько действий. Нужно стереть саму запись или заменить ее на необратимо обезличенный вариант, если это допускает процесс. Затем нужно убрать связь между user_id и всеми артефактами вокруг записи: логами, embeddings, кэшами, трассировками, экспортами и eval-наборами. Если данные еще можно восстановить из бэкапа, нельзя делать вид, что удаление уже завершено. И, наконец, нужно оставить аудит самого удаления без исходных ПДн: номер запроса, время, исполнитель и список затронутых систем.
Самая частая ошибка проста: команда удаляет запись в основной базе и забывает про следы вокруг нее. Для LLM-систем это обычная история. Один и тот же фрагмент мог попасть в prompt-лог, в векторную базу, в таблицу фидбека, в offline-выборку для eval и в резервную копию.
Полезно заранее договориться, когда вы отвечаете пользователю. Рабочих вариантов два. Первый: вы подтверждаете удаление из активных систем сразу и отдельно указываете срок очистки бэкапов. Второй: вы ждете полный цикл и только потом закрываете запрос. Смешивать эти статусы не стоит.
Если вы работаете через шлюз вроде RU LLM, где audit trail встроен в каждый запрос, отделяйте служебный след от содержимого запроса. Аудит нужен, но хранить в нем исходные ПДн после запроса на удаление не стоит.
Простой тест полезнее длинного регламента: попробуйте найти пользователя по всем известным идентификаторам, а затем задайте модели вопрос, который раньше вытаскивал его данные. Если система ничего не находит и не воспроизводит, вы близки к честному статусу "удалено".
Как обработать запрос по шагам
Удаление обычно ломается не на юридической части, а на рутине. Если в команде нет одного понятного порядка действий, любой запрос быстро превращается в ручной поиск по логам, чанкам, кэшу и старым выгрузкам.
Сначала примите запрос и проверьте, что его подал сам человек или его представитель. Не стоит удалять данные только по одному e-mail из письма. Личность лучше сверять по тому же каналу, где пользователь уже подтвержден: через аккаунт, договор, кабинет поддержки или другой проверенный способ.
После этого соберите набор идентификаторов. Одного user_id почти никогда не хватает. Нужны session_id, request_id, tenant_id, номера диалогов, хэши документов, e-mail, телефон и любые внутренние метки, по которым данные могли попасть в систему. Если запросы идут через единый шлюз, например RU LLM, эта часть проще: метки запроса и аудит уже собраны в одном месте.
Дальше важно временно остановить новые записи с этими данными. Иначе вы удалите старые следы, а через минуту система снова положит те же ПДн в лог, кэш или векторную базу. На практике это значит: блокировать повторную индексацию, отключать фоновые задачи для нужного объекта и замораживать экспорт в обучающие выборки.
Удобно идти по одному шаблону. Сначала ищите точные идентификаторы в логах запросов и ответов. Потом проверяйте трассировки, очереди, кэш и временные файлы. Затем поднимайте исходные документы и их чанки в векторной базе. После этого проверяйте датасеты для дообучения, eval-наборы и ручные выгрузки. Результат лучше фиксировать по каждому хранилищу отдельно, а не одной общей пометкой в тикете.
Удалять данные тоже нужно вместе со следами, которые система построила вокруг них. Если вы убрали документ, пересоберите индекс, удалите embeddings, обновите связи в retrieval-слое и проверьте, что поиск больше не находит старые чанки. Если чистите логи, убедитесь, что данные ушли из реплик, служебных таблиц и архивов в рамках вашей политики хранения.
В конце сделайте контрольную проверку тем же набором идентификаторов. Лучше, если ее проводит не тот же человек, который удалял данные. Сохраните журнал действий: кто принял запрос, как подтвердили личность, что искали, что удалили, что не смогли удалить сразу и почему. Такой журнал полезен не для отчетности. Он экономит часы, когда приходит повторная проверка или спор по 152-ФЗ.
Как чистить логи, кэш и трассировки
После запроса на удаление данные чаще всего остаются не в основной базе, а в служебных следах. Один и тот же фрагмент текста может лежать в логах API, в APM, в очереди событий, в отладочном дампе и в кэше ответа. Если убрать его только из одного места, риск останется.
Смотрите шире, чем просто тело запроса. ПДн попадают в сырые промпты, вложения, заголовки, тексты ошибок и stack trace, если приложение вставляет туда часть входных данных. Частая проблема - кастомные заголовки вроде user-email, phone или external-id. Еще одна ловушка - сообщения об ошибках, где SDK или proxy пишет кусок промпта рядом с кодом 400 или 429.
Обычно проверяют несколько мест: логи API-шлюза и приложения, APM и трассировки запросов, основную очередь событий и очередь неудачных сообщений, отладочные дампы и аварийные снимки памяти, кэш ответов и временные файлы на диске.
Сырые промпты лучше не хранить там, где для разбора инцидента хватает хеша, request_id или маски. Вместо полного текста обычно достаточно длины запроса, времени, модели, кода ответа и нескольких безопасных меток. Например, e-mail можно маскировать до вида a***@company.ru, а сам текст запроса заменить стабильным хешем. Для 152-ФЗ такой минимализм обычно полезнее, чем логи "на всякий случай".
Если у вас OpenAI-совместимый шлюз, проверьте сразу два слоя: сам proxy и приложение за ним. Иначе команда удалит данные в одном месте, а второй слой сохранит ту же строку снова. В случае с RU LLM имеет смысл смотреть вместе правила логирования, встроенное маскирование PII и audit trail, а не разбирать их по отдельности.
С кэшем все проще, но именно про него часто забывают. Очистите response cache, локальные временные файлы, результаты batch-задач и файлы, которые появились при OCR или предобработке. Если сервис пишет ответы на диск перед отправкой пользователю, эти копии тоже нужно удалить.
И еще один слой, который часто вспоминают слишком поздно, - ретраи. Фоновый воркер может взять старое сообщение из очереди и заново записать уже удаленные данные в лог или трассировку. Поэтому нужен стоп-флаг на уровне subject_id или request_id: если запись помечена к удалению, повторная обработка не должна вытаскивать исходный payload.
Как убирать данные из векторной базы
В векторной базе проблема почти никогда не живет в одном объекте. Один и тот же фрагмент текста обычно существует как чанк, embedding, набор метаданных, запись в очереди на переиндексацию и иногда как копия в тестовой среде. Если удалить только исходный документ, поиск еще какое-то время будет подтягивать старые следы.
Надежнее искать не по самому тексту, а по стабильным меткам: source_id, user_id, document_id, tenant_id, request_id. Это проще и точнее, чем пытаться выловить все варианты имени, телефона или адреса внутри чанков. Если таких меток нет, удаление быстро превращается в дорогой ручной разбор.
Обычно процесс выглядит так: команда находит все чанки и их версии по source_id, user_id и смежным меткам, удаляет embeddings вместе с метаданными, очищает очередь на переиндексацию и кэш поиска, а затем проверяет копии в тесте, песочницах и временных дампах.
На удалении записей работа не заканчивается. Некоторые движки сначала помечают данные как удаленные, а сам индекс чистят позже. Пока команда не пересоберет индекс или не дождется его физической очистки, старый чанк может еще появляться в выдаче. Именно поэтому бывает так: документ уже стерли, а поиск все равно что-то находит.
Проверка должна быть такой же строгой, как само удаление. Возьмите тот же запрос, который раньше вытаскивал нужный фрагмент, и прогоните его снова. Потом измените формулировку: короткий запрос, запрос с опечаткой, поиск по соседним словам. Если запись исчезла только в одном режиме поиска, задача не закрыта.
Отдельно проверьте тестовые стенды. Во многих командах продовую базу копируют "на время", а потом забывают о ней на месяцы. В итоге прод уже очищен, а персональные данные все еще лежат в тесте. Поэтому полезно держать простой реестр копий и удалять записи по тем же идентификаторам во всех средах.
Что делать с обучающими выборками
Частая ошибка в том, что production-логи и обучающие наборы вроде бы живут отдельно, но на деле куски пользовательских запросов уезжают в train, затем в eval, потом в набор для ручной разметки. Если приходит запрос на удаление, чистить нужно всю цепочку.
Сначала проверьте происхождение каждого набора. У датасета должен быть понятный ответ на два вопроса: из чего он собран и какие поля в него попали. Если ответа нет, удаление быстро превращается в разбор архивов вручную. Audit trail по запросам сильно упрощает поиск, потому что позволяет связать конкретный пользовательский ввод с файлами, снапшотами и задачами разметчиков.
Рабочий порядок обычно такой: физически отделить production-логи от обучающих наборов и доступов к ним, найти все семплы из запросов, чатов, обращений в поддержку и форм, удалить эти строки из train, eval и наборов для разметки, а затем пересобрать манифесты, индексы и версии датасета, если они ссылаются на удаленные записи.
Но на этом нельзя останавливаться. Если модель уже дообучили на этих примерах, простое удаление строк из CSV ничего не меняет. Модель уже получила эти данные. Для fine-tune это означает перезапуск обучения на очищенном наборе и замену старого артефакта новой версией. Иначе в контуре останется модель, которая формально обучена на данных, которые вы обязались удалить.
Простой пример: банк дообучал классификатор обращений на реальных диалогах. В один семпл попали ФИО и номер телефона. После запроса клиента команда удаляет запись из основного датасета, но забывает про eval и очередь на разметку. Через месяц тот же фрагмент всплывает в тестах качества. Это не редкая аномалия, а обычный пропуск в процессе.
После очистки обновите карточку датасета и журнал изменений. Зафиксируйте, что удалили, почему, из каких версий, кто согласовал замену модели и какие наборы пересобрали. Через полгода именно эта запись помогает понять, почему версия 1.8 уже не совпадает с результатами версии 1.7.
Пример из банка
В банке запрос на удаление редко выглядит как один клик. Клиент пишет в поддержку: он случайно отправил в чат с ботом серию и номер паспорта и просит удалить эти данные. Если смотреть только на интерфейс чата, половина следов останется за кадром.
Сначала инженер берет идентификатор диалога и поднимает журнал обработки. В исходном промпте паспортные данные есть целиком. Дальше команда находит тот же фрагмент в логах запросов и ответов, где чат сохранял текст для разбора ошибок. Потом выясняется, что данные уже попали в RAG-контур: в векторной базе лежат два чанка, где рядом встречаются номер паспорта и ФИО.
Параллельно ML-команда проверяет внутренние наборы данных. Оказывается, этот диалог уже уехал в датасет для оценки качества, потому что его добавили как пример сложного банковского вопроса. Это типичная история: данные удалили из продового чата, но оставили во вспомогательных контурах.
В таких случаях лучше вести один тикет на весь запрос. Тогда шаги не теряются. Команда удаляет исходный чат и связанные сырые промпты, вычищает записи из логов, трассировок и отладочных выгрузок, удаляет найденные чанки из векторной базы и пересобирает индексы, если это нужно, а затем исключает диалог из eval-датасета и пересобирает выборку.
После очистки не стоит верить системе на слово. Поиск по точному номеру паспорта, по маске номера и по соседним полям не должен ничего находить. Затем сотрудник задает контрольный вопрос боту, который раньше вытаскивал этот диалог через RAG. Бот не должен вернуть старый фрагмент ни дословно, ни в пересказе.
Ответ пользователю лучше готовить не в свободной форме, а по журналу действий. В нем должны быть время запроса, затронутые хранилища, исполнитель, проведенные проверки и их результат. Такой ответ звучит сухо, но его можно без лишних споров показать внутреннему контролю, ИБ и регуляторному блоку.
Где команды чаще ошибаются
Большинство ошибок начинается с узкого взгляда на удаление. Команда стирает запись из основной БД и считает задачу закрытой, хотя персональные данные уже ушли в логи приложений, APM, трассировки, алерты и выгрузки для поддержки. Иногда номер телефона в ошибке 500 живет дольше, чем исходная анкета клиента.
Еще одна ловушка - удалить исходный текст, но оставить embeddings и метаданные. Для векторной базы этого мало. Если у записи остался user_id, номер договора, путь к документу или даже стабильный хэш, вы все еще храните след, который можно связать с человеком.
Следы, о которых забывают
Команды редко держат в одной схеме все места, куда попадают данные. Из-за этого запрос пользователя не связывают с резервными копиями, тестовыми стендами и разовыми дампами для аналитики. Самый неприятный вариант - прод уже очищен, а тестовая копия с тем же диалогом лежит у соседней команды еще полгода.
Похожая проблема возникает, когда логи для отладки смешаны с датасетами для обучения или evaluation. Тогда удаление превращается в ручной поиск по нескольким контурам, а сроки начинают плыть. Если команда использует единый LLM-шлюз, например RU LLM, это упрощает точку входа для поиска и аудита, но не отменяет разбор всех downstream-хранилищ, куда данные уже успели попасть.
Остатки чаще всего находят в логах приложения и error trace, в векторных индексах и метаданных чанков, в архивах и снапшотах БД, в тестовых копиях и CSV-выгрузках, а также в датасетах для обучения, evaluation и prompt caching.
Ошибка в самом конце
Часто тикет закрывают сразу после команды delete. Это слабое место процесса. Нужна повторная проверка: поиск по идентификаторам, контрольная выборка в поиске, просмотр свежих логов и фиксация срока, когда данные исчезнут из бэкапов. Иначе через неделю те же данные всплывут в отладке, и команда пойдет по второму кругу.
Хорошая практика выглядит просто: сначала карта всех копий данных, потом удаление по каждому слою, потом проверка тем же набором признаков, по которым вы искали запись в начале.
Что сделать на этой неделе
Проблемы с удалением обычно начинаются не на уровне закона, а на мелочах. Команда удаляет запись в одном месте, а потом находит тот же номер телефона в логе ретрая, в векторной базе или в тестовом датасете. Поэтому не пытайтесь сразу охватить весь контур. Лучше взять один живой поток, например путь от формы обращения до ответа модели, и разобрать его до конца.
Если делать только одну практическую проверку на этой неделе, выберите сценарий, где точно есть ПДн, и опишите путь данных по шагам. Сразу назначьте владельца процесса, срок обработки запроса и шаблон ответа пользователю. Потом проверьте, маскирует ли сервис ПДн до записи в лог, умеют ли векторная база, кэш и датасеты удалять данные по ID, и что происходит с бэкапами, audit trail и служебными таблицами.
Лучше всего работает простой стандарт: каждый объект с ПДн получает стабильный идентификатор, а каждая копия этого объекта знает, откуда она пришла. Тогда запрос на удаление не превращается в охоту по всему контуру. Без этого даже аккуратная команда тратит часы на сверки и все равно не уверена в результате.
Есть и полезный тест на зрелость процесса. Возьмите один реальный идентификатор пользователя, прогоните его через логирование, индексацию, кэширование и обучение, а потом попробуйте удалить все следы за один рабочий день. Если не получилось, процесс пока не готов.
Для систем в российском контуре проверка должна быть шире. Смотрите не только на основную базу, но и на то, где лежат логи, бэкапы и аудит-записи, кто к ним имеет доступ и как долго они хранятся. Если вы строите инфраструктуру через RU LLM, часть базы для такого процесса уже есть: хранение логов и бэкапов внутри РФ, маскирование PII, AI-Law метки и audit trail встроены в запросы. Но даже в таком случае команде нужен свой порядок удаления в приложении, индексах и датасетах.
Зрелость здесь проверяется просто: вы можете показать путь запроса от его получения до подтверждения удаления без ручной импровизации. Если этого пока нет, начните с одного потока и доведите его до состояния, где любой инженер пройдет процедуру по инструкции за 20 минут.