MAATRIX / Блог / Отдать пользователям их данные перед закрытием: как организовать экспорт за неделю

Отдать пользователям их данные перед закрытием: как организовать экспорт за неделю

MAATRIX

Решение закрыть сервис принято, дата отключения известна, и в списке дел рядом с «снять бэкапы» и «уведомить платёжку» стоит пункт «дать пользователям выгрузить свои данные». На бумаге это мелочь, на практике — отдельный небольшой проект со своим сроком, если экспорта в продукте никогда не было. Если до отключения есть неделя, разбираться теоретически некогда: нужен минимальный работающий план, который закроет главное — люди не должны терять то, что сами создали, потому что вы выключили рубильник. Разберём, что реально отдавать, в каком виде и как успеть это за оставшееся время без экспорта «на глазок».

Что реально нужно отдать пользователям

Первая ошибка — выгрузить всё содержимое базы как есть и назвать это экспортом. Пользователю не нужен дамп таблицы sessions с токенами авторизации, служебные поля internal_status или логи очереди задач. Ему нужно то, что он сам создал, загрузил или накопил и что имеет ценность вне вашего сервиса:

  • контент, который пользователь ввёл сам: заметки, документы, посты, карточки, настройки проектов;
  • загруженные файлы: фото, сканы, вложения, отчёты;
  • история действий, если она сама по себе ценна — заказы, платежи, переписка с поддержкой;
  • метаданные, без которых контент не имеет смысла: даты создания, теги, связи между записями.

Не входит в список: внутренние ID из вашей схемы БД, флаги фичей, счётчики, телеметрия продукта — если только пользователь явно не просил именно их. Разграничение простое: если пользователь через год без вашего сервиса откроет файл экспорта и поймёт, что перед ним, и сможет этим воспользоваться — это нужно отдавать. Если поймёт только ваш бэкенд-разработчик — не нужно.

Отдельная грабля — вложения, логически привязанные к записям, но оставшиеся на сервере: в экспорте написано «см. приложение», а самого приложения нет. Разбор этой проблемы на стороне облачных сервисов — в статье «Экспорт забрал записи, а вложения остались у вендора».

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

Формат экспорта: простой и работающий без вашего сервиса

Экспорт бесполезен, если открыть его может только ваш сервис. Жалобы на выгрузку в закрытом внутреннем формате, с ID вместо названий и вложенным JSON без документации — одна из самых частых претензий к закрывающимся сервисам; разбор конкретных провалов — в статье «Выгрузка из SaaS пришла в формате, который никто не читает».

Правило простое: форматы должны открываться программой, установленной у обычного человека, без вашего участия.

Тип данныхФорматПочему
Табличные данные (заказы, транзакции)CSV или XLSXОткрывается в Excel/Google Таблицах
Записи со вложенностью (проекты с задачами, версии документов)JSONНе теряет структуру, легко парсится
Загруженные файлы (фото, PDF)Оригиналы в ZIP с понятной структурой папокОткрываются как обычные файлы
Переписка, история обращенийJSON или текст с датамиНе нужен собственный вьюер
Настройки проектаJSON или YAMLЧитаемо глазами, переносимо в другой инструмент

CSV удобен, но легко портится на кодировке, разделителях и переносах строк внутри полей, если формируется руками через конкатенацию строк. Вывод простой: генерируйте CSV штатным модулем языка (csv в Python, encoding/csv в Go), а не через join(',').

Структура архива на пользователя, которая облегчает жизнь получателю:

export_user_12345/
├── README.txt        # что внутри, дата выгрузки, до какого срока доступно
├── data.json          # структурированные данные целиком
├── orders.csv          # то же самое для тех, кому проще открыть в таблице
└── files/
    ├── document_2024-03-01.pdf
    └── photo_001.jpg

README.txt не формальность: строка «Экспорт аккаунта example@mail.com от 28.08.2026, сервис закрыт, данные доступны до 30.09.2026» экономит пользователю время на догадки, когда он найдёт архив через год.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Техническая реализация: скрипт массовой выгрузки

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

def export_user(user_id, db, storage, out_dir):
    user_dir = Path(out_dir) / f"user_{user_id}"
    user_dir.mkdir(parents=True, exist_ok=True)

    records = db.fetch_all("SELECT * FROM user_content WHERE user_id=%s", (user_id,))
    json.dump(records, open(user_dir / "data.json", "w"), default=str, ensure_ascii=False)

    if records:
        with open(user_dir / "data.csv", "w", newline="") as f:
            w = csv.DictWriter(f, fieldnames=records[0].keys())
            w.writeheader(); w.writerows(records)

    files_dir = user_dir / "files"; files_dir.mkdir(exist_ok=True)
    for meta in db.fetch_all("SELECT storage_key, name FROM user_files WHERE user_id=%s", (user_id,)):
        storage.download(meta["storage_key"], files_dir / meta["name"])

    zip_path = Path(out_dir) / f"export_{user_id}.zip"
    with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
        for f in user_dir.rglob("*"):
            zf.write(f, f.relative_to(user_dir.parent))
    return zip_path

