Slackalypsis. Часть 2

от автора

В первой части своей истории я рассказал, что происходило в Контуре в момент, когда многие российские ИТ-компании попали в санкционные листы, как мы писали свой велосипед экспорт из Slack, и о том, как мы начали переезд в Mattermost. Во второй части, как и обещал, я расскажу вам самые болезненные и интересные грабли.

Глава 5. Грабли

Напомню, что мы хотели начать историю с импортом данных с эмодзи. По документации они выглядели очень просто — всего два поля! А еще они были нужны для импорта постов, а стандартный механизм вообще не умел их переливать. Следом по плану были каналы — они тоже довольно простые и нужные для импорта пользователей. Дальше шли пользователи и посты без вложений. И в самом конце мы учились импортировать посты уже вместе с вложениями.

Эмодзи

Написали первые двадцать строчек кода в наш конвертер, запустили импорт и сразу словили ошибку: Emoji.IsValid: Name must be 1 to 64 lowercase alphanumeric characters. Оказалось, что ограничения на названия эмодзи в Slack и в Mattermost очень даже разные. И, помимо ограничений на название эмодзи, при импорте были недопустимы кириллица и пробелы в пути к файлу с картинкой. И, конечно, в ошибке нет ни слова про то, какая именно эмодзи не прошла валидацию. А у нас их было больше трех тысяч! 

Поскольку Mattermost — это опенсорс, мы подсмотрели в код, написали валидацию на стороне нашего конвертера и стали отбрасывать те эмодзи, которые мы точно не могли импортировать. Всего их было около 100 штук и после ручного маппинга мы не смогли импортировать всего 8 штук — те, что использовались за 10 лет нашей истории со Slack меньше 20 раз. 

Каналы

Первое, с чем мы столкнулись – каналы в Slack и Mattermost тоже отличаются. В Slack создание канала выглядело так:

Создание канала в Slack. Шаг 1

Создание канала в Slack. Шаг 1
Создание канала в Slack. Шаг 2

Создание канала в Slack. Шаг 2

На первом шаге задаем имя канала, например, #csharp. На втором шаге задаем тип канала. И всё, канал создан. Остается только добавить участников и начать святые войны за код стайл.

А вот так выглядело окошко создания канала в Mattermost:

У канала тоже есть имя, правда по факту оно называется DISPLAY_NAME. При этом в ссылке еще будет NAME. И когда вы автоматикой ходите в API, вам нужен NAME. А когда вы в интерфейсе что-то делаете, вы обычно видите DISPLAY_NAME

Фанфакт: DISPLAY_NAME — неуникальный, а вот NAME — очень даже!

Получается, что в Slack у нас было одно поле, а теперь нам нужно два. Ограничения на поле NAME, которые выставлял Mattermost, опять же отличались от Slack. Например, нельзя было использовать кириллицу. А у нас было довольно много таких каналов!

Решили транслитерировать. Перегоняли название канала известным способом – через словарик по ГОСТ-7.79-2000. Но опять похожая ошибка: Channel.IsValid: Name must be 1 to 64 lowercase alphanumeric characters

Но у нас уже была регулярка, которую мы написали для валидации имени эмодзи: ^[a-zA-Z0-9+_-]{1,64}$. А раз ошибка та же, решили им воспользоваться. Запускаем и… Ничего не работает 😭 Пришлось снова смотреть вкод — нашли другую регулярку: ^[a-z0-9]+([a-z-_0-9]+|(__)?)[a-z0-9]*$

Получается текст ошибки нам немного наврал. Кроме сказанного в ошибке название канала не может начинаться и заканчиваться на землю. Штош. Поправили валидацию в конвертере, запустили, но импорт все равно упал. С какими конкретно каналами была проблема – снова непонятно, в ошибке этого не сказано, и в логах было пусто. 

Но, как можно заметить, мы в спешке забыли про ограничение по длине — в нашей первой регулярке оно было, а в коде сервера, как оказалось, отдельный if был написан. Окей, добавили, импорт все равно падает! Мы добавили проверку длины названия канала до транслитерирования, при этом некоторые русские буквы при транслитерировании превращаются в несколько латинских, например, «я»  превращается в «ya». Правим, заливаем, запускаем… ура? Не ура – импорт упал с ошибкой, что такой канал уже есть. 

А какой канал есть-то? 😡 Опять забыли сказать. 

