Migrate a mailing list to Discourse (mbox, Listserv, Google Groups, etc)

Я пытаюсь импортировать стандартный дамп mbox из рассылки, но сталкиваюсь с ошибкой «Process killed» (процесс убит), обычно после длительного выполнения шага «indexing […]mbox». Это большой файл mbox от проекта с открытым исходным кодом, содержащий десять лет переписки.

Что я уже пробовал:

  • Разделение файла mbox на части. Это частично сработало: многие сообщения были успешно импортированы, но теперь я застрял на этапе индексации одной из этих частей. Я попытался разделить и этот файл на части: первая часть в итоге импортировалась, а вторая, похоже, зависла.

  • Увеличение доступной памяти на сервере. Использование памяти медленно растёт во время индексации и в настоящее время стабилизируется на уровне около 16 ГБ (из 32 ГБ) при попытке импорта одной из этих частей — файла mbox объёмом 80 МБ:

В это время один из процессоров постоянно работает на максимуме.

Буду очень признателен за любые советы, особенно по увеличению детализации отладочного вывода, если, возможно, процесс застрял на каком-то конкретном сообщении. Файл index.db в папке import занимает около 800 МБ.

Я новичок в Ruby и нечасто работаю с SQL, поэтому мне трудно понять, что происходит. Кроме того, этот сервер с 32 ГБ памяти дорог, поэтому я хотел бы вскоре уменьшить его конфигурацию до 4 ГБ :slight_smile:

Спасибо за любую помощь!

1 лайк

Похоже, что анализатор зависает на одном конкретном письме в этом mbox-файле. index.db — это база данных SQLite. Посмотрите таблицу email, отфильтруйте записи по имени mbox-файла в столбце filename и найдите максимальное значение в столбце last_line_number. Скорее всего, анализатор зависает на следующем письме после этой строки внутри mbox-файла.

3 лайка

Большое спасибо, @gerhard. Мне удалось определить последнее успешно проиндексированное письмо и следующее за ним (я предполагаю, что именно оно вызывает зависание). Однако, на мой взгляд, в этих письмах нет ничего необычного. Можно ли мне отправить вам эти два примера писем в личные сообщения, чтобы посмотреть, бросится ли что-то в глаза?

Конечно, вы можете отправить мне личное сообщение. Попробуйте также удалить эти письма из файла mbox и проверить, работает ли индексация.

3 лайка

Спасибо, отправил. Я не смог написать вам напрямую, поэтому отправил сообщение через групповой чат команды, надеюсь, это нормально. Я попробую также удалить email и посмотрим, насколько далеко мы продвинемся до следующего зависания.

1 лайк

Спасибо за письма. Я не заметил ничего необычного, и в моих тестах всё работало без проблем.

Это не протестировано, но вы можете попробовать применить следующий патч git перед запуском скрипта импорта. Он добавляет тайм-аут в 60 секунд для парсинга письма. Это может помочь вам найти виновника и двигаться дальше, если проблема затрагивает только несколько сообщений.

From 92efb4fc68724cfa20d5de48ba33b99c126a3a08 Mon Sep 17 00:00:00 2001
From: Gerhard Schlager
Date: Fri, 2 Oct 2020 17:27:39 +0200
Subject: [PATCH] Add timeout for parsing email in mbox importer

---
 script/import_scripts/mbox/support/indexer.rb | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/script/import_scripts/mbox/support/indexer.rb b/script/import_scripts/mbox/support/indexer.rb
index dc6e092c29..01523dad13 100644
--- a/script/import_scripts/mbox/support/indexer.rb
+++ b/script/import_scripts/mbox/support/indexer.rb
@@ -65,11 +65,15 @@ module ImportScripts::Mbox
     def index_emails(directory, category_name)
       all_messages(directory, category_name) do |receiver, filename, opts|
         begin