Нюансы, которые за неделю легко упустить, но дорого стоят на большой базе:

  • Не гоняйте выгрузку в один поток на проде. SELECT * по всем пользователям подряд создаёт постоянную нагрузку в момент, когда вам и так нужна стабильность БД. Выгружайте с реплики, батчами с паузами, или в окно низкой нагрузки.
  • Параллельте по пользователям, а не по запросам к одной таблице — пул из 4-8 воркеров поверх списка user_id ускоряет процесс без переусложнения кода: cat user_ids.txt | xargs -P 6 -I {} python export_user.py --id {}.
  • Логируйте прогресс в файл. Скрипт на десятки тысяч пользователей может упасть на середине из-за одной битой записи. Ловите исключение на уровне одного пользователя, пишите ID и ошибку в failed_users.log, продолжайте цикл.
  • Сверяйте число записей. Количество строк в data.json должно совпадать с SELECT COUNT(*) по тому же условию — это ловит случай, когда фильтр в запросе выгрузки не совпадает с реальными данными. Как проверять полноту выгрузки по числам, а не «на глаз» — в статье «Как проверить, что выгрузка полная».
  • Считайте дисковое место заранее. 20 000 пользователей по 50 МБ вложений в среднем — это уже терабайт; проверьте запас на диске, прежде чем скрипт упадёт на середине с No space left on device.

Приоритизация: если неделя есть только на часть данных

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

  1. Невосстановимый оригинальный контент — тексты, документы, фото, код, дизайн-файлы. Потеря здесь необратима для пользователя.
  2. Данные с юридической или финансовой значимостью — платежи, счета, документы по сделкам, нужные для бухгалтерии или споров.
  3. Чувствительные персональные данные (медицинские записи, документы, удостоверяющие личность) — выгрузить в первую очередь либо явно заранее предупредить об удалении без передачи.
  4. Функциональные настройки и метаданные — упрощают перенос в другой сервис, но не уникальны сами по себе.
  5. Аналитика и история использования — приятно иметь, но без них пользователь не теряет ничего критичного.

Если время не остаётся на пункты 4-5 — не страшно, честно скажите об этом в коммуникации. Хуже — тихо не выгрузить пункт 1 или 2, надеясь, что никто не заметит.

Хранение и раздача: где положить архивы и как выдать доступ

Готовые архивы нужно куда-то положить так, чтобы пользователь скачал их без нового аккаунта и лишней авторизации — часто к моменту раздачи основной сервис уже не работает в обычном режиме.

  • Объектное хранилище с предподписанными ссылками. Кладёте export_12345.zip в приватный бакет, генерируете временную ссылку (например, на 30 дней) и отправляете письмом. Ссылка не требует пароля и не индексируется поисковиками.
  • Простая страница со скачиванием по токену, если объектного хранилища нет и разворачивать его ради недели избыточно: статика через nginx с уникальным путём на пользователя (/exports/a1b2c3d4e5.zip, случайный токен, а не последовательный ID) и autoindex off;.
  • Явный срок доступности — например, 30 дней с момента отправки письма. После этого срока архивы можно удалить с диска раздачи (не обязательно с бэкапа), не держа вечную копию «на всякий случай».

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

Коммуникация с пользователями: письма, страница, сроки

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

  • Письмо на email каждого пользователя с прямой ссылкой на его архив (если удаётся сгенерировать заранее) или с инструкцией, как получить доступ. Четыре факта без воды: сервис закрывается, данные доступны до такой-то даты, вот ссылка, вот контакт, если ссылка не работает.
  • Баннер в самом продукте, если он ещё работает хотя бы в режиме чтения — не все открывают почту от сервиса, который скоро закроется.
  • Статичная страница с общей информацией о закрытии и сроках — единая точка правды, на которую можно сослаться в письме, соцсетях, ответах поддержки.
  • Дедлайн с запасом, отсчитанный от реальной даты отправки писем, а не от момента отключения сервиса — между ними обычно есть разрыв в несколько дней.

Более широкий контекст закрытия проекта — домен, подписки, юридические обязательства — собран в статье «Сколько стоит закрыть проект правильно: архив, экспорт, удаление»; пригодится, если экспорт данных — только один пункт из чек-листа закрытия.

Проверка перед отправкой: не разослать битые архивы

Разослать письма со ссылками на архивы, которые не открываются или пустые — хуже, чем не разослать вовсе: доверие тратится один раз. Перед массовой рассылкой стоит потратить час на выборочную проверку:

  • откройте 5-10 случайных архивов вручную: пользователь с большим объёмом данных, пользователь без файлов вообще, самый старый аккаунт (часто там устаревшая схема данных), самый новый;
  • проверьте, что ZIP не битый — распаковывается стандартным архиватором ОС, а не только вашей библиотекой;
  • откройте CSV в Excel или Google Таблицах, а не только текстовым редактором — кодировка и разделитель часто видны только там;
  • сверьте число файлов и записей с тем, что реально есть у пользователя в БД, для той же выборки;
  • отдельно проверьте пустые аккаунты — скрипт не должен падать или создавать файл ошибки вместо валидного архива с README.txt, объясняющим отсутствие данных.

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

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

У нас нет резервной базы для выгрузки, только прод. Это опасно?

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

Нужно ли шифровать архивы перед отправкой?

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

Что делать с пользователями, у которых почта уже недействительна?

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

Обязаны ли мы вообще делать экспорт, если это не прописано в оферте?

Это юридический вопрос, зависящий от юрисдикции, типа данных и применимого регулирования — уточните его отдельно с юристом; эта статья описывает только техническую сторону выгрузки.

Можно ли ограничиться экспортом только тех, кто сам его запросит?

Технически можно: вместо массового скрипта — форма запроса с ручной или полуавтоматической обработкой. Это снижает нагрузку на разработку за неделю, но требует, чтобы пользователи точно узнали о возможности запроса и успели её использовать до отключения.

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →