빈 discourse_permalink으로 인한 댓글 동기화 실패

환경

  • WP-Discourse 버전: 2.5.7

  • Discourse 버전: 3.5.0.beta5-dev

  • WordPress 버전: 6.9

  • 호스팅: WP Engine (WordPress 멀티사이트)

  • Discourse 인스턴스: 셀프 호스팅 / Discourse 호스팅 (forum.avweb.com)

요약

WordPress에서 게시된 게시물이 Discourse에서 주제(Topic)를 성공적으로 생성하지만, discourse_permalink이 게시물 메타(post meta)에 빈 문자열로 저장됩니다. 이로 인해 discourse-comment.php의 sync_comments() 함수가 퍼마링크가 비어 있으면 조기에 종료(bail early)하도록 되어 있어, 모든 후속 댓글 동기화가 조용히 실패합니다. Discourse 주제 ID와 웹훅 동기화 플래그는 올바르게 기록되지만, 퍼마링크만 누락됩니다.

멀티사이트 네트워크의 단일 사이트에서 174개의 영향받는 게시물을 확인했습니다. 이 문제는 2025년 12월 중순경부터 발생하기 시작한 것으로 보입니다.

재현 단계

  1. Discourse 주제 생성이 구성된 WordPress 게시물 게시

  2. Discourse에서 주제가 성공적으로 생성됨

  3. discourse_topic_id가 게시물 메타에 저장됨 ✓

  4. wpdc_sync_post_comments가 1로 설정됨 (웹훅이 올바르게 발화) ✓

  5. discourse_permalink이 빈 문자열로 저장됨 ✗

  6. discourse_comments_raw가 기록되지 않음 (동기화가 완료되지 않음)

예상 동작

주제 생성이 성공적으로 완료된 후, discourse_permalink에는 전체 Discourse 주제 URL(예: The China Chickens Come Home - AVweb - News Discussion - AVweb.com Discussion )이 포함되어야 합니다.

실제 동작

discourse_permalink은 메타 키로 존재하지만 그 값은 비어 있습니다. lib/discourse-comment.php의 209번째 줄의 이 가드(guard)를 만나 모든 후속 sync_comments() 호출이 조기에 반환됩니다:

if ( ! $discourse_permalink ) {
    return 0;
}

동기화가 완료되지 않기 때문에 discourse_last_sync가 기록되지 않으며, 플러그인은 모든 페이지 로드 시 재시도를 하지만 매번 실패합니다.

진단

다음 코드 경로를 통해 문제를 추적했습니다:

  1. DiscourseCommentFormatter::format()이 do_action('wpdc_sync_discourse_comments')를 호출하여 DiscourseComment::sync_comments()를 트리거합니다.

  2. sync_comments()가 discourse_permalink을 확인합니다 — 비어 있음을 발견하고 0을 반환합니다.

  3. format()은 게시물 커스텀(post custom)에서 discourse_comments_raw를 확인합니다 — 누락됨을 발견하고 bad_response_html()을 반환합니다.

  4. 영향받는 게시물에 대해 댓글이 표시되지 않습니다.

주제 생성 플로우가 discourse_topic_id를 기록하는 것은 성공하지만 퍼마링크를 영구적으로 저장하는 데 실패하고 있습니다. /t/{topic_id}.json에 대한 Discourse API를 쿼리하여 올바른 퍼마링크를 재구성하고 게시물 메타에 다시 기록함으로써 174개 게시물 모두에 대한 동기화 문제를 해결할 수 있었습니다.

임시 해결책 (Workaround)

다음과 같은 WP-CLI 복구 스크립트를 작성했습니다:

  1. wpdc_sync_post_comments = 1이지만 discourse_comments_raw가 누락된 게시물 찾기

  2. /t/{topic_id}.json에서 주제 슬러그(slug) 가져오기

  3. 퍼마링크 재구성 및 저장

  4. /t/{slug}/{topic_id}/wordpress.json에서 댓글 가져오기 및 저장

이것을 임시 조치로 예약된 크론(scheduled cron)으로 실행하고 있습니다.


조사 중 발견된 추가 문제

1. 전역 MySQL 잠금(Lock)으로 인한 조용한 동기화 실패