Пытались вручную бинпоиском по архиву и логам импорта найти, какие каналы залились, а какие — нет. Тщетно. Загрузка каналов шла в несколько потоков, в логах все вразброс, и вручную найти канал, на котором все упало, было просто нереально. Пошли за помощью к команде наших DBA, чтобы они включили DEBUG-логи в Postgres, и позже нашли, что дело в канале #openapi-kontur

Дело было вот в чем. Еще в 2022 году, когда мы только запускали нашу экспортилку из Slack, этот канал был приватным. А спустя какое-то время его сделали публичным, и оказалось, что при этом у него поменялся channel_id. Изменяемый идентификатор объекта… хммхмх…  При написании экспортилки мы на такое не рассчитывали. Хоть тогда и ничего не взорвалось, де-факто мы выгрузили историю канала дважды. А вот при импорте в Mattermost канал идентифицируется по NAME, а не по channel_id. И действительно получилось два одинаковых канала с идентичной историей. Дедлайны уже горели, переписывать экспортилку ой как не хотелось, поэтому прикрутили дедупликацию прямо в конвертер. 

Еще наткнулись на то, что заголовок (он же топик) пары каналов был больше 1024 символов — Slack такое допускал, а Mattermost — нет.

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

Пользователи

В отличие от каналов, с пользователями было довольно просто. Но интересные грабли тоже нашлись.

Membership

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

Дубли учеток

Со времен печенегов в наше пространство в Slack можно было регистрироваться и входить с разных почтовых доменов — у нас их два. Кто-то регался через @kontur, забывал об этом и в следующий раз использовал @skbkontur. Какое-то время мы так жили, а потом нормализовали все почты под один домен, а дубли заблочили. Но нужно было мигрировать всю историю за 10 лет, так что с этим тоже намучались.

Username не совпадает с User из Email

При регистрации в Slack проставляется username из почты, но его можно было поменять, и так сделали где-то 20 наших сотрудников. В Slack с этим не было проблемы, так как наши сервисы доверяли только email, по которому происходила верификация. А в Mattermost для импорта нужен именно username, который в нашем случае совпадал с доменным логином. 

Решили обрезать домен у почты и считать это за username. Пишем код, запускаем и… отпал локальный админ (эээээм), из под которого мы запускали импорт.

Смотрим в Slack и находим такое:

Скорее всего, это наши коллеги из отдела безопасности что-то тестировали и, судя по всему, автоматика отработала правильно – пользователь оказался заблокирован. Но нам же надо было перелить вообще всех пользователей и, когда мы запустили импорт, этот пользователь влился в нашего локального админа. Пришлось прикрутить валидацию и отбрасывать учетки с подобными почтами в отдельный список. Но импортировать их все равно нужно было, поэтому потом мы принудительно поставили таким пользователям почту вида username@example.com

Такую почту в нашем Mattermost честным способом получить было никак нельзя, так как почта с нашим правильным доменом проставляется при аутентификации. Поменять почту уже у существующего пользователя тоже нельзя – мы ограничили это настройками на сервере. Но из-за таких почт мы опять словили ошибку: UpdateUser: The email you provided does not belong to an accepted domain. Please contact your administrator or sign up with a different email.. В итоге на время импорта эту настройку пришлось отключить.

Также в Slack была фича, с помощью которой можно было подключить пользователей из других пространств. У нас суммарно было 50 таких гостей, о которых при написании экспорта не подумали и они не экспортировались, а в истории от них были тысячи сообщений. Так что пришлось быстренько учиться экспортировать таких пользователей. 

Посты

Первое, с чем мы столкнулись – различия в языке разметки: блоки кода, листы и так далее. Те самые 53 часа, про которые я упоминал в первой статье, при конвертации с помощью slack-advanced-exporter уходили именно на конвертацию всех постов. Причем, как оказалось, тот конвертер был однопоточный. 

Вот пара примеров отличий в разметке:

Slack

Mattermost

<#C53UMKYKD|infra_support> 

~infra_support

<@U3W72BL12|dstarasov>

@dstarasov

<!subteam^SKW266Z5K|@sentinel_duty>

@sentinel_duty

<https://tech.kontur.ru| Технологии в Контуре>