-          msg_id = receiver.message_id
-          parsed_email = receiver.mail
-          from_email, from_display_name = receiver.parse_from_field(parsed_email)
-          body, elided, format = receiver.select_body
-          reply_message_ids = extract_reply_message_ids(parsed_email)
+          msg_id = parsed_email = from_email = from_display_name = body = elided = format = reply_message_ids = nil
+
+          Timeout.timeout(60) do
+            msg_id = receiver.message_id
+            parsed_email = receiver.mail
+            from_email, from_display_name = receiver.parse_from_field(parsed_email)
+            body, elided, format = receiver.select_body
+            reply_message_ids = extract_reply_message_ids(parsed_email)
+          end
 
           email = {
             msg_id: msg_id,
-- 
2.28.0
3 лайка

Большое спасибо, @gerhard, ваш патч работает как по маслу. Для моих целей, думаю, пропуск ошибочных сообщений приемлем, поскольку их немного, однако теперь у нас есть дополнительная информация, которая может помочь решить проблему или сделать скрипт импортера более надежным:

Не удалось проиндексировать сообщение в /shared/import/data/lammps-users/chunk_10.mbox на строках 726814-729353
Истекло время выполнения
["/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5.rb:243:in `escape_text'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5.rb:214:in `serialize_node_internal'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5/node.rb:58:in `write_to'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node.rb:699:in `serialize'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node.rb:855:in `to_format'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node.rb:711:in `to_html'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5/node.rb:28:in `block in inner_html'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:238:in `block in each'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:237:in `upto'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:237:in `each'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5/node.rb:28:in `map'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogumbo-2.0.2/lib/nokogumbo/html5/node.rb:28:in `inner_html'",
"/var/www/discourse/lib/html_to_markdown.rb:74:in `block (2 levels) in hoist_line_breaks!'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:238:in `block in each'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:237:in `upto'",
"/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/nokogiri-1.10.10/lib/nokogiri/xml/node_set.rb:237:in `each'",
"/var/www/discourse/lib/html_to_markdown.rb:57:in `block in hoist_line_breaks!'",
"/var/www/discourse/lib/html_to_markdown.rb:54:in `loop'",
"/var/www/discourse/lib/html_to_markdown.rb:54:in `hoist_line_breaks!'",
"/var/www/discourse/lib/html_to_markdown.rb:16:in `initialize'",
"/var/www/discourse/lib/email/receiver.rb:387:in `new'",
"/var/www/discourse/lib/email/receiver.rb:387:in `select_body'",
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:74:in `block (2 levels) in index_emails'", 
"/usr/local/lib/ruby/2.6.0/timeout.rb:108:in `timeout'",
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:70:in `block in index_emails'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:139:in `block (2 levels) in all_messages'",
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:171:in `block in each_mail'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:190:in `block in each_line'",
 "/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:189:in `each_line'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:189:in `each_line'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:166:in `each_mail'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:132:in `block in all_messages'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:125:in `foreach'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:125:in `all_messages'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:66:in `index_emails'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:25:in `block in execute'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:22:in `each'", 
"/var/www/discourse/script/import_scripts/mbox/support/indexer.rb:22:in `execute'", 
"/var/www/discourse/script/import_scripts/mbox/importer.rb:43:in `index_messages'", 
"/var/www/discourse/script/import_scripts/mbox/importer.rb:27:in `execute'", 
"/var/www/discourse/script/import_scripts/base.rb:47:in `perform'", 
"script/import_scripts/mbox.rb:12:in `<module:Mbox>'", 
"script/import_scripts/mbox.rb:10:in `<module:ImportScripts>'", 
"script/import_scripts/mbox.rb:9:in `<main>'"]

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

4 лайка

Конечно, пожалуйста, отправьте мне сообщения, и я их изучу. Если обнаружится проблема, мы сможем её исправить — это улучшит не только импортер, но и сам Discourse, так как он использует тот же парсер для входящих писем.

1 лайк

Я запускаю этот скрипт ежедневно в течение последних нескольких месяцев для сайта, которому действительно нужно перейти на подписку категории на группу, но этого пока не сделано. Всё работает отлично, за исключением того, что время от времени мне приходится получать новый файл cookies.txt. Около месяца назад что-то произошло, и скрипт начал выдавать ошибку: «Похоже, у вас нет прав для просмотра адресов электронной почты. Прерывание». Я сделал… что-то… и всё снова заработало. Чуть более недели назад это повторилось, и я повторно скачал куки через несколько браузеров и плагинов для управления куки, но продолжаю получать версии постов без адресов электронной почты. Когда я вхожу в систему через веб-браузер, я вижу эти адреса.

У кого-нибудь ещё были подобные проблемы в последнее время? Есть ли какие-либо идеи, что можно сделать? Я пробовал экспериментировать с доменами в вызове add_cookies в скрипте, но это не помогло.

1 лайк

Что ж, я снова смотрю на это, и кажется, что ссылки вроде

 https://groups.google.com/forum/message/raw?msg=GROUP_NAME/THREAD_ID/POST_ID

раньше включали полные адреса электронной почты, но теперь нет. Я могу подтвердить, что когда я вхожу в систему, я могу нажать «О программе» и увидеть полные адреса электронной почты в веб-интерфейсе Google Groups, но если я перейду по указанной выше URL-адресу, который использует скрипт парсинга, в том же самом веб-браузере, то получу данные с скрытыми адресами электронной почты.

Мне кажется, они усилили защиту конфиденциальности или что-то в этом роде.

Вот ещё одна подсказка: я могу открыть эту ссылку в своём браузере, и она работает, но если я скопирую команду «Копировать как cURL», то команда curl не получает адреса электронной почты. Эх. Что ж, я попробовал в другом браузере, и команда curl сработала. Я не совсем понимаю, почему скрипт не получает адреса электронной почты.

Так что, возможно, есть какая-то другая особенность, зависящая от браузера, которую он использует?

Я недавно не пробовал, поэтому возможно, что произошли изменения, с которыми скрапер сейчас не справляется.

@riking заметил, что Google Takeout экспортирует файлы mbox для владельцев групп, так что это может быть вариантом, который стоит рассмотреть.

5 лайков

Спасибо. Ну, неделю назад это работало для второго сайта, а затем я ещё раз обновил файл cookie, и данные для первого сайта были успешно загружены. Похоже, это работало всего день-два, и теперь снова ничего не выходит — ни для одного из сайтов. Я вижу полный адрес электронной почты в браузере, скачиваю cookie для этой вкладки, но результата нет.

Я посмотрю на takout. РЕДАКТИРОВАНИЕ: Похоже, чтобы получить файл mbox, нужно быть супер-администратором, а не просто владельцем.

4 лайка

Здесь доступен инструмент командной строки для преобразования списка рассылки Mailman2 (т. е. содержимого config.pck с параметрами, участниками, модераторами, приватными или публичными флагами и т. д.) в категорию Discourse: Client Challenge

1 лайк

@gerhard, есть какие-то идеи, как адаптировать эти инструкции для использования dev-установки вместо стандартной установки? Мне кажется, я почти добился миграции listserv, используя всего несколько команд, но не могу заставить работать последний шаг, который, как я предполагаю, должен завершить процесс. Я пробовал оба варианта:

ruby /src/script/import_scripts/mbox.rb ~/import/settings.yml
bundle exec ruby /src/script/import_scripts/mbox.rb /home/discourse/import/settings.yml

Оба варианта не могут подтянуть все зависимости. Посмотрите здесь, чтобы увидеть полный набор использованных мной команд и ошибки. Есть какие-то идеи? Возможно, не хватает некоторых вызовов d/bundle?

Следующим шагом я попробую использовать Ubuntu VM и выполнить там «стандартную установку», но это кажется излишним, учитывая, что dev-установка в остальном работает вполне хорошо.

Я полный новичок в discourse (и в ruby, и в основном в docker), так что извините, если это очевидно или (что хуже) неактуально!

3 лайка

Похоже, вы уже разобрались с большей частью.

Я никогда не пробовал это с установкой для разработки на основе Docker, но, думаю, вам нужно добавить "gem 'sqlite3'" в Gemfile и выполнить apt install -y libsqlite3-dev внутри контейнера перед запуском d/bundle install.

После этого bundle exec ruby ... должно заработать.

3 лайка

@gerhard спасибо за мягкое напоминание — я запустил процесс заново с нуля (начиная с git clone), добавив gem 'sqlite3' в конец файла /src/Gemfile, так как предположил, что вы имели в виду именно это, и всё заработало! Для справки ниже приведены инструкции, которые я использовал (для списка рассылки mne_analysis):

1. На хосте Ubuntu

git clone https://github.com/discourse/discourse.git
cd discourse
d/boot_dev --init
d/rails db:migrate RAILS_ENV=development
d/shell
vim /src/Gemfile  # добавить gem 'sqlite3' в конец
exit
d/bundle

2. В оболочке Docker

sudo mkdir -p /shared/import/data
sudo chown -R discourse:discourse /shared/import
wget -r -l1 --no-parent --no-directories "https://mail.nmr.mgh.harvard.edu/pipermail//mne_analysis/" -P /shared/import/data/mne_analysis -A "*-*.txt.gz"
rm /shared/import/data/mne_analysis/robots.txt.tmp
gzip -d /shared/import/data/mne_analysis/*.txt.gz
wget https://gist.githubusercontent.com/larsoner/940cd6c7100b87c4c5668cb0bc540afb/raw/9e78513620d11355ad0e10f4a2470996c26ebc8c/mailmanToMBox.py -O ~/mailmanToMBox.py
python3 ~/mailmanToMBox.py /shared/import/data/mne_analysis/
rm /shared/import/data/mne_analysis/*.txt
sudo apt install -y libsqlite3-dev  # для меня без изменений

# проверка результатов
cat /shared/import/data/mne_analysis/*.mbox > ~/all.mbox
sudo apt install -y procmail
mkdir -p ~/split
export FILENO=0000
formail -ds sh -c 'cat > ~/split/msg.$FILENO' < ~/all.mbox
rm -rf ~/split ~/all.mbox

# настройки
wget https://raw.githubusercontent.com/discourse/discourse/master/script/import_scripts/mbox/settings.yml -O /shared/import/settings.yml

# запуск
cd /src
bundle exec ruby script/import_scripts/mbox.rb /shared/import/settings.yml

Это выдало много информативных сообщений, а в конце:

...
Обновление рекомендуемых тем в категориях
        5 / 5 (100.0%)  [6890 элементов/мин]   ]  
Сброс счётчиков тем


Готово (00ч 06мин 21сек)

Затем выход и на хосте Ubuntu:

d/unicorn &
google-chrome http://0.0.0.0:9292

Готово!

Скорее всего, я немного подправлю настройки, чтобы убрать префикс [Mne_analysis], но я в восторге, что всё работает так хорошо уже сейчас!

4 лайка

@gerhard, можно ли использовать ваш импортер mbox только при первоначальной установке Discourse или его можно применить позже, когда другие пользователи уже работают в Discourse? Если импортер будет использован, пока Discourse активно используется другими, увидят ли они какие-либо побочные эффекты?

1 лайк

Чтобы импортер мог извлекать сообщения из Google Groups, мне пришлось отменить это изменение в файле /script/import_scripts/google_groups.rb
https://review.discourse.org/t/fix-google-groups-import-changed-login-url-9432/10615
Я вернул строку

    wait_for_url { |url| url.start_with?("https://accounts.google.com") }

обратно к

    wait_for_url { |url| url.start_with?("https://myaccount.google.com") }

В противном случае я каждый раз получал бы следующее сообщение:

Вход в систему...
Ошибка входа. Пожалуйста, проверьте содержимое вашего файла cookies.txt
6 лайков

@gerhard Я заметил, что после импорта, хотя сообщения выглядят нормально, нет ни одного пользователя в статусе «staged», хотя, казалось бы, они должны быть (я использовал значение по умолчанию staged: true). Вывод выглядит так:

...
индексирование ответов и пользователей

создание категорий
        1 / 1 (100.0%)  [13440860 элементов/мин]  
создание пользователей

создание тем и сообщений
     7399 / 7399 (100.0%)  [1421 элемент/мин]     
...

Должен ли отображаться также счётчик пользователей?

Я также пробовал запускать с параметром staged: false, и вывод был таким же, при этом ни один пользователь из рассылки не попал ни в одну группу. На случай, если поможет увидеть, что именно обрабатывается, вот один из многих импортируемых файлов .mbox:

2020-December.zip (49.5 KB)

Единственным нестандартным параметром было добавление:

tags:
  "Mne_analysis": "mne_analysis"

Было бы здорово, если бы эти пользователи отображались в статусе «staged», чтобы они могли заявить о своих старых сообщениях при регистрации. Буду признателен за любые советы или идеи!

1 лайк

Наверное, оно должно принимать оба варианта?