파일: lib/discourse-comment.php, 176번째 줄

$got_lock = $wpdb->get_row( "SELECT GET_LOCK( 'discourse_lock', 0 ) got_it" );

sync_comments()는 전체 설치 환경의 모든 게시물에서 공유되는 단일 전역 MySQL 잠금(discourse_lock)을 사용합니다. 타임아웃은 0(비차단식)이므로, 현재 어떤 게시물이 동기화 중이라면 다른 모든 게시물은 조용히 동기화를 건너뜁니다 — 로깅도, 재시도도 없습니다.

하루에 여러 게시물을 게시하는 대량 트래픽 사이트에서는, 게시물이 일관되게 잠금을 잃고 동기화되지 않는 레이스 컨디션(race condition)이 발생합니다. 10분 단위 동기화 주기와 결합되면, 게시물이 처음 몇 번의 시도에서 잠금을 잃은 후 빈 퍼마링크 문제로 인해 향후 동기화가 방지되어 영구적으로 막힐 수 있습니다.

권장 수정: 게시물별 잠금 사용:

$got_lock = $wpdb->get_row( "SELECT GET_LOCK( 'discourse_lock_{$post_id}', 0 ) got_it" );

해당 해제 코드:

$wpdb->get_results( "SELECT RELEASE_LOCK( 'discourse_lock_{$post_id}' )" );

2. discourse_comments_raw의 이중 이스케이프(Double-Escaping)

파일: lib/discourse-comment.php, 222번째 줄

update_post_meta( $post_id, 'discourse_comments_raw', esc_sql( $raw_body ) );

update_post_meta()는 내부적으로 $wpdb->prepare()를 사용하므로, 값을 esc_sql()로 감싸면 이중 이스케이프가 발생합니다. 여러 동기화 사이클에 걸쳐 JSON 바디의 따옴표에 이스케이프 문자가 누적됩니다(\" → \\\" → \\\\\\\")가 JSON을 파싱할 수 없게 됩니다.

권장 수정:

update_post_meta( $post_id, 'discourse_comments_raw', $raw_body );

영향

이러한 문제들이 복합적으로 작용합니다: 빈 퍼마링크는 초기 동기화를 방지하고, 전역 잠금은 복구 노력을 막으며, 이중 이스케이프는 동기화에 성공한 게시물의 데이터를 손상시킬 수 있습니다. 우리 설치 환경에서는 근본 원인을 식별하기 전에 174개의 게시물(약 2개월 분량의 콘텐츠)이 영향을 받았습니다.

안녕하세요, 자세한 보고 감사합니다.

WP Discourse 플러그인은 문제가 발생하고 있는 기능 주변의 구현 방식을 변경하지 않았으므로, 그 시점에 무엇이 변경되었을지 생각해 보겠습니다. 혹시 그때 다른 플러그인을 업데이트하셨나요?

여기서 먼저 해결할 수 있는 문제는 WP Discourse 플러그인을 최신 버전인 2.6.1로 업데이트하는 것입니다.

감사합니다. 플러그인 업데이트를 진행하겠습니다. 참고로, 저희는 여러 사이트에서 이 플러그인을 사용 중이며, 해당 사이트들은 게시물 수가 많고 트래픽도 상당합니다.

알겠습니다.

이것이 핵심일 가능성이 높습니다. 2025년 12월 중순에 무슨 일이 있었나요?

WordPress는 본질적으로 다중 당사자(multi-party) 또는 복합(composed) 시스템입니다. 시스템의 한 부분이 다른 부분에 영향을 미치는 것은 비교적 쉽습니다. WP Discourse 플러그인에 문제가 없는 것은 아니라고 말하는 것이 아니라, 모든 것이 동일하다면 해결책을 고려하기 전에 무엇이 변경되었는지 그리고 그 변경 사항이 어떻게 문제를 일으켰는지를 이해해야 합니다.

플러그인을 업데이트했습니다. 상황이 나아질지 지켜보겠습니다. 데이터베이스를 정리하기 위한 커스텀 WP CLI 명령어를 만들었는데, 관심 있으시면 확인해 보세요. 초대장을 보내드렸습니다. 감사합니다.