Slackalypsis. Часть 1
Привет, Хабр. Я Денис Тарасов, руководитель отдела разработки инструментов эксплуатации в Контуре. У меня наконец дошли руки рассказать, как мы переехали всей командой разработки со Slack на Mattermost.
Если коротко, на момент апреля 2022 года у нас в Slack было около 4,5 тыс пользователей, больше половины из которых было активных, то есть хотя бы раз в неделю были онлайн. За месяц в среднем отправлялось 386 тысяч сообщений. Половина из них – это личные сообщения, по 25% приватные и публичные каналы. С начала 2016 года было загружено 3 миллиона файлов.

Глава 1. Что-то случилось
В начале марта 2022 многие российские ИТ-компании, такие как, Альфа Банк, Яндекс, Точка, Сбер, попали под санкции. Вечером команды разработки ушли домой, а с утра, когда пришли на работу, у них больше не было Slack.
В этом плане нам сильно повезло. Тем не менее Slack решили уйти из РФ и нам на почту пришло письмо о том, что нас переводят на бесплатный тариф. Бесплатно – это, конечно, хорошо, но в таком случае не отображается история и пропадают многие важные функции. Поэтому тогда пришло время делать бэкапы.

API для бэкапов на нашем тарифе не было, только выгрузка руками через окно экспорта – выбираешь период, жмешь Start Export
. И спустя время, в зависимости от выбранного периода, Slackbot присылал сообщение и бэкап можно было скачать, естественно, тоже руками.

Что включено в выгрузку:
-
Публичные сообщения
-
И в принципе на этом всё. Конец.
Зато в нем были алерты от Мойры – это наша система реалтайм-алёртинга. Кстати, опенсорс. Многие из команд делают канал с алертами публичным, поэтому он и попадал в экспорт. Мы жили в Slack ровно 10 лет и за это время у нас были, без преувеличения, миллионы уведомлений с алертами.
Что еще важного не было включено в выгрузку:
-
Приватные каналы и их история сообщений
-
Личная переписка
-
Логи
-
Кастомные эмодзи
-
Информация о пользователях
-
Аттачмены
-
UserGroups (платная фича, которую мы очень любили)
-
И еще куча всего
Мы решили провести эксперимент и попробовать залить в Mattermost один год истории из Slack. Официально рекомендуемым способом конвертация данных шла 3 дня и еще около суток шел импорт. По итогу собрали много артефактов импорта. Например, криво отображались некоторые сообщения, какие-то вообще не импортировались.
Главный вывод был понятен без слов:

Глава 2. Самописный Export
Посовещавшись, мы начали делать свою экспортилку данных.
В первой версии было то, что давала нам связка нескольких утилит, плюс еще пара очень важных для нас вещей. Мы добавили фильтрацию публичных каналов, то есть стали отбрасывать каналы, не имеющие особой исторической ценности, например, каналы с test
в названии или каналы с алертами. Следом научились экспортировать приватные каналы, но только те, куда добавили нашу автоматику – это ограничение тарифа Pro, которое мы не могли обойти. Выгрузка аттачментов и расширенной информации о пользователях также больше не требовала ручных действий, запуска сторонних утилит и происходила в фоне с минимальной задержкой.
Спустя некоторое время добавили еще экспорт кастомных эмодзи и UserGroups – эти фичи глубоко проникли во многие процессы и культуру общения, так что мы не могли их потерять.