[Технологии в Контуре]        (https://tech.kontur.ru)

<!channel>, <!everyone>, <!here|@here>

@all, @channel, @here

А еще Bold, Italic, Strikethrough, Inline Code, Snippet, Quote, List… Почти всё было отлично друг от друга. 

Решили не изобретать велосипед и подсмотрели в реализацию утилиты mmetl. Реализация была такая: составляем массив пар, где первым значением записываем регулярку с шаблоном «что меняем», а вторым значением новое значение. По сути, у нас было чуть больше, чем 10 тысяч таких пар с регулярками.

А еще на тот момент в базе было экспортировано 12 миллионов сообщений и КАЖДОЕ сообщение нужно было прогнать по всему набору регулярок. А раз mmetl был однопоточный, то это и занимало те самые 53 часа. Учитывая возникающие в процессе конвертации и импорта грабли, мы не могли себе это позволить, ведь на вообще весь проект было чуть меньше месяца, а тут двое суток на одну попытку… 

Решили запараллелить. Попросили у команды виртуализации самый жирный физический сервер, который могли себе позволить. Нашлась новая свободная железка с 24 Core/48Gb RAM/2 TiB Storage. На этой железке получилось уложить конвертацию в 40 минут – это уже прям хорошо.

Инженер может бесконечно смотреть на три вещи: как горит прод, течет память и работают машины

Инженер может бесконечно смотреть на три вещи: как горит прод, течет память и работают машины

Конвертацию постов наладили, а значит, пора делать импорт! Импорт был параллельный, но постов было много, и мы неплохо так всаживали диск у Postgres — иногда влетали в таймауты. Ретраев, к сожалению, не придумали, и импорт падал.

Когда затюнили базу и начали лить посты, сразу налетели на ошибки, связанные (внезапно) с эмодзи. Во-первых, нам попались эмодзи с модификатором. В Slack можно было выбирать цвет кожи у стандартных реакций с руками, например, с пальцем вверх: :thumbsup::skin-tone-4:. А в Mattermost такого не было (и нет до сих пор), поэтому пришлось добавить обработку и на лету отбрасывать все подобные модификаторы.

Во-вторых, список built-in реакций сильно отличался. Каких-то эмодзи вообще не было, у каких-то были другие названия. Повезло, что среди 6000+ эмодзи не совпали только ~100, так что я их смэтчил руками:

Посты + Вложения

Но самое интересно случилось, когда мы залили на тестовую площадку полную историю уже с вложениями. Оказалось, что в импортированной истории все картинки не показываются и не открываются. Полезли в наши архивы — не открывается. Скачали исходник с s3 — не открывается.

Я за 10 дней до блокировки нашего пространства в Slack

Я за 10 дней до блокировки нашего пространства в Slack

Когда я открыл «картинку» в блокноте, оказалось, что это html-страница. Наш код, который качал файлы, считал, что если пришел статус «код 200» и в теле ответа что-то есть, значит это тот файл, что мы запрашивали. Как оказалось — нет… И на тот момент у нас было 500Gb и где-то 800 тысяч таких вот вложений:

screenshot_20220215_145203.png

screenshot_20220215_145203.png

Было очень-очень нервно, но мы успели перекачать все файлы буквально за трое суток и ничего не потерялось.

Глава 6. Финал

За неделю до отключения Slack мы мигрировали каналы, пользователей, эмодзи — все то, без чего нельзя было вечером закрыть Slack, с утра открыть Mattermost и продолжить работу. С постами мы немного не уложились в дедлайн: полностью историю переписки мы залили только спустя неделю после отключения нашего пространства в Slack. Готовый архив для импорта со всеми нашими данными весил около 600GB, и у нас не получалось его залить за один раз, поэтому мы заливали историю пачками по первой букве имени канала. Это заняло чуть больше недели.

Всего мы перенесли из Slack в Mattermost около 3 тысяч Custom Emoji, 5 тысяч каналов, 7 тысяч пользователей, тысячу User Groups, 82 тысяч «членств» в каналах, 6,5 млн корневых сообщений, 7,5 млн реплаев и 813 тысяч вложений.

Также для упрощения процесса полного переезда мы сделали редиректилку ссылок вида https://<your-namespace>.slack.com/archives/<channel> и https://<your-namespace>.slack.com/archives/<channel>/<thread_ts> в наш Mattermost. Выглядит она примерно так:

Миграция данных из Slack в Mattermost оказалась сложным, но выполнимым процессом, который потребовал значительных усилий как в техническом, так и в организационном планах. Мы столкнулись с огромным множеством подводных камней, лишь о небольшой части которых я повествовал в статье. Однако благодаря слаженной работе небольшой команды нам удалось успешно завершить проект в сжатые сроки, а полная история переписки, сохраненные вложения и рабочие ссылки сделали переход всей нашей разработки менее болезненным. 

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


ссылка на оригинал статьи https://habr.com/ru/articles/895060/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *