Migrate_from_s3 problems

When migrating from S3 to local storage, we see a number of issues.

The main issue is that the migrate_from_s3 rake task is not taking the Uploads table as a starting point, but the posts. This causes it to skip a lot of uploads which are being left on S3.

  • uploads used as logo’s because they are not referenced in a post
  • uploads for avatars because they are not referenced in a post
  • uploads that are (for some reason) referenced by their CDN URL in raw because they do not match the regex that is being used to identify the uploads
  • uploads that are on non-AWS S3 storage because they do not match the regex because it requires amazonaws in the URL
  • uploads that for some reason do not match the second, different regex (we’re seeing this with non-image uploads like mp3 files, not sure why this is happening)
2개의 좋아요

We’re working on improving the way we associate uploads to posts (by using the upload:// scheme) that will make these storage migrations much more bulletproof. There’s little point to fixing these rake tasks before that’s done.

7개의 좋아요

최근 한 번에 적은 수의 게시글을 마이그레이션할 수 있도록 하는(예: 한 번에 하나씩 테스트하는 경우) 새로운 PR을 열었고, 해당 작업에 대한 테스트도 추가했습니다.

게시글부터 시작하는 것이 합리적이라고 생각합니다. 각 마이그레이션 후 cooked 게시글의 URL을 변경하기 위해 게시글을 rebake해야 하기 때문입니다. 모든 업로드를 마이그레이션한 뒤 rebake를 시작하면, 대규모 사이트의 경우 사이트가 매우 오래동안 깨진 상태로 남아있게 될 수 있습니다(저는 S3에서 로컬로 약 100GB를 마이그레이션해야 하므로 이 부분이 중요합니다). 다만 제가 작성한 내용은 게시글에 포함되지 않았던 업로드를 정리하기 위해 migrate_from_s3 실행 후에 실행될 migrate_uploads_from_s3 작업 작성에 도움이 될 수 있습니다.

@zogstrip "We’re working on…"의 현재 상태는 어떻게 되나요? 이 마이그레이션은 여전히 변동 중인가요, 아니면 이제 주목할 가치가 있는 단계에 접어들었나요?

5개의 좋아요

업로드 관련해서 게시물과 업로드 간의 연관 관계를 더 잘 관리할 수 있도록 많은 작업을 수행했습니다.

이 마이그레이션 작업은 원할 때 언제든 개선할 수 있습니다. :+1:

2개의 좋아요

사이트를 비피크 시간에 배치 업로드를 통해 이전할 수 있게 된 후에, 비포스트 업로드를 조사하는 것이 내 시간을 가치 있게 쓸 수 있을지 확인해 보겠습니다! :smiling_face: PR 리뷰 감사합니다!

1개의 좋아요

다음으로 다뤄야 할 두 가지가 있습니다. 첫째, limit은 유용하지만 검색 공간에 대한 제한일 뿐입니다. 다음으로, 수정할 게시글의 최대 수를 지정할 수 있도록 하고 싶습니다. 따라서 쿼리의 limit과 함께 수정할 게시글의 최대 수도 추가해야 합니다.

또한, max를 지정할 때 디버깅 목적으로 처리되는 대상이 무엇인지 자세히 출력하는 것이 합리적입니다. 따라서 max가 nil이 아닐 때 출력을 자세히(verbose) 표시하고 싶습니다. 이는 이 작업의 주요 사용 사례가 프로세스를 계속 진행하기 전에 검증하는 것이기 때문에, 사람들이 이를 통해 프로세스를 검증할 수 있게 해줍니다.

구체적인 max를 첫 번째 인수로, 선택적인 limit을 두 번째 인수로 사용하기로 했습니다. 실제로 max가 가장 중요한 사항이기 때문입니다. limit은 “단 하나의 ping만” 처리하는 비용을 줄이기 위한 것입니다.

둘째는 비업로드: 업로드입니다. 약 1년 반 전, Google+에서 Discourse로의 마이그레이션을 작성하는 방법을 알아내려 고군분투하던 중 가입한 Discourse 사이트에 비디오를 업로드해 보았는데, 작성된 내용이 https://#{SiteSettings.absolute_base_url}/original/3X/b/a/ba9e06ebc2f4397f26793bb5cd4e169308dd371d.mp4인 것을 보았습니다.

오늘날 비디오를 업로드하면 대신 ![file_example_MP4_480_1_5MG|video](upload://caJ9ykkpshw3PFK4464VUIPWJ4l.mp4)와 같은 형식이 생성됩니다.

적어도 제 최근 테스트에서는 migrate_from_s3가 이러한 URL을 완전히 망가뜨려 URL조차 되지 않게 만들었습니다. 따라서 이는 반드시 수정해야 합니다. 그런 다음, 이 작업이 실제로는 ![text](non-upload-url-to-migrate)에 부딪힐 가능성이 낮다고 생각합니다. 따라서 첫 번째 단계에서는 정규 표현식이 두 가지 경우를 모두 처리하도록 하여 가독성이 더 떨어지지 않도록, 마법 같은 upload 프로토콜 주위에 링크 마크다운을 추가하는 것만 하려고 합니다. 하지만 제 생각을 바꿀 수도 있습니다.

video 또는 audio 태그가 정규 표현식 매치를 통해 자바스크립트로 추가되는 것 같습니다. 따라서 이를 올바르게 식별하기 위해 app/assets/javascripts/discourse/app/lib/uploads.js의 정규 표현식을 작업으로 복사해 와야 합니다. 다음에 이를 찾는 사람이 어디서 업데이트해야 하는지 알 수 있도록 정규 표현식의 출처를 포함할 것입니다. :stuck_out_tongue:

…

오늘 밤, 이 작업을 위해 시간을 내어 초안 PR을 진행 중입니다. 아직 _완성_되지 않았으며, 버그가 남아 있음을 알고 있습니다. 현재 시점에서 upload: 유사 프로토콜 URL(이미지의 경우 일반적)에 대한 동작을 변경하지 않았다고 생각합니다. 다만 무결성 검사(sanity check)를 추가했습니다.

이 PR의 변경 사항으로, 현재 S3(제 경우 DigitalOcean Spaces)에 명시적으로 참조되어 있는 일반 upload: 유사 프로토콜 업로드와 비디오를 모두 성공적으로 마이그레이션했습니다. 다음 명령을 사용하여 한 번에 하나의 게시글만 수정하고 있습니다:

bin/rake uploads:batch_migrate_from_s3[1,1000]

데이터베이스 쿼리에서 무작위로 반환된 첫 번째 1000개 게시글을 넘지 않는다는 점에 유의하세요. 낮은 limit은 한 번에 하나의 게시글만 마이그레이션하면서 쿼리 속도를 높이기 위한 것이며, 해당 게시글의 올바른 동작을 검토한 후 처음부터 다시 시작하여 다른 게시글을 찾는 과정입니다. 이 명령은 현재 작업 중인 PR에서만 이 방식으로 작동합니다!

…

작업을 진행하면서 진단 출력을 계속 추가하고 있으며, 개발을 넘어 중요한 사항이라고 생각하기 시작했습니다. DigitalOcean Spaces에서 일시적인 다운로드 실패가 많이 발생하고 있으며, 게시글의 일부 다운로드만 마이그레이션되는 경우가 있습니다. 원래 코드에서는 단순히 .을 출력하고 계속 진행한 후 Done이라고 표시하지만, 실제로는 작업이 완료되지 않습니다. 모든 파일이 마이그레이션되도록 한 게시글에 대해 5~6번 정도 반복해야 했습니다. (처음에는 로컬 버그를 디버깅하는 것이라고 생각했기 때문에 개수를 세지 않았습니다.) 진단 결과가 깨끗해질 때까지 동일한 limit으로 이 마이그레이션을 반복적으로 실행해야 할 것으로 예상됩니다. 따라서 max가 설정된 경우에만 자세한 진행 상황을 출력하지만, 유용한 경고 메시지는 항상 출력하도록 하고 있습니다.

현재, 간헐적으로 다운로드가 실패하는 discourse spaces에 대해 다음과 같은 해킹 방식을 사용하고 있으며, 실제로 성공률을 크게 향상시켰습니다(수백 개의 마이그레이션된 게시글에 걸쳐 3회 재시도가 완전히 충분했습니다).

https://github.com/johnsonm/discourse/commit/7dfac12a2ea6ec04ba4e0616b4e0dbd1d806cff7

또한, Google+에서 가져왔을 때 제가 설정한 제한보다 큰 비디오가 어떻게 사이트에 들어오게 되었는지 모르겠지만, 몇 개의 과도하게 큰 비디오를 원샷 마이그레이션하는 동안 SiteSettings.max_image_size_kb와 SiteSettings.max_attachment_size_kb를 일시적으로 증가시켜야 했습니다. 이제 이를 깨뜨리고 싶지 않습니다… 과도한 업로드를 허용한 버그가 제 가져오기 스크립트, discourse, 또는 시간이 지남에 따라 설정에 적용한 변경 사항에 대한 제 기억 중 어디에 있는지 알 수 없습니다. :wink:

마이그레이션하는 것의 상당 부분이 G+에서 가져온 것이기 때문에, 일부 게시글이 현재 유효성 검사를 통과하지 못했습니다. Unhandled failure: Validation failed: Sorry, new users can only put one image in a post 오류를 몇 번 받았고, 처음에는 왜 다시 발생하지 않는지 이해하지 못했습니다. 나중에 알고 보니 업로드가 로컬로 성공적으로 이동되었고, 모두 upload: 유사 프로토콜을 사용했기 때문에 raw 데이터가 변경되지 않았습니다. 그러나 post.save!는 여전히 유효성 검사에서 해당 오류로 실패하여 post.rebake!가 실행되지 않았습니다. 따라서 3만 개 중 몇 개의 게시글에 이미지가 있어 rebake가 필요한 상태입니다. 불행히도 어떤 게시글인지 기록이 없습니다. 이제 post.save!(validate: false)로 전환하여 또 다른 수정을 가했기 때문에, 이 특정 문제는 다시 발생하지 않아야 합니다. 마이그레이션이 처리되지 않은 오류에서 중단되도록 한 것에 대해 매우 다행스럽게 생각합니다. 그렇지 않았다면 몇 개의 게시글보다 훨씬 더 큰 피해를 입혔을 수도 있었을 테니까요.

…

마이그레이션을 실행하는 동안 알림 전달을 포함하여 사이트가 사용 가능하게 유지하기 위해 sidekiq 큐를 스팸하지 않으려 합니다. 네이밍은 캐시 무효화와 오프바이원 오류와 함께 컴퓨터 과학에서 가장 어려운 두 문제 중 하나라는 것을 알고 있지만, 마이그레이션 중 rebake 후 다른 항목을 마이그레이션할 때 큐 슬롯(작업 슬롯이 아님)이 채워질 수 있는 총 허용 수를 지정하기 위해 DISCOURSE_MIGRATION_MAX_ENQUEUED 환경 변수를 제안합니다. 이를 통해 큐를 스팸하지 않고 사이트가 계속 기능하도록 합니다. lib/tasks/uploads.rake의 게시글별 rebake에 대해 이를 추가하는 패치를 가지고 있으며, 기본값은 0입니다. 저는 프로덕션 마이그레이션에서 이를 사용하고 있습니다.

https://github.com/discourse/discourse/blob/59a761851b9c8786d3a9692f8c595372b0534f77/lib/tasks/uploads.rake

4개의 좋아요

@zogstrip 최근 이 영역에서 내 마지막 PR을 리뷰하면서 맥락을 파악하셨으니, 이 PR도 리뷰해 주실 수 있으신가요? FIX: Make migrations from S3 more robust; fix bare URL migration - Pull Request #10093 - discourse/discourse - GitHub

상당히 큰 규모의 마이그레이션을 수행하면서 적용해 온 수정 사항들을 이 PR에 포함시켰습니다. 모든 수정 사항에 대해 테스트를 추가하려 하지는 않았습니다. 모든 형태의 오류를 주입하는 방법을 정확히 모르기 때문입니다. 하지만 적어도 새로운 기능은 테스트가 되어 있습니다.

@RGJ 현재 상태의 제 PR은 님의 첫 번째 두 가지 지점을 제외하고는 모두 처리할 수 있을 것 같습니다. 다만 CDN에 대해서는 확신이 없습니다. 제 사이트는 CDN을 사용했으며 CDN URL을 가진 비디오들을 마이그레이션했지만, 이는 discourse spaces의 네이밍으로 인한 부수적 효과였을 수도 있습니다. 추가적인 케이스가 있으시다면, 제 PR이 정규식에 추가하고 추가 변형에 대한 테스트 케이스를 작성하는 데 도움이 되는 좋은 기반이 될 것입니다.

우선 게시물 단위로 마이그레이션을 진행하는 것이 맞다고 생각합니다. 게시물의 업로드를 마이그레이션한 후에는 쿠킹된(post) 게시물이 올바른 URL을 포함하도록 다시 베이킹(rebake)해야 하기 때문입니다. 제 게시물의 마이그레이션을 완료하는 데에는 이제 레이트 리미팅을 큐 길이를 직접 확인하도록 변경했으니 두 주 이내가 걸릴 것 같습니다. 완료 후 정리할 남은 작업을 살펴볼 것입니다.

여러 명이 동일한 파일을 업로드한 경우 여러 게시물이 동일한 콘텐츠에 대한 참조를 공유할 수 있으므로, 구 URL을 검사하고 새 위치를 반영하도록 해당 게시물을 다시 베이킹하는 두 번째 패스가 필요합니다. 이는 큐가 과도하게 쌓이는 것을 방지하기 위해 동일한 레이트 리미팅을 사용할 수 있습니다.

makerforums에서 깨진 로고를 볼 가능성은 낮을 것입니다. “s3”(우리의 경우 DigitalOcean Spaces)에 새 콘텐츠를 올리는 것을 중단한 후 브랜딩을 조정했기 때문입니다. 하지만 최소한 아바타를 위해 여전히 S3에 남아있는 많은 업로드 파일들을 볼 가능성이 높습니다. 게시물과 연관되지 않은 업로드의 마이그레이션은 모든 게시물의 마이그레이션이 완료된 후에만 시작해야 하며, 게시물 내 업로드 마이그레이션을 완료한 후에 이를 작성해야 할 것입니다.

@pfaffman Bizarre Problems with migrate_from_s3 - #3 by pfaffman 에서 설명하고 있는, 재현되지 않았던 오류들을 확인했습니다. 현재 PR의 제 수정 사항이 없다면, 검증 실패를 포함한 오류들이 조용히 무시됩니다. 여기서 진행 중인 작업이 당시 님이 보셨던 문제 중 적어도 일부를 해결할 것이라고 생각합니다.

@hosna https://meta.discourse.org/t/what-does-rake-uploads-migrate-from-s3-exactly-do/97285 에서 제기하신 문제들은 이 PR에서 부분적이거나 완전히 해결되었습니다. 완전히 해결되지 않은 경우, 추가 수정 사항을 검증하기 위한 추가 테스트를 작성하기 더 쉽게 만들 테스트를 추가했습니다.

@sam PR에 2.6 라벨을 붙이셨으므로, 최소한 며칠은 머지되지 않을 것으로 추정합니다. 제 레이트 리미팅 기능 작업을 수정 사항과 함께 이 PR에 포함해야 할까요? 아니면 수정 사항과 기능 작업을 별도의 PR로 유지하는 것을 선호하시나요? 두 방식 모두 가능합니다. 레이트 리미팅 기능은 매우 잘 작동하고 있습니다. sidekiq 큐가 비워질 때까지 대기하도록 변경한 후, 사이트 가용성에 영향을 주지 않으면서 약 3배 빠른 속도로 마이그레이션을 수행하고 있으므로, 일반적으로 PR에서 허용되는 것이라면 포함하는 것이 합리적이라고 생각합니다. 그렇지 않다면, 이 작업이 기반을 둔 작업이 머지될 때까지 PR 머지를 기다려야 하므로, 어느 쪽이든 의견을 듣고 싶습니다.

..

마이그레이션 레이트 리미트 패치를 DRY하게 정리하고 PR에 포함했습니다. 실제로 작동하고 있으며, sar에 따르면 라이브 마이그레이션 중에도 사이트가 계속 기능하면서 거의 제로에 가까운 유휴 시간을 지속적으로 확인하고 있습니다. 배치 모드의 이점 중 하나는 마이그레이션 배치마다 새 Discourse 버전을 확인할 수 있다는 점입니다. 2.6.0beta1가 나온 직후 첫 번째 기회에 제 사이트를 업그레이드했고, 업데이트 이후 마이그레이션 PR을 위에 올린 상태로 2.6.0beta1에서 마이그레이션을 성공적으로 수행해 왔습니다.

이제 PR이 리뷰를 받을 준비가 되었다고 생각합니다. 마지막 몇 단계에 대해 별도의 PR을 제출할 계획이지만, 이 PR이 머지되면 마지막 조각들을 완료하기 전에 이미 모든 사용자의 일반적인 마이그레이션 경험을 향상시킬 것입니다.

5개의 좋아요

글쎄요, 그건 꽤 오래전의 일입니다. . .

이 문제가 해결되는 것을 빨리 보고 싶습니다. 현재 멀티사이트 환경에서 여러 사이트가 s3에서 이미지를 가져와야 하는데(지금 일부 이미지는 로컬에 있고 나머지는 s3에 있는 것 같습니다), 그 다음 s3로 다시 업로드해야 합니다.

1개의 좋아요

@pfaffman 마이그레이션 프로젝트를 시작할 당시 나중에 깨달은 것들을 몰랐던 것은 아마도 좋은 일이었을 겁니다. minio 클라이언트를 사용해 S3 버킷 전체를 로컬 업로드 폴더로 복사하고, rails 콘솔에서 모든 Upload.url을 nil로 수정한 뒤 사이트를 다시 빌드했다면, 모든 이미지를 재생성하지 않고 몇 시간 안에 모든 것을 마이그레이션할 수 있었을 것 같습니다. (반면, 지금은 S3에서 이미 처리된 이미지를 그냥 복사하는 것보다 로컬 CPU가 더 저렴하다는 듯이 모든 이미지 변환을 다시 실행하면서 레이트 리밋에 걸려 있습니다.)

그리고 만약 그렇게 쉬웠다면, 저는 마이그레이션을 전반적으로 더 신뢰할 수 있게 만들기 위해 이 작업을 하지 않았을 것이고, 다른 누구도 그 혜택을 받지 못했을 것입니다. :smiling_face:

4개의 좋아요

오. 제가 알고 싶었던 바로 그 내용 같네요. 한번 시도해 볼게요.

1개의 좋아요

저는… 분명히 그 방법을 시도하기 전에 백업을 꼭 만들겠습니다. 그냥 추측하는 것뿐이니까요. 여러분을 오해하게 만들고 싶지 않습니다.

아, 그리고 한 가지 더: 그 방법은 오디오와 비디오 업로드에서는 완전히 실패했을 것이고, 저는 나중에야 그걸 깨달았을 겁니다. 그리고 나서 문제를 파악하고 커스텀 코드를 작성하려고 했을 텐데요. 따라서 오디오와 비디오 업로드를 사용하신다면, 반드시 그 부분부터 시작해야 합니다. 현재 열려 있는 PR이 2.5 릴리스 이후에나 머지될 예정이라(@sam이 2.6으로 태그를 붙였으므로), 해당 PR 없이 제대로 작동하지 않습니다.

2개의 좋아요

이렇게 늦게까지 대화에 합류하게 되어 죄송합니다. 방금 PR을 확인했는데, 왜 URL만 수정하는 대신 Upload 모델을 다시 생성하고 계신지 궁금합니다. 그리고 OptimizedImages를 순회하면서 같은 작업을 수행하지 않는 이유는 무엇인가요?

2개의 좋아요

@RGJ 저는 그 부분을 전혀 변경하지 않았습니다. Upload를 다시 생성한 것은 @zogstrip이 https://meta.discourse.org/t/cannot-execute-the-rake-uploads-migrate-from-s3/78541/11에서 수행한 버그 수정이었습니다.

저는 이미 존재하던 코드가 동작하도록 하려는 것뿐이고, discourse 내부 구조를 잘 알지 못합니다. 어둠 속에서 헤매는 일이 많았습니다. 제 Ruby 경험은 discourse를 위해 작업한 몇 가지 PR 정도입니다. 기존 코드의 패턴을 따르는 것이 가장 효율적인 방법이 아닌 것 같습니다(단축 평가에 대해 @pfaffman과 나눈 위의 대화를 참고하세요). 전적으로 동의합니다. 오늘早些까지 OptimizedImages.url도 /uploads 경로로 수정되고 etag가 nil로 설정되어야 한다는 사실조차 깨닫지 못했다는 점으로 보아, 저는 여전히 눈감고 운전하는 상태입니다.

최소限으로 게시글 내의 기존 리터럴 URL을 수정하기 위해 게시글을 순회하는 작업이 여전히 필요합니다. 게시글을 재검증하지 않고, 오류를 조용히 무시하지 않는 등 다른 수정 사항들도 여전히 필요합니다. 또한 라이브 사이트への 영향을 제한하기 위해 레이트 리미팅을 적용하는 것도 여전히 바람직하다고 생각합니다.

게시글이 아닌 업로드 업데이트와 관련된 두 가지 우려 사항에 대해, 제 진행 중인 작업(아직 라이브 사이트에서 테스트되지 않음) (커밋)이 도움이 될 수 있을 것입니다. 다만 게시글 업로드 마이그레이션이 모두 완료될 때까지 제 라이브 사이트에서 테스트할 수 없습니다.

예전에 잘 작동하던 것을 조정하는 것 정도만 감당할 수 있었습니다. 더 빠른 마이그레이션을 원하신다면 전적으로 찬성합니다. 제 느리지만 적어도 더 나은 작업을 파레토 개선(Pareto improvement)으로 병합한 후, 그것을 훨씬 더 나은 것으로 완전히 교체하는 것이 합리적일 수 있으며, 그때 저는 그것을 사용할 위치에 있지 않더라도 가장 먼저 축하할 것입니다.

1개의 좋아요

@mcdanlj 설명해 주셔서 감사합니다. 저는 진심으로 질문을 던진 것일 뿐, 지금 문제가 있다는 식의 뉘앙스를 주려던 것이 아닙니다. 제가 제안한 방식이 정말로 더 “나은” 방식인지 모르겠습니다. 오히려 많은 새로운 문제를 초래할 수도 있으니까요. 현재 코드 상태는 누가 작성했든, 그 이유를 가지고 그렇게 작성된 것이라고 생각합니다.

3개의 좋아요

@RGJ 님도 마찬가지인데, 모든 이미지를 다시 빌드하는 번거로움을 피하는 확실한 방법을 아는 사람이 있다면 저는 전적으로 찬성합니다. 만약 제가 숙련된 Discourse 개발자였고 쉽게 할 수 있는 방법을 알았다면, 처음부터 그렇게 했을 가능성이 높았을 것입니다.

많은 이미지를 가진 사용자들에게 S3에서 마이그레이션하는 것은 흔한 경우가 아니라고 추측합니다. 따라서 Discourse에 보다 일반적으로 유용한 기능을 추가하는 것과 비교할 때, 이 작업에 시간을 들일 가치가 있는지 판단하기 어렵습니다. Google+에서 MakerForums로 가져오면서 약 100GB의 업로드가 발생했고, 많은 사람들이 해당 커뮤니티에서 MakerForums로 이동할 수 있다는 기대감으로 S3를 선택했습니다. 하지만 결국 활발하게 이동한 커뮤니티는 소수에 불과하여, 지속적인 성장은 S3에 머무는 것을 뒷받침하지 못했습니다. 저는 이것이 상당히 극단적인 예외라고 생각하며, 이미지 재처리 비용은 일회성 비용입니다.

마지막으로, 이 주제를 처음 열어서 정말 감사합니다. 그렇지 않았다면 게시물이 아닌(non-post) 업로드를 놓칠 수도 있었을 것입니다.

…

@RGJ 님, 삭제 후 재생성(destroy-and-recreate) 방식이 좋지 않다는 지적은 명확히 옳습니다. url != new_upload.short_url일 경우 raise "Error: upload url #{url} changed to #{new_upload.short_url}, should be unchanged."라는 체크를 추가하여, 손상된 소스에 대한 문제를 보고받게 했습니다. 오늘 Digital Ocean Spaces 장애로 인해 손상된 데이터가 전송되었고, 이 오류가 많은 업로드에서 발생했습니다 — 몇 건인지 정확히 알 수 없습니다. 하지만 기존 업로드가 이미 삭제된 상태였기 때문에, 일부 페이지가 손상되었고, 어떤 페이지가 손상되었는지 알려주는 로그가 포함된 스크롤백을 잃어버린 후에야 이를 인지하게 되었습니다. 따라서 백업에서 수동으로 수정할 수도 없는 상황입니다.

따라서 제 작업은 최소한 일부 오류를 보고한다는 점에서 기존 방식보다 개선되었지만, 파일을 다운로드하고 다운로드된 파일의 SHA1을 확인한 후 로컬 파일로 쓰는 방식이 훨씬 낫습니다. 그 사이, 저는 데이터 손실을 제한하기 위해 처리되지 않은 오류가 발생하면 마이그레이션을 중단하도록 PR을 수정했습니다.

저는 여전히 제 작업이 파레토 개선(pareto improvement)으로 병합되어야 한다고 생각합니다. 상황이 나빠지지 않고 더 좋아지므로, 하지만 올바른 방식으로 처리하는 것이 훨씬 나을 것입니다. 저는 lib/file_store/from_s3_migration.rb를 작성하여 lib/file_store/to_s3_migration.rb와 대칭을 이루게 하는 것이 그 방법이라고 생각하지만, 제가 감당할 수준은 아닙니다.

제 PR이 아직 검토되지 않았기 때문에, 더 많은 내용을 추가했습니다. raw 필드에서 업로드 객체가 존재하지 않는 upload://... 인스턴스를 찾는 uploads:report_missing_uploads 태스크를 추가했습니다. 적어도 제 경우처럼 백업이 있다면, 백업을 통해 고아 파일(orphaned files)을 찾아 사이트로 복원한 후, 해당 게시물을 다시 베이킹(rebake)하여 사라진 이미지를 복원할 수 있습니다. 백업에서 찾을 파일 678개 중 10개를 제외한 나머지를 모두 찾았으므로, 이 테스트를 작성해幸好했습니다. 나머지 게시물들을 다시 베이킹하는 단계로 넘어가기 전에 이 문제를 해결해야 합니다.

…

공유 업로드가 있는 게시물의 다시 베이킹과 게시물 외 업로드를 포함하지 않은 마이그레이션의 첫 번째 단계를 완료했습니다. 원격 백업을 한 번 더 완료한 후, 해당 기능들도 테스트하고 PR에 추가할 계획입니다.

…

이제 마이그레이션의 다음 단계인 다시 베이킹과 게시물 외 업로드 마이그레이션을 테스트하기 시작했습니다. 첫 번째 교훈은 다음 단계로 다시 베이킹을 수행하면 인용된 게시물 내의 게시물 외 업로드 아바타로 인해 실패한다는 것이었습니다. 따라서 게시물 다시 베이킹은 마지막 단계여야 합니다.

다음 발견: 외래 키 제약 조건에 감사드립니다! 게시물 외 업로드를 마이그레이션하는 동안, 게시물 외 업로드를 일반적으로 마이그레이션하기 전에 최소한 프로필을 먼저 마이그레이션해야 한다는 것을 발견했습니다.

ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR:  update or delete on table "uploads" violates foreign key constraint "fk_rails_1d362f2e97" on table "user_profiles"

프로필 마이그레이션은 까다로웠습니다. 프로필에 연결된 두 개의 업로드를 마이그레이션해야 했는데, 일부 사용자는 두 곳에 동일한 이미지를 사용했기 때문에 이 작업을 세 단계로 나누어 수행해야 했습니다. 제 사이트의 S3 URL을 가진 모든 프로필을 성공적으로 마이그레이션했습니다.

모든 게시물 외, 프로필 외 업로드를 마이그레이션했습니다. @RGJ 님, 제가 올린 브랜치를 시도해보고 싶으시다면, 제 작업과 동기화되어 있습니다. 여기 포함된 모든 내용은 이제 완료된 사이트 마이그레이션에 사용되었습니다.

…

@cvx 님, 언제 검토하실지 모르겠지만, 제 PR이 없다면 migrate_from_s3에는 분명히 침묵 데이터 파괴(silent data destruction) 버그가 가득합니다. 제가 한 작업은 완벽하지는 않지만, 실무에서 겪은 많은 버그로부터 보호합니다.

현재까지 하기로 한 모든 작업을 완료했습니다. 제 사이트를 마이그레이션했기 때문에, 요청된 변경 사항조차 효과적으로 평가할 수 없는 상태입니다. 이 PR을 거부하신다면, 기존 마이그레이션을 삭제하는 것을 강력히 권장합니다. 왜냐하면 그것은 사용자의 데이터를 침묵적으로 파괴할 것이기 때문입니다.

PR과 관련하여, CI(현재 버전)에서 다음과 같은 테스트 실패가 가끔 발생합니다:

7867 examples, 0 failures, 11 pending, 1 error occurred outside of examples

##[error]Process completed with exit code 1.

이것은 제 로컬에서는(지금까지) 재현되지 않고, CI 로그에서 CI에서 무엇이 잘못되었는지를 알려주는 정보를 찾지 못했기 때문에, 숨겨진 정보를 찾기 위해 CI 출력을 파고들며 시간을 더 낭비하지 않을 것입니다.

마지막으로 한 가지: 마이그레이션을 완료한 후, 일부 사용자의 프로필 사진이 마이그레이션 중 손실되었음을 발견했고, 그들은 수동으로 복원하고 있습니다. 그걸 성공적으로 하려면 추가 코드가 필요했을 것이라고 추측하지만, 너무 늦게 그 사실을 깨달았기 때문에 이를 검증할 테스트 케이스가 없습니다. 따라서 일부 설정에서 문제를 일으킬 수 있는 남은 버그이지만, 저는 이제 그 추가 수정을 작성할 위치에 있지 않습니다.

5개의 좋아요

침묵형 데이터 손괴(silent data corruption)를 수정하는 migrate_from_s3에 대한 PR이 한 번도 제대로 검토받지 못한 상황에서, migrate_to_s3은 최소한 일방통행(one-way street)임을 문서에 명시해 줄 수 있을까요?

현재 상태로는 솔직히 초보자에게 함정이나 다름없습니다.

@cvx @zogstrip 여러분의 의견은 어떻게 되나요? PR을 검토해 주시거나, 아니면 migrate_from_s3을 아예 제거하고 이것이 일방통행임을 솔직하게 인정하는 쪽이 좋을까요?

5개의 좋아요

죄송합니다, 제 잘못입니다. 이번 주에 PR을 검토하겠습니다.

7개의 좋아요

리뷰 내용은 기본적으로 현재 방식이 완전히 잘못되어 있어 처음부터 다른 방식으로 전면 재구축해야 하며, 기존에 작성된 방식대로 수정하려는 시도는 적절하지 않다는 것이었습니다. 저는 그 정도까지 감당할 수 없으므로, migrate_from_s3를 완전히 제거하고 migrate_to_s3는 영원히 유지해야 하는 일방적 결정(one-way door)임을 명시해야 한다고 진심으로 생각합니다. 현재 소스에 migrate_from_s3가 존재하는 것 자체가 Discourse의 데이터 무결성 결함입니다.

마이그레이션 작업을 진행할 때 적시에 리뷰를 받지 못했고, 이미 마이그레이션을 완료한 후(일부 아바타가 깨지는 등 일부 결과가 손상되었습니다) 의미 있는 테스트 환경을 잃어버렸습니다. 제가 이를 올바르게 수행할 수 있을지에 대한 확신이 전혀 없으므로, 이 논쟁에서 손을 뗀 것입니다. 이미지를 S3 계열의 객체 저장소 서비스에 저장하는 것이 좋은 아이디어라고 생각했던 다음 불쌍한 사람이 이 문제를 해결해야 할 것입니다. 죄송합니다!

2개의 좋아요

@CxV 이전 마이그레이션을 완전히 잘못 수행되어 처음부터 다시 시작해야 한다고 기각하셨으므로, 데이터 손상 버그를 제거하는 이번 PR을 검토하고 병합해 주시겠습니까?

2개의 좋아요

앞서 언급했듯이, 제가 진행한 마이그레이션 과정에서 몇 가지 문제에 부딪혔습니다. 이는 @cvx 님이 지적하신, 제가 마이그레이션을 완전히 잘못 작성했다는 주장을 뒷받침합니다. 누군가가 올바르게 처리하기 전까지, 기존 마이그레이션의 완전한 제거(이 마이그레이션은 침묵하는 데이터 손실로 가득 차 있음)는 정말 병합되어야 합니다. (매우 간단한 패치이며, 리뷰도 자명할 것입니다. 병합이 될수록, 누군가가 일방통행 문을 거꾸로 걷다가 사이트를 파괴할 가능성이 줄어듭니다. 병합을 더 강하게 호소하는 방법을 모르겠으며, 할 수 있다면 그렇게 하겠습니다. 수정: 승인, 병합 및 대량 마이그레이션이 비가역적으로 표시되도록 확인해 주신 @cvx 님께 감사드립니다.)

제가 겪은 알려진 문제들은 다음과 같습니다.

  • 최소한 discobot 아바타가 표시되지 않았습니다. 모든 discobot 참여는 일반 사람 아바타(흰색 배경에 연한 회색 머리-어깨)로 표시되었습니다.
  • 다른 몇몇 사람들의 아바타가 올바르게 마이그레이션되지 않았습니다. 많은 부분이 수동으로 수정되었습니다.
  • 사이트가 설정되는 동안, 원래 관리자가 “S3” 설정(우리의 경우, CDN이 있는 것과 없는 것의 digital ocean spaces) 변경을 실험한 결과, 마이그레이션에서 수정되지 않은 고아 상태의 최적화 이미지가 일부 발생했습니다.

제 브랜치를 사용하다가 이와 같이 일부 이미지가 사라진 분들을 위해, 저는 rails 콘솔에서 해킹을 실행하여 일부 문제를 해결했습니다. 이것은 올바른 방법이 아니며, 저는 모델을 충분히 잘 알지 못해 올바르게 수행할 수 없습니다. 제 ruby-foo(루비 실력)는 매우 약하며, Discourse에 대한 작은 기여를 제외하고는 ruby를 건드리지 않습니다.

그러나, 그 정도 수준이지만, 이 끔찍한 해킹은 (초기 설정 실수로 인한) 깨진 이미지를 수정하고, 최소한 discobot 아바타(그리고 아마도 다른 것들도, 하지만 제가 이전에 인지했던 것들은 모두 이미 복구되어 있었습니다)를 되돌려 놓았습니다.

여기서 보이지 않는 것은, 단계 사이에 제가 수행한 모든 작업입니다. 각 세트를 진행하면서 살펴보는 것이었죠. 이것은 실제로 작성하고 실행한 스크립트가 아니라, “rails 콘솔에서 무엇이 있는지 탐색” 세션의 일부였습니다. 여기에는 오류 처리가 없으며, 이 작업을 시작하기 전에 사이트 전체 백업을 수행했습니다.

이 코드가 이 수리를 위한 idiomatic ruby on rails 방식이 아님을 더 강하게 표현하는 방법을 모르겠습니다. 저는 단지 이전 시도에 digital ocean spaces에서 사이트를 마이그레이션하는 과정에서 제가 스스로에게 입힌 손상의 최소한 일부를 어떻게 수리했는지 공유하고 싶었을 뿐입니다.

require 'set'

uploadids = Set.new
optimizedimages = Set.new
OptimizedImage.where("url like '%UNIQUEPARTOFS3URL%'").each do |oi|
  uploadids.add(oi.upload_id)
  optimizedimages.add(oi)
end

postids = Set.new
uploadids.each do |u|
  PostUpload.where(upload_id: u).each do |pu|
    postids.add(pu.post_id)
  end
end

optimizedimages.each do |oi|
  oi.delete
end

postids.each do |pid|
  Post.where(id: pid).each do |p|
    p.rebake!
  end
end
2개의 좋아요