Все работало, но мы не понимали, достоверна ли наша самописная выгрузка. В Slack ведь были публичные и приватные каналы: в каких-то я сам состоял и мог сверить полноту руками, а в каких-то нет. И мы помним, что все публичные каналы экспортировались стандартным механизмом, а вот наша выгрузка отбрасывала часть из них – получается, что тоже нормально не сравнить.
Поэтому мы создали демона-проверяльщика, который бегал за первым, брал случайные сообщения, файлы, эмодзи и проверял, что они попали в наше хранилище.
И так шло время, Slack нас не отключал. Поэтому весь 2022 и 2023 год я на всякий случай каждую неделю руками делал бэкап за неделю, а каждый месяц – за всю историю. В сентябре 2023 и вовсе решили, что риска отключения Slack нет, планов на какое-то дальнейшее развитие самописного экспорта тоже и понизили приоритет этого проекта.
Глава 3. Снова что-то случилось
И вот 24 апреля 2024 года Slack прислал письмо, что с 26 апреля все-таки нас переведут на бесплатный тариф, а через месяц наш аккаунт будет удален.
Dear Customer:
This letter is to inform you that Slack Technologies, LLC (“Slack”), in accordance with Section 11 of the Salesforce Main Services Agreement (the “MSA”), as supplemented by the Slack Supplemental Terms (together with the MSA, the “Agreement”, which replaced and superseded Slack’s Customer Terms of Service) between Slack and Kontur (“[CUSTOMER]“), https://kontur.slack.com/, will not renew Kontur’s current Slack subscriptions. Accordingly, on April 26, 2024, the applicable end date of Kontur’s current Slack subscription term, i) all Slack subscriptions will not automatically renew and will expire, ii) the Agreement will expire pursuant to Section 11 of the MSA, and iii) Kontur’s account will be downgraded to our free tier on April 26, 2024, access to Slack will terminate on May 26, 2024.
Regards,
Slack Technologies, LLC
И спустя мгновение Slackbot прислал в личные сообщения всем пользователям похожее уведомление. Поднялась невероятная буря – команды разработки навалили гору вопросов «А какой план действий?», «А что будет?» в наш суппорт, и на эти вопросы у нас не было ответов.
Но действовать как-то нужно было, и мы начали действовать. Первым делом надо было разобраться, что происходит, и поделиться этим с командами.

Мы написали письмо в поддержку Slack, но у них была ночь и они не отвечали.
Поэтому пошли писать в наш новостной канал, а сразу после начали проверять состояние экспорта: в хранилище было 9,4 миллиона сообщений, информация о 2300 каналах, 6500 пользователях и 511 000 аттачментов. И, зная суммарное количество проведенных проверок от демона, мы посчитали вероятность того, что у нас в базе выгружено всё. Получилось 99,78% сообщений и 99,73% файлов – неплохо.
Но… Спустя день Slack молча продлил нашу подписку. А следующей ночью наконец-то ответил на наши обращения.

В любом случае нас хотели отключить, так что настала пора. Объявили переезд и пошли думать.
План А
Как я уже писал выше, еще при первых симптомах блокировок в 2022 году, мы провели ряд экспериментов с экспортом-импортом данных из Slack в Mattermost. Не сказать, что они были удачными, и поэтому в итоге мы сделали свою экспортилку. А вот задачи импортировать куда-либо эти данные в тот момент не было – нас не блочили, ну мы и не стали тратить время на это.
Поэтому наш план был надежный, как швейцарские часы: сперва пойти известным и рекомендуемым документацией путем, а там поглядеть, что получится. У нас было ощущение, что за два года все могло поменяться в лучшую сторону, так как многие российские компании «съезжали» со Slack на Mattermost и могли законтрибьютить в общий тулинг.
Алгоритм был такой же, как и в экспериментах в 2022:
-
Через админку Slack’а делаем экспорт данных
-
С помощью опенсорсной утилиты slack-advanced-exporter догружаем список пользователей и аттачменты
-
Через специальную утилиту для конвертации данных в формат, который понимает Mattermost, называемую
mmetl
, конвертируем архив -
Через mmctl загружаем новый архив в Mattermost и запускаем импорт
-
Восхваляем Ктулху, приносим жертвы, беснуемся
-
Все получилось, радуемся
Но, если бы все прошло так гладко, то моей статьи тут бы не было, да?
Первые проблемы:
-
Оказалось, что среди экспортированных данных Email заполнен только примерно у 10% пользователей. Но для импорта e-mail является обязательным и для всех, у кого там было пусто, конвертер брал их юзернейм и добавлял
@example.com
.
В гайдах по Mattermost предлагается включать настройку Restrict new system and team members to specified email domains
, и мы, естественно, следовали этим рекомендациям, поэтому импорт целиком падал, когда доходил до первого такого пользователя.
-
В Slack максимальная длина пользовательского title была 249 символов, а в Mattermost – всего 64 символа. У нескольких наших пользователей был установлен слишком длинный Title, и из-за этого импорт тоже падал с ошибкой.
В тот момент решили не отказываться от стандартного механизма экспорта/импорта и договорились подскостылить: написали Post-export скрипт, в котором сопоставили email, подрезали длинные title и, cпустя пару попыток и пару дополнительных костылей, получилось залить историю за февраль. Но, когда мы попытались дозалить историю за три месяца, всё опять упало с ошибкой: «Такой пользователь уже есть». А какой конкретно пользователь – непонятно. Еще, экстраполируя время на конвертацию за один и за три месяца на полный размер, у нас получалось ~53 часа на одну попытку конвертации всей истории…

