서버 부하 이슈 때문에;; 통합검색 자동완성 애드온에 캐시를 적용해보려고 하는데요
화제의 글 애드온의 캐시 적용 코드를 가져와서 재활용을 했습니다.
(애드온 제작자 Canto님께 감사 말씀 드립니다~)
근데 제가 캐시 적용을 처음 해보는 것이어서 이게 제대로 되는 건지 확신이 없네요;;;
1. 일단은 페이지 로딩 시에 json 데이터를 가져오는 게 아니라 검색창에 focus를 했을 때 가져오도록 작업해놨구요ㅎ
1-1. 데이터는 클라이언트의 로컬저장소에 보관을 해놔서 가급적 서버와의 통신 시도를 제한하고 있어요.
2. (로컬저장소에 데이터가 없을 경우에 한해) js에서 별도의 php 파일로 json 데이터를 호출하는데 캐시 여부에 따라 쿼리를 실행하게끔 하는 의도입니다.
아래는 php 파일 전체 소스인데요.
캐시 적용을 제대로 한 건지 살펴봐주시면 감사하겠습니다.
<?php
define('__XE__', true);
require_once '../../config/config.inc.php';
$oContext = &Context::getInstance();
$oContext->init();
// 키워드 수집 대상 : tag or title
$target = $_REQUEST['target'];
// 캐시 타임 설정
if ( !$_REQUEST['cache_time'] )
{
$cache_time = 0;
}
else
{
$cache_time = 60 * (int)$_REQUEST['cache_time'];
}
// 자동완성 JSON 데이터 반환용 더미 변수
$autocompleteIS = array();
$args = new stdClass();
$args->list_count = (int)$_REQUEST['list_count'];
$args->module_srl = $_REQUEST['module_srl'];
// 캐시 관련 ( 캐시 설정이 되어있는 상태에서 캐시가 만료 되지 않았을 경우에는 캐시에서 데이터를 취득 )
$oCacheHandler = CacheHandler::getInstance();
// 사이트가 캐시를 지원하고 자동완성 캐시가 존재 할 때
if( $cache_time && $oCacheHandler->isSupport() && $oCacheHandler->isValid('autocompleteIS', $cache_time) )
{
// 캐시를 가져와서 더미 변수에 입력
$cache = $oCacheHandler->get('autocompleteIS', $cache_time);
$autocompleteIS = $cache;
}
// 캐시가 만료 되거나 캐시 시간이 설정 되어 있지 않는 경우 DB를 통해 데이터 취득
else
{
// 쿼리로 게시물 데이터 가져오기
if ( $target === 'tag' )
{
$output = executeQueryArray('addons.ap_autocompleteIS.getTagList', $args);
}
else if ( $target === 'title' )
{
$output = executeQueryArray('addons.ap_autocompleteIS.getDocumentTitle', $args);
}
// 결과 값이 있을 때 해당 키워드를 더미 변수에 저장 && 중복값 회피
if( $output->toBool() && $output->data )
{
foreach( $output->data as $val ) {
if ( !in_array($val->$target, $autocompleteIS) )
{
$autocompleteIS[] = $val->$target;
}
}
// 캐시를 지원하고 캐시 타임이 설정 되어 있을 경우 수집된 데이터를 캐시로 만들기
if( $oCacheHandler->isSupport() && $cache_time !== 0 )
{
$oCacheHandler->put('autocompleteIS', $autocompleteIS, $cache_time);
}
}
}
echo json_encode($autocompleteIS, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE);
unset($output);
unset($args);
unset($autocompleteIS);
$oContext->close();
?>
define('__XE__', true);
require_once '../../config/config.inc.php';
$oContext = &Context::getInstance();
$oContext->init();
// 키워드 수집 대상 : tag or title
$target = $_REQUEST['target'];
// 캐시 타임 설정
if ( !$_REQUEST['cache_time'] )
{
$cache_time = 0;
}
else
{
$cache_time = 60 * (int)$_REQUEST['cache_time'];
}
// 자동완성 JSON 데이터 반환용 더미 변수
$autocompleteIS = array();
$args = new stdClass();
$args->list_count = (int)$_REQUEST['list_count'];
$args->module_srl = $_REQUEST['module_srl'];
// 캐시 관련 ( 캐시 설정이 되어있는 상태에서 캐시가 만료 되지 않았을 경우에는 캐시에서 데이터를 취득 )
$oCacheHandler = CacheHandler::getInstance();
// 사이트가 캐시를 지원하고 자동완성 캐시가 존재 할 때
if( $cache_time && $oCacheHandler->isSupport() && $oCacheHandler->isValid('autocompleteIS', $cache_time) )
{
// 캐시를 가져와서 더미 변수에 입력
$cache = $oCacheHandler->get('autocompleteIS', $cache_time);
$autocompleteIS = $cache;
}
// 캐시가 만료 되거나 캐시 시간이 설정 되어 있지 않는 경우 DB를 통해 데이터 취득
else
{
// 쿼리로 게시물 데이터 가져오기
if ( $target === 'tag' )
{
$output = executeQueryArray('addons.ap_autocompleteIS.getTagList', $args);
}
else if ( $target === 'title' )
{
$output = executeQueryArray('addons.ap_autocompleteIS.getDocumentTitle', $args);
}
// 결과 값이 있을 때 해당 키워드를 더미 변수에 저장 && 중복값 회피
if( $output->toBool() && $output->data )
{
foreach( $output->data as $val ) {
if ( !in_array($val->$target, $autocompleteIS) )
{
$autocompleteIS[] = $val->$target;
}
}
// 캐시를 지원하고 캐시 타임이 설정 되어 있을 경우 수집된 데이터를 캐시로 만들기
if( $oCacheHandler->isSupport() && $cache_time !== 0 )
{
$oCacheHandler->put('autocompleteIS', $autocompleteIS, $cache_time);
}
}
}
echo json_encode($autocompleteIS, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE);
unset($output);
unset($args);
unset($autocompleteIS);
$oContext->close();
?>

