Пользователь отвечает боту на третий вопрос анкеты ровно в ту минуту, когда вы переключаете сервер. Первые два ответа он дал час назад, и бот их помнил. Но где именно? Если в коде, то нигде. Код не помнит ничего: он описывает, что делать с ответом, а не то, на каком шаге сейчас находится конкретный человек. Эта память живёт в другом месте, и весь перенос сводится к тому, чтобы найти это место и аккуратно переставить его на новый сервер вместе с самим ботом.
Ниже разберём, из чего состоит «память» бота, как не потерять сообщения, которые пришли в момент переезда, почему у потока обновлений должен быть ровно один владелец и как убедиться, что анкета, начатая на старом сервере, спокойно закончится на новом.
Три вещи, которые выглядят как одна
Когда говорят «перенести бота», обычно имеют в виду копирование репозитория. Это самая простая часть: код лежит в системе контроля версий, зависимости зафиксированы, окружение воспроизводится. Куда интереснее то, что в репозиторий не попадает. Полезно разложить бота на три слоя и для каждого честно ответить, где он хранится.
- Код — логика переходов, тексты вопросов, правила валидации. Переносится клонированием и установкой зависимостей.
- Состояние — на каком шаге анкеты находится каждый пользователь, какие ответы уже собраны, какие файлы загружены, что ожидается дальше.
- Входящее событие — очередное обновление (update) от Telegram: сообщение, нажатие кнопки, пересланный файл. Оно существует отдельно от кода и от вашего состояния.
Ошибка переезда почти всегда в том, что эти слои смешивают. Код перенесли, состояние осталось на старом диске, а входящее событие пришло на сервер, который о состоянии ничего не знает. Результат знаком каждому, кто видел бота, внезапно забывшего собеседника: «Привет! Начнём сначала?»
Карта «код — состояние — входящее событие»
Прежде чем трогать серверы, составьте таблицу для своего бота. Она занимает полчаса и показывает все места, которые иначе обнаруживаются уже после переключения. Вот пример для бота, собирающего анкеты на подключение к услуге.
| Что | Слой | Где обычно хранится | Как попадёт на новый VPS |
|---|---|---|---|
| Порядок вопросов, тексты | Код | Репозиторий | Клонирование, установка зависимостей |
| Токен бота, ключи интеграций | Конфигурация | Переменные окружения или файл вне репозитория | Ручной перенос по защищённому каналу |
| Текущий шаг пользователя | Состояние | Память процесса, файл или БД | Зависит от места: из памяти не перенести вообще |
| Собранные ответы | Бизнес-данные | База данных | Дамп и восстановление либо подключение к общей БД |
| Загруженные документы | Вложения | Каталог на диске или идентификатор файла Telegram | Копирование каталога; идентификаторы хранятся в БД |
| Отложенные напоминания | Очередь | Планировщик, Redis, таблица задач | Перенос очереди или пересоздание задач из БД |
| Новое сообщение пользователя | Входящее событие | У Telegram, пока вы его не забрали | Забирает тот, кто сейчас владеет потоком |
Самая опасная строка — «память процесса». Если шаг анкеты хранится в словаре внутри программы, то при остановке он исчезает, и никакой дамп базы его не спасёт. Для небольших ботов это частая ситуация: удобные встроенные хранилища состояний в памяти работают прекрасно, пока процесс жив. Тут есть два пути. Либо до переезда перевести состояние в постоянное хранилище (файл, SQLite, PostgreSQL, Redis с сохранением на диск) и перенести его, либо принять потерю и заранее решить, как пользователь узнает, что анкету нужно продолжить с начала. Второй путь честнее придумать заранее, чем объяснять постфактум.
Вложения и то, что на них ссылается
Файлы, которые пользователь присылал в анкету, обычно хранятся двояко: сам файл на диске и его идентификатор в базе. Если вы перенесли базу, но забыли каталог, анкета формально цела, а документ по ссылке не открывается. Проверьте это сопоставление явно: выберите три незавершённые анкеты с вложениями и убедитесь, что файлы из них находятся на новом диске по тем же путям, которые записаны в базе. Если пути абсолютные и содержат имя старого пользователя системы, их придётся либо воссоздать, либо переписать.
Очередь и фоновые действия
Фоновые действия — это всё, что бот делает не в ответ на сообщение: напоминание через сутки «вы не закончили анкету», отправка собранных данных в CRM, ночная выгрузка. У них свои состояния: запланировано, выполняется, выполнено. Особенно коварно «выполняется»: если задача была взята старым сервером в работу и сервер остановили, на новом она либо потеряется, либо выполнится дважды. Перед переездом полезно дать очереди опустеть или хотя бы остановить взятие новых задач и дождаться завершения текущих. Всё, что остаётся запланированным, должно переехать вместе с хранилищем очереди либо воссоздаваться из бизнес-данных, например из таблицы, где у каждой анкеты записано время последнего действия.
Здесь же вспомните про побочные эффекты наружу. Если задача «отправить анкету в CRM» могла отработать на старом сервере, но не успеть записать в вашу базу отметку «отправлено», на новом сервере она повторится. Защититься можно уникальным ключом анкеты на стороне получателя, если он такое поддерживает, либо собственной проверкой перед отправкой. Это проверка, которую нужно сделать для вашей интеграции отдельно; общего решения, работающего везде, не существует.
Какие сообщения ещё не пришли, а какие уже пришли, но не обработаны
Теперь о самом тонком: о событиях. Здесь легко перепутать две разные категории, а потери и дубли возникают как раз на границе между ними.
Первая категория — обновления, которые ваш бот ещё не получил. Они лежат у Telegram и ждут, пока вы их заберёте. Важная деталь: по описанию Bot API входящие обновления хранятся ограниченное время. Значит, длительная пауза между остановкой старого получателя и запуском нового — это не просто неудобство, а риск того, что часть сообщений просто исчезнет. Пока пользователи пишут редко, окно в несколько минут безопасно, а вот переезд, растянутый на ночь с возвратом «на следующий день», уже нет. Точный срок хранения смотрите в документации и планируйте окно переключения с запасом внутри него.
Вторая категория — обновления, которые бот принял, но не довёл до конца. Он получил сообщение, начал обработку, записал половину ответа и был остановлен. Telegram считает такое обновление доставленным, а ваша система — недообработанным. Из истории чата Telegram это не восстановить: история не заменяет резервную копию состояния приложения, и сбрасывать накопленные обновления «для чистоты» при обычном переносе нельзя без осознанного плана потерь. Единственная защита здесь — собственная дисциплина обработки: фиксировать идентификатор обновления и порядок записи так, чтобы повторная обработка не портила данные.
Практическое правило: обработчик должен быть безопасен к повтору. Если вы записываете ответ на вопрос номер три, повторная запись того же update_id не должна ни сдвигать пользователя на шаг дальше, ни дублировать ответ. Простейший приём — хранить в базе идентификатор последнего обработанного обновления для каждого чата и пропускать всё, что не новее. Это ваша защита, и она работает независимо от того, на каком сервере крутится бот.
Один поток — один владелец
Получать обновления можно двумя способами: опрашивать Telegram через getUpdates или принимать запросы на адрес вашего webhook. Они взаимоисключающие, о чём прямо сказано в той же документации. Пока установлен webhook, опрос работать не будет, и наоборот. Для переезда это одновременно и неприятность, и подарок.
Неприятность: если на старом сервере работает опрос, а вы запускаете новый тоже с опросом, оба начинают дёргать один и тот же поток. Каждый заберёт часть обновлений, и половина сообщений пользователя достанется одному серверу, половина другому. Анкета рассыплется: второй ответ обработан здесь, третий там, а состояние у каждого своё. Это худший сценарий, потому что внешне всё «работает», бот отвечает, просто невпопад.
Подарок: механизм взаимоисключения можно использовать как предохранитель. Webhook, установленный на новый сервер, автоматически отключает опрос на старом. Если же вы переезжаете между webhook-адресами, смена адреса означает, что новые обновления сразу идут на новый сервер, а старый просто перестаёт их получать.
Порядок действий при опросе
Если бот работает на long polling, последовательность такая:
- Подготовьте новый VPS полностью: код, конфигурация, база, вложения, очередь. Не запускайте получение обновлений.
- На старом сервере остановите опрос и дайте обработчику закончить текущие обновления. Не убивайте процесс посреди записи.
- Сделайте финальную синхронизацию состояния: дамп базы, копия каталога вложений, состояние очереди.
- Восстановите данные на новом сервере и запустите бота.
- Убедитесь, что старый процесс действительно не работает и не перезапустится сам из-за службы автозапуска.
Пятый пункт кажется очевидным, но именно он чаще всего подводит. Служба с автоперезапуском или контейнер с политикой restart спокойно оживят остановленного «вручную» бота, и вы получите двух конкурирующих получателей. Отключайте автозапуск, а не просто завершайте процесс.
Порядок действий с webhook
При webhook картина проще: пока адрес указывает на старый сервер, туда и приходят обновления. Новый сервер можно полностью подготовить и проверить на своём адресе, не затрагивая рабочий поток. Переключение — это смена адреса webhook. Но и здесь есть окно: между остановкой старого приёмника и появлением нового ответчика Telegram будет получать ошибки доставки. Практичнее сначала поднять новый приёмник и убедиться, что он отвечает, и только затем менять адрес, чтобы окно без владельца сократилось до секунд. В идеале старый сервер ещё какое-то время остаётся включённым, чтобы дообработать то, что успел принять.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.
Offset: как бот говорит «это я уже получил»
При опросе центральную роль играет параметр offset. Как объясняет FAQ по long polling, он подтверждает получение обновлений, а неподтверждённые могут прийти снова. Поэтому, если бот получает одно и то же обновление снова и снова, дело, как правило, в том, что offset не передаётся или передаётся неверно.
Для переезда отсюда вытекают два практических следствия.
Первое: offset обычно держится в памяти библиотеки, а значит, при остановке сервера теряется. На новом сервере опрос начнётся без него, и Telegram отдаст все ещё неподтверждённые обновления, в том числе те, которые старый сервер уже обработал, но не успел подтвердить. Это не катастрофа, а как раз повод для идемпотентной обработки, описанной выше. Если же вы хотите минимизировать повторы, сохраняйте последний обработанный update_id и при старте нового процесса передавайте значение на единицу больше.
Второе: не стоит «чистить» очередь, чтобы избавиться от повторов. Легко соблазниться перескочить offset вперёд и выбросить всё накопленное, но вместе с повторами исчезнут и настоящие, ещё никем не прочитанные сообщения пользователей. Если потери всё же допустимы (скажем, бот был выключен неделю и накопился мусор), решение должно быть принято сознательно и записано, а не получено как побочный эффект.
Пример: анкета, прерванная на третьем вопросе
Разберём условный сценарий. Бот собирает заявку из пяти вопросов: имя, город, тип услуги, удобное время, комментарий. Состояние хранится в таблице sessions, где у каждого чата есть поля текущего шага и собранных ответов, а напоминания живут в таблице задач. Числа и названия здесь выдуманы для наглядности.
В момент переключения в базе три незавершённые анкеты:
- чат A: шаг 3, имя и город записаны;
- чат B: шаг 5, ждём комментарий, есть вложение;
- чат C: шаг 2, запланировано напоминание на завтра.
Как выглядит перенос:
- На новом VPS поднимаем базу и каталог вложений, бота не запускаем.
- Останавливаем старого бота командой остановки службы и отключаем её автозапуск. Фиксируем время.
- Делаем дамп таблиц и копируем каталог вложений. Проверяем, что в дампе те же три сессии с теми же шагами.
- Восстанавливаем данные на новом сервере и запускаем бота.
- Проверяем таблицу задач: напоминание чата C на месте и не помечено выполненным.
Теперь представим худшее: пока шёл дамп, пользователь A ответил на третий вопрос. Старый бот уже остановлен, поэтому обновление осталось у Telegram. Новый бот при запуске заберёт его, прочитает шаг 3 из восстановленной сессии, запишет тип услуги и перейдёт к четвёртому вопросу. Никто не потерян. Если же ответ успел дойти до старого бота и тот записал его, но дамп сделан раньше этой записи, получится рассинхрон: Telegram считает ответ доставленным, а в новой базе его нет. Поэтому дамп всегда делается строго после остановки получателя, а не «заранее, чтобы быстрее».
Критерии переключения и условия возврата
Репетиция в вакууме мало что даёт, куда полезнее иметь заранее выбранные условия, при которых вы считаете переезд удавшимся, и условия, при которых возвращаетесь на старое место. Для бота с диалогами подойдут такие признаки.
Переезд успешен, если одновременно выполняется следующее:
- контрольная анкета, начатая до остановки, продолжается с того же шага и завершается;
- в логах нового сервера нет повторной обработки уже закрытых обновлений либо она безвредна;
- напоминание, запланированное на старом сервере, сработало ровно один раз;
- вложение из незавершённой анкеты открывается по записанному пути;
- старый получатель действительно молчит: его логи не фиксируют новых обновлений.
Возвращаемся, если новый сервер не справляется с состоянием: пользователи получают предложение начать заново, шаги перепутаны, записи дублируются. В этом случае старый сервер должен быть сохранён нетронутым на период наблюдения. Но возврат тоже требует соблюдения правила одного владельца: сначала останавливаем новый получатель, затем запускаем старый, и данные, накопленные на новом сервере за время его работы, нужно вернуть назад, иначе вы потеряете то, что пользователи написали после переключения. Именно поэтому вариант «просто включить старый обратно» верен только пока новых записей почти нет.
Что даёт новый сервер для такой работы
Для переезда с сохранением диалогов от сервера нужно немногое, но конкретное: возможность поднять второй экземпляр рядом со старым, не останавливая рабочий, и достаточно диска, чтобы держать копии базы и вложений. Тут полезен root-доступ: вы сами управляете службами, автозапуском и расписанием, а это прямо влияет на пятый пункт нашего порядка действий. На VPS MAATRIX заявлены NVMe-диски, процессоры AMD EPYC и root-доступ; расположение можно выбрать из списка в публичном предложении. Ресурсы тарифов (от 1 vCPU, 2 ГБ RAM и 40 ГБ NVMe у Ba до 16 vCPU, 32 ГБ и 320 ГБ у Djed) показывают размер виртуальной машины, а не гарантированную ёмкость под определённое число пользователей, так что нагрузку бота стоит оценивать по его собственным показателям. Для небольшого бота-анкеты обычно хватает младших конфигураций, но это решается замером, а не догадкой. Если вы выбираете сервер с нуля, пригодится материал про выбор VPS для Telegram-бота.
Резервные копии при этом остаются вашей ответственностью: автоматические копии для всех тарифов в публичном предложении не заявлены, поэтому дамп перед переездом и копия каталога вложений делаются вами и проверяются восстановлением.
Контрольный диалог как последняя проверка
Последний этап переезда — не пассивное наблюдение за логами, а живое прохождение анкеты по границе переключения. Подготовьте тестовый чат и сделайте так: начните анкету на старом сервере, ответьте на два вопроса, остановите старого бота, выполните перенос по порядку, запустите нового и ответьте на третий вопрос. Если бот продолжил с третьего шага, а не предложил начать заново, главное условие выполнено.
Затем доведите анкету до конца и проверьте всё, что она запускает: запись в базе, отправку во внешнюю систему, уведомление менеджеру. Отдельно проверьте напоминание: создайте анкету, оставьте её незавершённой, дождитесь (или временно сократите) срок и убедитесь, что оно пришло один раз. Фоновые действия ломаются тише всего, и их отсутствие замечают позже всех.
Пока старый сервер ещё жив, а новый уже работает, у вас есть редкая возможность сравнить: открыть обе базы и сопоставить состояния. Если расхождений нет, а в логах нет конкурирующих обращений за обновлениями, старый сервер можно выводить из эксплуатации. Но не торопитесь: держите его выключенным, но не удалённым, пока не пройдёт срок самой длинной отложенной задачи, потому что только тогда проявятся ошибки, спрятанные в расписании.
А тот самый пользователь, который ответил на третий вопрос во время переезда? Его сообщение дождалось у Telegram, пока новый бот не занял место получателя, затем нашло в восстановленной базе его сессию на третьем шаге, записало ответ рядом с двумя прежними и задало четвёртый вопрос так, будто никакого переезда не было. Для человека по ту сторону экрана это и есть правильный результат: анкета продолжилась с того же места, а сервер, на котором она живёт, его не касается.