Ruby 멀티 CPU 스레딩

안녕하세요. 이전 게시물에서 언급했듯이, 저는 기존 사이트를 내리고 약 200만 개의 게시물이 포함된 프로덕션 데이터를 이전하기 전에 모든 해결책을 갖추기 위해 Drupal → Discourse 마이그레이션 테스트를 진행 중입니다. 지금까지 배운 점은, 3개의 vCPU 코어를 가진 상당히 빠른 VPS에서도 가져오기(import) 과정이 거의 48시간 정도나 걸린다는 것입니다. 그리고 나서 아마도 rake 태스크와/또는 rails c를 사용하여 몇 가지 정리를 해야 할 것이며, rake posts:rebake가 필요한 작업의 경우 추가로 약 20시간이 더 소요될 것으로 보입니다.

저는 Ruby 도구 체인(toolchain)의 기본 원리를 잘 이해하지 못합니다. 하지만 이 작업에 더 많은 CPU 코어를 투입하면 이러한 프로세스 중 하나라도 완료되는 데 걸리는 시간을 크게 줄일 수 있을까요? 예를 들어 bundle 명령어나 rake 명령어가 사용 가능한 CPU 사이에 작업을 분배할 수 있는가, 아니면 추가 코어가 주로 여러 사용자가 웹사이트에 동시 접속할 때 여러 프로세스를 병렬로 실행하는 데에만 유용한 것인가?

화제에서 약간 벗어난 이야기이지만, 같은 수의 게시글로 포럼 마이그레이션을 진행했을 때, 가져오기 스크립트를 수정하여 주제와 게시글의 1/100 또는 1/1000만 가져오도록 했습니다.

가져오기가 안정적인지, 조정이나 디버깅이 필요한지를 확인하는 더 빠른 방법입니다.

@Canapin 사실 그걸 언급해 주셔서 정말 감사합니다. 어떻게 하셨는지 정말 궁금합니다. 저도 같은 일을 해보고 싶었는데, 부분 가져오기를 하면 데이터베이스 불일치 문제가 생길 것 같아서 그 생각을 포기했거든요. 그래서 테스트용으로 스켈레톤 Drupal 테스트 포럼을 만들어서 진행했습니다. 하지만 가능하면 프로덕션 DB의 복사본을 테스트에 사용하고 싶습니다.

주된 우려는 결국 최종 프로덕션 마이그레이션에 관한 것입니다. 기존 포럼을 오프라인으로 두거나 최소한 읽기 전용으로 만들어야 하는데, 아무리 빨라도 최소 48시간의 다운타임이 필요해 보입니다. CPU 코어를 두 배로 늘리면 시간이 절반으로 줄어든다고 볼 수 있을까요?

복구에 오랜 시간이 소요되는 작업들은 실제로 멀티스레드로 처리됩니다. 다만, CPU를 2배로 늘린다고 해서 성능이 2배로 향상되는 경우는 거의 없습니다.

또 다른 점은, rake posts:rebake 작업과 포럼 자체의 콘텐츠 복구 및 최적화라는 무거운 작업은 포럼이 라이브 상태일 때에도 수행될 수 있다는 것입니다. 이렇게 하면 포럼을 오프라인 또는 읽기 전용으로 두어야 하는 시간을 줄이고, 다소 열화된 경험을 제공하면서도 운영을 유지할 수 있습니다.

제 추천은 다음과 같습니다. 먼저 테스트를 해보세요. 마이그레이션을 수행하고, 소요되는 시간과 모든 리베이크(rebake)가 적용되지 않은 상태에서 포럼이 어떻게 보이는지 확인해 보세요. 만약 그 상태가 충분히 괜찮다면, 마이그레이션이 포럼의 트래픽이 낮은 시간대에 끝나도록 조정하면, 많은 사용자의 불만 없이 4~10시간 정도의 마이그레이션 시간을 확보할 수 있습니다.

좋습니다. 이 옵션에 대해 저도 궁금해하고 있었는데, 확인해 주셔서 감사합니다.

불행히도 그걸 적어두지 않았고 잊어버렸어요… 하지만 코딩을 아신다면 그다지 어렵지 않을 거예요.
BATCH_SIZE와 offset 값을 조정해서 루프를 변경하고, 게시글 배치(batch)를 건너뛰게 하거나 그런 식으로 했을 수도 있어요…
지금 바로 다시 시도할 수는 없어요. 현재 가져오기(import)할 포럼이 없어서요. 하지만 꽤 유용하다고 생각하니까 다음에는 간단한 튜토리얼을 만들어볼게요.

두 가지를 언급하고 싶습니다.

  • 네, CPU는 중요합니다. 따라서 더 큰 VPS를 구비하고 rebaking 및 이미지 처리를 위해 여러 sidekiq 인스턴스를 실행하면 속도가 빨라집니다.
  • 가져오기가 완전히 완료되면 백업/복원을 수행하는 것이 항상 좋은 아이디어입니다. 이를 통해 더 나은 데이터베이스 성능을 얻을 수 있습니다.

이 두 가지를 결합하면: 가져오기를 위해 큰 VPS를 사용하고, 완료되면 백업과 복원을 통해 더 작은 프로덕션 VPS로 이전하면 됩니다.

일반적으로 가져오기 후에는 게시물을 다시 rebake할 필요가 없습니다.

