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

메일링 리스트의 표준 mbox 덤프를 가져오려고 했는데, “Process killed” 오류가 발생하고 있습니다. 보통 “indexing […]mbox” 단계에서 아주 오랜 시간이 지난 후에 문제가 생깁니다. 이 mbox 파일은 10년치 게시글이 있는 오픈소스 프로젝트에서 가져온 큰 파일입니다.

시도해 본 방법:

  • mbox 파일을 여러 조각으로 나누기. 이 방법은 부분적으로 효과가 있었으며 많은 게시글을 성공적으로 가져왔습니다. 하지만 현재 이 조각 중 하나를 인덱싱하는 단계에서 막혀 있습니다. 이 파일도 더 작은 조각으로 나누어 보았는데, 첫 번째 조각은 결국 가져오기가 완료되었고, 두 번째 조각은 현재 멈춘 것처럼 보입니다.

  • 서버에서 사용할 수 있는 메모리 증가. 인덱싱 중에 메모리 사용량이 서서히 증가하다가, 80 MB 크기의 mbox 파일 하나를 가져오려고 시도할 때 약 16 GB(전체 32 GB 중)에서 안정화됩니다:

이 시간 동안 CPU 1개가 계속 최대 용량으로 사용되고 있습니다.

특정 게시글에서 멈추고 있는 것인지 확인하기 위해 디버그 출력의 상세 수준(verbosity)을 높이는 방법 등 조언을 주시면 감사하겠습니다. import 폴더 안의 index.db 파일은 약 800 MB입니다.

저는 Ruby에 익숙하지 않고 SQL도 자주 사용하지 않아서 무슨 일이 일어나고 있는지 파악하기가 어렵습니다. 또한 이 32 GB 서버는 비용이 많이 들기 때문에 조만간 4 GB로 다시 줄이고 싶습니다 :slight_smile:

도움 주시면 감사하겠습니다!

1개의 좋아요

해석기가 해당 mbox 파일의 특정 이메일에서 멈추는 것 같습니다. index.db는 SQLite 데이터베이스입니다. email 테이블을 확인하고, filename 열에서 mbox 파일명으로 필터링한 뒤 last_line_number 열의 가장 큰 값을 찾아보세요. mbox 파일 내에서 해당 행 번호 바로 다음 이메일에서 해석기가 멈추고 있을 가능성이 매우 높습니다.

3개의 좋아요

@gerhard, 감사합니다. 마지막에 성공적으로 인덱싱된 이메일과 바로 다음에 (추측컨대) 정지를 유발하는 이메일을 찾았습니다. 그런데 제 기준으로는 이 두 이메일에 특별히 이례적인 점이 없어 보입니다. 두 예시 이메일을 개인 메시지로 보내드려서 혹시 눈에 띄는 부분이 있는지 확인해 보아도 될까요?

네, 쪽지를 보내주세요. 그리고 mbox 파일에서 해당 이메일들을 삭제한 후, 인덱싱이 정상적으로 작동하는지 테스트해 보세요.

3개의 좋아요

감사합니다. 보내드렸습니다. 직접 DM을 보내지 못해서 팀 그룹 채팅으로 보냈습니다. 괜찮을 거라고 생각합니다. 이메일도 제거해 보고, 다음에 다시 멈출 때까지 얼마나 진행될 수 있을지 확인해 보겠습니다.

1개의 좋아요

메일을 보내주셔서 감사합니다. 특별히 이상한 점은 보이지 않았고, 제 테스트에서는 문제없이 작동했습니다.

아직 테스트는 하지 않았지만, 가져오기(import) 스크립트를 실행하기 전에 아래 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 자체에도 도움이 될 것입니다.

1개의 좋아요

저는 몇 달 동안 이 스크립트를 매일 실행해 왔습니다. 해당 사이트는 실제로 그룹으로 카테고리를 구독하도록 전환해야 하지만, 아직 그렇게 하지 못했습니다. 가끔 cookies.txt 파일을 새로 받아야 하는 경우가 있지만, 그 외에는 잘 작동합니다. 약 한 달 전쯤 뭔가 일이 일어나서 "이메일 주소를 볼 권한이 없습니다. 중단합니다."라는 오류가 발생하기 시작했습니다. 제가 . . . 뭔가 . . . 조치를 취했고, 다시 작동하기 시작했습니다. 그런데 일주일 전쯤 다시 같은 문제가 발생했고, 여러 브라우저/쿠키 플러그인으로 쿠키를 다시 다운로드해 보았지만, 이메일 주소가 없는 버전의 게시물을 계속 받아 보고 있습니다. 웹 브라우저로 로그인하면 이메일 주소는 확인할 수 있습니다.

최근에 비슷한 문제를 겪은 분이 계신가요? 어떤 조치를 취해야 할지 아이디어가 있으신가요? 스크립트의 add_cookies 호출에 포함된 도메인을 이것저것 바꿔 보았지만, 도움이 되지 않았습니다.

1개의 좋아요

음, 다시 확인해 보니

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

이런 링크에는 예전에는 전체 이메일 주소가 포함되어 있었는데, 지금은 그렇지 않네요. 로그인한 상태에서 웹 인터페이스의 '정보’를 클릭하면 전체 이메일 주소를 볼 수 있다는 것을 확인했습니다. 하지만 동일한 웹 브라우저에서 스크래핑 스크립트가 접근하는 위의 URL을 직접 열면, 이메일 주소가 편집된(가려진) 상태로 데이터가 반환됩니다.

