Мягкие и жёсткие удаления: реальные компромиссы для аналитики, поддержки, GDPR‑похожих запросов и сложности запросов, а также безопасные схемы восстановления.

Кнопка «удалить» в интерфейсе может означать две совершенно разные вещи для базы данных.
Жёсткое удаление удаляет строку навсегда. После этого запись исчезает, если только у вас нет бэкапов, логов или реплик, где она осталась. Это просто для рассуждений, но окончательно.
Мягкое удаление сохраняет строку, но помечает её как удалённую, обычно полем вроде deleted_at или is_deleted. Приложение затем трактует помеченные строки как невидимые. Вы сохраняете связанные данные, историю и иногда возможность восстановления записи.
Этот выбор встречается в повседневной работе чаще, чем думают. Он влияет на ответы на вопросы: «Почему упала выручка в прошлом месяце?», «Можно ли восстановить мой удалённый проект?» или «К нам пришёл запрос на удаление по GDPR — действительно ли мы удаляем персональные данные?» Также это формирует, что значит «удалено» в интерфейсе. Пользователи часто предполагают, что можно отменить действие, пока не сталкиваются с невозможностью восстановления.
Практическое правило:
Пример: клиент удалил рабочую область и затем понял, что в ней были счета, необходимые для бухгалтерии. С мягким удалением поддержка сможет восстановить её (если приложение умеет безопасно восстанавливать). С жёстким удалением вам скорее придётся объяснять бэкапы, задержки или «это невозможно».
Ни один подход не универсально «лучше». Наименее болезненный вариант зависит от того, что вы хотите защитить: доверие пользователей, точность отчётов или соблюдение конфиденциальности.
Выбор удаления быстро проявляет себя в аналитике. В день, когда вы начинаете отслеживать активных пользователей, конверсию или выручку, «удалён» перестаёт быть простым состоянием и становится решением для отчётов.
При жёстком удалении многие метрики выглядят «чистыми», потому что удалённые записи исчезают из запросов. Но вы теряете контекст: прошлые подписки, размер команды раньше или как выглядел воронка месяц назад. Удалённый клиент может сместить исторические графики при повторном запуске отчётов — это пугает финансы и рост.
При мягком удалении вы сохраняете историю, но можете случайно раздуть числа. Простой «COUNT users» может включать ушедших людей. График оттока может двойственно считать события, если в одном отчёте вы считаете deleted_at как отток, а в другом игнорируете его. Даже выручка может запутаться, если счета остаются, а аккаунт помечен как удалённый.
Что обычно работает — выбрать согласованный шаблон отчётности и придерживаться его:
Ключ — документация, чтобы аналитики не гадали. Запишите, что значит «активный», включены ли мягко удалённые пользователи и как учитывается выручка, если аккаунт позже удалён.
Конкретный пример: рабочая область была удалена по ошибке, затем восстановлена. Если ваш дашборд считает рабочие области без фильтрации, вы увидите резкое падение и восстановление, которого на самом деле не было в пользовательской активности. С snapshot‑таблицами исторический график остаётся стабильным, в то время как представления продукта могут скрывать удалённые области.
Большинство заявок в поддержку вокруг удаления звучат одинаково: «Я удалил(а) это по ошибке» или «Куда делась моя запись?» Ваша стратегия удаления решает, сможет ли поддержка ответить за минуты или честный ответ будет «Это исчезло».
С мягким удалением вы обычно можете выяснить, что случилось, и отменить действие. С жёстким удалением поддержке часто придётся полагаться на бэкапы (если они есть), а это может быть медленно, неполно или вообще невозможно для одного элемента. Поэтому выбор — это не только деталь базы данных: он формирует, насколько «полезен» ваш продукт, когда что‑то идёт не так.
Если вы рассчитываете на реальную поддержку, добавьте пару полей, которые объясняют события удаления:
deleted_at (метка времени)deleted_by (id пользователя или система)delete_reason (необязательно, краткий текст)deleted_from_ip или deleted_from_device (необязательно)restored_at и restored_by (если поддерживаете восстановление)Даже без полного лога активности эти детали позволяют поддержке ответить: кто удалил, когда это произошло и было ли это случайностью или автоматической очисткой.
Жёсткие удаления могут быть приемлемы для временных данных, но для пользовательских объектов они меняют возможности поддержки.
Поддержка не сможет восстановить одиночную запись, если вы не сделали отдельную «корзину». Придётся восстанавливать из бэкапа, что может повлиять на другие данные. Также сложно доказать, что именно произошло, что ведёт к долгим разбирательствам.
Функции восстановления меняют нагрузку на поддержку. Если пользователи могут сами восстановить в окне времени, количество тикетов падает. Если восстановление требует ручного вмешательства поддержки, тикеты могут увеличиться, но они становятся быстрыми и повторяемыми вместо единичных расследований.
«Право быть забытым» обычно означает, что вы должны прекратить обработку персональных данных и удалить их там, где они остаются идентифицируемыми. Это не всегда значит, что нужно немедленно стирать все исторические агрегаты, но это точно значит, что нельзя хранить идентифицируемые данные «на всякий случай», если у вас нет законного основания.
Вот где выбор мягкого/жёсткого удаления становится больше, чем продуктовый вопрос. Мягкое удаление (например, установка deleted_at) часто просто скрывает запись в приложении. Данные всё ещё лежат в базе, доступны администраторам и часто остаются в экспортированных файлах, индексах поиска и аналитических таблицах. Для многих запросов на удаление по GDPR это не является стиранием.
Вам всё ещё нужна очистка, когда:
Бэкапы и логи — то, о чём команды забывают. Вы не сможете удалить отдельную строку из зашифрованного бэкапа, но можно установить правило: бэкапы хранятся недолго, а при восстановлении нужно заново применять события удаления до того, как система станет рабочей. Логи должны избегать хранения «сырых» персональных данных и иметь ясные сроки хранения.
Простая практическая политика — двухэтапное удаление:
Если ваша платформа поддерживает экспорт исходников или данных, обращайтесь с экспортированными файлами как с хранилищами данных: определите, где они хранятся, кто имеет доступ и когда они удаляются.
Мягкое удаление кажется простым: добавил deleted_at (или is_deleted) и скрываешь строку. Скрытая цена в том, что теперь везде, где читаются данные, нужно помнить об этом флаге. Промахнитесь один раз — и получите странные баги: итоги включают удалённые объекты, поиск показывает «призраки», или пользователь видит то, что думал удалённым.
UI и UX‑краевые случаи быстро проявятся. Представьте, что команда удаляет проект «Roadmap» и затем пытается создать новый «Roadmap». Если в базе есть уникальное ограничение по имени, создание может упасть, потому что удалённая строка всё ещё существует. Поиск тоже может запутать: если вы скрываете удалённые элементы в списках, но не в глобальном поиске, пользователи подумают, что приложение сломалось.
Фильтры мягкого удаления часто забывают:
Производительность обычно нормальна в начале, но дополнительное условие даёт работу. Если большинство строк активны, фильтр deleted_at IS NULL дешёв. Если много строк удалены, базе придётся пропускать больше строк, если только вы не добавите нужный индекс. Проще говоря: это как искать текущие документы в ящике, где много старых.
Отдельная область «архива» может уменьшить путаницу. Делайте дефолтный вид только с активными записями, а удалённые перемещайте в одно место с понятными метками и окном хранения. В быстро собранных внутренних инструментах (например, на Koder.ai) это продуктовое решение часто предотвращает больше тикетов, чем любые хитрые запросы.
Мягкое удаление — это не одна фича. Это выбор модели данных, и от него зависит всё: правила запросов, поведение восстановления и что значит «удалено» для продукта.
deleted_at и deleted_byСамый распространённый паттерн — nullable метка времени. Когда запись удаляют, ставят deleted_at (и часто deleted_by с id пользователя). «Активные» записи — те, у которых deleted_at равно null.
Это удобно для чистого восстановления: восстановление — просто очистить deleted_at и deleted_by. Также даёт простой сигнал для поддержки.
Вместо метки времени некоторые используют поле status с понятными состояниями: active, archived, deleted. Это полезно, когда «архивировано» — реальное продуктовое состояние (скрыто с большинства экранов, но учитывается в биллинге, например).
Цена — набор правил. Нужно везде определить, что значит каждое состояние: поиск, уведомления, экспорты и аналитика.
Для чувствительных или ценных объектов можно перемещать удалённые строки в отдельную таблицу или записывать событие в append‑only лог.
deleted_at, deleted_bystatus с именованными состояниямиЭто часто применяется, когда восстановление должно быть строго контролируемым, или когда нужен аудит без смешивания удалённых данных в повседневных запросах.
Дочерние записи тоже требуют осознанного правила. Если рабочая область удалена, что происходит с проектами, файлами и участниками?
archived (не удалять)Выберите одно правило для каждой связи, задокументируйте и держитесь его. Большинство багов «восстановление прошло неправильно» происходит из-за разных смыслов удаления у родителя и детей.
Кнопка «восстановить» кажется простой, но она тихо может ломать проверки прав, воскресить старые данные в неправильном месте или запутать пользователей, если «восстановлено» не означает то, чего они ждут. Начните с чёткого описания обещания продукта.
Используйте небольшой строгий сценарий, чтобы восстановление было предсказуемым и поддавалось аудиту.
Если вы быстро строите приложения в чат‑управляемом инструменте вроде Koder.ai, включите эти проверки в сгенерированный рабочий процесс, чтобы каждый экран и endpoint следовали единым правилам.
Самая большая боль с мягкими удалениями — не само удаление, а все места, которые забывают, что запись «удалена». Многие команды выбирают мягкое удаление для безопасности, а затем случайно показывают удалённые элементы в результатах поиска, бейджах или итогах. Пользователи замечают быстро, когда дашборд показывает «12 проектов», а в списке только 11.
Второе — контроль доступа. Если пользователь, команда или рабочая область мягко удалены, они не должны иметь возможность войти, обращаться к API или получать уведомления. Это часто проскакивает, когда проверка логина ищет запись по email, находит строку и не проверяет флаг удаления.
Типичные ловушки, создающие тикеты:
Коллизии уникальности особенно неприятны при восстановлении. Если кто‑то создал новый аккаунт с тем же email, пока старый был мягко удалён, восстановление либо упадёт, либо перезапишет чужую учётную запись. Решите правило заранее: блокировать повторное использование до очистки, позволить повторное использование, но запретить восстановление, или восстанавливать под новым идентификатором.
Один распространённый сценарий: агент поддержки восстанавливает мягко удалённую рабочую область. Рабочая область возвращается, но её участники остаются удалёнными, и интеграция возобновляет синхронизацию старых записей в партнёрский инструмент. С точки зрения пользователя, восстановление «полузакончилось» и привело к новой путанице.
Прежде чем выпускать восстановление, сделайте явными следующие поведения:
B2B SaaS-команда имеет кнопку «Удалить рабочую область». В одну пятницу админ запустил очистку и удалил 40 рабочих областей, которые выглядели неактивными. В понедельник три клиента пожаловались, что проекты исчезли и потребовали немедленного восстановления.
Команда думала, что решение будет простым. Это не так.
Первая проблема: поддержка не может восстановить то, что действительно удалено. Если строка рабочей области жестко удалена и каскадно убирает проекты, файлы и участников, единственный вариант — бэкапы. Это значит время, риск и неловкий ответ клиенту.
Вторая проблема: аналитика выглядит сломанной. Дашборд считает «активные рабочие области», запрашивая строки с deleted_at IS NULL. Ошибочное удаление даёт резкий провал в графиках. Ещё хуже: недельный отчёт сравнивает с прошлой неделей и сигнализирует о ложном всплеске оттока. Данные не были потеряны, но были исключены в неправильно выбранных местах.
Третья проблема: пришёл запрос на удаление персональных данных от одного из пользователей. Простое мягкое удаление этому не соответствует. Команде нужна политика по очистке персональных полей (имя, email, IP‑логи), сохраняя при этом неперсональные агрегаты, такие как суммы выставленных счетов и номера счетов.
Четвёртая проблема: все спрашивают «Кто нажал удалить?» Если следа нет, поддержка не может ничего объяснить.
Более безопасный паттерн — рассматривать удаление как событие с метаданными:
deleted_by, deleted_at и причину или id тикетаЭто тот рабочий поток, который команды часто быстро строят на платформах вроде Koder.ai, а затем понимают, что политика удаления требует столько же проектирования, сколько и остальные фичи.
Выбор между мягкими и жёсткими удалениями — это не про вкусы, а про то, какие гарантии ваш продукт должен соблюдать после того, как запись «удалена». Задайте себе эти вопросы до того, как напишете первый запрос.
Простой способ проверить решение — пройти один реалистичный инцидент: например, кто‑то удалил рабочую область в пятницу вечером. В понедельник поддержке нужно увидеть событие удаления, безопасно восстановить и не воскресить связанные данные, которые должны остаться удалёнными. Если вы строите приложение на платформе вроде Koder.ai, определите эти правила заранее, чтобы сгенерированный бэкенд и UI следовали одной политике, а не разбрасывали особые кейсы по коду.
Выберите подход, записав простую политику, которой можно делиться с командой и поддержкой. Если политика не фиксирована письменно, она будет дрейфовать, и пользователи почувствуют несогласованность.
Начните с набора простых правил:
Затем постройте два чётких пути, которые не смешиваются: путь «восстановление админом» для ошибок и путь «очистка по приватности» для окончательного удаления. Путь восстановления должен быть обратимым и логгируемым. Путь очистки должен быть окончательным и удалять или анонимизировать все связанные данные, которые могут идентифицировать человека, включая бэкапы или экспорты, если это требуется политикой.
Добавьте защитные механизмы, чтобы удалённые данные не просочились обратно в продукт. Проще всего — считать «удалено» первоклассным состоянием в тестах. Добавьте контрольные точки для каждого нового запроса, страницы списка, поиска, экспорта и аналитической задачи. Хорошее правило: если экран показывает пользовательские данные, у него должно быть явное решение по удалённым записям (скрывать, показывать с пометкой или только для админа).
Если вы только начинаете продукт, прототипируйте оба потока до окончательного выбора схемы. В Koder.ai можно набросать политику удаления в режиме планирования, сгенерировать базовый CRUD и быстро прогнать сценарии восстановления и очистки, затем подправить модель данных до коммита.