결론 코드부터
아는 사람은 바로 functions.php 넣으면 됨, 근데 고급 사용자용 이니까 어떻게 써야할 지 견적 안 나오면 그냥 안 쓰는걸 추천.
// replace query . posts.* to ID . hardfilter.com
function replace_posts_query( $query ) {
if ( !is_feed() && ( is_home() || is_archive() || ( is_search() && !is_admin() ) ) ) {
global $wpdb;
$query = str_replace( "$wpdb->posts.*", "$wpdb->posts.ID", $query );
}
return $query;
}
add_filter( 'query', 'replace_posts_query' );
add_filter('pre_wp_nav_menu', function() { remove_filter('query', 'replace_posts_query'); });
is_*조건문으로 본인이 원하는 범위를 지정$wpdb->posts.*를 replace 할 범위를 2번째 인자에 지정- 범위 추가하려면
"$wpdb->posts.ID, $wpdb->posts.post_title"이런식으로 - 예제에서는
$wpdb->posts.ID만 사용해서,WP_Post객체 생성시 포스트ID만 효율적으로 생성
- 저대로 쓰면 오직
ID값만 존재하기 때문에ID값만 활용해 직접 SQL or 캐싱 시스템을 만들어야함, 고로 압도적 성능 최적화 가능 - ↑ 할 줄 모르면 범위를 애초에
ID만 하는게 아니라 필요한 만큼 확장해서 쓰면 됨(e.g. 타이틀, 링크, etc.) singular에선 쿼리가 이루어지고,archive등에서만 쿼리를 막아야 하기 때문에 필수적으로 영역을 지정해야 함.is_search에서!is_admin안 걸면 대시보드에서 포스트 검색 안됨!is_feed걸어야 피드 제대로 표현됨- 네비게이션 메뉴에서도 쿼리를 변형하면 제대로 출력이 안 되기 때문에
pre_wp_nav_menu필터로 네비쪽에선 비활성화 함.
보너스 코드
posts 를 교체해 전체 콘텐츠 쿼리를 막는 것 말고도 더 최적화 할 수 있다.
FROM wp_postmeta 를 찾아 메타정보 쿼리를 막을 수 있다(e.g. id, mete_key, etc.)
function remove_meta_query($query) {
if (strpos($query, 'FROM wp_postmeta') !== false) {
return '/**/';
}
return $query;
}
add_filter('query', 'remove_meta_query');
t.term_id, tr.object_id 를 찾아 taxonomy 쿼리를 막을 수 있다(e.g. 카테고리, 태그, etc.)
function remove_term_query($query) {
if (strpos($query, 't.term_id, tr.object_id') !== false) {
return '/**/';
}
return $query;
}
add_filter('query', 'remove_term_query');
return을 빈 값이나 null 하면 오류나기 때문에 안전하게 주석을 반환해 발동은 하지만 쿼리할게 없게 만든다.
이 두 보너스코드와 posts코드를 합치려면 이렇게 쓰면 된다.
// replace query full . hardfilter.com
function query_set_c($query) {
if ( !is_feed() && ( is_home() || is_archive() || ( is_search() && !is_admin() ) ) ) {
global $wpdb;
$query = str_replace("$wpdb->posts.*", "$wpdb->posts.ID", $query);
if (strpos($query, 'FROM wp_postmeta') !== false || strpos($query, 't.term_id, tr.object_id') !== false) {
return '/**/';
}
}
return $query;
}
add_filter('query', 'query_set_c');
add_filter('pre_wp_nav_menu', function() { remove_filter('query', 'query_set_c'); });
WP_Post 의 문제점
워드프레스는 굉장히 비효율적으로 포스트 관련 내장 함수를 하나라도 쓰는 순간 쿼리시 WP_Post 객체가 자동 생성되고 메모리에 로드된다.
이후에는 메모리에 로드(페이지 끝나면 산화됨)된 내용을 참조하기 때문에 효율적인 논리라고 생각하는거 같은데 터무늬없다.
왜냐면 WP_Post 객체 생성시 $wpdb->posts.* 를 하기 때문에, 해당 포스트의 콘텐츠 전문을 쿼리하기 때문.
쉽게말해 포스트를 길게 쓰는 사람이 아니라도 평균적으로 굉장히 짧게 잡아 2000자 정도를 쓴다고 치자(1000자로 예를 들려고 했는데 지금 이 포스트의 이 지점까지만 와도 2000자가 넘는다).
아카이브에 한 페이지당 글이 20개 필요하면, 워드프레스 내장 함수를 단 하나라도 쓰는 순간 무조건 20000자의 본문도 반드시 쿼리된다.
게다가 본문만으로 끝나는건 당연히 아니고 +α 도 잔뜩 추가된다(e.g. meta, taxo, etc.).
백 번 양보해서 +α 는 그렇다 치더라도, 전체 콘텐츠를 로드하는 순간 무조건 손해가 발생할 수 밖에 없는 구조다.
아무리 최대한 긍정적으로 여러 변수를 종합 고려해도 대부분의 유저, 대부분의 상황에서 무조건 손해다.
근데 기본 기능으로 안전하게 저걸 막을 수 있는 설정 방법이 없다(나는 강제로 해낸거니까 예외).
아무리 테마를 하나부터 열까지 뜯어고쳐도, 워프 내장기능부터 오는거기 때문에 WP_Post 객체 생성을 완벽히 제거하는건 불가능.
또한 이걸 효율적으로 캐시하는것도 불가능하다.
결과값을 아무리 효율적으로 캐시해도 내장 함수가 발동되는 순간 무조건 객체가 또 생성되고, 그 이후에 캐시가 작동하기 때문에 캐시를 하면 오히려 비용이 증가하는 굉장히 충격적인 구조다.
때문에 차선책으로 WP_Post 객체가 생성되더라도, 애초에 쿼리파트를 str_replace 변조해서 전체 쿼리를 막는것, 현재로선 유일한 해법.
이렇게 ID 만 쿼리하게 하고, 해당 ID 를 통해 효율적인 쿼리 or 캐시구조를 만들면 됨, 아니면 적절히 필요하게 조절하던가.
워드프레스 내장 비효율성에 대해 할 말이 정말 많은데 여러가지를 고려해 이만 줄임.