업로드 파일이 고아 처리되고 정리되지 않음

안녕하세요,

최근 저는 2021년(인 것 같습니다)에 서버에 설치되어 대부분 다른 사람이 업데이트하던 기본 Discourse Docker 이미지 인스턴스의 마지막 남은 관리자 및 유지보수자가 되었습니다. 어느 정도 기간 동안, 아마도 처음부터 그랬을지도 모르지만, 소프트 삭제된 게시물의 업로드 파일이 고아(orphan) 처리되고 정리되지 않는 문제가 발생하고 있었습니다. 오래된 파일들이 계속 쌓여 저장 공간을 낭비하고 있어 며칠째 이 문제를 해결하려고 노력하고 있습니다. 우리는 S3를 사용하지 않고 있으며, 실제로 유지하고자 하는 업로드 파일에 대해 충분한 저장 공간이 확보되어 있습니다.

저는 공식 Discourse Docker 설치 가이드를 따라 app.yml을 기반으로 재구축한 후, 커맨드라인에서 백업을 복원하여 전체 Discourse 백업 파일(업로드 포함)을 테스트를 위해 별도의 스테이징 서버로 이전했습니다. 두 설치 환경 모두 다른 명백한 문제 없이 동일하게 정상적으로 실행되고 있지만, 업로드 문제는 여전히 남아 있습니다.

로그에서 관련 에러를 찾지 못하고 있으며, Sidekiq는 예정대로 정리 작업을 실행하고 있습니다. 스테이징 버전에서 rake db:migrate를 실행하고 여러 번 재구축했으며, 게시물을 영구 삭제하고 설정을 확인해 보기도 했습니다. Rails 콘솔에서 일부 게시물을 영구 삭제하고 정리 작업을 수동으로 실행해 본 결과, 어느 시점에서 tombstone 디렉터리의 크기가 약간 증가한 것을 확인했습니다. 원래부터 일부 파일이 존재했으므로, 이 메커니즘은 어떤 상황에서는 작동했던 것이 맞지 않나요? 작은 크기 증가를 판단해 볼 때, 대부분의 오래된 파일은 여전히 고아 파일로 감지되지 않고 있는 것 같습니다.

현재 관련 관리자 패널 설정은 아래와 같습니다. 테스트 기간 동안 유예 기간을 효과적으로 건너뛰기 위해 마지막 두 항목을 0으로 설정해도 될까요?

clean up uploads = true
clean orphan uploads grace period hours = 1
purge deleted uploads grace period days = 1

이 문제를 효율적으로 트러블슈팅하는 방법은 무엇일까요? 저는 커맨드라인 사용에 익숙하지만 데이터베이스 기술은 초보 수준이라, 현재 무엇을 찾아야 하는지 명확하지 않은 상태에서 가능한 모든 서버 설정 세부 사항을 하나씩 확인해야 하는 상황을 피할 수 있도록 팁을 주시면 감사하겠습니다.

이 포럼에서 유사한 사례를 절박하게 검색하고 읽어 보았지만, 관련 스레드는 몇 개 없고, 그마저도 막다른 길에 도달하거나 단일 파일에 대한 수동 해결책으로 끝나는 경우가 많아 이 사용 사례에 바로 적용하기 어렵습니다.

필요하시면 더 많은 세부 정보를 요청해 주십시오. 이 문제를 근본적으로 해결하기 위해 최선을 다하고 있습니다.

안녕하세요, @Uphill4721 :slight_smile:

기억이 맞다면, 다음 주제들에 관련 정보가 있을 것 같습니다:

빠른 답변 감사합니다!

이 문제를 해결하기 위해 시도하는 동안 해당 주제와 거기서 링크된 몇 가지 추가 주제에 대해 꽤 익숙해졌지만, unfortunately 이 문제의 결정적인 해결책은 제공되지 않았습니다.

어제 스테이징 서버에서 삭제된 지 9일 이상 지난 주제와 게시글을 대상으로 수정된 명령어를 실행했습니다:

이후 tombstone 디렉토리의 콘텐츠 크기가 약간 증가하는 것을 확인했고, 아직 유예 기간(grace period)이 남아 있어 상황을 계속 모니터링하고 있습니다. 테스트 중에 대기 시간을 우회할 수 있는지, 관련 설정을 0시간/0일로 변경하면 되는지 궁금합니다.

이전에는 원본 서버에서 최신 게시글 수정본의 업로드를 제거해 보았지만, 유예 기간이 지나도 파일이 여전히 접근 가능했습니다.

