Mailing リストを Discourse へ移行する (mbox、Listserv、Google Groups など)

メーリングリストの標準的な mbox ダンプをインポートしようとしていますが、「プロセスが終了しました」というエラーに遭遇しています。特に「インデックス作成 […]mbox」のステップで長い時間がかかった後に発生します。これは、10 年分の投稿を含むオープンソースプロジェクトの大きな mbox ファイルからのものです。

私が試した対応策:

  • mbox ファイルをチャンクに分割しました。これは部分的に機能し、多くの投稿を正常にインポートしましたが、現在、これらのチャンクの一つのインデックス作成で詰まっています。そのファイルもさらにチャンクに分割してみましたが、最初のものは最終的にインポートされましたが、2 番目のものは現在フリーズしているようです。

  • サーバーの利用可能メモリを増やしました。インデックス作成中はメモリ使用量が徐々に増加し、現在、80 MB の mbox ファイルのチャンクの一つのインポート試行において、約 16 GB(32 GB のうち)で頭打ちになっています。

この間、1 つの CPU が常に最大負荷状態になっています。

特に、特定の投稿でフリーズしている可能性がある場合のデバッグ出力の詳細化など、ご助言をいただければ幸いです。import フォルダ内の index.db ファイルは約 800 MB です。

私は Ruby に慣れておらず、SQL も普段使いしていないため、何が起きているのか把握するのが難しい状況です。また、この 32 GB サーバーは高価なため、できるだけ早く 4 GB に縮小したいと考えています :slight_smile:

ご支援をよろしくお願いいたします!

「いいね!」 1

おそらく、その mbox 内の特定の 1 通のメールでパーサーがフリーズしているのでしょう。index.db は SQLite データベースです。email テーブルを確認し、filename 列で mbox ファイル名をフィルターして、last_line_number 列内の最大値を探してください。その行番号の次のメールでパーサーがフリーズしている可能性が非常に高いです。

「いいね!」 3

gerhard さん、どうもありがとうございます。正常にインデックスされた最後のメールと、その直後に続く(おそらくフリーズの原因となっていると思われる)メールを特定することができました。ただし、私にはこれらのメールに特別に目立った点は見当たりません。もしよろしければ、この 2 つの例となるメールをプライベートメッセージでお送りしてもよろしいでしょうか?何か目立つ点があるか確認させていただきたいのです。

もちろん、PMを送ってください。また、mboxファイルからそのメールを削除して、インデックス作成が機能するかテストしてみてください。

「いいね!」 3

ありがとうございます、送信しました。直接メッセージを送れなかったので、チームグループ経由で送りました。これで問題ないでしょうか。メールも削除して、またフリーズするまでどこまで進めるか試してみます。

「いいね!」 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 様、ありがとうございます。パッチが完璧に機能しています。私の目的では、不良メッセージをスキップするのは問題ないと思います。なぜなら、その数はわずかだからです。ただし、問題解決やインポートスクリプトの堅牢性向上に役立つ場合、追加の出力を以下に提供します。