답변해 주셔서 감사합니다, Richard. 그렇다면 이 중 어떤 것을 사용해야 하나요?

  • UNICORN_WORKERS
  • UNICORN_SIDEKIQS
  • DISCOURSE_SIDEKIQ_WORKERS

흥미롭네요. 이전에는 이런 권장 사항을 본 적이 없습니다. 이게 조각(fragmentation)을 줄이거나 그런 효과가 있는 건가요?

네, 처음에는 Postgres 콘솔에서 regexp_replace()를 사용하여 일부 [QUOTE] 문제와 Textile → Markdown 변환을 수정하고 모든 게시물을 다시 베이킹하려 했습니다. rake posts:remap 명령이 너무 느렸기 때문입니다. 하지만 Postgres가 사용하는 정규식 형식이 PCRE와 호환되지 않으며, 의도치 않은 이상 현상이 너무 많아 이를 신뢰하기 어렵다는 것을 발견했습니다. 그래서 수입 과정에서 Pandoc을 통해 게시물을 처리해 보려 합니다. 이렇게 하면 수입된 사이트를 보기 좋은 상태로 실행에 올려 놓고, 이모지 키워드 같은 작은 문제는 rake posts:remap으로 나중에 수정할 수 있을 것입니다.

  • UNICORN_SIDEKIQS → 프로세스 수 (기본값 1)
  • DISCOURSE_SIDEKIQ_WORKERS → 프로세스 내 스레드 수 (기본값 5)

단편화를 줄여줄 뿐만 아니라, 가져오기(import)로 인해 Postgres 통계가 왜곡되는 문제를 수정해 줍니다.

:+1:

저도 이전에 이 조언을 본 적이 없습니다. 만약 이것이 "항상 좋은 아이디어"라면, Pre-launch checklist after migrating from another platform? 에 추가해야 하지 않을까요?

몇 년 전에 샘이나 제프가 이 조언을 해준 것 같은데, 이제 찾을 수가 없어요. 아직도 좋은 아이디어인지, 그리고 그만한 가치가 있는지 한번 확인해 봐야겠어요 :wink:

혹시 가장 빠르게 가져오기(import) 스크립트를 다시 실행하여 데이터를 다시 가져오는 방법에 대한 팁을 공유해 주실 수 있나요? 가져오기 스크립트에서 텍스트 치환을 조정하고 있는데, 제대로 되지 않을 때마다 Discourse 데이터베이스를 삭제하고 ./launcher rebuild import를 실행해야 해서 꽤 시간이 걸립니다. 가져오기 스크립트를 수정한 후 처음부터 다시 시작하게 하고 싶습니다. (현재 사이트의 작은 골격 목업 데이터베이스를 사용 중이라 가져오기를 실행하는 데는 매우 빠릅니다.)

음. 이번에는 8개의 가상 코어와 16GB의 RAM을 갖춘 상당히 강력한 VPS에서 프로덕션 포럼 데이터를 다시 가져오는 것을 테스트하고 있습니다. 다음과 같이 설정했습니다:
UNICORN_SIDEKIQS=4
DISCOURSE_SIDEKIQ_WORKERS=20
UNICORN_WORKERS=16

이 설정으로 import_topics 단계에서는 모든 코어를 활용하지 않는 것 같습니다:

다만 user_import 단계에서는 CPU 그래프가 600% 이상(즉, 8개 코어 중 약 6개가 100% 사용)으로 고정되어 있다는 점이 흥미롭습니다.

또한 이 env 변수도 발견했습니다: RUBY_GLOBAL_METHOD_CACHE_SIZE=131072. 이 값이 너무 작은가요?

사용자 생성 상태에서는 Sidekiq가 비동기로 처리하는 액션이 더 많을 것이라고 생각합니다.
가장 큰 부분은 불행히도 병렬화에서 이점을 얻지 못할 것이며, 대신 단일 코어 CPU 속도를 최적화하는 것이 좋습니다.

이론적으로는 주제(topic) 가져오기의 서로 다른 청크를 병렬로 실행할 수 있지만, 이를 위해서는 가져오기 도구의 상당한 리팩터링과 모든 항목이 순서대로 처리되도록 하는 확인이 필요합니다. 몇 번의 반복으로 끝나는 일회성 작업에 대해서는 그만한 가치가 없습니다.

두 개의 가이드[1]와[2]를 조합하여, 소스 포럼 데이터베이스의 MySQL 사본을 실행하는 다른 Docker 컨테이너에 접근하여 가져오기를 수행했습니다. 하지만 별도의 import 컨테이너를 만들지 않고, 단일 app 컨테이너를 사용하고 mysql-dep.tempate를 추가하면 된다는 것을 깨달았습니다:

templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
  - "templates/web.ssl.template.yml"
  - "templates/web.letsencrypt.ssl.template.yml"
  - "templates/import/mysql-dep.template.yml"

이렇게 하면 가져오기 스크립트가 실행되는 동안에도 작동하는 Discourse 인스턴스를 유지할 수 있습니다. 모든 사용자와 카테고리가 가져와진 직후에 포럼을 공개하고, 완전한 데이터 입력까지 며칠이 걸릴 것이라는 배너를 통해 사용자에게 알려주는 방식에는 어떤 단점이 있을까요? 적어도 모든 주제와 게시글은 가져왔지만 개인 메시지는 아직 가져오기 전 단계에서 공개할 수 있을 것 같다는 생각이 듭니다. 개인 메시지만 가져오는 데도 상당한 24시간이 소요될 테니까요.