같은 옵션에 경쟁을 붙이면 어떻게 될까?

발생 원인을 알아보자

의문점이 생길 수 밖에 없습니다. 의문점을 요약해보자면,

  • 값의 누락은 왜 발생하는가?
  • 왜 autoload=’yes’가 더 빈번한 누락이 발생하는가?
  • 이런 현상을 방지하는 방법은?

우선 이런 현상이 왜 일어나는지부터 알아보도록 하지요. AJAX 요청을 처리하는 핵심적인 코드는 아래와 같습니다. 참고로 get_option(), update_option() 시 사용하는 이름은 섹션마다 서로 다릅니다.

$id    = intval( $_GET['id'] ?? '0' );
$value = get_option( 'rcond_autoload_yes', [] );
if ( ! is_array( $value ) ) {
	$value = [];
}
$value[ $id ] = true;
update_option( 'rcond_autoload_yes', $value );
Code language: PHP (php)
  1. GET 요청으로부터 id를 받는다.
  2. 옵션 값을 읽는다. 없으면 빈 배열로 초기화.
  3. 옵션 값인 연관 배열에 키로 아이디를, 값으로는 적당히 true를 준다.
  4. 갱신된 옵션을 저장한다.

문제의 핵심은 2번째 줄의 get_option()에서 발생합니다. 자, 생각해 보죠. 여러 AJAX 요청이 동시다발적으로 일어났습니다. 동시에 admin-ajax.php 스크립트가 일어났을 것입니다. 참고로 저는 아파치 서버 2.4에서 libphp, prefork 방식으로 이 코드를 사용하고 있습니다. 여러 프로세스가 동시에 실행되고, 데이터베이스 연결을 하고, 거의 동시에 get_option()을 실행할 것입니다.

이 때 경쟁적으로 앞다투어 get_option()을 실행하고 upate_option()을 실행해 버립니다. 아무리 update_option() 이 순차적으로 일어난다 하더라도 get_option() 에서 경쟁이 일어났기 때문에 값의 누락은 피할 수 없습니다.

게다가 autoload=’yes’라면 상황을 나쁘게 만듭니다. 왜냐면 autoload=’yes’이기 때문에 이미 이 경쟁이 일어나기도 한참 전에 이미 캐싱되어 있는 값을 사용하기 때문입니다. 여기 말고도 get_option() 함수는 여기저기 많이 불리기 때문이죠. 그래서 서로 거의 초기값을 가져다시피 한 걸 겁니다. 그리고 자신의 ID를 더해 각자 경쟁적으로 업데이트 처리를 합니다.

반면 autoload=’no’가 그래도 데이터를 더 많이 보존했습니다. 오토로딩을 쓰지 않았기 때문에 get_option() 시점에서 막 데이터베이스에서 쿼리한 신선한 데이터를 썼기 때문일 것입니다.

여기까지 보자면 둘 다 일반적인 race condition이 일어나는데, autoload=’yes’인 녀석은 캐싱된 값을 쓰기 때문에 경쟁 시점에서 한참 밀려난, 말하자면 거의 초기값인 빈 배열을 가져다 오기 때문에 값의 누락이 심해진 거라 볼 수 있습니다.

알겠습니다. 그럼 값의 빈번한 업데이트가 일어난다면, 액면 그대로 보자면 autoload=’no’가 좀 더 유리하겠군요. 하지만 이 쪽도 데이터의 유실이 있기 때문에 안정적이라 볼 수 없습니다. 유실을 방지하기 위해서는 어떻게 해야 할까요?

댓글 남기기