윤삼
Lv. 19
아무래도 중급 초반 수준의 코딩 오타쿠인 것 같습니다.
댓글 44
캐시핸들러 호출 방식은 맞습니다. 그러나
1. 달라질 가능성이 있는 값에 따라 캐시 키(autocompleteIS)도 달라져야 합니다. 위의 소스를 보면 module_srl, list_count 등이 달라지는 것 같네요. 검색어(target) 변수는 어디에 들어가는지 잘 모르겠고요... 아무튼 데이터가 달라질 수 있는 경우의 수에 따라 캐시 키도 구분하여 써야 합니다. 엉뚱한 게시판의 검색 결과가 나올 수 있어요.
2. isValid()는 쓰지 마세요. 그냥 get() 해서 값이 있으면 쓰고, 없으면 버리면 그만입니다. 예전에 이것과 관련해서 어디에 댓글을 단 적이 있는데 찾을 수가 없네요. isValid()는 값이 있는지 없는지 가볍게 체크하는 함수가 아니라, 기껏 데이터 불러와서 버리는 매우 비효율적인 함수입니다.
그런데 서버 부하는 꼭 DB에 쿼리가 들어가지 않아도 ajax로 XE를 호출하는 것만으로도 상당히 많이 발생합니다. 인덱스 잘 타는 간단한 쿼리라면 ajax로 XE를 호출한다는 사실만으로 발생하는 부하가 95% 이상, DB 쿼리에서 발생하는 부하는 5% 미만이라고 보시면 됩니다. 캐시를 적용해도 5%밖에 아껴지지 않는 거죠.
따라서 ajax 호출 빈도를 근본적으로 줄이는 것이 훨씬 더 중요합니다. 꼭 ajax로 로딩하지 않아도 되는 데이터는 최초 페이지 로딩시 소스에 박아버린다던가, 키보드 입력을 하는 도중에는 잠시 기다렸다가 마지막에 한꺼번에 로딩한다던가... 그런 면에서 focus 이벤트와 로컬저장소 사용은 아주 좋은 방법입니다^^
캐시유지 기간이 20분 이라면..
1명이 요청한 것으로 수천명이 사용하게 되는 즉 수천의 요청이 사라지는...
이런것이 요청회수를 극적으로 줄이는 방법으로 이해했거든요.
브라우저 캐시와 로컬저장소는 사용자마다 따로 있기 때문에, 말씀하신 것처럼 사용자들 사이에 공유되지는 않아요. 서버에 저장된 캐시는 모든 사용자가 서버에 요청해서 불러와야 하지요. json 데이터만 별도 파일로 만들어서 어디 CDN에다가 올려놓는다면 몰라도...
캐시해 놓은 것을 달라고 하는 것 자체가 요청인가요? 그럼 요청 횟수를 줄이는게 캐시의 역할이 되지 못하네요.
그럼 요청 자체를 줄이는 방법은 없는거 아닌가요?
캐시 요청마저도 1인당 1회로 한정하도록 하게 하는 게 로컬 저장소 담당이라 보시면 될 것 같아요.
제가 우려하는 것은 로컬저장소 사용을 하지 못하는 특수한 경우 계속 반복적인 요청의 경우 캐시가 도움이 될 줄 알았는데 그게 아니라 질문을 드렸어요,
사실 제가 얼마전에 503을 만난건 부하가 굉장히 걸렸다기 보다는 우연하게 재수없게 내 차례에서 딱 끊어지고 만 그런 상황이었거든요.
요청 자체를 줄이려면 위의 댓글에 쓴 것처럼 아예 요청할 필요가 없도록 애드온에서 쓸 데이터를 페이지 소스에 박아버리거나, 아니면 json 파일을 따로 만들어 CDN으로 넘겨서 서버에 직접 요청할 필요가 없도록 해야 합니다. 물론 후자의 경우에는 서버 대신 CDN이 요청을 왕창 받겠지요.
요청하는 횟수는 똑 같기 때문에 많은 요청이 몰려서 503이 발생하는 건 여전하다는 것이네요.
로컬저장소 사용: 9%
검색창에 focus 이벤트가 발생하기 전에는 아예 요청을 안 하도록 변경: 90%
의 효과가 있을 것으로 예상됩니다.
https://xetown.com/rxe_point/989428
업그레이드 감사합니다!
일단 0.1.6 버전 기준으로 js 파일에서 169행, 173행, 180행을 각각
keyword.attr('autocomplete', 'off')one('focus', function() {
autoComplete($(this), 'title');
});
keyword.attr('autocomplete', 'off')one('focus', function() {
autoComplete($(this), 'nick_name');
});
keyword.attr('autocomplete', 'off')one('focus', function() {
autoComplete($(this), 'tag');
});
로 바꾸시면 임시방편은 될 것 같습니다.
// 페이지 로드 시, 검색 대상 확인 후 애드온 설정에 따라 자동완성 키워드 수집
if ( (target.val() === 'title_content' && autocomplete_title_content === 'Y')
|| (target.val() === 'title' && autocomplete_title === 'Y') )
keyword.attr('autocomplete', 'off')one('focus', function() {
autoComplete($(this), 'title');
});
autoComplete(keyword, 'title');
}
else if ( target.val() === 'nick_name' && autocomplete_nick_name === 'Y' )
keyword.attr('autocomplete', 'off')one('focus', function() {
autoComplete($(this), 'nick_name');
});
autoComplete(keyword, 'nick_name');
}
else if ( (target.val() === 'title_content' && autocomplete_title_content === 'T')
|| (target.val() === 'title' && autocomplete_title === 'T')
|| (target.val() === 'content' && autocomplete_content === 'T')
|| (target.val() === 'tag' && autocomplete_tags === 'Y') )
keyword.attr('autocomplete', 'off')one('focus', function() {
autoComplete($(this), 'tag');
});
autoComplete(keyword, 'tag');
}
one 앞에 마침표(.)가 빠졌어요;;;
// 페이지 로드 시, 검색 대상 확인 후 애드온 설정에 따라 자동완성 키워드 수집
if ( (target.val() === 'title_content' && autocomplete_title_content === 'Y')
|| (target.val() === 'title' && autocomplete_title === 'Y') )
keyword.attr('autocomplete', 'off').one('focus', function() {
autoComplete($(this), 'title');
});
}
else if ( target.val() === 'nick_name' && autocomplete_nick_name === 'Y' )
keyword.attr('autocomplete', 'off').one('focus', function() {
autoComplete($(this), 'nick_name');
});
}
else if ( (target.val() === 'title_content' && autocomplete_title_content === 'T')
|| (target.val() === 'title' && autocomplete_title === 'T')
|| (target.val() === 'content' && autocomplete_content === 'T')
|| (target.val() === 'tag' && autocomplete_tags === 'Y') )
keyword.attr('autocomplete', 'off').one('focus', function() {
autoComplete($(this), 'tag');
});
}
if ( (target.val() === 'title_content' && autocomplete_title_content === 'Y')
|| (target.val() === 'title' && autocomplete_title === 'Y') )
keyword.attr('autocomplete', 'off').one('focus', function() {
autoComplete($(this), 'title');
});
} <---- Uncaught SyntaxError: missing ) after argument list 라고 에러가 나요.
else if (
에잉? 중간에 중괄호가 사라졌는데요?
이걸로 해주세요
if ( (target.val() === 'title_content' && autocomplete_title_content === 'Y')
|| (target.val() === 'title' && autocomplete_title === 'Y') )
{
keyword.attr('autocomplete', 'off').one('focus', function() {
autoComplete($(this), 'title');
});
}
else if ( target.val() === 'nick_name' && autocomplete_nick_name === 'Y' )
{
keyword.attr('autocomplete', 'off').one('focus', function() {
autoComplete($(this), 'nick_name');
});
}
else if ( (target.val() === 'title_content' && autocomplete_title_content === 'T')
|| (target.val() === 'title' && autocomplete_title === 'T')
|| (target.val() === 'content' && autocomplete_content === 'T')
|| (target.val() === 'tag' && autocomplete_tags === 'Y') )
{
keyword.attr('autocomplete', 'off').one('focus', function() {
autoComplete($(this), 'tag');
});
}
동작하는 class 같은게 있나요? 알려주시면 추가하겠습니다.
들어가서 보니까 무리 없이 동작하는 거 같은데요.
지금 지연시간 설정한 것보다 타이핑이 먼저 들어가면 안되는 것 같은 느낌입니다.
차이가 있다면 저는 지연시간을 기본값인 2000으로 설정한 거고 웹지기님은 4000
그리고 수집된 태그 숫자가 웹지기님은 5000개 문서에서 36598개고 저는 10000개 문서에서 13000개예요.
거기서 오는 차이 같기도 하고 그러네요.
저희 /ssearch 검색창은 구글검색창을 불러오는게 아니다 보니 실제 지연시간을 주면 타이핑 시도하는 시간보다 늦게 js 가 동작해서 문제가 발생되는 것이라서요.
이번에 그것이 필요없나 해서 그냥 지연 있는 채로 해 봤더니 문제가 되네요.
구글검색을 불러오는 곳에서는 2초,4초 때문에 큰 차이는 없습니다.
구글 검색이 아닌 검색청은 워낙 빠르게 불러오기 때문에 js가 시작 안해서 문제가 되는것이라서요.
특수한 경우이긴 하죠. 저희는 검색창을 다르게 쓰니까요.
구글검색이 아닌 페이지에서는 js에 지연시간 없도록 하면 문제 없이 작동합니다~
1. 그리고 module_srl, list_count는 애드온 파일 -> js -> php 이런 경로로 넘어온 건데요. 통합검색이다보니 module_srl은 현재 모듈값이 아니라 통합검색 모듈에서 설정된 대상 모듈값(아, 차라리 target_srls로 이름을 바꾸는 게 낫겠네요)이어서 굳이 캐시 키로 구분 안 해도 되지 않나 싶어서 뺐었습니다. 그리고 어지간한 건 클라이언트의 로컬 저장소의 json object를 확인하도록 해놔서 캐시는 관리자가 설정해놓은 상태의 쿼리 결과만 저장해두면 되겠다 싶기도 하구요.
2. $oCacheHandler->isValid('autocompleteIS', $cache_time) 대신 $oCacheHandler->get('autocompleteIS', $cache_time)으로 바꾸면 되는 거겠죠? 감사합니다~!
네, 그리고 get하신 결과를 if문에서 참/거짓 구분하는 용도(33줄)로만 사용하지 마시고 실제 변수에 저장해 두었다가 if문 안에서(36줄) 재사용하시면 됩니다. 동일한 키를 두 번 get할 필요는 없으니까요.
keyup/keydown으로 하면 난리나죠;;; 사람들 타자 속도가 얼마나 빠른데... ㅋㅋㅋ
게시판 로딩할 때마다 ajax 호출이 되는 거니까요ㅜ
위에서 이야기한대로 게시판 검색어 자동완성 애드온은 일단 (0.1.6 버전 기준으로) js 파일에서 169행, 173행, 180행을 각각
keyword.attr('autocomplete', 'off').one('focus', function() {
autoComplete($(this), 'title');
});
keyword.attr('autocomplete', 'off').one('focus', function() {
autoComplete($(this), 'nick_name');
});
keyword.attr('autocomplete', 'off').one('focus', function() {
autoComplete($(this), 'tag');
});
로 바꾸시면 임시방편은 될 것 같습니다.
170행,174행,181행을 교체하라고 하시면 될 것 같습니다.
{
교체대상
}
{ 를 교체하라고 하셔서 그걸 교체했네요.
https://xetown.com/rxe_point/986687