개인정보 보호 정책이 강화되었거나 그런 식의 변경이 있었을 것 같네요.

여기 또 다른 단서가 있습니다: 브라우저에서 해당 링크를 열면 정상적으로 동작하는데, 'cURL로 복사’한 curl 명령을 실행하면 이메일 주소를 가져오지 못합니다. 아, 그랬군요. 다른 브라우저로 시도해 보니 curl 명령이 정상적으로 동작했습니다. 스크립트가 이메일 주소를 가져오지 못하는 이유가 정확히 무엇인지 파악이 안 됩니다.

혹시 브라우저마다 다른 특정 동작을 수행하고 있는 건 아닐까요?

최근에 시도해 보지 않아서, 현재 스크래퍼가 처리하지 못하는 변경 사항이 생겼을 수도 있습니다.

@riking 님은 Google Takeout이 그룹 소유자를 위해 mbox 파일을 내보낸다는 사실을 알아냈으므로, 이를 확인해 볼 가치가 있는 옵션일 수 있습니다.

5개의 좋아요

감사합니다. 사실 일주일 전에는 두 번째 사이트에서는 작동했는데, 그 후 쿠키 파일을 한 번 더 업데이트하니 첫 번째 사이트의 데이터가 다운로드되었습니다. 하루 이틀 정도만 작동했던 것 같고, 지금은 다시 작동하지 않습니다. 두 사이트 모두 마찬가지입니다. 브라우저에서 전체 이메일 주소를 확인하고 해당 탭의 쿠키를 다운로드해 보았지만, 여전히 문제가 해결되지 않습니다.

takout를 확인해 보겠습니다. 편집: mbox 파일을 받으려면 단순히 소유자가 아니라 수퍼 관리자여야 하는 것 같습니다.

4개의 좋아요

mailman2 메일링 리스트(즉, options, members, moderators, private or public flags 등이 포함된 config.pck의 내용)을 discourse 카테고리로 변환하는 커맨드 라인 도구가 여기에서 사용할 수 있습니다: Client Challenge

1개의 좋아요

@gerhard 표준 설치 대신 개발용 설치를 사용할 때 이 지침을 어떻게 수정해야 하는지 아이디어가 있으신가요? 몇 개의 명령어만 사용하여 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 기반 개발 환경 설치로는 시도해 본 적이 없지만, 컨테이너 안에서 d/bundle install을 실행하기 전에 Gemfile에 "gem 'sqlite3'"을 추가하고 apt install -y libsqlite3-dev를 실행해야 할 것 같습니다.

그 후 bundle exec ruby ...를 실행하면 작동할 것입니다.

3개의 좋아요

@gerhard 친절한 도움 주셔서 감사합니다. 말씀하신 것이 gem 'sqlite3'일 것 같아 /src/Gemfile 끝에 추가하고, git clone부터 시작해 처음부터 다시 실행해 봤더니 잘 작동했습니다! 기록을 위해 제가 사용한 명령어들을 공유합니다 (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. 도커 셸에서

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

이 과정에서 유용한 출력들이 많이 나왔고, 마지막에는 다음과 같은 메시지가 표시되었습니다:

...
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 mbox 가져오기 도구는 Discourse를 처음 설치할 때에만 사용할 수 있나요, 아니면 다른 사용자가 Discourse를 사용 중인 경우에도 나중에 사용할 수 있나요? 다른 사용자가 Discourse를 사용 중인 상태에서 가져오기 도구를 사용하면 그들에게 어떤 부작용이 발생합니까?

1개의 좋아요

Google Groups에서 메시지를 스크래핑하도록 가져오기(importer)를 작동시키기 위해, /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 가져오기(import) 후 메시지를 확인해 보니 메시지 자체는 정상적으로 보이지만, staged: true 기본값을 사용했음에도 불구하고 스테이징(staged)된 사용자가 전혀 없습니다. 스테이징된 사용자가 있어야 할 것 같은데 말이죠. 출력 결과는 다음과 같습니다:

...
indexing replies and users

creating categories
        1 / 1 (100.0%)  [13440860 items/min]  
creating users

creating topics and posts
     7399 / 7399 (100.0%)  [1421 items/min]     
...

사용자 카운터도 함께 표시되어야 하는 건가요?

또한 staged: false로 실행해 보았지만 출력 결과는 동일했고, 메일링 리스트 사용자는 어떤 그룹에도 속해 있지 않았습니다. 실제로 어떤 데이터가 처리되고 있는지 확인하는 데 도움이 될 수 있도록, 가져오기 중인 수많은 .mbox 파일 중 하나를 첨부합니다:

2020-December.zip (49.5 KB)

기본값이 아닌 설정은 다음과 같은 항목만 추가했습니다:

tags:
  "Mne_analysis": "mne_analysis"

사용자들이 가입할 때 이전 게시물을 주장(claim)할 수 있도록 스테이징된 사용자로 표시되는 것이 좋겠습니다. 팁이나 아이디어가 있다면 알려주시면 감사하겠습니다!

1개의 좋아요

아마도 둘 다 받아들이는 게 맞지 않을까요?