이 시점에서 개인적으로는 다른 어디에도 참조되지 않는 주제 하나와 그 게시글 및 업로드를 영구적으로 삭제하는 작동하는 수동 해결책이라도 알게 된다면 매우 기쁠 것입니다. 하지만 이는 Discourse를 사용하는 다른 사람들에게 큰 문제가 될 수 있습니다. 관리 패널의 정리 설정이 설명된 대로 효과적일 것이라고 당연하게 생각하고, 실제로 그렇지 않다는 점을 반드시 알아차리지 못해, 영구적으로 삭제되어야 할 것으로 예상되는 민감한 업로드 파일들이 사실은 파일시스템에 남아 있는 상황이 생길 수 있기 때문입니다. 우리의 문제는 다행히 낭비된 저장 공간에 국한되어 있지만, 다른 누군가에게는 훨씬 더 심각한 문제가 될 수 있습니다.

2개월 전에도 비슷한 언급이 있었습니다:

그러면, 이것이 우리 측의 잘못된 설정인지 아니면 실제 버그인지 어떻게 파악할 수 있는지 조언을 구합니다. 그 외에는 Discourse에 매우 만족하고 있으며, 이 문제를 해결하고 그 과정에서 다른 사람들을 도울 수 있기를 매우 열망하고 있습니다.

이것은 순수한 추측이지만, post, post_upload, upload 모델을 간단히 살펴보면 다음 쿼리로 고아 업로드(데이터베이스 객체)가 있는지 확인할 수 있을 것입니다:

Upload.find_by_sql("select * from uploads where id in (select upload_id from post_uploads where post_id not in (select id from posts))")

