워드프레스 Site Health에서 꽤 오랫동안 아래 경고가 사라지지 않았다.
예정된 이벤트가 늦습니다
예약한 이벤트[사용 중인 플러그인 중 무언가의 이름]은(는) 늦게 실행되었습니다. 사이트는 작동하고 있지만, 글 예약이나 자동 업데이트가 의도대로 작동되지 않는다는 것을 의미할 수도 있습니다.
경고에 등장하는 플러그인이 일정치 않았다. 어느 때는 koko_analytics_aggregate_stats였고, 어느 때는 docketcache_gc였다. 둘 다 cron 기능으로 주기적으로 실행되어야 하는 플러그인이다.
내 워드프레스는 기본 WP-Cron을 사용하지 않고 시스템 cron으로 예약 작업을 실행한다.
wp-config.php에는 다음 설정을 넣어 두고,
define('DISABLE_WP_CRON', true);
서버의 crontab에서는 5분마다 WP-CLI로 due 상태의 cron event를 실행한다.
실제로 로그를 보면 작업은 계속 정상적으로 실행되고 있었다.
Executed the cron event 'koko_analytics_aggregate_stats' in 0.075s.
Executed the cron event 'docketcache_gc' in 1.005s.
Success: Executed a total of 2 cron events.
그런데 Site Health에서는 계속 “예정된 이벤트가 늦습니다”라고 했다.
서버가 느린 문제 아님
이 블로그의 호스팅 환경은 메모리 1GB와 공유형 1CPU(가상 2코어)를 제공한다. 네트워크는 아쉽지 않지만 성능이 쾌적하다고 할 수준은 아니다.
처음에는 cron 실행 자체는 되고 있지만 서버가 느려 이벤트 처리가 늦는 거고, 좀 더 성능이 나은 서버로 옮기면 이 Site Health 경고도 자연스럽게 없어질 것이라고 기대했다.
그런데 시험삼아 4CPU와 수십GB 메모리의 훨씬 성능이 좋은 서버에 워드프레스를 옮기고 시스템 cron까지 다시 설정했는데도 똑같은 경고가 나타났다. 서버 성능 문제는 아니었다.
WP-CLI 기준 ‘이상 없음’
서버에서 WordPress가 실제로 어떤 cron event를 가지고 있는지 확인했다.
koko_analytics_aggregate_stats, docketcache_gc를 비롯한 이벤트들은 계속 다음 실행 시각이 잡혀 있었고 시스템 cron도 5분마다 정상 실행됐다.
Site Health가 사용하는 WordPress 코어 메서드를 서버에서 WP-CLI에서 호출한 결과는 정상이었다.
관리자 웹 화면에서는 예정된 이벤트가 제때 실행되지 않았다고 판정하는데, WP-CLI에서 같은 Site Health 검사를 실행하면 정상이라고?
같은 검사가 웹 요청에서만 ‘이상 있음’
이번엔 WP-CLI와 동일한 Site Health 검사를 실행하는 코드를 웹으로 요청하는 임시 PHP 파일을 만들고 PHP-FPM을 통해 WP_Site_Health의 값을 직접 확인했다.
그랬더니 관리자 화면과 똑같은 지연 판정 결과가 나왔다.
A scheduled event is late
당시 Koko Analytics 이벤트는 고작 79초 늦어 있었다. DISABLE_WP_CRON=true 설정이 적용된 상태라면 Site Health가 1분 남짓의 지연을 곧바로 late로 판정할 이유가 없었다.
설치된 WordPress의 WP_Site_Health 생성자 코드를 확인해 보니 예정된 이벤트 실행 판정 기준 기본값(초 단위)은 다음과 같았다. 정각에 실행된 게 아니면 5분(300초)까지는 지연된 실행으로 보고 5분을 초과하면 실행되지 않은 걸로 보는 구조다.
timeout_late_cron = 0
timeout_missed_cron = -300
하지만 DISABLE_WP_CRON이 true이면 다음 값으로 바뀐다. 즉 외부 cron을 사용하는 경우 15분(900초) 지연까지는 late로 보지 않고, 1시간(3600초) 이상 늦어져야 missed로 판단하도록 여유를 둔다.
timeout_late_cron = -900
timeout_missed_cron = -3600
그런데 실제 내부값을 웹과 CLI에서 각각 찍어 보니 결과가 달랐다.
웹 PHP-FPM:
DISABLE_WP_CRON value: true
timeout_late_cron: 0
timeout_missed_cron: -300
WP-CLI:
DISABLE_WP_CRON=true
timeout_late_cron=-900
timeout_missed_cron=-3600
같은 워드프레스 설정 환경이니 설정된 상수 자체를 확인하면 당연히 둘 다 true다. 그런데 이미 만들어진 Site Health 객체의 지연 판정값만 달랐다.
원인은 wp-config.php 편집 실수
wp-config.php를 확인했다.
62: require_once(ABSPATH . 'wp-settings.php');
63: define('DISABLE_WP_CRON', true);
DISABLE_WP_CRON 설정이 wp-config.php 파일의 맨 끝, wp-settings.php를 불러온 뒤에 정의돼 있었다. 이게 문제였다.
WP_Site_Health 생성자에는 다음 코드가 있다.
$this->timeout_late_cron = 0;
$this->timeout_missed_cron = - 5 * MINUTE_IN_SECONDS;
if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
$this->timeout_late_cron = - 15 * MINUTE_IN_SECONDS;
$this->timeout_missed_cron = - 1 * HOUR_IN_SECONDS;
}
객체가 만들어지는 순간 DISABLE_WP_CRON을 확인해 내부의 timeout 값을 정한다.
내 설정에서는 wp-settings.php가 실행되는 동안 웹 요청에서 Site Health 객체가 먼저 만들어질 수 있는데, 그 시점에는 아직 DISABLE_WP_CRON이 정의되지 않은 상태다. 그러면 객체에는 기본값(정각 실행시 정상 판정, 5분 이내 실행시 지연 판정, 초과시 미실행 판정)이 적용된다.
wp-settings.php의 실행이 끝나고 다음 줄이 실행된다.
define('DISABLE_WP_CRON', true);
이러면 나중에 disable_wp_cron 설정 적용 여부를 나타내는 상수 자체는 true로 나오지만, 이미 만들어진 WP_Site_Health 객체의 timeout_late_cron과 timeout_missed_cron 값은 이미 적용된 기본값으로 유지되고, disable_wp_cron 설정 기준으로 바뀌지 않는다.
반면 내가 WP-CLI에서 직접 Site Health 객체를 가져와 검사했을 때는 wp-config.php에 들어 있는 DISABLE_WP_CRON 설정 기준을 제대로 적용한 검사 결과를 반환다. 이것이 웹과 CLI 결과가 달랐던 이유였다.
이걸 확인하고 wp-config.php 파일 내용을 고쳤다.
수정 전:
require_once(ABSPATH . 'wp-settings.php');
define('DISABLE_WP_CRON', true);
수정 후:
define('DISABLE_WP_CRON', true);
/* ... */
require_once(ABSPATH . 'wp-settings.php');
이 상태에서 Site Health를 다시 확인했다. 경고가 바로 사라졌다.
알게 된 것
DISABLE_WP_CRON을 설명하는 글은 대부분 이런 코드 한 줄을 보여준다.
define('DISABLE_WP_CRON', true);
그리고 wp-config.php에 추가하라고 설명한다. 중요한 것은 단순히 이 코드가 파일 안에 존재하느냐가 아니다. 위치가 중요하다. WordPress가 그 값을 읽는 시점보다 먼저 정의되는 위치에 있어야 한다.
표준 wp-config-sample.php에 포함된 아래 문구가 이 점을 강조하기 위한 경계선이다.
/* Add any custom values between this line and the "stop editing" line. */
/* That's all, stop editing! Happy publishing. */
이 뒤에 WordPress 본체 초기화를 시작하는 wp-settings.php를 불러오기 때문에, 사용자 정의 상수가 올바르게 적용되려면 그 설정은 이 문구보다 앞에 둬야 한다.
나처럼 wp-cron 해제 설정 같은 사용자 정의 상수를 wp-settings.php 실행 이후에 두면 안된다. 이미 초기화된 블로그 환경에 저 상수를 통해 제어되는 설정값이 먹히지 않게 되고, 이 상태에서는 이번처럼 내부적으로 엉뚱한 동작을 하는데 원인을 파악하기 어려워진다.
몇주 전 작성, 261017 편집 완료.