Мы решили добить известные проблемы импорта трехмесячного дампа и пока временно не обращать внимание на слона в комнате – допилили post-скрипт и, после всех его патчей, залили пользователей, историю и аттачменты за три месяца. Это было для нас большой победой, ведь мы подтвердили, что мы можем реализовать программу «минимум». Но вот реакции на сообщениях все так же не переливались, а значит пришло время придумывать нашему велосипеду второе колесо.
Глава 4. Самописный Import
Итак, к первым праздничным майским праздникам мы подошли с пониманием того, что надо делать свой процесс импорта, а точнее конвертации.
Так родился План Б:
-
Реализуем самописную тулу для конвертации данных из нашего хранилища в формат, который понимает Mattermost, прогоняем её, получаем архив
-
Через mmctl загружаем архив и запускаем импорт
-
Восхваляем Ктулху, приносим жертвы, беснуемся
-
Все получилось, радуемсяРешаем проблемы с конвертацией и повторяем заново, пока не получится
Формат импорта
Упомянутый выше архив — это zip-архив, в корне которого лежит файл в формате JSON Lines, еще его кратко называют jsonl. В этом файле записана информация о командах, каналах, пользователях, emoji и вся история переписки. Каждая строка – это отдельный json объект, в котором есть дискриминатор типа объекта – свойство type. Рядом с файлом в подпапках лежат аттачменты.
{ «type»: «version», «version»: 1 }
{ «type»: «channel», «channel»: { «team»: «TeamA», «name»: «channel_a1», … } }
{ «type»: «channel», «channel»: { «team»: «TeamA», «name»: «channel_a2», … } }
{ «type»: «channel», «channel»: { «team»: «TeamB», «name»: «channel_b1», … } }
{ «type»: «channel», «channel»: { «team»: «TeamB», «name»: «channel_b2», … } }
{ «type»: «user», «user»: { «username»: «user001», … } }
{ «type»: «user», «user»: { «username»: «user002», … } }
{ «type»: «user», «user»: { «username»: «user003», … } }
{ «type»: «post», «team»: { «team»: «TeamA», «name»: «channel_a1», «user»: «user001», … } }
{ «type»: «post», «team»: { «team»: «TeamA», «name»: «channel_a1», «user»: «user001», … } }
{ «type»: «post», «team»: { «team»: «TeamA», «name»: «channel_a1», «user»: «user001», … } }
…
Типы объектов
-
Version. Объект очень простой – по сути это well-known константа, скорее всего сделали для будущего расширения формата.
-
Team. В Контуре мы используем одну команду и та на момент импорта уже была создана, так что поэтому мы этим типом объекта при импорте не пользовались.
-
Emoji. Всего всего два свойства – название и путь к картинке в архиве.
-
Channel. Чуть посложнее – название команды, название канала и его display_name, тип канала (приватный или публичный) и еще header, который в Slack назывался topic.
-
User. Еще посложнее, так как внутри пользователя указываются команды, в которых он участвует, а внутри команды все каналы в которых пользователь состоит, в основном десятки штук.
-
Post. Не сложные, но ветвистые – название команды, название канала, имя пользователя, текст сообщения, включая реплаи, реакции и аттачменты. Реплаи – это посты, но попроще, хотя у реплаев тоже могут быть свои реакции и аттачменты. Аттачменты – это просто путь к файлу в архиве.
Начали мы с emoji, так как их было никак не перелить через стандартный экспорт, и у них всего два поля: название и файл. А еще они нужны для импорта постов и реакций. Дальше нам нужно было залить список каналов, потому что членство в канале импортируется вместе с пользователем. Затем пользователи и только потом – посты. Вообще их надо грузить вместе с аттачментами, но сперва мы хотели научиться переносить чисто историю. Поэтому финальный порядок заливки был такой:
-
Эмодзи
-
Каналы
-
Пользователи
-
Посты
-
Посты + Аттачменты
На этом казалось бы победном моменте закончу первую часть своего рассказа. А в следующей части подробно опишу грабли, на которые мы наступали, пока проходили по этому порядку заливки. Их было так много, что на это нужна отдельная статья.
Автор: ArXa1L