기사 디렉토리
최근 워드프레스 웹사이트 작업을 하면서 고성능 사이트 전체 검색 엔진을 추가하고 싶었습니다. 여러 가지를 알아본 끝에 Meilisearch를 선택하고 Scry Search라는 플러그인과 함께 사용하기로 했습니다.
처음에는 모든 것이 순조로웠습니다. 플러그인을 설치하고 설정하니 색인이 자동으로 생성되었습니다. 검색 속도는 엄청나게 빨랐고, 사용 경험은 정말 훌륭했습니다.
하지만 여기서 문제가 발생합니다.
웹사이트에는 특이한 게시물이 몇 개 있습니다. 일부는 내부 테스트용이고, 일부는 특정 고객을 위한 전용 랜딩 페이지이며, 나머지는 삭제하고 싶지 않은 미완성 콘텐츠입니다. 이러한 게시물 ID가 검색 결과에서 완전히 사라지도록 해야 합니다.
찾을 수 없다는 것만으로는 부족한 것이 아니라, 메일리서치(Meilisearch)의 색인에도 아예 등록되어 있지 않다는 것이 문제입니다.

이건 간단한 문제인 줄 알았어요. 그냥 몇몇 ID만 제외하면 될 것 같았거든요. 플러그인 문서에 해당 훅이 나와 있을 테니, 필터를 추가하기만 하면 될 것 같았어요.
결과적으로 제 생각이 틀렸습니다.
기존의 도청 방식이 실패하는 이유는 무엇일까요?
먼저 공식 문서에 언급된 필터 훅을 시도해 봤습니다. functions.php 파일에 몇 줄의 코드를 추가하고 저장한 다음, 백엔드를 새로고침하고 다시 인덱싱했습니다.
그다음 메일리서치 백엔드를 확인해 봤습니다.
그 기사들은 여전히 남아 있습니다.
나는 충격을 받았다.
제가 코드를 잘못 작성한 줄 알고 여러 번 확인해 봤지만 아무런 문제가 없었습니다. 그래서 플러그인의 GitHub 이슈를 검색해 보니 다른 여러 사용자들도 비슷한 문제를 겪고 있다는 것을 알게 되었습니다.
알고 보니 Scry Search 플러그인은 백그라운드 저장 프로세스의 속도 저하를 방지하기 위해 비동기 작업 큐 메커니즘을 사용합니다. 즉, 백그라운드에서 "기사 저장"을 클릭하면 데이터가 일반적인 개별 기사 필터링 과정을 건너뛰고 사용자 지정 작업 테이블에 즉시 추가될 수 있습니다.
더욱 교묘한 것은 백그라운드에서 "게시물 색인"을 클릭하여 전역 색인을 다시 생성할 때 플러그인이 하위 수준에서 일괄 데이터베이스 쿼리를 직접 수행한다는 점입니다. 이 경우 이전에 추가한 필터 후크는 실행될 기회를 전혀 얻지 못합니다.
즉, 기존의 차단 방식은 사용자가 수동으로 기사를 저장하는 순간에만 효력이 발생합니다. 하지만 Scry Search의 동기화 로직은 생각보다 훨씬 복잡합니다.
이중 보험 인터셉터 코어 코드
곰곰이 생각해 보니, 이 문제를 이대로 둘 수는 없다는 생각이 들었습니다.
기존 방식은 "단일 게시물 저장" 진입점만 제어하기 때문에 다른 곳에서도 해당 진입을 차단할 수 있는 방법을 찾아야 합니다.
저는 두 가지 방안을 생각해냈습니다.
첫 번째 문제는 네트워크 전송 단계입니다. 단일 기사 동기화든 일괄 동기화든 최종 데이터는 HTTP 요청을 통해 메일리서치 서버로 전송되어야 하죠? 따라서 HTTP 요청을 보내기 전에 요청 본문에 제외된 기사의 ID가 포함되어 있는지 확인해야 합니다. 만약 그렇다면 요청 전송을 차단해야 합니다.
두 번째는 데이터베이스 쿼리와 관련된 사항입니다. 플러그인이 일괄 인덱싱 중에 데이터베이스에서 직접 기사 목록을 가져오기 때문에, 데이터베이스 쿼리를 실행하기 전에 쿼리 결과에서 해당 특정 ID들을 제거하겠습니다. 플러그인 입장에서는 이러한 기사들은 아예 존재하지 않는 것으로 간주되어 검색되지 않을 것입니다.
두 개의 경로, 이중 안전장치. 한 경로를 막을 수 있다면, 다른 경로가 백업 역할을 합니다.
그 이유를 알아낸 후, 코딩을 시작했습니다.
첫 번째 인터셉터로는 워드프레스에서 제공하는 기본 인터셉터를 사용했습니다.pre_http_request필터입니다. 이 훅은 워드프레스가 HTTP 요청을 보내기 전에 실행됩니다. 제 로직은 요청된 URL에 특정 요소가 포함되어 있는지 감지하는 것입니다.meilisearch요청 본문에 제외할 기사의 ID가 포함되어 있으면 해당 요청은 차단됩니다.
플러그인이 오류를 보고하지 않도록 하려면 성공적인 응답인 것처럼 위장해야 합니다. 메일리서치의 표준 응답 형식은 다음과 같습니다...{"taskUid":0,"status":"enqueued"}저는 단순히 이 값을 반환했고, 그 결과 플러그인은 동기화가 성공했다고 인식하게 되었습니다.
제가 사용한 두 번째 요격기pre_get_posts이 훅은 WordPress가 데이터베이스 쿼리를 실행하기 전에 트리거됩니다. 제 로직은 관리자가 백엔드에서 작업을 수행하거나 플러그인이 비동기/동기 작업을 수행할 때마다 제외된 ID를 병합해야 한다는 것입니다.post__not_in매개변수에서.
작성을 마친 후 테스트를 해봤습니다.
먼저 관리자 페이지에 접속하여 제외된 기사 중 하나를 열고 몇 가지 사소한 변경 사항을 적용한 후 업데이트를 클릭했습니다. 오류 없이 성공적으로 저장되었습니다. 그 후 메일리서치 관리자 페이지를 확인해 보니 기사 색인에는 아무런 변화가 없었고, 새로운 내용도 추가되지 않았습니다.
"인덱스 게시물"을 다시 클릭하고 전체 인덱스를 재구축했습니다. 잠시 기다린 후 메일리서치 관리자 페이지를 확인해 보니 제외되었던 게시물들이 여전히 남아 있었습니다.
끝났습니다.
심층 분석: 이중 보험 메커니즘은 어떻게 작동하는가?
솔직히 말해서, 이 과정은 제게 아주 흥미로운 무언가를 떠올리게 했습니다.
아시다시피, 1880년대 미국에서 전기가 막 보급되기 시작했을 때, 많은 공장주들이 발전기와 전기 모터를 구입하여 공장에 설치하는 데 많은 돈을 투자했습니다. 그러나 설치 후 생산 효율이 크게 향상되지 않았다는 사실을 많은 사람들이 발견했습니다.
为什么?
증기기관을 전기 모터로 교체했을 뿐, 공장의 전체적인 구조, 공정, 관리 방식은 그대로 유지되었기 때문입니다. 전기는 새로운 것이었지만, 그것을 사용하는 사고방식은 구시대적이었습니다.
전기 붐의 진정한 수혜자는 바로 "전기의 진정한 의미"를 가장 먼저 이해한 사람들이었습니다. 그들은 단순히 에너지원을 바꾼 것이 아니라, 생산 공정 전체를 재설계했습니다.
인공지능 시대 에도 마찬가지입니다 . 많은 사람들이 인공지능을 도구로 사용하지만, 그 이면에 깔린 논리가 어떻게 변하는지에 대해서는 거의 생각하지 않습니다. 도구 자체는 새롭지만, 그것을 사용하는 사고방식은 여전히 시대에 뒤떨어져 있을지도 모릅니다.
예를 들어, 제가 워드프레스 검색 차단 기능을 설정할 때 플러그인 설명서에 나와 있는 표준적인 방법만 따랐다면 제대로 작동시키지 못했을 겁니다. Scry Search의 동기화 로직은 더 이상 기존의 "글 하나를 저장하고, 다른 하나를 동기화하는" 방식이 아니라, 비동기 큐, 일괄 처리, 그리고 자체적인 메커니즘을 사용하기 때문입니다.
진정한 돌파구를 찾으려면 먼저 이 메커니즘이 어떻게 작동하는지 파악해야 합니다.
이 계획의 한계점 및 주의사항
하지만 솔직히 말씀드리자면, 이 계획 역시 완벽하지는 않습니다.
이 기능에는 중요한 한계가 있습니다. 향후 동기화만 관리할 수 있으며 메일리서치에 이미 저장된 과거 기록을 자동으로 삭제할 수는 없습니다. 즉, 이미 동기화한 기사의 경우 메일리서치 대시보드에서 수동으로 삭제하거나 API 명령어를 사용해야 합니다.
이 작업은 한 번만 수행하면 되므로, 완료 후에는 더 이상 신경 쓸 필요가 없습니다. 하지만 코드를 배포한 후에도 검색 결과에 해당 글이 계속 표시되어 코드가 적용되지 않았다고 오해하는 일이 없도록 미리 이 점을 명확히 알려드려야겠습니다.
또 하나 유의해야 할 점은 이 접근 방식이 WordPress의 기본 네트워크 및 데이터베이스 아키텍처에 의존한다는 것입니다. Scry Search 플러그인의 향후 버전이 이 아키텍처를 기반으로 계속 작동하는 한, 이 인터셉터는 유효할 것입니다. 그러나 동기화 메커니즘이 완전히 다른 방식으로 전환될 경우, 재적응이 필요할 수 있습니다.
솔직히 말씀드리면, 그럴 가능성은 낮습니다. 워드프레스 생태계 전체가 이러한 아키텍처를 기반으로 구축되어 있기 때문에 플러그인이 이를 완전히 우회하는 것은 사실상 불가능합니다.
3단계 구현 가이드
/**
* Scry Search 双保险拦截器:彻底排除指定文章 ID 同步到 Meilisearch
*/
// ==========================================
// 【配置】请在这里填写你想要排除的文章或页面 ID
// ==========================================
define('MEILI_EXCLUDED_IDS', array(1067, 1014, 1474, 34020));
/**
* 保险一:拦截单篇保存/更新时的网络推送 (方案 A 优化版)
* 原理:当 WordPress 发送网络请求时,如果发现是发往 meilisearch 且带有排除的 ID,直接掐断
*/
add_filter('pre_http_request', function($preempt, $parsed_args, $url) {
// 1. 检查是不是发往 Meilisearch 的请求
if (strpos($url, 'meilisearch') !== false && isset($parsed_args['body'])) {
$body_content = $parsed_args['body'];
// 2. 检查请求体里是否包含任何一个被排除的文章 ID
foreach (MEILI_EXCLUDED_IDS as $id) {
// 匹配格式如 "id":1067 或 字符串中的 ID
if (strpos($body_content, (string)$id) !== false) {
// 找到匹配,直接拦截网络请求,并向插件伪造一个标准的成功响应
return array(
'response' => array('code' => 200, 'message' => 'OK'),
'body' => '{"taskUid":0,"status":"enqueued"}'
);
}
}
}
return $preempt;
}, 10, 3);
/**
* 保险二:拦截后台全量索引时的数据库查询 (方案 B)
* 原理:在插件试图从数据库捞取文章列表时,直接将这几个 ID 从查询结果中剔除
*/
add_action('pre_get_posts', function($query) {
// 仅在后台管理员操作,或者插件执行异步同步时生效
if (is_admin() || (defined('DOING_ASYNC') && DOING_ASYNC)) {
// 获取当前查询已经存在的排除 ID(如果有的话)
$current_excluded = $query->get('post__not_in');
if (!is_array($current_excluded)) {
$current_excluded = array();
}
// 将我们的专属排除 ID 合并进去
$query->set('post__not_in', array_merge($current_excluded, MEILI_EXCLUDED_IDS));
}
});
마지막으로 작업 단계를 요약해 보겠습니다.
먼저, 해당 코드를 functions.php 파일의 맨 아래에 복사하거나 Code Snippets 플러그인을 사용하여 추가하세요. 그런 다음, 맨 위에...define배열에 제외하려는 기사 또는 페이지의 ID를 입력하세요.
두 번째 단계는 과거 색인을 정리하는 것입니다. 메일리서치 대시보드에 로그인하거나 API 명령어를 사용하여 제외할 기사에 대한 이전 색인 데이터를 수동으로 삭제하세요.
세 번째 단계는 테스트입니다. 관리자 페이지에 접속하여 제외할 기사에 필요한 사소한 변경 사항을 적용하고, 업데이트를 클릭한 다음 메일리서치 관리자 페이지에서 확인하세요. 새 색인이 생성되지 않으면 차단이 적용된 것입니다.
솔직히 말해서, 이런 종류의 기술 정보 공유 글을 쓰는 것에 대해 항상 약간의 죄책감을 느껴왔습니다.
제가 공유하는 내용들은 어떤 사람들에게는 유용할 수 있지만, 다른 사람들에게는 그저 기본적인 작업일 뿐일 수도 있습니다.
하지만 이번에 워드프레스 검색 차단 기능을 구현하는 과정은 정말 많은 것을 깨닫게 해주었습니다. 우리가 겪는 문제들은 해결책이 부족해서가 아니라, 기존 프레임워크에 갇혀 사고방식이 제한되기 때문인 경우가 많다는 것을 알게 되었습니다.
Scry Search 플러그인은 필터링 기능을 제공하므로 이것이 유일한 해결책이라고 생각할 수 있습니다. 그러나 WordPress의 전체 아키텍처는 훨씬 더 많은 가능성을 제공합니다. 네트워크 계층에서도, 데이터베이스 계층에서도 가로채기가 가능합니다. 조금만 생각하면 방법은 항상 있습니다.
그래서 저는 이런 전자기기를 만지작거리는 걸 좋아합니다. 과시하거나 인상적으로 보이려는 게 아니라, 문제를 완전히 이해했을 때 느끼는 만족감이 정말 크기 때문입니다.
이번에도 처음의 혼란부터 중간에의 성찰, 그리고 최종 해결책에 이르기까지 전체 과정이 마치 퍼즐을 푸는 것 같았습니다.
수수께끼가 풀렸습니다. 해답이 밝혀졌습니다. 알고 보니 아주 간단했습니다.
하지만 그 복잡한 과정을 겪어보지 않았다면, 이 단순한 기쁨을 결코 이해할 수 없을 겁니다.
여기까지 읽어주셔서 감사합니다. 도움이 되셨다면 좋아요와 공유 부탁드립니다. 최신 소식을 가장 먼저 받아보고 싶으시다면 팔로우도 해주세요.
제 글을 읽어주셔서 감사합니다. 다음에 또 뵙겠습니다.
Chen Weiliang님의 블로그( https://www.chenweiliang.com/ ) 에 공유된 "WordPress에서 Meilisearch와 동기화할 특정 게시물 ID를 완전히 제외하는 방법(Scry 검색 플러그인 활용)"이라는 글이 여러분께 도움이 되기를 바랍니다.
이 기사 링크( https://www.chenweiliang.com/cwl-34343.html )를 자유롭게 공유해 주세요.