Failed to index message in /shared/import/data/lammps-users/chunk_10.mbox at lines 726814-729353
execution expired
["/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 自体も改善できます。Discourse は受信メールの解析にも同じパーサーを使用しているためです。

「いいね!」 1

過去数ヶ月間、このスクリプトを毎日実行してきました。このサイトではカテゴリをグループに購読させる必要があるのですが、それがまだ行われていません。基本的には問題なく動作していますが、時々新しい cookies.txt ファイルを取得する必要があります。約 1 ヶ月前に何かが起こり、「メールアドレスを表示する権限がないようです。処理を中止します」というエラーが出るようになりました。何かを操作したら再び動作するようになりました。1 週間ほど前に再び同じ問題が発生し、複数のブラウザやクッキープラグインを使ってクッキーを再ダウンロードしましたが、依然としてメールアドレスが表示されないバージョンの投稿が取得されてしまいます。ブラウザでログインした状態ではメールアドレスを確認できるのですが。

最近、他の人も同様の問題に直面していますか?何か解決策のアイデアはありますか?スクリプト内の add_cookies 呼び出しに含まれるドメインをいろいろ試してみましたが、効果はありませんでした。

「いいね!」 1

さて、これを再度確認すると、以下のようなリンク

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

は以前は完全なメールアドレスを含んでいましたが、現在は含まれていません。ログインしている状態では、Google グループの Web インターフェース上で「About」をクリックすれば完全なメールアドレスが表示されることを確認できます。しかし、同じ Web ブラウザで上記の URL(スクレイピングスクリプトがアクセスしている URL)にアクセスすると、メールアドレスが伏せられたデータが取得されます。

おそらく、プライバシー保護の強化などが行われたのだと思います。

別の手がかりがあります:そのリンクをブラウザで開くと動作しますが、「コピーとして cURL」を取得して実行すると、curl コマンドではメールアドレスが取得できません。 ため息。さて、別のブラウザで試したところ、curl コマンドは動作しました。なぜスクリプトがメールアドレスを取得できないのか、いまいち分かりません。

もしかすると、ブラウザ固有の何らかの処理が関係しているのでしょうか?

最近試していないので、スクレイパーが現在処理できない変更が行われている可能性があります。

@riking は、Google Takeoutグループのオーナー向けに mbox ファイルをエクスポートすることを指摘しました。そのため、これも検討できる選択肢かもしれません。

「いいね!」 5

ありがとうございます。さて、1 週間前には別のサイトで一時的に機能し、その後クッキーファイルをもう一度更新したところ、最初のサイトのデータをダウンロードできました。しかし、それは 1〜2 日しか機能しなかったようで、現在では再び両方のサイトで動作していません。ブラウザで完全なメールアドレスを確認し、そのタブのクッキーをダウンロードしても、結果は得られません。

takout を確認してみます。編集:mbox ファイルを取得するには、所有者ではなくスーパー管理者である必要があるようです。

「いいね!」 4

mailman2 メーリングリスト(config.pck の内容、オプション、メンバー、モデレーター、非公開または公開フラグなど)を Discourse カテゴリに変換するコマンドラインツールは、こちらで利用可能です: Client Challenge

「いいね!」 1

@gerhard 標準的なインストールではなく、dev install を使用するようにこれらの手順を適応させる方法について、何かアイデアはありますか?私は数コマンドだけで 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 ベースの開発環境インストールで試したことはありませんが、Gemfilegem 'sqlite3' を追加し、コンテナ内で apt install -y libsqlite3-dev を実行してから bundle install を実行する必要があると思われます。

その後、bundle exec ruby ... が正常に動作するはずです。

「いいね!」 3

@gerhard 優しいフォローありがとうございます。git clone から完全にやり直し、/src/Gemfile の末尾に gem 'sqlite3' を追加しました(あなたが指していたのはこれだと推測したため)。これで動作しました!参考までに、私が使用した手順(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  # 私の場合は何もしませんでした(no-op)

# 結果を確認
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

これにより多くの情報出力があり、最後は以下のようになりました:

...
Updating featured topics in categories
        5 / 5 (100.0%)  [6890 items/min]   ]  
Resetting topic counters


Done (00h 06min 21sec)

その後終了し、Ubuntu ホスト上で:

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

完了です!

設定を調整して [Mne_analysis] のプレフィックスを削除するかもしれませんが、これほど早く動作していることに大喜びです!

「いいね!」 4

@gerhard Discourse の mbox インポータは、Discourse を初めてインストールする際のみ使用できるのでしょうか、それとも他のユーザーが Discourse を使い始めた後でも使用できるのでしょうか?もし Discourse が他のユーザーによって使用されている最中にインポータを実行した場合、彼らに何らかの副作用は現れますか?

「いいね!」 1

Google グループからメッセージをスクレイピングするためにインポーターが動作するようにするには、/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") }

に戻しました。

そうしなければ、毎回次のメッセージが表示されていました。

Logging in...
Failed to login. Please check the content of your cookies.txt
「いいね!」 6

@gerhard インポート後、メッセージは正常に見えるものの、staged: true(デフォルト)を使用しているにもかかわらず、ステージングされたユーザーが全く存在しないことに気づきました。本来はいるはずです。出力は以下の通りです:

...
返信とユーザーのインデックス作成

カテゴリの作成
        1 / 1 (100.0%)  [13440860 items/min]  
ユーザーの作成

トピックと投稿の作成
     7399 / 7399 (100.0%)  [1421 items/min]     
...

ユーザーカウンターも表示されるべきでしょうか?

また、staged: falseで実行してみましたが、同じ出力が表示され、メーリングリストのユーザーはどのグループにも所属していませんでした。実際に処理されている内容を確認できれば役立つと思い、インポート対象の .mbox ファイルの 1 つを以下に添付します:

2020-December.zip (49.5 KB)

デフォルト以外の設定は、以下を追加しただけです:

tags:
  "Mne_analysis": "mne_analysis"

これらのユーザーがステージング状態として表示され、サインアップ時に過去の投稿を主張できるようにできれば素晴らしいです。何かヒントやアイデアがあれば、ぜひお聞かせください!

「いいね!」 1

おそらく、その両方を受け付けるべきでしょう?