직접 테스트해 보지 않아서 고아 업로드를 정확히 찾을 수 있는지, 심지어 오류 없이 실행되는지 확신할 수 없습니다. 만약 그대로 작동하지 않고 다른 누군가가 이를 작동시킬 수 있기를 바라며, 관심 있는 다른 사람들을 위해 의도를 단계별로 설명하겠습니다.

  1. Upload.find_by_sql()는 제공된 SQL 쿼리와 일치하는 Upload 객체들의 컬렉션을 반환합니다.
  2. (select id from posts)는 기존 게시물의 모든 ID를 가져옵니다.
  3. (select upload_id from post_uploads where post_id not in ()는 해당 게시물이 존재하지 않는 게시물 업로드의 모든 ID를 가져옵니다.
  4. select * from uploads where id in ()는 해당 게시물 업로드 ID와 일치하는 모든 업로드를 가져옵니다.

이것은 조사할 수 있는 하나의 방법일 뿐이며, 불행히도 업로드 시스템을 충분히 잘 알지 못하여 다른 많은 기여를 할 수는 없습니다. 다만, 위 방법은 분명히 모든 상황을 고려하지 않고 있다는 점만 언급하겠습니다. 수정되었거나 삭제된 게시물들이 그 명백한 예시입니다.

프로필 사진 업로드와 같은 사용자 업로드와 같이 고려되지 않는 다른 유형의 업로드도 있습니다.

플러그인도 업로드를 생성하고 이를 유지할 수 있습니다. 플러그인이 제거되면 어떻게 되는지는 모르지만, 플러그인이 제거된 후에도 플러그인 데이터가 데이터베이스에 남아 있다고 생각합니다. 이는 잠재적으로 해당 플러그인이 생성한 업로드들이 그 상황에서 영원히 제거되지 않을 수 있음을 의미합니다.

답변 주셔서 감사합니다!

쿼리는 작동하지만 업로드된 항목과 그 세부 정보 중 단 두 개만 나열됩니다. 고아(orphan) 기준에 해당하는 업로드 항목은 수백 개에서 수천 개에 달해야 하며, 대부분은 사용자가 일반적인 게시물을 작성할 때 업로드한 이미지 파일입니다.

현재 공식 플러그인만 사용 중입니다:

hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git
          - git clone https://github.com/discourse/discourse-chat-integration.git
          - git clone https://github.com/discourse/discourse-prometheus.git
          - git clone https://github.com/discourse/discourse-bbcode-color
          - git clone https://github.com/discourse/discourse-data-explorer

최초 설치 후 어느 시점에 업로드 프로세스에 대한 대규모 개편이 있었는데, 이것이 우리의 상황과 관련이 있을 수 있는지 궁금합니다: A new era for file uploads in Discourse

스테이징 서버에서는 이제 그 유예 기간이 지났을 텐데, 업로드 디렉터리의 크기에 변화가 보이지 않고 테스트 파일도 여전히 접근 가능합니다. 다음으로 무엇을 확인해야 할까요? 잘못된 파일시스템 권한이나 그런 문제로 인한 것일 수 있으며, 쉽게 확인할 방법이 있을까요? 구체적인 조사 대상에 대한 아이디어가 다 떨어져서, 다른 모든 것은 잘 돌아가고 있지만 현재 우리가 직면한 유일한 문제가 이것입니다.

유사한 주제들을 살펴보며 잠재적으로 일치하는 미해결 사례를 수집하던 중, 이러한 상황이 고아(Orphan) 파일로 처리되지 않고 영구적으로 삭제되지 않아 법적 문제를 일으킬 수 있는 좋은 예를 하나 찾았습니다:

2016년에 있었던 또 다른 유사한 상황입니다:

이러한 조건들은 악용이나 불법 콘텐츠 업로드를 위한 표적 공격에 큰 틈을 만들어 줍니다. 관리자가 삭제된 것으로 생각하더라도 서버에서 영구적으로 제거되지 않을 수 있기 때문입니다. 물론 파일 시스템에서 단일 파일을 수동으로 삭제하는 것은 가능하지만, 특히 자동 정리 프로세스를 나타내는 GUI 설정이 있고, 또한 модератор(모더레이터)는 보통 서버에 직접 접근 권한이 없는 상황에서, 이러한 기본적인 요구 사항에 대해 사람들이 그러한 방법을 강요당해서는 안 된다고 생각합니다. 또한 삭제된 여러 주제에 흩어져 있는 대량의 파일에 대해 수동 삭제는 실용적이지 않습니다.

이것은 실제 버그 리포트로 충분한 근거가 될까요? 우리 측의 잘못된 구성 가능성은 아직 배제하지 않고 있지만, 오류 메시지가 전혀 없다는 점과 다른 모든 것이 정상적으로 작동하는 것에 대해 당혹감을 느끼고 있습니다. 저는 문제 해결과 테스트에 점점 더 많은 시간을 보내면서 Discourse와 그 구성 요소에 대한 지식을 쌓아 왔으므로, 몇 가지 안내를 주신다면 이 기이한 동작을 유발하는 모서리 케이스(Corner case) 세부 사항을 파악하는 데 도움을 줄 수 있을 것이라고 생각합니다. 지금 @zogstrip 님을 핑(Ping)하는 것이 괜찮을까요?

임시 해결책으로, 모든 업로드 파일을 톰스톤(Tombstone) 디렉토리로 수동으로 이동한 후 업로드 복구 방법을 사용하여 고아 파일이 아닌 파일만 원래의 올바른 디렉토리로 복원하는 것이 가능한가요? 실제로 오늘 이 작업을 시도해 보았지만 rake uploads:recover_from_tombstone 명령이 어떤 파일도 복원하지 않았습니다. 이것이 업로드 데이터베이스 항목의 더 큰 문제를 가리키는 것일 수 있을까요?

안녕하세요. 저도 동일하거나 유사한 문제를 겪고 있는데, 파일이 삭제되지 않는 이유가 무엇인지 파악이 되지 않습니다. 이 문제를 여전히 겪고 계신 다른 분이 있을까요?

SQL 쿼리를 몇 개 실행해 보니, "고정"된 업로드 참조는 모두 Draft(초안)인 것 같습니다. 하지만 제 초안과 다른 사용자의 초안을 확인해 보았는데 아무것도 없었습니다. Draft 테이블은 비어 있습니다.

고아 데이터(Orphan) 정리가 활성화되어 있고, 설정은 고아 데이터를 가능한 한 빨리 삭제하도록 되어 있습니다.

SQL 쿼리를 첨부했습니다.

SELECT 
    uploads.original_filename,
    ROUND(uploads.filesize / 1000000.0, 2) AS size_in_mb,
    uploads.extension,
    uploads.created_at,
    uploads.url,
    upload_references.upload_id,
    upload_references.target_id,
    upload_references.target_type,
    upload_references.created_at,
    upload_references.updated_at
FROM upload_references
JOIN uploads ON uploads.id = upload_references.upload_id
ORDER BY uploads.filesize DESC
LIMIT 250

sql.csv (46,1 KB)

이 문제는 포럼을 설치한 이후로 계속되고 있습니다. 커스텀 테마나 플러그인이 설치되어 있지 않았을 때도 마찬가지였습니다.
아니, 심지어 몇 번이나 업로드했던 구 포럼 로고(가장 처음 업로드한 파일)조차 여전히 Draft로 참조되고 있으며 업로드 폴더에 남아 있습니다. :man_facepalming:

이론적으로는 모든 업로드 참조를 필터링하고 target_type이 Draft인 항목을 찾아서 데이터베이스에서 삭제한 뒤, sidekiq 작업이 정리하도록 할 수 있을 것 같습니다(제 생각이 맞나요?).
하지만 저는 셀프 호스팅 인스턴스를 사용 중이고 Discourse에는 아직 초보자라, 여기서 질문하는 것이 더 나은 것 같아 이렇게 글을 남깁니다…

이것은 임시 해결책이겠지만, 여전히 이런 일이 왜 발생하는지에 대한 의문이 남습니다.

누군가 조언을 주실 수 있기를 바랍니다. 디스크 공간이 기하급수적으로 증가하고 있어서요. :smile:

네, 저희도 여전히 같은 문제를 겪고 있습니다.

어떻게든 해결하고 싶은데, 저희 포럼에는 업로드가 많이 들어오지만 장기적으로 보존해야 할 파일은 그중 일부뿐이라 디스크 공간이 많이 낭비되고 있습니다. 문제 해결을 위한 조언이 있다면 환영합니다.

실용적이라면 임시 조치로 이 방법에 관심이 있습니다. :thinking:

2주 전에 포럼을 설치했는데, 처음부터 이 문제가 있었습니다. 어떤 버그인 것 같습니다.
동일한 SQL 쿼리를 실행해서 막힌 “Drafts” 참조가 많이 있는지 확인해 주실 수 있을까요? 확인하기가 쉽습니다. 제 경우엔 수십 개의 항목이 있지만 drafts 테이블에는 실제로 2~3개의 초안만 있습니다. 편집 후(더 이상 초안이 아니게 되었을 때)에도 데이터베이스에 참조가 남아 있는 것처럼 보입니다. 예를 들어 게시물을 편집할 때마다 참조가 삭제되지 않고 계속 남아 있습니다.

데이터베이스에서 참조 항목을 삭제하는 방법을 알아내야 합니다. 먼저 파일 하나에 대한 참조를 삭제한 후, 정리 작업이 제대로 작동하는지 확인해야 합니다.
이 작업이 얼마나 안전한지 모르겠지만, 이 수없이 많은 Drafts 항목들은 그냥 잘못된 것 같습니다.

스태프/개발자에게 로그를 제공할 수 있습니다. 다만 저는 Discourse에 익숙하지 않아 어떤 로그 파일이 도움이 될지 모르겠습니다.

수정:
데이터베이스 구조를 이해하려고 노력 중인데, 추가적인 문제 없이 해당 업로드 항목을 삭제해도 될까요? (중요한 DB 관계를 놓치고 싶지 않습니다.) 또한 draft_sequences가 정확히 무엇인지도 이해가 되지 않습니다.
하지만 테스트하려면 프로덕션 포럼을 로컬 VM으로 복제해야만 합니다…

관련된 또 다른 주제인데, 이 주제를 몰라서 거기에 게시했습니다.

이미지를 자동으로 완전히 삭제하는 유일한 방법은 삭제 전에 게시글에서 수동으로 편집하여 제거하는 것인 것 같습니다. 하지만 그 방법도 확실하지는 않습니다. 저 역시 정화(purging) 설정에서 당신과 동일한 설정을 사용하지만(S3 호환 스토리지를 사용함), 해당 이미지를 포함하는 유일한 게시글(여러 게시글이 동일한 이미지를 포함할 수 있으며, 아마도 아바타와 사용자 배너도 해당될 것입니다)이 삭제된 경우에도 이미지가 절대 정화되지 않는다는 점을 확인했습니다.

@RGJ 님이 제공해 주신 이 솔루션을 사용하여 이미지가 추가 게시글에서 사용되는지 검색합니다.

이 기능이 자동으로 처리될 수 있다면 정말 좋겠습니다. 특히 Discourse는 많은 게시글이 동일한 이미지를 사용할 경우 중복 파일을 생성하는 것을 방지하는 등 이미지를 스마트하게 처리하기 때문입니다. 그 반대 측면으로는, 많이 사용된 개별 이미지를 제거하는 것이 매우 번거롭다는 점이 있습니다.

과거에 여러 계정을 통해 긴급한 삭제가 필요한 스팸 콘텐츠를 올린 사람이 있었는데, 이를 처리하고 완전히 제거되었는지(모든 원본 파일, 최적화된 파일, CDN 캐시, 게시글, 아바타, 사용자 배너 등) 확인하는 것은 매우 스트레스가 많았습니다.

개인적으로 이 기능이 매우 유용할 것 같아 이 기능 제안을 했습니다. 이 기능이 구현되고, 삭제된 게시글에 포함된 콘텐츠도 자동으로 정화된다면, 모든 경우가 커버되어 SSH 접근 없이도 처리할 수 있다고 생각합니다.