AI를 사용해 미디어를 만들 때, 기사 생성 API 호출만으로는 해결되지 않는 문제가 있습니다. 생성 중에 관리 화면을 닫으면 어떻게 할지, 번역 중에 원문이 바뀌면 어떻게 할지, 공개된 기사와 캐시 내용물을 어떻게 일치시킬지 등이 문제입니다. 운영을 지속하려면 이러한 처리 연결 방식이 필요합니다.
제가 운영하는 AI Syncs는 AI 뉴스나 생성형 AI 활용, AI 검색 등을 다루는 미디어입니다. WordPress를 중심으로 일본어를 원문으로 하여 11개 언어로 전개할 수 있는 구조로 구성했습니다.
본 기사에서는 기사 제작, 번역, 열람의 경로를 정리하면서 비동기 작업(asynchronous job), HTML 캐시, 다국어 SEO, 예약 공개 설계를 소개합니다. 같은 방식으로 WordPress에 AI 처리를 통합하고 싶은 분들에게 참고가 되기를 바랍니다.
※ 구성 및 공개 페이지 확인일은 2026년 10월 21일입니다. 일본어를 포함한 12개 언어에 대응하지만, 모든 기사에 전 언어의 번역이 갖춰져 있음을 의미하지는 않습니다.
기사는 WordPress 표준 포스트로 저장하며, 이미지는 미디어로 취급합니다. 공개 화면에는 SWELL을 사용하고, 기사 작성이나 번역 같은 독자적인 기능은 플러그인으로 분리했습니다.
대략적인 구성은 다음과 같습니다.
독자 플러그인의 역할은 다음과 같이 나누었습니다.
| 플러그인 | 담당하는 처리 |
|---|
| Editorial Desk | RSS 등의 정보 수집, 조사, 기사/이미지 작성, 초안, 공개 예약 |
| ... | |
| 여기서 분할 단위는 WordPress 플러그인입니다. 같은 WordPress 환경을 사용하면서도, 처리의 책임과 저장하는 데이터를 나누고 있습니다. | |
기사 본문을 표준 포스트에 저장해 두면, 편집 화면, 테마, 카테고리, 미디어 등 WordPress의 시스템을 그대로 사용할 수 있습니다. 독자적인 제작 화면에서 만든 기사도 공개 후에는 일반적인 WordPress 기사로 취급할 수 있습니다.
기사 본문이나 이미지 생성은 외부 API 응답을 기다리는 처리입니다. 이를 한 번의 관리 화면 요청에 몰아넣으면, 통신 단절이나 타임아웃이 발생할 때마다 어디까지 완료되었는지 알 수 없게 됩니다.
Editorial Desk에서는 요청을 데이터베이스의 작업(job)으로 저장하고, Action Scheduler를 이용해 백그라운드에서 진행합니다. 브라우저를 닫아도 요청과 처리된 정보는 남아 있습니다. Action Scheduler는 WordPress용 작업 큐입니다.
기사 작성 흐름에는 조사 및 구성 확인, 본문 작성, 완성된 본문으로부터 제목 생성, 이미지 작성 등의 단계가 있습니다. 구성안 확인을 생략하는 경로도 준비되어 있지만, 생성된 기사는 우선 초안 상태가 됩니다.
공정별 결과를 저장해 두면, 도중에 멈춰도 재사용할 수 있습니다. 예를 들어 본문이 완성된 후 제목 작성에서 실패한 경우, 저장된 본문을 사용하여 계속 진행할 수 있습니다.
또한, 본문 작성 시에는 요청 당시의 프로바이더나 모델을 보존합니다. 관리자가 설정을 변경하더라도, 진행 중인 요청이 중간부터 다른 조건으로 작동하는 것을 방지하기 위함입니다.
이 방식에서는 화면에 '처리 중'이라고 표시할 뿐만 아니라, 서버 측에서 어느 공정까지 저장했는지를 관리해야 합니다.
외부 API로 전송한 후 통신이 끊긴 경우, API 측에서는 처리가 완료되었을 가능성이 있습니다.
Editorial Desk에서는 결과가 불분명한 요청을 중지하고, 예상 비용의 예약 슬롯을 유지합니다. 관리 화면에서도 저장된 결과를 사용하여 나머지를 진행하는 '재개(Resume)'와 새로운 API 요청을 보내는 '새로 재시도(Retry New)'를 구분했습니다.
이는 재시도로 인한 중복 생성과 추가 비용을 줄이기 위한 설계입니다. 모든 플러그인이 공통의 비용 장부를 사용하는 것은 아니며, 번역 등 다른 플러그인의 비용은 개별적으로 관리합니다.
다국어화에는 Polylang을 사용하고 있습니다. 일본어와 각 언어의 기사는 별도의 포스트 ID를 가지며, 번역 그룹으로 연관시킵니다. 독자 플러그인도 Polylang의 공개 API를 사용하여 이 관계를 등록합니다.
번역은 설정에 따라 일본어 기사의 공개/예약/업데이트를 감지하여 큐에 등록합니다. 포스트 저장 처리 중에 번역 API를 호출하지 않고, 본문 단위나 이미지 단위로 처리를 진행합니다.
기사 간의 일관성(consistency)에 대해서는 다음과 같이 처리하고 있습니다.
| 발생한 상황 | 번역 측의 처리 |
|---|
| 번역 중에 원문이나 번역 설정이 변경되는 경우 | 이전 결과 저장을 멈추고, 변경된 내용을 처리합니다 |
| ... | |
번역문을 공개 전에 확인하고 싶다면, 일본어 기사를 여유 있는 날짜로 예약하고 완성된 번역본을 미리 볼 수 있는 운영이 필요합니다.
또한, Growth Desk에서 새로 만든 초안은 자동 번역 대상에서 초기 상태로 제외됩니다. 먼저 일본어로 내용을 검증한 후, 번역할 기사를 선택할 수 있도록 했습니다.
공개 화면에서는 테마와 각 플러그인이 출력한 완성된 HTML을 Nginx의 FastCGI 캐시에 저장합니다.
캐시가 HIT하면, 해당 HTML을 반환하기 위해 PHP-FPM이나 WordPress를 실행할 필요가 없습니다. MISS이거나 캐시 대상이 아닌 요청에서는 평소대로 WordPress가 처리합니다. Redis는 WordPress가 작동할 때 객체 캐시로 사용됩니다.
이 방법으로는 테마의 렌더링 결과를 캐싱하므로, 기사 페이지를 다른 프런트엔드로 다시 만들 필요가 없습니다. 이미지 참조, CSS・JavaScript, 구조화 데이터 등도 원래 HTML에 포함된 형태로 유지합니다.
다만, HTML을 저장한다고 해서 이미지나 CSS 파일 자체가 복사되는 것은 아닙니다. 정적 파일 전송이나 REST API, AJAX와 같은 동적인 처리는 계속 필요합니다.
대상은 공유해도 되는 공개 페이지의 HTML입니다. 로그인 중인 열람, 미리 보기, 검색 결과, 관리 화면, REST API 등은 대상에서 제외됩니다. 알 수 없는 쿠키나 개인별 표시가 섞이는 요청도 무조건 공유하지 않습니다.
같은 미생성 URL로 요청이 집중되는 것에 대비하여 Nginx의 fastcgi_cache_lock도 사용하고 있습니다. 개별 디렉티브의 동작은 Nginx 공식 문서를 확인하실 수 있습니다.
SWELL과의 호환 대응에서는 공개 페이지용 nonce도 확인 대상이 되었습니다. nonce를 포함하는 페이지는 원칙적으로 제외하며, 대상을 확인한 예외에서는 유효 기간을 검증합니다. 그 호환 모드는 저장 기간을 최대 600초로 하고, 만료된 HTML 전송과 백그라운드 업데이트를 비활성화하는 조건입니다.
테마 고유의 처리까지 확인할 필요가 있는 점은, 완성 HTML을 캐싱하는 방식에서도 변함이 없습니다.
2026년 10월 21일에 일본어 메인 페이지를 가져왔을 때는 다음 응답 헤더를 확인할 수 있었습니다.
X-AISS-Nginx-Cache: HIT
CF-Cache-Status: DYNAMIC
이 조회 시점에서는 Cloudflare를 거쳐 오리진의 Nginx 캐시가 HIT하고 있습니다. Cloudflare 측에서 HTML이 캐싱 HIT한 상태와는 구분하여 처리합니다.
캐시는 저장 후 기사가 업데이트되었을 때의 처리도 필요합니다.
Static Accelerator에서는 기사 단위, 아카이브, 사이트 전체의 세대 정보를 관리하며, 업데이트 시 해당 세대를 전환합니다. Nginx 캐시 키에는 URL이나 단말 구분 외에 실체 해결된 세대의 경로를 포함합니다.
예를 들어, 다음과 같은 충돌이 발생했다고 가정해 봅시다.
- 오래된 본문을 사용한 페이지 생성이 시작된다.
- 편집자가 본문을 업데이트하고, 세대를 전환한다.
- 절차 1의 생성이 업데이트 후에 완료된다.
세대를 키에 포함해 두면, 오래된 생성 결과가 저장되어도 구세대 키에 들어갑니다. 업데이트 후 열람은 새로운 세대를 참조합니다.
또한, 전체 만료 시에도 모든 캐시 파일을 그 자리에서 삭제할 필요가 없습니다. 먼저 참조 대상을 전환하고, 오래된 데이터의 청소는 분할하여 진행합니다. 공개/업데이트한 기사와 메인 페이지는 중복을 제외한 큐에 등록하여 사전 생성합니다.
의존 관계에는 한계도 있습니다. 일반적인 본문 업데이트에서는 다른 기사에 표시된 관련 기사 발췌 등이 TTL까지 남아 있는 경우가 있을 수 있습니다. 즉시 반영이 필요한 변경은 전체 만료 등을 선택해야 합니다.
WordPress의 WP-Cron은 보통 페이지 접근을 계기로, 만료된 처리를 실행합니다. WordPress 공식 설명에도 이 동작과 실행이 지연되는 조건이 기재되어 있습니다.
Nginx 캐시 HIT에서는 WordPress가 시작되지 않습니다. 접속이 늘어나도 그것이 그대로 예약 공개나 번역의 실행 기회가 된다고 할 수는 없기 때문에, AI Syncs에서는 서버 측 Cron을 1분 간격으로 작동시키고 있습니다.
게다가, Cron 실행 확인만으로는 부족한 경우도 있었습니다.
2026년 10월 20일에는, 서버 Cron과 워커가 작동하고 있는 와중에 예약 시간을 지났음에도 미공개 상태로 남아있는 기사가 있었습니다. 확인해 보니, 해당 기사에 대응하는 WordPress 표준의 publish_future_post
이벤트가 없었습니다. 이벤트가 손실된 구체적인 원인은 확보한 로그만으로는 특정할 수 없습니다.
이러한 현상에 대응하여, Editorial Desk의 관리 대상 기사에 예약 공개 감시 처리를 추가했습니다.
- 예약 저장 후, 공개 이벤트가 존재하는지 확인합니다.
- 정기 처리에서도 관리 대상을 확인하는 예약 기사를 검토하고, 향후 예약 시 이벤트가 없으면 등록을 복구합니다.
- 예약 시간을 지난 대상 기사는
wp_publish_post()를 사용하여 WordPress 표준 공개 처리로 되돌립니다.
표준 공개 처리를 거치면서, 공개와 연동되는 번역이나 캐시 무효화(失効) 훅도 실행됩니다.
이 경험을 통해 스케줄러의 작동 여부, 예약 이벤트의 존재 유무, 기사의 공개 상태를 각각 관측하도록 했습니다.
일본어 최상위 페이지는 루트 URL이고, 영어 버전의 AI Syncs는 /en/와 같이 언어별로 URL을 부여했습니다. 언어 선택 기억은 브라우저 측에서 처리하며, 방문자마다 동일한 URL의 HTML을 덮어쓰지 않도록 했습니다.
Language Switcher에서는 Polylang의 번역 관계를 사용하여 hreflang를 조정합니다. 대상은 동등한 내용을 가진, 공개되었고 인덱싱 대상인 페이지입니다.
예를 들어 일본어 기사의 프랑스어 번역이 아직 없는 경우, 프랑스어 최상위 페이지로 UI에서 안내할 수는 있습니다. 하지만 그 최상위 페이지를 기사의 번역으로 hreflang에 등록하면, 다른 내용을 대응시키게 됩니다.
canonical이나 구조화 데이터 같은 메타 정보도 기존 SEO 플러그인과의 중복 출력을 피합니다.
확인해야 할 것은 설정 화면뿐만이 아닙니다. 이번에 확보한 일본어/영어 최상위 페이지에서는 각각 자신의 URL을 가리키는 canonical이 하나씩 있었습니다. 기사 목록의 2페이지도, 최상위 페이지로 정규화하지 않고 해당 페이지 자신을 가리키는 canonical이었습니다.
다국어화와 캐시를 결합할 때는 최종적으로 공개되는 HTML에서 URL, 언어, 메타 정보가 일치하는지 확인해야 합니다.
Growth Desk에서는 관리 대상 초안에 출처 URL, 확인일, 대상 환경 및 조건, 자체 검증 내용을 저장할 수 있습니다.
확인 완료 기록은 제목・본문・발췌와 확인 정보로부터 계산한 SHA-256 해시 값과 연결됩니다. 확인 후 본문이나 근거를 변경하면 해시가 일치하지 않아, 확인 완료 상태가 무효화됩니다.
이를 통해