diff --git a/backend/src/main/java/com/opensource/docgrid/domain/search/repository/VectorSearchRepository.java b/backend/src/main/java/com/opensource/docgrid/domain/search/repository/VectorSearchRepository.java
index 31c5b164..d33bda84 100644
--- a/backend/src/main/java/com/opensource/docgrid/domain/search/repository/VectorSearchRepository.java
+++ b/backend/src/main/java/com/opensource/docgrid/domain/search/repository/VectorSearchRepository.java
@@ -12,7 +12,9 @@
* pgvector 코사인 거리 검색 전용 Repository.
*
*
queryVector는 float[]를 서비스 레이어에서 '[v1,v2,...]' 문자열로 변환해 전달하며,
- * SQL에서 CAST(:queryVector AS vector)로 pgvector 타입으로 변환한다.
+ * SQL에서 CAST(:queryVector AS vector)로 pgvector 타입으로 변환한다. 이 변환은 스칼라 서브쿼리
+ * {@code (SELECT CAST(:queryVector AS vector))}로 감싸 실행마다 한 번만 계산한다. 같은 SQL이 반복돼 Generic Plan이
+ * 쓰이면 감싸지 않은 CAST가 행마다 다시 실행돼 검색이 수십 배 느려지기 때문이다(#549).
* permittedIds는 권한 pre-filter(F-SEARCH-04) 결과이며, 빈 목록이면 서비스에서 조기 반환해야 한다.
*/
public interface VectorSearchRepository extends JpaRepository {
@@ -27,7 +29,7 @@ public interface VectorSearchRepository extends JpaRepository {
dc.section_title AS section_title,
dc.metadata_json AS metadata_json,
d.title AS document_title,
- (e.vector <=> CAST(:queryVector AS vector)) AS distance
+ (e.vector <=> (SELECT CAST(:queryVector AS vector))) AS distance
FROM embeddings e
JOIN documents d ON d.id = e.document_id
JOIN document_chunks dc ON dc.id = e.chunk_id
@@ -37,7 +39,7 @@ AND e.document_id IN (:permittedIds)
AND d.deleted_at IS NULL
AND d.status = 'INDEXED'
AND d.current_version_id = e.document_version_id
- ORDER BY e.vector <=> CAST(:queryVector AS vector)
+ ORDER BY e.vector <=> (SELECT CAST(:queryVector AS vector))
LIMIT :topK
""", nativeQuery = true)
List findTopK(
@@ -139,7 +141,7 @@ sel AS (
dc.section_title AS section_title,
dc.metadata_json AS metadata_json,
d.title AS document_title,
- (e.vector <=> CAST(:queryVector AS vector)) AS distance,
+ (e.vector <=> (SELECT CAST(:queryVector AS vector))) AS distance,
sel.coverage AS coverage
FROM sel
JOIN embeddings e ON e.id = sel.embedding_id
diff --git a/backend/src/test/java/com/opensource/docgrid/domain/search/repository/VectorSearchRepositoryGenericPlanIntegrationTest.java b/backend/src/test/java/com/opensource/docgrid/domain/search/repository/VectorSearchRepositoryGenericPlanIntegrationTest.java
new file mode 100644
index 00000000..4a6ba8d4
--- /dev/null
+++ b/backend/src/test/java/com/opensource/docgrid/domain/search/repository/VectorSearchRepositoryGenericPlanIntegrationTest.java
@@ -0,0 +1,235 @@
+package com.opensource.docgrid.domain.search.repository;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * 실제 PostgreSQL에서 벡터 검색 쿼리({@link VectorSearchRepository#findTopK})가 Generic Plan에서도 질문 Vector를
+ * 한 번만 계산하는지 검증한다(#549).
+ *
+ * 같은 SQL이 반복되면 PostgreSQL은 값과 무관한 Generic Plan을 재사용한다. 이때 {@code CAST(:queryVector AS vector)}를
+ * 그대로 두면 1024차원 문자열을 행마다 다시 파싱해 검색이 수십 배 느려진다. 스칼라 서브쿼리로 감싸 InitPlan으로 한 번만
+ * 계산하는지, 결과가 Custom Plan과 같은지, HNSW 정렬 Index Scan도 여전히 쓸 수 있는지를 실행 계획으로 확인한다.
+ * 실행 계획 검사는 운영 Repository의 {@code @Query} SQL을 그대로 읽어 PREPARE한다.
+ */
+@Tag("integration")
+@DataJpaTest
+@ActiveProfiles("test")
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@DisplayName("벡터 검색 쿼리 Generic Plan 통합 테스트")
+class VectorSearchRepositoryGenericPlanIntegrationTest {
+
+ private static final String TEST_SCHEMA = "docgrid_vector_search_generic_plan_test";
+ private static final int VECTOR_DIMENSION = 1024;
+ private static final int TOP_K = 10;
+ private static final String CONTENT_HASH =
+ "26e4a23eec4241e034f1b4631f0222f1895847637c35e77687d5945f75edb42c";
+ private static final String QUERY_VECTOR = vector(0.0);
+
+ @Autowired private JdbcTemplate jdbcTemplate;
+ @Autowired private VectorSearchRepository vectorSearchRepository;
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+ private Long userId;
+ private Long embeddingModelId;
+
+ @DynamicPropertySource
+ static void configureDatabase(DynamicPropertyRegistry registry) {
+ registry.add("TEST_DB_SCHEMA", () -> TEST_SCHEMA);
+ registry.add("jwt.secret", () -> "docgrid-vector-search-generic-plan-test-secret-key-2026");
+ }
+
+ @BeforeEach
+ void setUp() {
+ jdbcTemplate.execute("""
+ TRUNCATE TABLE embeddings, document_chunks, document_versions, documents, users
+ RESTART IDENTITY CASCADE
+ """);
+ userId = jdbcTemplate.queryForObject("""
+ INSERT INTO users (email, password_hash, name, status, created_at, updated_at)
+ VALUES (?, 'password-hash', 'Generic Plan Test User', 'ACTIVE', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
+ RETURNING id
+ """, Long.class, "vector-search-generic-plan-" + UUID.randomUUID() + "@example.com");
+ embeddingModelId = jdbcTemplate.queryForObject("""
+ SELECT id FROM embedding_models WHERE is_active = TRUE AND is_searchable = TRUE
+ """, Long.class);
+ }
+
+ @AfterAll
+ void dropSchema() {
+ jdbcTemplate.execute("DROP SCHEMA IF EXISTS " + TEST_SCHEMA + " CASCADE");
+ }
+
+ @Test
+ @DisplayName("Generic Plan으로 실행해도 Custom Plan과 같은 청크를 같은 거리 순서로 돌려준다")
+ void findTopK_genericPlanReturnsSameRowsAsCustomPlan() {
+ Long documentId = documentWithChunks(4);
+
+ jdbcTemplate.execute("SET LOCAL plan_cache_mode = force_custom_plan");
+ List custom = findTopK(documentId);
+ jdbcTemplate.execute("SET LOCAL plan_cache_mode = force_generic_plan");
+ List generic = findTopK(documentId);
+
+ assertThat(custom).hasSize(4);
+ assertThat(generic).extracting(VectorSearchRow::getChunkId)
+ .containsExactlyElementsOf(custom.stream().map(VectorSearchRow::getChunkId).toList());
+ assertThat(generic).extracting(VectorSearchRow::getDistance)
+ .containsExactlyElementsOf(custom.stream().map(VectorSearchRow::getDistance).toList());
+ }
+
+ @Test
+ @DisplayName("Generic Plan의 정렬 키는 행마다 질문 Vector를 변환하지 않고 InitPlan 결과를 쓴다")
+ void findTopK_genericPlanEvaluatesQueryVectorOnce() throws Exception {
+ Long documentId = documentWithChunks(4);
+
+ JsonNode plan = explainGenericTopK(documentId);
+
+ List orderingKeys = orderingKeys(plan);
+ assertThat(orderingKeys).isNotEmpty();
+ assertThat(orderingKeys).noneMatch(key -> key.contains("$1"));
+ assertThat(plan.toString()).contains("InitPlan");
+ }
+
+ @Test
+ @DisplayName("질문 Vector를 서브쿼리로 감싸도 HNSW 정렬 Index Scan을 쓸 수 있다")
+ void findTopK_genericPlanCanStillUseHnswIndex() throws Exception {
+ Long documentId = documentWithChunks(4);
+ String hnswIndex = jdbcTemplate.queryForObject("""
+ SELECT indexname FROM pg_indexes
+ WHERE schemaname = current_schema() AND tablename = 'embeddings' AND indexdef ILIKE '%USING hnsw%'
+ """, String.class);
+ jdbcTemplate.execute("SET LOCAL enable_sort = off");
+ jdbcTemplate.execute("SET LOCAL enable_seqscan = off");
+
+ JsonNode plan = explainGenericTopK(documentId);
+
+ assertThat(indexNames(plan)).contains(hnswIndex);
+ assertThat(orderingKeys(plan)).isNotEmpty().noneMatch(key -> key.contains("$1"));
+ }
+
+ private List findTopK(Long documentId) {
+ return vectorSearchRepository.findTopK(QUERY_VECTOR, embeddingModelId, List.of(documentId), TOP_K);
+ }
+
+ /**
+ * 운영 findTopK SQL을 허용 문서 1건짜리 Prepared Statement로 만들고 Generic Plan을 강제해 실행 계획을 읽는다.
+ */
+ private JsonNode explainGenericTopK(Long documentId) throws Exception {
+ String sql = VectorSearchRepository.class
+ .getMethod("findTopK", String.class, Long.class, List.class, int.class)
+ .getAnnotation(Query.class)
+ .value()
+ .replace(":queryVector", "$1")
+ .replace(":modelId", "$2")
+ .replace("(:permittedIds)", "($3)")
+ .replace(":topK", "$4");
+ String statement = "generic_plan_" + UUID.randomUUID().toString().replace("-", "");
+ jdbcTemplate.execute("PREPARE " + statement + "(text, bigint, bigint, int) AS " + sql);
+ try {
+ jdbcTemplate.execute("SET LOCAL plan_cache_mode = force_generic_plan");
+ String explain = jdbcTemplate.queryForObject(
+ "EXPLAIN (VERBOSE, FORMAT JSON) EXECUTE " + statement + "('" + QUERY_VECTOR + "', "
+ + embeddingModelId + ", " + documentId + ", " + TOP_K + ")",
+ String.class);
+ return objectMapper.readTree(explain).get(0).path("Plan");
+ } finally {
+ jdbcTemplate.execute("DEALLOCATE " + statement);
+ }
+ }
+
+ /** 실행 계획 전체에서 정렬 기준식(Sort Key, Index Scan의 Order By)을 모은다. */
+ private static List orderingKeys(JsonNode node) {
+ List keys = new ArrayList<>();
+ collect(node, keys, new ArrayList<>());
+ return keys;
+ }
+
+ private static List indexNames(JsonNode node) {
+ List names = new ArrayList<>();
+ collect(node, new ArrayList<>(), names);
+ return names;
+ }
+
+ private static void collect(JsonNode node, List orderingKeys, List indexNames) {
+ node.path("Sort Key").forEach(key -> orderingKeys.add(key.asText()));
+ if (node.has("Order By")) {
+ orderingKeys.add(node.path("Order By").asText());
+ }
+ if (node.has("Index Name")) {
+ indexNames.add(node.path("Index Name").asText());
+ }
+ node.path("Plans").forEach(child -> collect(child, orderingKeys, indexNames));
+ }
+
+ private Long documentWithChunks(int chunkCount) {
+ Long documentId = jdbcTemplate.queryForObject("""
+ INSERT INTO documents (
+ owner_user_id, title, document_type, source_type, status, visibility, created_at, updated_at
+ )
+ VALUES (?, '규정', 'DOCX', 'UPLOAD', 'INDEXED', 'PRIVATE', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
+ RETURNING id
+ """, Long.class, userId);
+ Long versionId = jdbcTemplate.queryForObject("""
+ INSERT INTO document_versions (
+ document_id, version_no, title_snapshot, content_type, status, created_by, created_at, updated_at
+ )
+ VALUES (?, 1, '규정', 'text/plain', 'INDEXED', ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
+ RETURNING id
+ """, Long.class, documentId, userId);
+ jdbcTemplate.update("UPDATE documents SET current_version_id = ? WHERE id = ?", versionId, documentId);
+
+ for (int index = 0; index < chunkCount; index++) {
+ String text = "규정 본문 " + index;
+ Long chunkId = jdbcTemplate.queryForObject("""
+ INSERT INTO document_chunks (
+ document_version_id, chunk_index, chunk_text, token_count, char_start, char_end,
+ content_hash, created_at, updated_at
+ )
+ VALUES (?, ?, ?, 1, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
+ RETURNING id
+ """, Long.class, versionId, index, text, index * 100, index * 100 + text.length(), CONTENT_HASH);
+ jdbcTemplate.update("""
+ INSERT INTO embeddings (
+ chunk_id, document_id, document_version_id, embedding_model_id,
+ vector, dimension, vector_hash, status, created_at, updated_at
+ )
+ VALUES (?, ?, ?, ?, CAST(? AS vector), ?, ?, 'ACTIVE', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
+ """, chunkId, documentId, versionId, embeddingModelId, vector(index * 0.5), VECTOR_DIMENSION,
+ CONTENT_HASH);
+ }
+ return documentId;
+ }
+
+ /** 첫 성분은 1, 둘째 성분은 secondComponent, 나머지는 0인 Vector 문자열이다. 0이면 질의 Vector가 된다. */
+ private static String vector(double secondComponent) {
+ StringBuilder builder = new StringBuilder("[1.0,").append(secondComponent);
+ for (int i = 2; i < VECTOR_DIMENSION; i++) {
+ builder.append(",0.0");
+ }
+ return builder.append(']').toString();
+ }
+}
diff --git a/docs/design/gimin-#549-generic-plan-query-vector-cast.md b/docs/design/gimin-#549-generic-plan-query-vector-cast.md
new file mode 100644
index 00000000..6cef6760
--- /dev/null
+++ b/docs/design/gimin-#549-generic-plan-query-vector-cast.md
@@ -0,0 +1,72 @@
+# Generic Plan에서 질문 Vector를 행마다 변환해 벡터 검색이 느려지는 문제 수정 (#549)
+
+closes #549
+
+## 1. 배경
+
+#545(PR #547) 측정에서 같은 벡터 검색 SQL을 반복하면 6번째 실행부터 15~21배 느려지는 현상을 발견했다.
+
+`VectorSearchRepository.findTopK`는 질문 Vector를 `'[v1,v2,...]'` 문자열로 받아 SQL 안에서 `CAST(:queryVector AS vector)`로 바꾼다.
+
+- **Custom Plan:** PostgreSQL이 파라미터 값을 알고 계획을 세우면 이 CAST를 계획 단계에서 한 번 계산해 상수로 넣는다.
+- **Generic Plan:** 같은 Prepared Statement가 반복되면 PostgreSQL은 값과 무관한 계획을 재사용한다. 이때 CAST는 `($1)::vector`라는 **식으로 남아 행마다 실행**된다. 1024차원 문자열 파싱이 검색 대상 청크 수만큼(정렬 키와 SELECT 거리 계산에서 각각) 반복된다.
+
+운영에서도 일어나는 조건이다.
+
+| 단계 | 기본값 | 효과 |
+|---|---|---|
+| JDBC 드라이버 | `prepareThreshold=5` | 같은 SQL을 5번 넘게 실행한 Connection은 Server Prepared Statement를 쓴다 |
+| PostgreSQL | `plan_cache_mode=auto` | 그 Statement의 6번째 실행부터 Generic Plan을 고를 수 있다 |
+| 검색 SQL | `IN (:permittedIds)` 목록 길이만 다르다 | 허용 문서 수가 같은 사용자의 검색은 SQL 문자열이 같아 Connection Pool에서 반복된다 |
+
+#545 측정값(5만 청크, 허용 100%, topK 10)
+- 서비스 호출 p50: Custom Plan 204.2ms, Generic Plan 4,226.3ms
+- psql `auto` 모드: 같은 Statement 1~5회째 140.4~144.0ms → 6~8회째 2,099.0~2,114.9ms
+
+## 2. 변경
+
+질문 Vector 변환을 스칼라 서브쿼리로 감쌌다. 상관되지 않은 스칼라 서브쿼리는 PostgreSQL이 **InitPlan으로 실행마다 한 번만** 계산하고, 각 행은 그 결과를 참조한다.
+
+```sql
+-- findTopK
+(e.vector <=> (SELECT CAST(:queryVector AS vector))) AS distance
+...
+ORDER BY e.vector <=> (SELECT CAST(:queryVector AS vector))
+
+-- findLexicalCandidates (최종 SELECT의 거리 계산)
+(e.vector <=> (SELECT CAST(:queryVector AS vector))) AS distance,
+```
+
+- `findLexicalCandidates`는 거리를 수십~수백 행에만 계산해 영향이 작다. 같은 규칙을 지키려고 함께 바꿨다.
+- `countPatternMatches`는 질문 Vector를 쓰지 않는다.
+- pgvector의 HNSW 정렬 Index Scan은 `ORDER BY 열 <=> (SELECT ...)` 형태도 쓸 수 있다. 회귀 테스트로 확인했다.
+- 파라미터 타입, 서비스 코드, DB 스키마, 설정은 바꾸지 않았다.
+
+### 고려한 다른 방법
+
+| 방법 | 채택하지 않은 이유 |
+|---|---|
+| Vector 타입으로 Bind (pgvector-java 등) | 의존성과 Hibernate 타입 등록이 필요해 변경이 크다 |
+| JDBC `prepareThreshold=0` 또는 DB 역할의 `plan_cache_mode=force_custom_plan` | 모든 쿼리의 계획 재사용을 끈다. 근본 원인(행마다 변환)은 남는다 |
+| 검색 Transaction에서 `SET LOCAL plan_cache_mode` | DB 왕복이 늘고 서비스 코드가 바뀐다 |
+
+## 3. API 계약과 오류
+
+API 경로, 요청, 응답 필드, 오류 코드는 그대로다. 검색 결과(청크와 순서)도 같아야 한다.
+
+| 상황 | 수정 전 | 수정 후 |
+|---|---|---|
+| Custom Plan으로 실행 | 질문 Vector 1회 변환 | 같음(InitPlan 1회) |
+| Generic Plan으로 실행 | 질문 Vector를 행마다 변환 | InitPlan 1회 |
+| 허용 문서가 없음 | 서비스가 DB를 조회하지 않고 빈 결과 | 같음 |
+| 질문 Vector 문자열 형식 | 서비스가 `float[]`로 만든 `'[v1,...]'` 문자열만 전달 | 같음(외부 입력이 SQL에 직접 들어가지 않음) |
+
+## 4. 검증
+
+[측정 결과](../test-results/gimin-%23549-generic-plan-query-vector-cast.md)
+
+- **회귀 통합 테스트** `VectorSearchRepositoryGenericPlanIntegrationTest` 3건
+ 1. Generic Plan 결과가 Custom Plan과 같은 청크·거리 순서인지
+ 2. Generic Plan 실행 계획의 정렬 키가 `$1`을 직접 쓰지 않고 InitPlan 결과를 쓰는지
+ 3. `enable_sort=off`일 때 HNSW 정렬 Index Scan을 여전히 쓰는지
+- **재측정:** #545와 같은 환경(격리 컨테이너, 같은 이미지 digest·데이터·Seed·질문·하네스)에서 수정 전후를 비교했다.
diff --git a/docs/test-results/evidence/issue-549/ab-after-results.json.gz b/docs/test-results/evidence/issue-549/ab-after-results.json.gz
new file mode 100644
index 00000000..78f53436
Binary files /dev/null and b/docs/test-results/evidence/issue-549/ab-after-results.json.gz differ
diff --git a/docs/test-results/evidence/issue-549/ab-before-results.json.gz b/docs/test-results/evidence/issue-549/ab-before-results.json.gz
new file mode 100644
index 00000000..bd85cf2c
Binary files /dev/null and b/docs/test-results/evidence/issue-549/ab-before-results.json.gz differ
diff --git a/docs/test-results/evidence/issue-549/ab-comparison.csv b/docs/test-results/evidence/issue-549/ab-comparison.csv
new file mode 100644
index 00000000..5d4759e0
--- /dev/null
+++ b/docs/test-results/evidence/issue-549/ab-comparison.csv
@@ -0,0 +1,13 @@
+scale,ratio,topK,method,questions,vectorP50Before,vectorP50After,vectorP50ImprovementPercent,vectorP95Before,vectorP95After,vectorP95ImprovementPercent,hybridP50Before,hybridP50After,hybridP50ImprovementPercent,hybridP95Before,hybridP95After,hybridP95ImprovementPercent,hnswPlanBefore,hnswPlanAfter,resultMismatchQuestions
+50000,1.0,10,exact,30,256.219833,225.427875,12.017788646361344,420.281958,369.010458,12.199310254474439,1496.363334,1361.575291,9.007708217474928,2443.961083,2052.756791,16.006977145470376,0,0,0
+50000,1.0,10,planner_default,30,234.158791,231.007875,1.3456321612114897,376.120958,341.292541,9.259897982074145,1403.736875,1379.757875,1.7082261232184301,2051.691333,1952.563958,4.831495527889928,0,0,0
+50000,1.0,10,planner_default_generic,30,4465.733959,218.508708,95.10699226585969,5178.187875,255.871833,95.0586606902516,5799.949542,1380.563125,76.19698042193761,7221.173584,2250.282667,68.83771535438554,0,0,0
+50000,0.1,10,exact,30,27.579625,29.8945,-8.393424493625279,32.971959,39.835625,-20.81667637643248,154.944958,161.808833,-4.429879544709017,225.504459,235.928125,-4.62237689056073,0,0,0
+50000,0.1,10,planner_default,30,27.185375,29.249875,-7.594156784668223,29.575084,38.2515,-29.336910759070033,152.066458,158.714458,-4.371772767930187,211.954542,224.351167,-5.8487187313966595,0,0,0
+50000,0.1,10,planner_default_generic,30,461.130917,27.262958,94.08780522083275,545.481083,28.639583,94.74966522349594,596.396917,152.834667,74.37366581826245,691.36925,210.724834,69.52065282047184,0,0,0
+50000,0.01,10,exact,30,6.996542,7.167459,-2.4428782104073727,10.525792,9.600625,8.789523866707592,20.804209,21.204667,-1.924889333692045,29.60425,29.208584,1.3365175608231996,0,0,0
+50000,0.01,10,planner_default,30,6.833667,6.739042,1.3846884842354732,8.531833,9.595792,-12.470462091791983,19.357959,20.424416,-5.509139677380244,28.396583,30.252959,-6.537321761565471,0,0,0
+50000,0.01,10,planner_default_generic,30,44.573333,7.2835,83.65951229180013,46.766,9.100709,80.53990292092546,57.987334,20.121083,65.30090002068383,81.2835,28.655042,64.74679116917947,0,0,0
+50000,0.001,10,exact,30,3.218125,3.040875,5.507865604971836,5.735167,5.890875,-2.714968892797727,7.78125,7.305666,6.111922891566259,11.455833,11.456209,-0.0032821707509114863,0,0,0
+50000,0.001,10,planner_default,30,2.789291,3.169583,-13.634002332492374,4.220708,5.691666,-34.850977608496,7.127291,7.803667,-9.489945057666375,10.594333,12.867208,-21.453686607736408,0,0,0
+50000,0.001,10,planner_default_generic,30,7.802958,2.614125,66.49828180543841,8.993167,3.803083,57.71141578934318,11.627,5.456167,53.07330351767439,13.414708,6.724833,49.86970271734576,0,0,0
diff --git a/docs/test-results/evidence/issue-549/ab-comparison.md b/docs/test-results/evidence/issue-549/ab-comparison.md
new file mode 100644
index 00000000..a2572b7a
--- /dev/null
+++ b/docs/test-results/evidence/issue-549/ab-comparison.md
@@ -0,0 +1,14 @@
+| 규모 | 허용 비율 | topK | 방식 | 벡터 p50 전 → 후 (ms) | 개선율 | 벡터 p95 전 → 후 (ms) | 개선율 | 하이브리드 p50 전 → 후 (ms) | 개선율 | HNSW 사용 전/후 | 결과 다른 질문 |
+|---|---:|---:|---|---|---:|---|---:|---|---:|---|---:|
+| 50000 | 100% | 10 | Exact | 256.2 → 225.4 | +12.0% | 420.3 → 369.0 | +12.2% | 1,496.4 → 1,361.6 | +9.0% | 0/0 | 0/30 |
+| 50000 | 100% | 10 | 현재 계획 Custom | 234.2 → 231.0 | +1.3% | 376.1 → 341.3 | +9.3% | 1,403.7 → 1,379.8 | +1.7% | 0/0 | 0/30 |
+| 50000 | 100% | 10 | 현재 계획 Generic | 4,465.7 → 218.5 | +95.1% | 5,178.2 → 255.9 | +95.1% | 5,799.9 → 1,380.6 | +76.2% | 0/0 | 0/30 |
+| 50000 | 10% | 10 | Exact | 27.6 → 29.9 | -8.4% | 33.0 → 39.8 | -20.8% | 154.9 → 161.8 | -4.4% | 0/0 | 0/30 |
+| 50000 | 10% | 10 | 현재 계획 Custom | 27.2 → 29.2 | -7.6% | 29.6 → 38.3 | -29.3% | 152.1 → 158.7 | -4.4% | 0/0 | 0/30 |
+| 50000 | 10% | 10 | 현재 계획 Generic | 461.1 → 27.3 | +94.1% | 545.5 → 28.6 | +94.7% | 596.4 → 152.8 | +74.4% | 0/0 | 0/30 |
+| 50000 | 1% | 10 | Exact | 7.0 → 7.2 | -2.4% | 10.5 → 9.6 | +8.8% | 20.8 → 21.2 | -1.9% | 0/0 | 0/30 |
+| 50000 | 1% | 10 | 현재 계획 Custom | 6.8 → 6.7 | +1.4% | 8.5 → 9.6 | -12.5% | 19.4 → 20.4 | -5.5% | 0/0 | 0/30 |
+| 50000 | 1% | 10 | 현재 계획 Generic | 44.6 → 7.3 | +83.7% | 46.8 → 9.1 | +80.5% | 58.0 → 20.1 | +65.3% | 0/0 | 0/30 |
+| 50000 | 0.1% | 10 | Exact | 3.2 → 3.0 | +5.5% | 5.7 → 5.9 | -2.7% | 7.8 → 7.3 | +6.1% | 0/0 | 0/30 |
+| 50000 | 0.1% | 10 | 현재 계획 Custom | 2.8 → 3.2 | -13.6% | 4.2 → 5.7 | -34.9% | 7.1 → 7.8 | -9.5% | 0/0 | 0/30 |
+| 50000 | 0.1% | 10 | 현재 계획 Generic | 7.8 → 2.6 | +66.5% | 9.0 → 3.8 | +57.7% | 11.6 → 5.5 | +53.1% | 0/0 | 0/30 |
diff --git a/docs/test-results/evidence/issue-549/after-fix/h1-final-results.svg b/docs/test-results/evidence/issue-549/after-fix/h1-final-results.svg
new file mode 100644
index 00000000..3f4a8e4e
--- /dev/null
+++ b/docs/test-results/evidence/issue-549/after-fix/h1-final-results.svg
@@ -0,0 +1,137 @@
+
+
+
+허용 문서 비율별 벡터 검색 최종 결과 수 (50,000 청크)
+질문 30개 평균. HNSW 계열은 enable_sort=off로 HNSW를 쓰게 한 값이며, 현재 운영 계획은 Exact와 같은 결과를 낸다.
+
+Exact = 현재 계획
+
+HNSW 강제 ef40
+
+HNSW 강제 ef200
+
+HNSW 강제 iterative relaxed
+topK 5
+
+0
+
+1
+
+2
+
+3
+
+4
+
+5
+
+topK 5
+100%
+10%
+1%
+0.1%
+
+HNSW 강제 ef40 · 100%: 5.0
+HNSW 강제 ef40 · 10%: 1.7
+HNSW 강제 ef40 · 1%: 0.2
+HNSW 강제 ef40 · 0.1%: 0.0
+
+HNSW 강제 ef200 · 100%: 5.0
+HNSW 강제 ef200 · 10%: 4.1
+HNSW 강제 ef200 · 1%: 0.6
+HNSW 강제 ef200 · 0.1%: 0.1
+
+HNSW 강제 iterative relaxed · 100%: 5.0
+HNSW 강제 iterative relaxed · 10%: 5.0
+HNSW 강제 iterative relaxed · 1%: 4.9
+HNSW 강제 iterative relaxed · 0.1%: 4.9
+
+Exact = 현재 계획 · 100%: 5.0
+Exact = 현재 계획 · 10%: 5.0
+Exact = 현재 계획 · 1%: 4.9
+Exact = 현재 계획 · 0.1%: 5.0
+topK 10
+
+0
+
+2
+
+4
+
+6
+
+8
+
+10
+
+topK 10
+100%
+10%
+1%
+0.1%
+
+HNSW 강제 ef40 · 100%: 9.8
+HNSW 강제 ef40 · 10%: 1.8
+HNSW 강제 ef40 · 1%: 0.2
+HNSW 강제 ef40 · 0.1%: 0.0
+
+HNSW 강제 ef200 · 100%: 10.0
+HNSW 강제 ef200 · 10%: 5.8
+HNSW 강제 ef200 · 1%: 0.6
+HNSW 강제 ef200 · 0.1%: 0.1
+
+HNSW 강제 iterative relaxed · 100%: 9.8
+HNSW 강제 iterative relaxed · 10%: 10.0
+HNSW 강제 iterative relaxed · 1%: 10.0
+HNSW 강제 iterative relaxed · 0.1%: 6.6
+
+Exact = 현재 계획 · 100%: 9.9
+Exact = 현재 계획 · 10%: 10.0
+Exact = 현재 계획 · 1%: 10.0
+Exact = 현재 계획 · 0.1%: 7.9
+topK 20
+
+0
+
+5
+
+10
+
+15
+
+20
+
+topK 20
+100%
+10%
+1%
+0.1%
+
+HNSW 강제 ef40 · 100%: 17.3
+HNSW 강제 ef40 · 10%: 1.8
+HNSW 강제 ef40 · 1%: 0.2
+HNSW 강제 ef40 · 0.1%: 0.0
+
+HNSW 강제 ef200 · 100%: 19.1
+HNSW 강제 ef200 · 10%: 6.1
+HNSW 강제 ef200 · 1%: 0.6
+HNSW 강제 ef200 · 0.1%: 0.1
+
+HNSW 강제 iterative relaxed · 100%: 19.0
+HNSW 강제 iterative relaxed · 10%: 20.0
+HNSW 강제 iterative relaxed · 1%: 20.0
+HNSW 강제 iterative relaxed · 0.1%: 6.6
+
+Exact = 현재 계획 · 100%: 19.1
+Exact = 현재 계획 · 10%: 20.0
+Exact = 현재 계획 · 1%: 20.0
+Exact = 현재 계획 · 0.1%: 7.9
+y축: 앱 필터(최소 유사도·문서당 2개) 뒤 최종 결과 수 평균
+
diff --git a/docs/test-results/evidence/issue-549/after-fix/h2-sql-candidates.svg b/docs/test-results/evidence/issue-549/after-fix/h2-sql-candidates.svg
new file mode 100644
index 00000000..6cad1af2
--- /dev/null
+++ b/docs/test-results/evidence/issue-549/after-fix/h2-sql-candidates.svg
@@ -0,0 +1,47 @@
+
+
+
+허용 100%에서 SQL이 돌려준 후보 수
+50,000 청크, 질문 30개 평균. SQL 요청 수는 topK × 4이다. HNSW 강제 ef100은 Exact와 같은 값이라 겹친다.
+
+Exact = 요청 수
+
+HNSW 강제 ef40
+
+HNSW 강제 ef100
+topK별 SQL 반환 후보
+
+0
+
+20
+
+40
+
+60
+
+80
+
+topK 5 (요청 20)
+topK 10 (요청 40)
+topK 20 (요청 80)
+
+HNSW 강제 ef40 · topK 5 (요청 20): 20.0
+HNSW 강제 ef40 · topK 10 (요청 40): 40.0
+HNSW 강제 ef40 · topK 20 (요청 80): 40.0
+
+HNSW 강제 ef100 · topK 5 (요청 20): 20.0
+HNSW 강제 ef100 · topK 10 (요청 40): 40.0
+HNSW 강제 ef100 · topK 20 (요청 80): 80.0
+
+Exact = 요청 수 · topK 5 (요청 20): 20.0
+Exact = 요청 수 · topK 10 (요청 40): 40.0
+Exact = 요청 수 · topK 20 (요청 80): 80.0
+y축: SQL 반환 후보 수 평균
+
diff --git a/docs/test-results/evidence/issue-549/after-fix/results.json.gz b/docs/test-results/evidence/issue-549/after-fix/results.json.gz
new file mode 100644
index 00000000..7ad82ee8
Binary files /dev/null and b/docs/test-results/evidence/issue-549/after-fix/results.json.gz differ
diff --git a/docs/test-results/evidence/issue-549/after-fix/rows.csv.gz b/docs/test-results/evidence/issue-549/after-fix/rows.csv.gz
new file mode 100644
index 00000000..9062125d
Binary files /dev/null and b/docs/test-results/evidence/issue-549/after-fix/rows.csv.gz differ
diff --git a/docs/test-results/evidence/issue-549/after-fix/summary.csv b/docs/test-results/evidence/issue-549/after-fix/summary.csv
new file mode 100644
index 00000000..94d91e48
--- /dev/null
+++ b/docs/test-results/evidence/issue-549/after-fix/summary.csv
@@ -0,0 +1,193 @@
+scale,ratio,permittedDocuments,permittedChunks,topK,candidateLimit,method,questions,sqlRowsMean,sqlRowsMin,sqlRowsMax,vectorFinalMean,vectorShortQuestions,hybridFinalMean,hybridShortQuestions,exactVectorFinalMean,exactHybridFinalMean,sqlRecallMean,sqlRecallMin,vectorFinalRecallMean,vectorFinalRecallMin,hybridFinalRecallMean,hybridFinalRecallMin,vectorP50Millis,vectorP95Millis,hybridP50Millis,hybridP95Millis,hnswPlanQuestions,unstableQuestions
+real,1.0,251,3572,5,20,exact,30,20.0,20,20,4.933333333333334,2,4.933333333333334,2,4.933333333333334,4.933333333333334,1.0,1.0,1.0,1.0,1.0,1.0,17.277083,24.818333,102.59925,156.390667,0,0
+real,1.0,251,3572,5,20,planner_default,30,20.0,20,20,4.933333333333334,2,4.933333333333334,2,4.933333333333334,4.933333333333334,1.0,1.0,1.0,1.0,1.0,1.0,16.15125,18.994209,98.227875,136.042917,0,0
+real,1.0,251,3572,5,20,planner_default_generic,30,20.0,20,20,4.933333333333334,2,4.933333333333334,2,4.933333333333334,4.933333333333334,1.0,1.0,1.0,1.0,1.0,1.0,15.263708,18.902292,97.802083,134.824792,0,0
+real,1.0,251,3572,5,20,hnsw_ef40,30,20.0,20,20,4.933333333333334,2,4.633333333333334,5,4.933333333333334,4.933333333333334,0.83,0.0,0.8,0.0,0.8466666666666666,0.0,4.913041,7.46425,84.916625,124.896333,30,0
+real,1.0,251,3572,5,20,hnsw_ef100,30,20.0,20,20,4.933333333333334,2,4.633333333333334,5,4.933333333333334,4.933333333333334,0.9033333333333334,0.1,0.8733333333333333,0.0,0.8866666666666667,0.0,5.415458,7.4705,87.119167,129.494209,30,0
+real,1.0,251,3572,5,20,hnsw_ef200,30,20.0,20,20,4.933333333333334,2,4.8,3,4.933333333333334,4.933333333333334,0.9683333333333334,0.15,0.9600000000000001,0.0,0.9666666666666667,0.2,6.223708,11.893291,94.422792,129.561875,30,0
+real,1.0,251,3572,5,20,iterative_relaxed,30,20.0,20,20,4.933333333333334,2,4.633333333333334,5,4.933333333333334,4.933333333333334,0.83,0.0,0.8,0.0,0.8466666666666666,0.0,5.241166,8.432625,88.842125,166.696667,30,0
+real,1.0,251,3572,5,20,iterative_strict,30,20.0,20,20,4.933333333333334,2,4.633333333333334,5,4.933333333333334,4.933333333333334,0.83,0.0,0.8,0.0,0.8466666666666666,0.0,5.098291,7.997125,88.493958,145.057792,30,0
+real,1.0,251,3572,10,40,exact,30,40.0,40,40,10.0,0,9.866666666666667,3,10.0,9.866666666666667,1.0,1.0,1.0,1.0,1.0,1.0,18.184167,19.700417,100.974708,140.034375,0,0
+real,1.0,251,3572,10,40,planner_default,30,40.0,40,40,10.0,0,9.866666666666667,3,10.0,9.866666666666667,1.0,1.0,1.0,1.0,1.0,1.0,18.039125,21.379917,100.062542,141.63625,0,0
+real,1.0,251,3572,10,40,planner_default_generic,30,40.0,40,40,10.0,0,9.866666666666667,3,10.0,9.866666666666667,1.0,1.0,1.0,1.0,1.0,1.0,17.818708,21.481583,101.928583,138.2605,0,0
+real,1.0,251,3572,10,40,hnsw_ef40,30,40.0,40,40,10.0,0,9.133333333333333,4,10.0,9.866666666666667,0.8408333333333334,0.3,0.8033333333333333,0.0,0.8282407407407407,0.1,7.205042,8.979458,90.54325,134.123666,30,0
+real,1.0,251,3572,10,40,hnsw_ef100,30,40.0,40,40,10.0,0,9.166666666666666,4,10.0,9.866666666666667,0.9283333333333332,0.4,0.8766666666666667,0.0,0.8919444444444444,0.1,7.801583,12.42475,93.709916,131.882667,30,0
+real,1.0,251,3572,10,40,hnsw_ef200,30,40.0,40,40,10.0,0,9.633333333333333,3,10.0,9.866666666666667,0.975,0.4,0.9600000000000001,0.0,0.9674999999999999,0.125,7.352875,9.206042,90.101459,127.192709,30,0
+real,1.0,251,3572,10,40,iterative_relaxed,30,40.0,40,40,10.0,0,9.133333333333333,4,10.0,9.866666666666667,0.8408333333333334,0.3,0.8033333333333333,0.0,0.8282407407407407,0.1,6.74675,9.553,87.742792,133.722375,30,0
+real,1.0,251,3572,10,40,iterative_strict,30,40.0,40,40,10.0,0,9.133333333333333,4,10.0,9.866666666666667,0.8408333333333334,0.3,0.8033333333333333,0.0,0.8282407407407407,0.1,6.597166,8.629667,87.339583,125.477334,30,0
+real,1.0,251,3572,20,80,exact,30,80.0,80,80,20.0,0,18.633333333333333,4,20.0,18.633333333333333,1.0,1.0,1.0,1.0,1.0,1.0,21.259292,28.210917,105.912375,144.263584,0,0
+real,1.0,251,3572,20,80,planner_default,30,80.0,80,80,20.0,0,18.633333333333333,4,20.0,18.633333333333333,1.0,1.0,1.0,1.0,1.0,1.0,21.294208,23.98225,104.234125,144.478334,0,0
+real,1.0,251,3572,20,80,planner_default_generic,30,80.0,80,80,20.0,0,18.633333333333333,4,20.0,18.633333333333333,1.0,1.0,1.0,1.0,1.0,1.0,21.431167,25.961917,102.049375,138.910667,0,0
+real,1.0,251,3572,20,80,hnsw_ef40,30,40.0,40,40,18.6,9,16.866666666666667,10,20.0,18.633333333333333,0.49624999999999997,0.425,0.7416666666666667,0.15,0.7267022792022791,0.125,6.919916,8.55,89.78325,128.1945,30,0
+real,1.0,251,3572,20,80,hnsw_ef100,30,80.0,80,80,20.0,0,17.833333333333332,4,20.0,18.633333333333333,0.92375,0.5375,0.9083333333333333,0.3,0.8954059829059828,0.125,10.3215,12.277791,93.818834,132.528667,30,0
+real,1.0,251,3572,20,80,hnsw_ef200,30,80.0,80,80,20.0,0,18.4,4,20.0,18.633333333333333,0.9783333333333334,0.6,0.9733333333333333,0.35,0.9674999999999999,0.125,11.226834,13.253375,96.183292,136.188042,30,0
+real,1.0,251,3572,20,80,iterative_relaxed,30,80.0,80,80,20.0,0,17.8,4,20.0,18.633333333333333,0.9029166666666666,0.5375,0.8033333333333333,0.15,0.8833689458689459,0.125,10.612333,12.622084,95.122166,134.151417,30,0
+real,1.0,251,3572,20,80,iterative_strict,30,80.0,80,80,20.0,0,17.8,4,20.0,18.633333333333333,0.8466666666666666,0.425,0.7983333333333333,0.15,0.8167022792022791,0.125,11.062209,14.174625,93.926375,133.467458,30,0
+real,0.1,25,301,5,20,exact,30,20.0,20,20,4.833333333333333,2,4.266666666666667,11,4.833333333333333,4.266666666666667,1.0,1.0,1.0,1.0,1.0,1.0,4.360958,7.096625,13.6065,18.842083,0,0
+real,0.1,25,301,5,20,planner_default,30,20.0,20,20,4.833333333333333,2,4.266666666666667,11,4.833333333333333,4.266666666666667,1.0,1.0,1.0,1.0,1.0,1.0,4.366291,5.918208,14.01,19.40475,0,0
+real,0.1,25,301,5,20,planner_default_generic,30,20.0,20,20,4.833333333333333,2,4.266666666666667,11,4.833333333333333,4.266666666666667,1.0,1.0,1.0,1.0,1.0,1.0,4.336958,5.995542,13.073334,18.101625,0,0
+real,0.1,25,301,5,20,hnsw_ef40,30,4.1,0,20,2.2333333333333334,27,3.033333333333333,22,4.833333333333333,4.266666666666667,0.20500000000000002,0.0,0.4222222222222223,0.0,0.6116666666666667,0.0,2.053667,3.844334,10.2895,14.951916,30,0
+real,0.1,25,301,5,20,hnsw_ef100,30,9.133333333333333,1,20,4.1,11,3.8666666666666667,15,4.833333333333333,4.266666666666667,0.45,0.05,0.8,0.2,0.8605555555555555,0.4,3.070083,5.234667,11.783917,17.891958,30,0
+real,0.1,25,301,5,20,hnsw_ef200,30,15.6,8,20,4.7,4,4.1,13,4.833333333333333,4.266666666666667,0.7716666666666666,0.4,0.9533333333333334,0.4,0.9433333333333334,0.4,4.387708,7.536708,13.410459,19.072542,30,0
+real,0.1,25,301,5,20,iterative_relaxed,30,20.0,20,20,4.8,3,4.166666666666667,12,4.833333333333333,4.266666666666667,0.9616666666666667,0.65,0.9133333333333333,0.4,0.9533333333333334,0.5,4.833667,7.558875,13.829583,18.817875,30,0
+real,0.1,25,301,5,20,iterative_strict,30,20.0,20,20,4.833333333333333,2,4.1,11,4.833333333333333,4.266666666666667,0.88,0.3,0.8333333333333334,0.2,0.8427777777777778,0.5,5.401125,7.925625,15.112125,18.668416,30,0
+real,0.1,25,301,10,40,exact,30,40.0,40,40,10.0,0,7.6,12,10.0,7.6,1.0,1.0,1.0,1.0,1.0,1.0,5.647541,7.119958,15.177709,20.180959,0,0
+real,0.1,25,301,10,40,planner_default,30,40.0,40,40,10.0,0,7.6,12,10.0,7.6,1.0,1.0,1.0,1.0,1.0,1.0,5.461208,6.690125,14.756416,19.479958,0,0
+real,0.1,25,301,10,40,planner_default_generic,30,40.0,40,40,10.0,0,7.6,12,10.0,7.6,1.0,1.0,1.0,1.0,1.0,1.0,5.605083,7.856584,14.731333,19.893,0,0
+real,0.1,25,301,10,40,hnsw_ef40,30,4.1,0,20,2.3666666666666667,30,3.7333333333333334,28,10.0,7.6,0.10250000000000001,0.0,0.22666666666666666,0.0,0.46694444444444444,0.0,2.114167,3.985916,10.412,15.934625,30,0
+real,0.1,25,301,10,40,hnsw_ef100,30,9.366666666666667,1,25,5.666666666666667,25,5.433333333333334,25,10.0,7.6,0.2341666666666667,0.025,0.5466666666666666,0.1,0.6972222222222223,0.2,2.918916,4.86575,12.333792,17.571667,30,0
+real,0.1,25,301,10,40,hnsw_ef200,30,17.8,8,33,8.566666666666666,14,6.633333333333334,18,10.0,7.6,0.445,0.2,0.8433333333333334,0.2,0.85,0.2,4.604,7.157875,14.44075,19.252583,30,0
+real,0.1,25,301,10,40,iterative_relaxed,30,40.0,40,40,10.0,0,7.6,12,10.0,7.6,0.9941666666666666,0.95,0.94,0.6,0.9933333333333334,0.9,8.001708,11.62425,18.582792,25.474083,30,0
+real,0.1,25,301,10,40,iterative_strict,30,40.0,40,40,10.0,0,7.266666666666667,14,10.0,7.6,0.925,0.625,0.83,0.3,0.8377777777777777,0.5,7.549916,9.130084,17.280667,23.882292,30,0
+real,0.1,25,301,20,80,exact,30,80.0,80,80,20.0,0,12.3,19,20.0,12.3,1.0,1.0,1.0,1.0,1.0,1.0,4.71825,7.954083,13.742625,18.655375,0,0
+real,0.1,25,301,20,80,planner_default,30,80.0,80,80,20.0,0,12.3,19,20.0,12.3,1.0,1.0,1.0,1.0,1.0,1.0,4.904458,6.744208,14.625875,22.035667,0,0
+real,0.1,25,301,20,80,planner_default_generic,30,80.0,80,80,20.0,0,12.3,19,20.0,12.3,1.0,1.0,1.0,1.0,1.0,1.0,4.492875,6.487583,12.666834,17.078167,0,0
+real,0.1,25,301,20,80,hnsw_ef40,30,4.1,0,20,2.3666666666666667,30,4.133333333333334,29,20.0,12.3,0.051250000000000004,0.0,0.11666666666666667,0.0,0.4014647246458392,0.0,2.155792,4.17175,11.153084,15.808,30,0
+real,0.1,25,301,20,80,hnsw_ef100,30,9.366666666666667,1,25,5.8,30,6.0,29,20.0,12.3,0.11708333333333334,0.0125,0.29,0.05,0.597234262125903,0.1,3.285792,5.094667,12.222416,17.164208,30,0
+real,0.1,25,301,20,80,hnsw_ef200,30,17.8,8,33,10.333333333333334,30,8.2,29,20.0,12.3,0.2225,0.1,0.5166666666666667,0.1,0.7573321840698001,0.1,4.714958,7.01425,14.37025,20.055208,30,0
+real,0.1,25,301,20,80,iterative_relaxed,30,80.0,80,80,20.0,0,12.3,19,20.0,12.3,0.9975,0.9875,0.985,0.9,0.9983333333333333,0.95,8.860209,14.286,18.475833,27.342084,30,0
+real,0.1,25,301,20,80,iterative_strict,30,80.0,80,80,20.0,0,11.833333333333334,19,20.0,12.3,0.9566666666666667,0.8125,0.8816666666666666,0.55,0.8669659833630422,0.5,9.0245,14.375417,20.601333,26.009583,30,0
+real,0.01,3,34,5,20,exact,30,20.0,20,20,4.933333333333334,2,2.3,24,4.933333333333334,2.3,1.0,1.0,1.0,1.0,1.0,1.0,3.67575,5.206542,7.156584,10.347916,0,0
+real,0.01,3,34,5,20,planner_default,30,20.0,20,20,4.933333333333334,2,2.3,24,4.933333333333334,2.3,1.0,1.0,1.0,1.0,1.0,1.0,3.145333,4.237041,6.112125,8.293416,0,0
+real,0.01,3,34,5,20,planner_default_generic,30,20.0,20,20,4.933333333333334,2,2.3,24,4.933333333333334,2.3,1.0,1.0,1.0,1.0,1.0,1.0,3.349792,4.324709,5.74225,7.898208,0,0
+real,0.01,3,34,5,20,hnsw_ef40,30,0.4,0,2,0.4,30,1.4666666666666666,29,4.933333333333334,2.3,0.020000000000000004,0.0,0.08166666666666668,0.0,0.65,0.0,1.556542,2.73425,4.030583,5.540292,30,0
+real,0.01,3,34,5,20,hnsw_ef100,30,0.7,0,8,0.5333333333333333,30,1.5,29,4.933333333333334,2.3,0.035,0.0,0.11000000000000001,0.0,0.6727272727272727,0.0,1.880417,3.57125,4.169209,6.384959,30,0
+real,0.01,3,34,5,20,hnsw_ef200,30,1.3666666666666667,0,9,0.9666666666666667,30,1.6333333333333333,28,4.933333333333334,2.3,0.06833333333333333,0.0,0.19666666666666668,0.0,0.7545454545454546,0.0,2.580084,4.159542,4.887708,7.876708,30,0
+real,0.01,3,34,5,20,iterative_relaxed,30,20.0,20,20,4.933333333333334,2,2.3,24,4.933333333333334,2.3,1.0,1.0,1.0,1.0,1.0,1.0,8.813875,10.804417,11.394166,15.569334,30,0
+real,0.01,3,34,5,20,iterative_strict,30,20.0,20,20,4.933333333333334,2,2.2333333333333334,24,4.933333333333334,2.3,0.9883333333333333,0.95,0.9533333333333334,0.8,0.9568181818181819,0.5,9.289,11.384833,12.155125,15.199625,30,0
+real,0.01,3,34,10,40,exact,30,34.0,34,34,6.0,30,2.4,30,6.0,2.4,1.0,1.0,1.0,1.0,1.0,1.0,3.292583,5.300208,6.555042,9.797833,0,0
+real,0.01,3,34,10,40,planner_default,30,34.0,34,34,6.0,30,2.4,30,6.0,2.4,1.0,1.0,1.0,1.0,1.0,1.0,2.670541,5.310834,5.56675,9.497208,0,0
+real,0.01,3,34,10,40,planner_default_generic,30,34.0,34,34,6.0,30,2.4,30,6.0,2.4,1.0,1.0,1.0,1.0,1.0,1.0,2.637542,4.364833,4.289792,7.304875,0,0
+real,0.01,3,34,10,40,hnsw_ef40,30,0.4,0,2,0.4,30,1.5,30,6.0,2.4,0.01176470588235294,0.0,0.06666666666666667,0.0,0.6439393939393939,0.0,1.748166,2.634208,4.287875,6.210959,30,0
+real,0.01,3,34,10,40,hnsw_ef100,30,0.7,0,8,0.5333333333333333,30,1.5333333333333334,30,6.0,2.4,0.020588235294117647,0.0,0.08888888888888888,0.0,0.6666666666666666,0.0,2.000917,3.00225,4.575917,6.449167,30,0
+real,0.01,3,34,10,40,hnsw_ef200,30,1.3666666666666667,0,9,0.9666666666666667,30,1.6666666666666667,30,6.0,2.4,0.04019607843137255,0.0,0.1611111111111111,0.0,0.7454545454545454,0.0,2.748,4.54475,4.997167,7.0285,30,0
+real,0.01,3,34,10,40,iterative_relaxed,30,34.0,34,34,6.0,30,2.4,30,6.0,2.4,1.0,1.0,1.0,1.0,1.0,1.0,13.397667,17.224125,16.4445,20.704292,30,0
+real,0.01,3,34,10,40,iterative_strict,30,33.766666666666666,33,34,5.966666666666667,30,2.3333333333333335,30,6.0,2.4,0.9931372549019607,0.9705882352941176,0.961111111111111,0.8333333333333334,0.9568181818181819,0.5,13.139,17.083667,16.788917,20.454375,30,0
+real,0.01,3,34,20,80,exact,30,34.0,34,34,6.0,30,2.4,30,6.0,2.4,1.0,1.0,1.0,1.0,1.0,1.0,3.602625,5.108167,6.490542,9.334875,0,0
+real,0.01,3,34,20,80,planner_default,30,34.0,34,34,6.0,30,2.4,30,6.0,2.4,1.0,1.0,1.0,1.0,1.0,1.0,4.213542,5.789042,7.228042,12.456,0,0
+real,0.01,3,34,20,80,planner_default_generic,30,34.0,34,34,6.0,30,2.4,30,6.0,2.4,1.0,1.0,1.0,1.0,1.0,1.0,3.959292,5.263958,6.395958,8.762292,0,0
+real,0.01,3,34,20,80,hnsw_ef40,30,0.4,0,2,0.4,30,1.5,30,6.0,2.4,0.01176470588235294,0.0,0.06666666666666667,0.0,0.6439393939393939,0.0,2.18175,4.344334,4.980667,8.60375,30,0
+real,0.01,3,34,20,80,hnsw_ef100,30,0.7,0,8,0.5333333333333333,30,1.5333333333333334,30,6.0,2.4,0.020588235294117647,0.0,0.08888888888888888,0.0,0.6666666666666666,0.0,2.377958,4.730958,5.102792,8.542625,30,0
+real,0.01,3,34,20,80,hnsw_ef200,30,1.3666666666666667,0,9,0.9666666666666667,30,1.6666666666666667,30,6.0,2.4,0.04019607843137255,0.0,0.1611111111111111,0.0,0.7454545454545454,0.0,3.3945,5.948958,6.066833,10.67825,30,0
+real,0.01,3,34,20,80,iterative_relaxed,30,34.0,34,34,6.0,30,2.4,30,6.0,2.4,1.0,1.0,1.0,1.0,1.0,1.0,14.137834,17.002583,17.633958,23.545167,30,0
+real,0.01,3,34,20,80,iterative_strict,30,33.766666666666666,33,34,5.966666666666667,30,2.3333333333333335,30,6.0,2.4,0.9931372549019607,0.9705882352941176,0.961111111111111,0.8333333333333334,0.9568181818181819,0.5,13.92275,15.64125,17.299375,20.019958,30,0
+real,0.001,1,10,5,20,exact,30,10.0,10,10,2.0,30,0.8666666666666667,30,2.0,0.8666666666666667,1.0,1.0,1.0,1.0,1.0,1.0,2.429167,6.250333,3.9205,11.022542,0,0
+real,0.001,1,10,5,20,planner_default,30,10.0,10,10,2.0,30,0.8666666666666667,30,2.0,0.8666666666666667,1.0,1.0,1.0,1.0,1.0,1.0,1.607084,3.268542,2.614625,4.61075,0,0
+real,0.001,1,10,5,20,planner_default_generic,30,10.0,10,10,2.0,30,0.8666666666666667,30,2.0,0.8666666666666667,1.0,1.0,1.0,1.0,1.0,1.0,0.799084,1.545417,1.630416,2.739458,0,0
+real,0.001,1,10,5,20,hnsw_ef40,30,0.23333333333333334,0,2,0.23333333333333334,30,0.6,30,2.0,0.8666666666666667,0.023333333333333334,0.0,0.11666666666666667,0.0,0.7333333333333333,0.0,1.654375,2.856333,3.017625,4.416416,30,0
+real,0.001,1,10,5,20,hnsw_ef100,30,0.26666666666666666,0,2,0.26666666666666666,30,0.6,30,2.0,0.8666666666666667,0.02666666666666667,0.0,0.13333333333333333,0.0,0.7333333333333333,0.0,1.788458,3.249625,2.953583,5.4345,30,0
+real,0.001,1,10,5,20,hnsw_ef200,30,0.5333333333333333,0,3,0.4666666666666667,30,0.7,30,2.0,0.8666666666666667,0.05333333333333334,0.0,0.23333333333333334,0.0,0.8333333333333334,0.0,2.502375,3.756209,3.819875,6.141083,30,0
+real,0.001,1,10,5,20,iterative_relaxed,30,10.0,10,10,2.0,30,0.8666666666666667,30,2.0,0.8666666666666667,1.0,1.0,1.0,1.0,1.0,1.0,9.801875,11.462541,11.092708,14.934791,30,0
+real,0.001,1,10,5,20,iterative_strict,30,9.933333333333334,9,10,2.0,30,0.8666666666666667,30,2.0,0.8666666666666667,0.9933333333333334,0.9,0.9666666666666667,0.5,0.9666666666666667,0.5,10.254584,12.044333,12.036125,14.9105,30,0
+real,0.001,1,10,10,40,exact,30,10.0,10,10,2.0,30,0.8666666666666667,30,2.0,0.8666666666666667,1.0,1.0,1.0,1.0,1.0,1.0,2.512083,4.411583,4.474125,7.933667,0,0
+real,0.001,1,10,10,40,planner_default,30,10.0,10,10,2.0,30,0.8666666666666667,30,2.0,0.8666666666666667,1.0,1.0,1.0,1.0,1.0,1.0,2.312083,4.012417,3.820458,7.142833,0,0
+real,0.001,1,10,10,40,planner_default_generic,30,10.0,10,10,2.0,30,0.8666666666666667,30,2.0,0.8666666666666667,1.0,1.0,1.0,1.0,1.0,1.0,1.733,3.173542,3.373625,4.815916,0,0
+real,0.001,1,10,10,40,hnsw_ef40,30,0.23333333333333334,0,2,0.23333333333333334,30,0.6,30,2.0,0.8666666666666667,0.023333333333333334,0.0,0.11666666666666667,0.0,0.7333333333333333,0.0,1.537917,2.941417,2.983708,5.475875,30,0
+real,0.001,1,10,10,40,hnsw_ef100,30,0.26666666666666666,0,2,0.26666666666666666,30,0.6,30,2.0,0.8666666666666667,0.02666666666666667,0.0,0.13333333333333333,0.0,0.7333333333333333,0.0,1.95075,3.208333,3.45075,5.633708,30,0
+real,0.001,1,10,10,40,hnsw_ef200,30,0.5333333333333333,0,3,0.4666666666666667,30,0.7,30,2.0,0.8666666666666667,0.05333333333333334,0.0,0.23333333333333334,0.0,0.8333333333333334,0.0,2.582375,3.968792,3.978875,6.948625,30,0
+real,0.001,1,10,10,40,iterative_relaxed,30,10.0,10,10,2.0,30,0.8666666666666667,30,2.0,0.8666666666666667,1.0,1.0,1.0,1.0,1.0,1.0,10.357875,12.355167,12.118667,15.595833,30,0
+real,0.001,1,10,10,40,iterative_strict,30,9.933333333333334,9,10,2.0,30,0.8666666666666667,30,2.0,0.8666666666666667,0.9933333333333334,0.9,0.9666666666666667,0.5,0.9666666666666667,0.5,10.153583,11.652583,11.497333,14.263,30,0
+real,0.001,1,10,20,80,exact,30,10.0,10,10,2.0,30,0.8666666666666667,30,2.0,0.8666666666666667,1.0,1.0,1.0,1.0,1.0,1.0,2.514583,4.014542,3.997791,6.869709,0,0
+real,0.001,1,10,20,80,planner_default,30,10.0,10,10,2.0,30,0.8666666666666667,30,2.0,0.8666666666666667,1.0,1.0,1.0,1.0,1.0,1.0,2.317708,3.779,4.162542,8.023375,0,0
+real,0.001,1,10,20,80,planner_default_generic,30,10.0,10,10,2.0,30,0.8666666666666667,30,2.0,0.8666666666666667,1.0,1.0,1.0,1.0,1.0,1.0,2.28,3.755833,3.711292,5.656416,0,0
+real,0.001,1,10,20,80,hnsw_ef40,30,0.23333333333333334,0,2,0.23333333333333334,30,0.6,30,2.0,0.8666666666666667,0.023333333333333334,0.0,0.11666666666666667,0.0,0.7333333333333333,0.0,1.741167,3.206375,2.847584,5.358875,30,0
+real,0.001,1,10,20,80,hnsw_ef100,30,0.26666666666666666,0,2,0.26666666666666666,30,0.6,30,2.0,0.8666666666666667,0.02666666666666667,0.0,0.13333333333333333,0.0,0.7333333333333333,0.0,1.929584,3.498417,3.204375,5.035958,30,0
+real,0.001,1,10,20,80,hnsw_ef200,30,0.5333333333333333,0,3,0.4666666666666667,30,0.7,30,2.0,0.8666666666666667,0.05333333333333334,0.0,0.23333333333333334,0.0,0.8333333333333334,0.0,2.72425,4.387541,4.21175,5.868833,30,0
+real,0.001,1,10,20,80,iterative_relaxed,30,10.0,10,10,2.0,30,0.8666666666666667,30,2.0,0.8666666666666667,1.0,1.0,1.0,1.0,1.0,1.0,9.646375,12.358917,11.704958,14.857458,30,0
+real,0.001,1,10,20,80,iterative_strict,30,9.933333333333334,9,10,2.0,30,0.8666666666666667,30,2.0,0.8666666666666667,0.9933333333333334,0.9,0.9666666666666667,0.5,0.9666666666666667,0.5,10.009917,13.899417,11.684417,15.659875,30,0
+50000,1.0,3559,50008,5,20,exact,30,20.0,20,20,5.0,0,5.0,0,5.0,5.0,1.0,1.0,1.0,1.0,1.0,1.0,214.92475,290.64025,1340.441917,1891.37825,0,0
+50000,1.0,3559,50008,5,20,planner_default,30,20.0,20,20,5.0,0,5.0,0,5.0,5.0,1.0,1.0,1.0,1.0,1.0,1.0,220.969416,380.901041,1350.275625,2001.463625,0,0
+50000,1.0,3559,50008,5,20,planner_default_generic,30,20.0,20,20,5.0,0,5.0,0,5.0,5.0,1.0,1.0,1.0,1.0,1.0,1.0,204.443917,245.759417,1305.707833,1847.093041,0,0
+50000,1.0,3559,50008,5,20,hnsw_ef40,30,20.0,20,20,5.0,0,5.0,0,5.0,5.0,0.9333333333333333,0.0,0.9333333333333333,0.0,0.9466666666666667,0.0,14.808291,19.1645,1158.122208,1661.9185,30,0
+50000,1.0,3559,50008,5,20,hnsw_ef100,30,20.0,20,20,5.0,0,5.0,0,5.0,5.0,0.9783333333333334,0.8,0.9733333333333333,0.8,0.9866666666666667,0.8,15.061,19.252666,1130.768416,1693.656667,30,0
+50000,1.0,3559,50008,5,20,hnsw_ef200,30,20.0,20,20,5.0,0,5.0,0,5.0,5.0,0.9833333333333333,0.8,0.98,0.8,0.9866666666666667,0.8,15.268625,23.537125,1141.161834,1725.2065,30,0
+50000,1.0,3559,50008,5,20,iterative_relaxed,30,20.0,20,20,5.0,0,5.0,0,5.0,5.0,0.9333333333333333,0.0,0.9333333333333333,0.0,0.9466666666666667,0.0,14.97225,25.426459,1194.428708,1954.731333,30,0
+50000,1.0,3559,50008,5,20,iterative_strict,30,20.0,20,20,5.0,0,5.0,0,5.0,5.0,0.9333333333333333,0.0,0.9333333333333333,0.0,0.9466666666666667,0.0,14.657583,17.537708,1125.166125,1685.045667,30,0
+50000,1.0,3559,50008,10,40,exact,30,40.0,40,40,9.933333333333334,1,10.0,0,9.933333333333334,10.0,1.0,1.0,1.0,1.0,1.0,1.0,217.44475,327.269625,1371.06275,2040.724125,0,0
+50000,1.0,3559,50008,10,40,planner_default,30,40.0,40,40,9.933333333333334,1,10.0,0,9.933333333333334,10.0,1.0,1.0,1.0,1.0,1.0,1.0,208.930417,286.687583,1328.082708,1903.460417,0,0
+50000,1.0,3559,50008,10,40,planner_default_generic,30,40.0,40,40,9.933333333333334,1,10.0,0,9.933333333333334,10.0,1.0,1.0,1.0,1.0,1.0,1.0,207.391375,223.840042,1321.618625,1856.109958,0,0
+50000,1.0,3559,50008,10,40,hnsw_ef40,30,40.0,40,40,9.833333333333334,2,10.0,0,9.933333333333334,10.0,0.925,0.0,0.9116666666666667,0.0,0.9266666666666666,0.0,16.946917,20.405542,1126.786375,1646.843125,30,0
+50000,1.0,3559,50008,10,40,hnsw_ef100,30,40.0,40,40,9.933333333333334,1,10.0,0,9.933333333333334,10.0,0.9758333333333333,0.875,0.9658333333333334,0.8,0.9766666666666667,0.8,17.388125,26.09375,1160.747042,1725.289333,30,0
+50000,1.0,3559,50008,10,40,hnsw_ef200,30,40.0,40,40,9.966666666666667,1,10.0,0,9.933333333333334,10.0,0.9825,0.875,0.9733333333333333,0.8,0.9833333333333333,0.9,17.408625,25.869166,1150.442542,1728.755083,30,0
+50000,1.0,3559,50008,10,40,iterative_relaxed,30,40.0,40,40,9.833333333333334,2,10.0,0,9.933333333333334,10.0,0.925,0.0,0.9116666666666667,0.0,0.9266666666666666,0.0,15.909958,22.854375,1129.156583,1643.395709,30,0
+50000,1.0,3559,50008,10,40,iterative_strict,30,40.0,40,40,9.833333333333334,2,10.0,0,9.933333333333334,10.0,0.925,0.0,0.9116666666666667,0.0,0.9266666666666666,0.0,16.412875,23.764875,1127.018208,1638.785167,30,0
+50000,1.0,3559,50008,20,80,exact,30,80.0,80,80,19.1,5,20.0,0,19.1,20.0,1.0,1.0,1.0,1.0,1.0,1.0,216.616875,325.262041,1331.252375,1892.89875,0,0
+50000,1.0,3559,50008,20,80,planner_default,30,80.0,80,80,19.1,5,20.0,0,19.1,20.0,1.0,1.0,1.0,1.0,1.0,1.0,221.876916,292.835667,1362.955625,1930.059833,0,0
+50000,1.0,3559,50008,20,80,planner_default_generic,30,80.0,80,80,19.1,5,20.0,0,19.1,20.0,1.0,1.0,1.0,1.0,1.0,1.0,210.061417,219.132292,1311.778291,1898.185416,0,0
+50000,1.0,3559,50008,20,80,hnsw_ef40,30,40.0,40,40,17.333333333333332,15,19.666666666666668,3,19.1,20.0,0.48374999999999996,0.0125,0.8534334525123999,0.0,0.87,0.0,16.246917,21.177083,1140.250209,1701.289583,30,0
+50000,1.0,3559,50008,20,80,hnsw_ef100,30,80.0,80,80,19.033333333333335,5,20.0,0,19.1,20.0,0.9629166666666666,0.8375,0.9686858974358974,0.85,0.9683333333333334,0.85,19.800209,22.682792,1150.098791,1681.740458,30,0
+50000,1.0,3559,50008,20,80,hnsw_ef200,30,80.0,80,80,19.066666666666666,5,20.0,0,19.1,20.0,0.9779166666666667,0.8875,0.9762500000000001,0.85,0.9733333333333333,0.85,20.933833,27.3155,1140.979542,1799.34675,30,0
+50000,1.0,3559,50008,20,80,iterative_relaxed,30,80.0,80,80,19.033333333333335,5,20.0,0,19.1,20.0,0.94375,0.5125,0.9370192307692308,0.6,0.9633333333333333,0.85,19.536084,25.500709,1163.030584,1711.913916,30,0
+50000,1.0,3559,50008,20,80,iterative_strict,30,80.0,80,80,19.0,5,20.0,0,19.1,20.0,0.9129166666666666,0.0125,0.9111217948717949,0.0,0.9183333333333333,0.0,20.16925,38.388417,1168.385041,1715.336792,30,0
+50000,0.1,356,5244,5,20,exact,30,20.0,20,20,5.0,0,5.0,0,5.0,5.0,1.0,1.0,1.0,1.0,1.0,1.0,25.397958,28.298833,149.721333,210.678541,0,0
+50000,0.1,356,5244,5,20,planner_default,30,20.0,20,20,5.0,0,5.0,0,5.0,5.0,1.0,1.0,1.0,1.0,1.0,1.0,24.257917,27.599875,147.070417,213.261833,0,0
+50000,0.1,356,5244,5,20,planner_default_generic,30,20.0,20,20,5.0,0,5.0,0,5.0,5.0,1.0,1.0,1.0,1.0,1.0,1.0,23.430666,25.03425,146.993125,204.453625,0,0
+50000,0.1,356,5244,5,20,hnsw_ef40,30,2.6666666666666665,0,12,1.7333333333333334,27,3.466666666666667,17,5.0,5.0,0.13333333333333333,0.0,0.33999999999999997,0.0,0.5399999999999999,0.0,8.250542,11.070166,134.179834,192.017959,30,0
+50000,0.1,356,5244,5,20,hnsw_ef100,30,6.966666666666667,0,20,3.1,18,3.966666666666667,11,5.0,5.0,0.34833333333333333,0.0,0.58,0.0,0.7200000000000001,0.0,9.20475,11.569167,134.116333,193.831709,30,0
+50000,0.1,356,5244,5,20,hnsw_ef200,30,12.033333333333333,0,20,4.066666666666666,11,4.433333333333334,7,5.0,5.0,0.5833333333333334,0.0,0.7533333333333334,0.0,0.8133333333333332,0.2,10.6655,14.081125,136.798375,200.817708,30,0
+50000,0.1,356,5244,5,20,iterative_relaxed,30,20.0,20,20,4.966666666666667,1,4.966666666666667,1,5.0,5.0,0.9133333333333333,0.65,0.8866666666666667,0.4,0.9533333333333334,0.6,12.344292,16.997209,135.855625,194.682333,30,0
+50000,0.1,356,5244,5,20,iterative_strict,30,20.0,20,20,4.966666666666667,1,4.966666666666667,1,5.0,5.0,0.8200000000000001,0.0,0.7933333333333333,0.0,0.8466666666666666,0.2,12.847,15.019,135.417917,196.722667,30,0
+50000,0.1,356,5244,10,40,exact,30,40.0,40,40,10.0,0,10.0,0,10.0,10.0,1.0,1.0,1.0,1.0,1.0,1.0,26.522125,29.085209,151.783417,211.010458,0,0
+50000,0.1,356,5244,10,40,planner_default,30,40.0,40,40,10.0,0,10.0,0,10.0,10.0,1.0,1.0,1.0,1.0,1.0,1.0,26.651584,29.573708,150.73075,207.877,0,0
+50000,0.1,356,5244,10,40,planner_default_generic,30,40.0,40,40,10.0,0,10.0,0,10.0,10.0,1.0,1.0,1.0,1.0,1.0,1.0,24.856542,43.983,149.160125,209.5005,0,0
+50000,0.1,356,5244,10,40,hnsw_ef40,30,2.6666666666666665,0,12,1.8,30,5.1,22,10.0,10.0,0.06666666666666667,0.0,0.18000000000000002,0.0,0.35333333333333333,0.0,8.06475,9.283542,130.06075,189.446833,30,0
+50000,0.1,356,5244,10,40,hnsw_ef100,30,7.133333333333334,0,25,3.6,30,6.1,21,10.0,10.0,0.17833333333333332,0.0,0.35666666666666663,0.0,0.49,0.0,8.894167,11.244625,131.180625,193.390375,30,0
+50000,0.1,356,5244,10,40,hnsw_ef200,30,13.933333333333334,0,38,5.8,25,7.233333333333333,17,10.0,10.0,0.34833333333333333,0.0,0.5533333333333333,0.0,0.63,0.1,10.55775,13.175541,134.226958,190.701167,30,0
+50000,0.1,356,5244,10,40,iterative_relaxed,30,40.0,40,40,10.0,0,10.0,0,10.0,10.0,0.9008333333333333,0.775,0.86,0.6,0.9166666666666666,0.8,16.568625,18.918708,140.824958,197.513167,30,0
+50000,0.1,356,5244,10,40,iterative_strict,30,40.0,40,40,10.0,0,10.0,0,10.0,10.0,0.7808333333333334,0.4,0.7333333333333333,0.0,0.7566666666666666,0.3,16.789916,19.866292,141.042833,200.075666,30,0
+50000,0.1,356,5244,20,80,exact,30,80.0,80,80,20.0,0,20.0,0,20.0,20.0,1.0,1.0,1.0,1.0,1.0,1.0,30.791333,35.930958,157.919292,215.984792,0,0
+50000,0.1,356,5244,20,80,planner_default,30,80.0,80,80,20.0,0,20.0,0,20.0,20.0,1.0,1.0,1.0,1.0,1.0,1.0,30.718333,35.650709,157.718583,220.441667,0,0
+50000,0.1,356,5244,20,80,planner_default_generic,30,80.0,80,80,20.0,0,20.0,0,20.0,20.0,1.0,1.0,1.0,1.0,1.0,1.0,28.758083,30.865042,151.001209,212.300375,0,0
+50000,0.1,356,5244,20,80,hnsw_ef40,30,2.6666666666666665,0,12,1.8,30,6.6,26,20.0,20.0,0.03333333333333333,0.0,0.09000000000000001,0.0,0.22333333333333333,0.0,8.14175,9.448208,131.296167,190.195667,30,0
+50000,0.1,356,5244,20,80,hnsw_ef100,30,7.133333333333334,0,25,3.6,30,7.9,26,20.0,20.0,0.08916666666666666,0.0,0.17833333333333332,0.0,0.3016666666666667,0.0,9.284042,11.64675,134.073791,195.192,30,0
+50000,0.1,356,5244,20,80,hnsw_ef200,30,13.933333333333334,0,38,6.066666666666666,30,9.433333333333334,25,20.0,20.0,0.17416666666666666,0.0,0.3,0.0,0.39666666666666667,0.05,10.242166,12.992541,133.9065,190.911958,30,0
+50000,0.1,356,5244,20,80,iterative_relaxed,30,80.0,80,80,20.0,0,20.0,0,20.0,20.0,0.9354166666666667,0.8375,0.855,0.6,0.9466666666666667,0.85,22.247125,29.83625,150.880833,219.821834,30,0
+50000,0.1,356,5244,20,80,iterative_strict,30,80.0,80,80,20.0,0,19.9,1,20.0,20.0,0.8004166666666667,0.5875,0.675,0.35,0.7233333333333333,0.35,23.615209,32.636542,153.254042,228.340708,30,0
+50000,0.01,36,434,5,20,exact,30,20.0,20,20,4.933333333333334,1,4.9,2,4.933333333333334,4.9,1.0,1.0,1.0,1.0,1.0,1.0,5.787542,7.234541,18.62375,24.320458,0,0
+50000,0.01,36,434,5,20,planner_default,30,20.0,20,20,4.933333333333334,1,4.9,2,4.933333333333334,4.9,1.0,1.0,1.0,1.0,1.0,1.0,5.047,6.315417,17.795625,24.427625,0,0
+50000,0.01,36,434,5,20,planner_default_generic,30,20.0,20,20,4.933333333333334,1,4.9,2,4.933333333333334,4.9,1.0,1.0,1.0,1.0,1.0,1.0,4.559709,6.576459,16.388417,25.014875,0,0
+50000,0.01,36,434,5,20,hnsw_ef40,30,0.23333333333333334,0,4,0.16666666666666666,30,2.5,21,4.933333333333334,4.9,0.011666666666666667,0.0,0.042222222222222223,0.0,0.33722222222222226,0.0,6.516167,8.01175,19.168542,25.433167,30,0
+50000,0.01,36,434,5,20,hnsw_ef100,30,0.6333333333333333,0,10,0.3333333333333333,30,2.566666666666667,21,4.933333333333334,4.9,0.03166666666666666,0.0,0.08,0.0,0.36166666666666664,0.0,7.187166,9.218917,19.572917,31.091209,30,0
+50000,0.01,36,434,5,20,hnsw_ef200,30,1.6333333333333333,0,20,0.6,30,2.7333333333333334,19,4.933333333333334,4.9,0.08166666666666668,0.0,0.13333333333333333,0.0,0.39499999999999996,0.0,8.202916,18.5625,21.749916,38.184083,30,0
+50000,0.01,36,434,5,20,iterative_relaxed,30,20.0,20,20,4.933333333333334,1,4.9,2,4.933333333333334,4.9,0.9450000000000001,0.6,0.9066666666666666,0.6,0.9733333333333333,0.8,40.458667,53.624291,53.474458,72.042208,30,0
+50000,0.01,36,434,5,20,iterative_strict,30,20.0,20,20,4.933333333333334,1,4.9,2,4.933333333333334,4.9,0.8316666666666667,0.1,0.7466666666666667,0.0,0.8533333333333334,0.2,42.593917,52.616417,54.350125,82.17975,30,0
+50000,0.01,36,434,10,40,exact,30,40.0,40,40,9.966666666666667,1,9.366666666666667,6,9.966666666666667,9.366666666666667,1.0,1.0,1.0,1.0,1.0,1.0,7.110917,8.74925,21.717916,28.2245,0,0
+50000,0.01,36,434,10,40,planner_default,30,40.0,40,40,9.966666666666667,1,9.366666666666667,6,9.966666666666667,9.366666666666667,1.0,1.0,1.0,1.0,1.0,1.0,7.391625,9.00525,22.425667,28.042375,0,0
+50000,0.01,36,434,10,40,planner_default_generic,30,40.0,40,40,9.966666666666667,1,9.366666666666667,6,9.966666666666667,9.366666666666667,1.0,1.0,1.0,1.0,1.0,1.0,6.753667,9.618417,19.021625,23.522375,0,0
+50000,0.01,36,434,10,40,hnsw_ef40,30,0.23333333333333334,0,4,0.16666666666666666,30,3.1666666666666665,28,9.966666666666667,9.366666666666667,0.005833333333333334,0.0,0.01740740740740741,0.0,0.2439814814814815,0.0,6.916042,16.368541,20.0005,31.139667,30,0
+50000,0.01,36,434,10,40,hnsw_ef100,30,0.6333333333333333,0,10,0.3333333333333333,30,3.2666666666666666,28,9.966666666666667,9.366666666666667,0.01583333333333333,0.0,0.03444444444444444,0.0,0.25435185185185183,0.0,8.288167,13.775709,22.241625,32.444334,30,0
+50000,0.01,36,434,10,40,hnsw_ef200,30,1.9,0,28,0.6,30,3.433333333333333,28,9.966666666666667,9.366666666666667,0.047499999999999994,0.0,0.061111111111111116,0.0,0.27101851851851855,0.0,8.530833,11.498125,22.031792,29.910375,30,0
+50000,0.01,36,434,10,40,iterative_relaxed,30,40.0,40,40,9.966666666666667,1,9.366666666666667,6,9.966666666666667,9.366666666666667,0.9166666666666666,0.675,0.9533333333333334,0.7,0.9666666666666667,0.8,48.669916,62.732833,62.962042,76.220959,30,0
+50000,0.01,36,434,10,40,iterative_strict,30,40.0,40,40,9.933333333333334,1,9.266666666666667,6,9.966666666666667,9.366666666666667,0.8450000000000001,0.375,0.7688888888888888,0.2,0.8343518518518518,0.5,49.521334,64.581666,65.040667,80.076875,30,0
+50000,0.01,36,434,20,80,exact,30,80.0,80,80,20.0,0,17.333333333333332,7,20.0,17.333333333333332,1.0,1.0,1.0,1.0,1.0,1.0,7.204917,12.052791,21.851416,30.307333,0,0
+50000,0.01,36,434,20,80,planner_default,30,80.0,80,80,20.0,0,17.333333333333332,7,20.0,17.333333333333332,1.0,1.0,1.0,1.0,1.0,1.0,6.209041,8.846708,20.653208,27.576542,0,0
+50000,0.01,36,434,20,80,planner_default_generic,30,80.0,80,80,20.0,0,17.333333333333332,7,20.0,17.333333333333332,1.0,1.0,1.0,1.0,1.0,1.0,5.507834,7.990209,16.679333,24.604291,0,0
+50000,0.01,36,434,20,80,hnsw_ef40,30,0.23333333333333334,0,4,0.16666666666666666,30,3.533333333333333,29,20.0,17.333333333333332,0.002916666666666667,0.0,0.008333333333333333,0.0,0.20535304020598139,0.0,6.775458,8.337083,19.370083,25.477125,30,0
+50000,0.01,36,434,20,80,hnsw_ef100,30,0.6333333333333333,0,10,0.3333333333333333,30,3.6333333333333333,29,20.0,17.333333333333332,0.007916666666666666,0.0,0.016666666666666666,0.0,0.21035304020598136,0.0,6.934875,8.337458,19.433416,24.902125,30,0
+50000,0.01,36,434,20,80,hnsw_ef200,30,1.9,0,28,0.6,30,3.8,29,20.0,17.333333333333332,0.023749999999999997,0.0,0.030000000000000002,0.0,0.21868637353931472,0.0,7.7565,12.425667,20.693041,29.749333,30,0
+50000,0.01,36,434,20,80,iterative_relaxed,30,80.0,80,80,20.0,0,17.3,7,20.0,17.333333333333332,0.8375,0.525,0.91,0.7,0.9327450980392158,0.75,54.417708,82.304958,71.502584,109.41625,30,0
+50000,0.01,36,434,20,80,iterative_strict,30,80.0,80,80,20.0,0,17.233333333333334,7,20.0,17.333333333333332,0.8,0.525,0.8033333333333333,0.6,0.8465503069914835,0.65,56.24425,85.793041,73.696583,92.093459,30,0
+50000,0.001,4,40,5,20,exact,30,20.0,20,20,4.966666666666667,1,4.033333333333333,12,4.966666666666667,4.033333333333333,1.0,1.0,1.0,1.0,1.0,1.0,4.16425,6.18825,9.019167,13.986667,0,0
+50000,0.001,4,40,5,20,planner_default,30,20.0,20,20,4.966666666666667,1,4.033333333333333,12,4.966666666666667,4.033333333333333,1.0,1.0,1.0,1.0,1.0,1.0,4.475,6.137667,10.231292,13.253125,0,0
+50000,0.001,4,40,5,20,planner_default_generic,30,20.0,20,20,4.966666666666667,1,4.033333333333333,12,4.966666666666667,4.033333333333333,1.0,1.0,1.0,1.0,1.0,1.0,5.0505,6.490208,8.727916,10.070625,0,0
+50000,0.001,4,40,5,20,hnsw_ef40,30,0.0,0,0,0.0,30,1.4333333333333333,29,4.966666666666667,4.033333333333333,0.0,0.0,0.0,0.0,0.3761111111111111,0.0,6.5675,8.931667,10.808917,14.614542,30,0
+50000,0.001,4,40,5,20,hnsw_ef100,30,0.03333333333333333,0,1,0.03333333333333333,30,1.4333333333333333,29,4.966666666666667,4.033333333333333,0.0016666666666666668,0.0,0.006666666666666667,0.0,0.3761111111111111,0.0,6.731084,10.277708,10.946833,16.684709,30,0
+50000,0.001,4,40,5,20,hnsw_ef200,30,0.06666666666666667,0,1,0.06666666666666667,30,1.4333333333333333,29,4.966666666666667,4.033333333333333,0.0033333333333333335,0.0,0.013333333333333334,0.0,0.3761111111111111,0.0,7.313542,9.484417,11.488583,14.931042,30,0
+50000,0.001,4,40,5,20,iterative_relaxed,30,15.833333333333334,10,20,4.9,3,3.8666666666666667,16,4.966666666666667,4.033333333333333,0.64,0.35,0.8466666666666668,0.4,0.9166666666666666,0.4,50.756292,67.455625,57.024375,70.2045,30,0
+50000,0.001,4,40,5,20,iterative_strict,30,15.566666666666666,10,20,4.9,3,3.8666666666666667,16,4.966666666666667,4.033333333333333,0.6233333333333333,0.35,0.7866666666666667,0.4,0.8566666666666667,0.0,49.604333,60.309459,55.598292,70.601208,30,0
+50000,0.001,4,40,10,40,exact,30,40.0,40,40,7.9,30,5.066666666666666,30,7.9,5.066666666666666,1.0,1.0,1.0,1.0,1.0,1.0,4.167125,6.638833,9.036625,13.575125,0,0
+50000,0.001,4,40,10,40,planner_default,30,40.0,40,40,7.9,30,5.066666666666666,30,7.9,5.066666666666666,1.0,1.0,1.0,1.0,1.0,1.0,3.090709,5.125875,8.07275,11.287625,0,0
+50000,0.001,4,40,10,40,planner_default_generic,30,40.0,40,40,7.9,30,5.066666666666666,30,7.9,5.066666666666666,1.0,1.0,1.0,1.0,1.0,1.0,3.667125,5.7295,6.64525,9.30125,0,0
+50000,0.001,4,40,10,40,hnsw_ef40,30,0.0,0,0,0.0,30,1.4666666666666666,30,7.9,5.066666666666666,0.0,0.0,0.0,0.0,0.34460317460317463,0.0,6.339166,8.328958,10.5565,14.1955,30,0
+50000,0.001,4,40,10,40,hnsw_ef100,30,0.03333333333333333,0,1,0.03333333333333333,30,1.4666666666666666,30,7.9,5.066666666666666,0.0008333333333333334,0.0,0.004166666666666667,0.0,0.34460317460317463,0.0,6.7325,8.925625,10.84375,14.439375,30,0
+50000,0.001,4,40,10,40,hnsw_ef200,30,0.06666666666666667,0,1,0.06666666666666667,30,1.4666666666666666,30,7.9,5.066666666666666,0.0016666666666666668,0.0,0.008333333333333333,0.0,0.34460317460317463,0.0,7.124,8.967375,11.036334,14.299541,30,0
+50000,0.001,4,40,10,40,iterative_relaxed,30,16.266666666666666,10,26,6.6,30,4.466666666666667,30,7.9,5.066666666666666,0.4066666666666666,0.25,0.6391666666666667,0.25,0.8562301587301587,0.3333333333333333,49.838166,60.833209,56.800041,66.936833,30,0
+50000,0.001,4,40,10,40,iterative_strict,30,15.933333333333334,10,26,6.6,30,4.466666666666667,30,7.9,5.066666666666666,0.3983333333333333,0.25,0.5991666666666667,0.25,0.8006349206349206,0.0,49.261208,59.511917,55.067792,63.279958,30,0
+50000,0.001,4,40,20,80,exact,30,40.0,40,40,7.9,30,5.066666666666666,30,7.9,5.066666666666666,1.0,1.0,1.0,1.0,1.0,1.0,3.834875,5.9895,8.756917,11.864208,0,0
+50000,0.001,4,40,20,80,planner_default,30,40.0,40,40,7.9,30,5.066666666666666,30,7.9,5.066666666666666,1.0,1.0,1.0,1.0,1.0,1.0,4.132667,6.434917,9.170209,13.358959,0,0
+50000,0.001,4,40,20,80,planner_default_generic,30,40.0,40,40,7.9,30,5.066666666666666,30,7.9,5.066666666666666,1.0,1.0,1.0,1.0,1.0,1.0,2.951416,5.590333,6.782,9.7685,0,0
+50000,0.001,4,40,20,80,hnsw_ef40,30,0.0,0,0,0.0,30,1.4666666666666666,30,7.9,5.066666666666666,0.0,0.0,0.0,0.0,0.34460317460317463,0.0,6.654916,9.752041,11.036459,14.653916,30,0
+50000,0.001,4,40,20,80,hnsw_ef100,30,0.03333333333333333,0,1,0.03333333333333333,30,1.4666666666666666,30,7.9,5.066666666666666,0.0008333333333333334,0.0,0.004166666666666667,0.0,0.34460317460317463,0.0,6.724417,8.72375,10.629541,13.715334,30,0
+50000,0.001,4,40,20,80,hnsw_ef200,30,0.06666666666666667,0,1,0.06666666666666667,30,1.4666666666666666,30,7.9,5.066666666666666,0.0016666666666666668,0.0,0.008333333333333333,0.0,0.34460317460317463,0.0,7.647666,9.693709,11.778792,15.649375,30,0
+50000,0.001,4,40,20,80,iterative_relaxed,30,16.266666666666666,10,26,6.6,30,4.466666666666667,30,7.9,5.066666666666666,0.4066666666666666,0.25,0.6391666666666667,0.25,0.8562301587301587,0.3333333333333333,51.472916,75.506625,57.670583,96.20425,30,0
+50000,0.001,4,40,20,80,iterative_strict,30,15.933333333333334,10,26,6.6,30,4.466666666666667,30,7.9,5.066666666666666,0.3983333333333333,0.25,0.5991666666666667,0.25,0.8006349206349206,0.0,53.060459,81.853708,60.847,110.290375,30,0
diff --git a/docs/test-results/evidence/issue-549/after-fix/tables.md b/docs/test-results/evidence/issue-549/after-fix/tables.md
new file mode 100644
index 00000000..b3a3375c
--- /dev/null
+++ b/docs/test-results/evidence/issue-549/after-fix/tables.md
@@ -0,0 +1,240 @@
+### 규모 real: 문서 251건, 청크 3,572개 (합성 비율 0.0%)
+
+| 허용 비율 | 허용 문서 | 허용 청크 |
+|---:|---:|---:|
+| 100% | 251 | 3,572 |
+| 10% | 25 | 301 |
+| 1% | 3 | 34 |
+| 0.1% | 1 | 10 |
+
+#### real · topK 5 (SQL 요청 20개)
+
+| 허용 비율 | 방식 | HNSW 사용 | SQL 후보 평균 (최소~최대) | 벡터 최종 평균 | topK 미달 질문 | 하이브리드 최종 평균 | SQL Recall | 벡터 최종 Recall (최소) | 하이브리드 최종 Recall | 벡터 p50 / p95 ms | 하이브리드 p50 / p95 ms |
+|---:|---|---:|---|---:|---:|---:|---:|---|---:|---|---|
+| 100% | Exact (인덱스 없음) | 0/30 | 20.0 (20~20) | 4.9 | 2/30 | 4.9 | 1.00 | 1.00 (1.00) | 1.00 | 17.3 / 24.8 | 102.6 / 156.4 |
+| 100% | 현재 계획 (Custom Plan) | 0/30 | 20.0 (20~20) | 4.9 | 2/30 | 4.9 | 1.00 | 1.00 (1.00) | 1.00 | 16.2 / 19.0 | 98.2 / 136.0 |
+| 100% | 현재 계획 (Generic Plan) | 0/30 | 20.0 (20~20) | 4.9 | 2/30 | 4.9 | 1.00 | 1.00 (1.00) | 1.00 | 15.3 / 18.9 | 97.8 / 134.8 |
+| 100% | HNSW 강제 ef40 | 30/30 | 20.0 (20~20) | 4.9 | 2/30 | 4.6 | 0.83 | 0.80 (0.00) | 0.85 | 4.9 / 7.5 | 84.9 / 124.9 |
+| 100% | HNSW 강제 ef100 | 30/30 | 20.0 (20~20) | 4.9 | 2/30 | 4.6 | 0.90 | 0.87 (0.00) | 0.89 | 5.4 / 7.5 | 87.1 / 129.5 |
+| 100% | HNSW 강제 ef200 | 30/30 | 20.0 (20~20) | 4.9 | 2/30 | 4.8 | 0.97 | 0.96 (0.00) | 0.97 | 6.2 / 11.9 | 94.4 / 129.6 |
+| 100% | HNSW 강제 iterative relaxed | 30/30 | 20.0 (20~20) | 4.9 | 2/30 | 4.6 | 0.83 | 0.80 (0.00) | 0.85 | 5.2 / 8.4 | 88.8 / 166.7 |
+| 100% | HNSW 강제 iterative strict | 30/30 | 20.0 (20~20) | 4.9 | 2/30 | 4.6 | 0.83 | 0.80 (0.00) | 0.85 | 5.1 / 8.0 | 88.5 / 145.1 |
+| 10% | Exact (인덱스 없음) | 0/30 | 20.0 (20~20) | 4.8 | 2/30 | 4.3 | 1.00 | 1.00 (1.00) | 1.00 | 4.4 / 7.1 | 13.6 / 18.8 |
+| 10% | 현재 계획 (Custom Plan) | 0/30 | 20.0 (20~20) | 4.8 | 2/30 | 4.3 | 1.00 | 1.00 (1.00) | 1.00 | 4.4 / 5.9 | 14.0 / 19.4 |
+| 10% | 현재 계획 (Generic Plan) | 0/30 | 20.0 (20~20) | 4.8 | 2/30 | 4.3 | 1.00 | 1.00 (1.00) | 1.00 | 4.3 / 6.0 | 13.1 / 18.1 |
+| 10% | HNSW 강제 ef40 | 30/30 | 4.1 (0~20) | 2.2 | 27/30 | 3.0 | 0.21 | 0.42 (0.00) | 0.61 | 2.1 / 3.8 | 10.3 / 15.0 |
+| 10% | HNSW 강제 ef100 | 30/30 | 9.1 (1~20) | 4.1 | 11/30 | 3.9 | 0.45 | 0.80 (0.20) | 0.86 | 3.1 / 5.2 | 11.8 / 17.9 |
+| 10% | HNSW 강제 ef200 | 30/30 | 15.6 (8~20) | 4.7 | 4/30 | 4.1 | 0.77 | 0.95 (0.40) | 0.94 | 4.4 / 7.5 | 13.4 / 19.1 |
+| 10% | HNSW 강제 iterative relaxed | 30/30 | 20.0 (20~20) | 4.8 | 3/30 | 4.2 | 0.96 | 0.91 (0.40) | 0.95 | 4.8 / 7.6 | 13.8 / 18.8 |
+| 10% | HNSW 강제 iterative strict | 30/30 | 20.0 (20~20) | 4.8 | 2/30 | 4.1 | 0.88 | 0.83 (0.20) | 0.84 | 5.4 / 7.9 | 15.1 / 18.7 |
+| 1% | Exact (인덱스 없음) | 0/30 | 20.0 (20~20) | 4.9 | 2/30 | 2.3 | 1.00 | 1.00 (1.00) | 1.00 | 3.7 / 5.2 | 7.2 / 10.3 |
+| 1% | 현재 계획 (Custom Plan) | 0/30 | 20.0 (20~20) | 4.9 | 2/30 | 2.3 | 1.00 | 1.00 (1.00) | 1.00 | 3.1 / 4.2 | 6.1 / 8.3 |
+| 1% | 현재 계획 (Generic Plan) | 0/30 | 20.0 (20~20) | 4.9 | 2/30 | 2.3 | 1.00 | 1.00 (1.00) | 1.00 | 3.3 / 4.3 | 5.7 / 7.9 |
+| 1% | HNSW 강제 ef40 | 30/30 | 0.4 (0~2) | 0.4 | 30/30 | 1.5 | 0.02 | 0.08 (0.00) | 0.65 | 1.6 / 2.7 | 4.0 / 5.5 |
+| 1% | HNSW 강제 ef100 | 30/30 | 0.7 (0~8) | 0.5 | 30/30 | 1.5 | 0.04 | 0.11 (0.00) | 0.67 | 1.9 / 3.6 | 4.2 / 6.4 |
+| 1% | HNSW 강제 ef200 | 30/30 | 1.4 (0~9) | 1.0 | 30/30 | 1.6 | 0.07 | 0.20 (0.00) | 0.75 | 2.6 / 4.2 | 4.9 / 7.9 |
+| 1% | HNSW 강제 iterative relaxed | 30/30 | 20.0 (20~20) | 4.9 | 2/30 | 2.3 | 1.00 | 1.00 (1.00) | 1.00 | 8.8 / 10.8 | 11.4 / 15.6 |
+| 1% | HNSW 강제 iterative strict | 30/30 | 20.0 (20~20) | 4.9 | 2/30 | 2.2 | 0.99 | 0.95 (0.80) | 0.96 | 9.3 / 11.4 | 12.2 / 15.2 |
+| 0.1% | Exact (인덱스 없음) | 0/30 | 10.0 (10~10) | 2.0 | 30/30 | 0.9 | 1.00 | 1.00 (1.00) | 1.00 | 2.4 / 6.3 | 3.9 / 11.0 |
+| 0.1% | 현재 계획 (Custom Plan) | 0/30 | 10.0 (10~10) | 2.0 | 30/30 | 0.9 | 1.00 | 1.00 (1.00) | 1.00 | 1.6 / 3.3 | 2.6 / 4.6 |
+| 0.1% | 현재 계획 (Generic Plan) | 0/30 | 10.0 (10~10) | 2.0 | 30/30 | 0.9 | 1.00 | 1.00 (1.00) | 1.00 | 0.8 / 1.5 | 1.6 / 2.7 |
+| 0.1% | HNSW 강제 ef40 | 30/30 | 0.2 (0~2) | 0.2 | 30/30 | 0.6 | 0.02 | 0.12 (0.00) | 0.73 | 1.7 / 2.9 | 3.0 / 4.4 |
+| 0.1% | HNSW 강제 ef100 | 30/30 | 0.3 (0~2) | 0.3 | 30/30 | 0.6 | 0.03 | 0.13 (0.00) | 0.73 | 1.8 / 3.2 | 3.0 / 5.4 |
+| 0.1% | HNSW 강제 ef200 | 30/30 | 0.5 (0~3) | 0.5 | 30/30 | 0.7 | 0.05 | 0.23 (0.00) | 0.83 | 2.5 / 3.8 | 3.8 / 6.1 |
+| 0.1% | HNSW 강제 iterative relaxed | 30/30 | 10.0 (10~10) | 2.0 | 30/30 | 0.9 | 1.00 | 1.00 (1.00) | 1.00 | 9.8 / 11.5 | 11.1 / 14.9 |
+| 0.1% | HNSW 강제 iterative strict | 30/30 | 9.9 (9~10) | 2.0 | 30/30 | 0.9 | 0.99 | 0.97 (0.50) | 0.97 | 10.3 / 12.0 | 12.0 / 14.9 |
+
+#### real · topK 10 (SQL 요청 40개)
+
+| 허용 비율 | 방식 | HNSW 사용 | SQL 후보 평균 (최소~최대) | 벡터 최종 평균 | topK 미달 질문 | 하이브리드 최종 평균 | SQL Recall | 벡터 최종 Recall (최소) | 하이브리드 최종 Recall | 벡터 p50 / p95 ms | 하이브리드 p50 / p95 ms |
+|---:|---|---:|---|---:|---:|---:|---:|---|---:|---|---|
+| 100% | Exact (인덱스 없음) | 0/30 | 40.0 (40~40) | 10.0 | 0/30 | 9.9 | 1.00 | 1.00 (1.00) | 1.00 | 18.2 / 19.7 | 101.0 / 140.0 |
+| 100% | 현재 계획 (Custom Plan) | 0/30 | 40.0 (40~40) | 10.0 | 0/30 | 9.9 | 1.00 | 1.00 (1.00) | 1.00 | 18.0 / 21.4 | 100.1 / 141.6 |
+| 100% | 현재 계획 (Generic Plan) | 0/30 | 40.0 (40~40) | 10.0 | 0/30 | 9.9 | 1.00 | 1.00 (1.00) | 1.00 | 17.8 / 21.5 | 101.9 / 138.3 |
+| 100% | HNSW 강제 ef40 | 30/30 | 40.0 (40~40) | 10.0 | 0/30 | 9.1 | 0.84 | 0.80 (0.00) | 0.83 | 7.2 / 9.0 | 90.5 / 134.1 |
+| 100% | HNSW 강제 ef100 | 30/30 | 40.0 (40~40) | 10.0 | 0/30 | 9.2 | 0.93 | 0.88 (0.00) | 0.89 | 7.8 / 12.4 | 93.7 / 131.9 |
+| 100% | HNSW 강제 ef200 | 30/30 | 40.0 (40~40) | 10.0 | 0/30 | 9.6 | 0.97 | 0.96 (0.00) | 0.97 | 7.4 / 9.2 | 90.1 / 127.2 |
+| 100% | HNSW 강제 iterative relaxed | 30/30 | 40.0 (40~40) | 10.0 | 0/30 | 9.1 | 0.84 | 0.80 (0.00) | 0.83 | 6.7 / 9.6 | 87.7 / 133.7 |
+| 100% | HNSW 강제 iterative strict | 30/30 | 40.0 (40~40) | 10.0 | 0/30 | 9.1 | 0.84 | 0.80 (0.00) | 0.83 | 6.6 / 8.6 | 87.3 / 125.5 |
+| 10% | Exact (인덱스 없음) | 0/30 | 40.0 (40~40) | 10.0 | 0/30 | 7.6 | 1.00 | 1.00 (1.00) | 1.00 | 5.6 / 7.1 | 15.2 / 20.2 |
+| 10% | 현재 계획 (Custom Plan) | 0/30 | 40.0 (40~40) | 10.0 | 0/30 | 7.6 | 1.00 | 1.00 (1.00) | 1.00 | 5.5 / 6.7 | 14.8 / 19.5 |
+| 10% | 현재 계획 (Generic Plan) | 0/30 | 40.0 (40~40) | 10.0 | 0/30 | 7.6 | 1.00 | 1.00 (1.00) | 1.00 | 5.6 / 7.9 | 14.7 / 19.9 |
+| 10% | HNSW 강제 ef40 | 30/30 | 4.1 (0~20) | 2.4 | 30/30 | 3.7 | 0.10 | 0.23 (0.00) | 0.47 | 2.1 / 4.0 | 10.4 / 15.9 |
+| 10% | HNSW 강제 ef100 | 30/30 | 9.4 (1~25) | 5.7 | 25/30 | 5.4 | 0.23 | 0.55 (0.10) | 0.70 | 2.9 / 4.9 | 12.3 / 17.6 |
+| 10% | HNSW 강제 ef200 | 30/30 | 17.8 (8~33) | 8.6 | 14/30 | 6.6 | 0.45 | 0.84 (0.20) | 0.85 | 4.6 / 7.2 | 14.4 / 19.3 |
+| 10% | HNSW 강제 iterative relaxed | 30/30 | 40.0 (40~40) | 10.0 | 0/30 | 7.6 | 0.99 | 0.94 (0.60) | 0.99 | 8.0 / 11.6 | 18.6 / 25.5 |
+| 10% | HNSW 강제 iterative strict | 30/30 | 40.0 (40~40) | 10.0 | 0/30 | 7.3 | 0.93 | 0.83 (0.30) | 0.84 | 7.5 / 9.1 | 17.3 / 23.9 |
+| 1% | Exact (인덱스 없음) | 0/30 | 34.0 (34~34) | 6.0 | 30/30 | 2.4 | 1.00 | 1.00 (1.00) | 1.00 | 3.3 / 5.3 | 6.6 / 9.8 |
+| 1% | 현재 계획 (Custom Plan) | 0/30 | 34.0 (34~34) | 6.0 | 30/30 | 2.4 | 1.00 | 1.00 (1.00) | 1.00 | 2.7 / 5.3 | 5.6 / 9.5 |
+| 1% | 현재 계획 (Generic Plan) | 0/30 | 34.0 (34~34) | 6.0 | 30/30 | 2.4 | 1.00 | 1.00 (1.00) | 1.00 | 2.6 / 4.4 | 4.3 / 7.3 |
+| 1% | HNSW 강제 ef40 | 30/30 | 0.4 (0~2) | 0.4 | 30/30 | 1.5 | 0.01 | 0.07 (0.00) | 0.64 | 1.7 / 2.6 | 4.3 / 6.2 |
+| 1% | HNSW 강제 ef100 | 30/30 | 0.7 (0~8) | 0.5 | 30/30 | 1.5 | 0.02 | 0.09 (0.00) | 0.67 | 2.0 / 3.0 | 4.6 / 6.4 |
+| 1% | HNSW 강제 ef200 | 30/30 | 1.4 (0~9) | 1.0 | 30/30 | 1.7 | 0.04 | 0.16 (0.00) | 0.75 | 2.7 / 4.5 | 5.0 / 7.0 |
+| 1% | HNSW 강제 iterative relaxed | 30/30 | 34.0 (34~34) | 6.0 | 30/30 | 2.4 | 1.00 | 1.00 (1.00) | 1.00 | 13.4 / 17.2 | 16.4 / 20.7 |
+| 1% | HNSW 강제 iterative strict | 30/30 | 33.8 (33~34) | 6.0 | 30/30 | 2.3 | 0.99 | 0.96 (0.83) | 0.96 | 13.1 / 17.1 | 16.8 / 20.5 |
+| 0.1% | Exact (인덱스 없음) | 0/30 | 10.0 (10~10) | 2.0 | 30/30 | 0.9 | 1.00 | 1.00 (1.00) | 1.00 | 2.5 / 4.4 | 4.5 / 7.9 |
+| 0.1% | 현재 계획 (Custom Plan) | 0/30 | 10.0 (10~10) | 2.0 | 30/30 | 0.9 | 1.00 | 1.00 (1.00) | 1.00 | 2.3 / 4.0 | 3.8 / 7.1 |
+| 0.1% | 현재 계획 (Generic Plan) | 0/30 | 10.0 (10~10) | 2.0 | 30/30 | 0.9 | 1.00 | 1.00 (1.00) | 1.00 | 1.7 / 3.2 | 3.4 / 4.8 |
+| 0.1% | HNSW 강제 ef40 | 30/30 | 0.2 (0~2) | 0.2 | 30/30 | 0.6 | 0.02 | 0.12 (0.00) | 0.73 | 1.5 / 2.9 | 3.0 / 5.5 |
+| 0.1% | HNSW 강제 ef100 | 30/30 | 0.3 (0~2) | 0.3 | 30/30 | 0.6 | 0.03 | 0.13 (0.00) | 0.73 | 2.0 / 3.2 | 3.5 / 5.6 |
+| 0.1% | HNSW 강제 ef200 | 30/30 | 0.5 (0~3) | 0.5 | 30/30 | 0.7 | 0.05 | 0.23 (0.00) | 0.83 | 2.6 / 4.0 | 4.0 / 6.9 |
+| 0.1% | HNSW 강제 iterative relaxed | 30/30 | 10.0 (10~10) | 2.0 | 30/30 | 0.9 | 1.00 | 1.00 (1.00) | 1.00 | 10.4 / 12.4 | 12.1 / 15.6 |
+| 0.1% | HNSW 강제 iterative strict | 30/30 | 9.9 (9~10) | 2.0 | 30/30 | 0.9 | 0.99 | 0.97 (0.50) | 0.97 | 10.2 / 11.7 | 11.5 / 14.3 |
+
+#### real · topK 20 (SQL 요청 80개)
+
+| 허용 비율 | 방식 | HNSW 사용 | SQL 후보 평균 (최소~최대) | 벡터 최종 평균 | topK 미달 질문 | 하이브리드 최종 평균 | SQL Recall | 벡터 최종 Recall (최소) | 하이브리드 최종 Recall | 벡터 p50 / p95 ms | 하이브리드 p50 / p95 ms |
+|---:|---|---:|---|---:|---:|---:|---:|---|---:|---|---|
+| 100% | Exact (인덱스 없음) | 0/30 | 80.0 (80~80) | 20.0 | 0/30 | 18.6 | 1.00 | 1.00 (1.00) | 1.00 | 21.3 / 28.2 | 105.9 / 144.3 |
+| 100% | 현재 계획 (Custom Plan) | 0/30 | 80.0 (80~80) | 20.0 | 0/30 | 18.6 | 1.00 | 1.00 (1.00) | 1.00 | 21.3 / 24.0 | 104.2 / 144.5 |
+| 100% | 현재 계획 (Generic Plan) | 0/30 | 80.0 (80~80) | 20.0 | 0/30 | 18.6 | 1.00 | 1.00 (1.00) | 1.00 | 21.4 / 26.0 | 102.0 / 138.9 |
+| 100% | HNSW 강제 ef40 | 30/30 | 40.0 (40~40) | 18.6 | 9/30 | 16.9 | 0.50 | 0.74 (0.15) | 0.73 | 6.9 / 8.6 | 89.8 / 128.2 |
+| 100% | HNSW 강제 ef100 | 30/30 | 80.0 (80~80) | 20.0 | 0/30 | 17.8 | 0.92 | 0.91 (0.30) | 0.90 | 10.3 / 12.3 | 93.8 / 132.5 |
+| 100% | HNSW 강제 ef200 | 30/30 | 80.0 (80~80) | 20.0 | 0/30 | 18.4 | 0.98 | 0.97 (0.35) | 0.97 | 11.2 / 13.3 | 96.2 / 136.2 |
+| 100% | HNSW 강제 iterative relaxed | 30/30 | 80.0 (80~80) | 20.0 | 0/30 | 17.8 | 0.90 | 0.80 (0.15) | 0.88 | 10.6 / 12.6 | 95.1 / 134.2 |
+| 100% | HNSW 강제 iterative strict | 30/30 | 80.0 (80~80) | 20.0 | 0/30 | 17.8 | 0.85 | 0.80 (0.15) | 0.82 | 11.1 / 14.2 | 93.9 / 133.5 |
+| 10% | Exact (인덱스 없음) | 0/30 | 80.0 (80~80) | 20.0 | 0/30 | 12.3 | 1.00 | 1.00 (1.00) | 1.00 | 4.7 / 8.0 | 13.7 / 18.7 |
+| 10% | 현재 계획 (Custom Plan) | 0/30 | 80.0 (80~80) | 20.0 | 0/30 | 12.3 | 1.00 | 1.00 (1.00) | 1.00 | 4.9 / 6.7 | 14.6 / 22.0 |
+| 10% | 현재 계획 (Generic Plan) | 0/30 | 80.0 (80~80) | 20.0 | 0/30 | 12.3 | 1.00 | 1.00 (1.00) | 1.00 | 4.5 / 6.5 | 12.7 / 17.1 |
+| 10% | HNSW 강제 ef40 | 30/30 | 4.1 (0~20) | 2.4 | 30/30 | 4.1 | 0.05 | 0.12 (0.00) | 0.40 | 2.2 / 4.2 | 11.2 / 15.8 |
+| 10% | HNSW 강제 ef100 | 30/30 | 9.4 (1~25) | 5.8 | 30/30 | 6.0 | 0.12 | 0.29 (0.05) | 0.60 | 3.3 / 5.1 | 12.2 / 17.2 |
+| 10% | HNSW 강제 ef200 | 30/30 | 17.8 (8~33) | 10.3 | 30/30 | 8.2 | 0.22 | 0.52 (0.10) | 0.76 | 4.7 / 7.0 | 14.4 / 20.1 |
+| 10% | HNSW 강제 iterative relaxed | 30/30 | 80.0 (80~80) | 20.0 | 0/30 | 12.3 | 1.00 | 0.98 (0.90) | 1.00 | 8.9 / 14.3 | 18.5 / 27.3 |
+| 10% | HNSW 강제 iterative strict | 30/30 | 80.0 (80~80) | 20.0 | 0/30 | 11.8 | 0.96 | 0.88 (0.55) | 0.87 | 9.0 / 14.4 | 20.6 / 26.0 |
+| 1% | Exact (인덱스 없음) | 0/30 | 34.0 (34~34) | 6.0 | 30/30 | 2.4 | 1.00 | 1.00 (1.00) | 1.00 | 3.6 / 5.1 | 6.5 / 9.3 |
+| 1% | 현재 계획 (Custom Plan) | 0/30 | 34.0 (34~34) | 6.0 | 30/30 | 2.4 | 1.00 | 1.00 (1.00) | 1.00 | 4.2 / 5.8 | 7.2 / 12.5 |
+| 1% | 현재 계획 (Generic Plan) | 0/30 | 34.0 (34~34) | 6.0 | 30/30 | 2.4 | 1.00 | 1.00 (1.00) | 1.00 | 4.0 / 5.3 | 6.4 / 8.8 |
+| 1% | HNSW 강제 ef40 | 30/30 | 0.4 (0~2) | 0.4 | 30/30 | 1.5 | 0.01 | 0.07 (0.00) | 0.64 | 2.2 / 4.3 | 5.0 / 8.6 |
+| 1% | HNSW 강제 ef100 | 30/30 | 0.7 (0~8) | 0.5 | 30/30 | 1.5 | 0.02 | 0.09 (0.00) | 0.67 | 2.4 / 4.7 | 5.1 / 8.5 |
+| 1% | HNSW 강제 ef200 | 30/30 | 1.4 (0~9) | 1.0 | 30/30 | 1.7 | 0.04 | 0.16 (0.00) | 0.75 | 3.4 / 5.9 | 6.1 / 10.7 |
+| 1% | HNSW 강제 iterative relaxed | 30/30 | 34.0 (34~34) | 6.0 | 30/30 | 2.4 | 1.00 | 1.00 (1.00) | 1.00 | 14.1 / 17.0 | 17.6 / 23.5 |
+| 1% | HNSW 강제 iterative strict | 30/30 | 33.8 (33~34) | 6.0 | 30/30 | 2.3 | 0.99 | 0.96 (0.83) | 0.96 | 13.9 / 15.6 | 17.3 / 20.0 |
+| 0.1% | Exact (인덱스 없음) | 0/30 | 10.0 (10~10) | 2.0 | 30/30 | 0.9 | 1.00 | 1.00 (1.00) | 1.00 | 2.5 / 4.0 | 4.0 / 6.9 |
+| 0.1% | 현재 계획 (Custom Plan) | 0/30 | 10.0 (10~10) | 2.0 | 30/30 | 0.9 | 1.00 | 1.00 (1.00) | 1.00 | 2.3 / 3.8 | 4.2 / 8.0 |
+| 0.1% | 현재 계획 (Generic Plan) | 0/30 | 10.0 (10~10) | 2.0 | 30/30 | 0.9 | 1.00 | 1.00 (1.00) | 1.00 | 2.3 / 3.8 | 3.7 / 5.7 |
+| 0.1% | HNSW 강제 ef40 | 30/30 | 0.2 (0~2) | 0.2 | 30/30 | 0.6 | 0.02 | 0.12 (0.00) | 0.73 | 1.7 / 3.2 | 2.8 / 5.4 |
+| 0.1% | HNSW 강제 ef100 | 30/30 | 0.3 (0~2) | 0.3 | 30/30 | 0.6 | 0.03 | 0.13 (0.00) | 0.73 | 1.9 / 3.5 | 3.2 / 5.0 |
+| 0.1% | HNSW 강제 ef200 | 30/30 | 0.5 (0~3) | 0.5 | 30/30 | 0.7 | 0.05 | 0.23 (0.00) | 0.83 | 2.7 / 4.4 | 4.2 / 5.9 |
+| 0.1% | HNSW 강제 iterative relaxed | 30/30 | 10.0 (10~10) | 2.0 | 30/30 | 0.9 | 1.00 | 1.00 (1.00) | 1.00 | 9.6 / 12.4 | 11.7 / 14.9 |
+| 0.1% | HNSW 강제 iterative strict | 30/30 | 9.9 (9~10) | 2.0 | 30/30 | 0.9 | 0.99 | 0.97 (0.50) | 0.97 | 10.0 / 13.9 | 11.7 / 15.7 |
+
+### 규모 50000: 문서 3,559건, 청크 50,008개 (합성 비율 92.9%)
+
+| 허용 비율 | 허용 문서 | 허용 청크 |
+|---:|---:|---:|
+| 100% | 3,559 | 50,008 |
+| 10% | 356 | 5,244 |
+| 1% | 36 | 434 |
+| 0.1% | 4 | 40 |
+
+#### 50000 · topK 5 (SQL 요청 20개)
+
+| 허용 비율 | 방식 | HNSW 사용 | SQL 후보 평균 (최소~최대) | 벡터 최종 평균 | topK 미달 질문 | 하이브리드 최종 평균 | SQL Recall | 벡터 최종 Recall (최소) | 하이브리드 최종 Recall | 벡터 p50 / p95 ms | 하이브리드 p50 / p95 ms |
+|---:|---|---:|---|---:|---:|---:|---:|---|---:|---|---|
+| 100% | Exact (인덱스 없음) | 0/30 | 20.0 (20~20) | 5.0 | 0/30 | 5.0 | 1.00 | 1.00 (1.00) | 1.00 | 214.9 / 290.6 | 1340.4 / 1891.4 |
+| 100% | 현재 계획 (Custom Plan) | 0/30 | 20.0 (20~20) | 5.0 | 0/30 | 5.0 | 1.00 | 1.00 (1.00) | 1.00 | 221.0 / 380.9 | 1350.3 / 2001.5 |
+| 100% | 현재 계획 (Generic Plan) | 0/30 | 20.0 (20~20) | 5.0 | 0/30 | 5.0 | 1.00 | 1.00 (1.00) | 1.00 | 204.4 / 245.8 | 1305.7 / 1847.1 |
+| 100% | HNSW 강제 ef40 | 30/30 | 20.0 (20~20) | 5.0 | 0/30 | 5.0 | 0.93 | 0.93 (0.00) | 0.95 | 14.8 / 19.2 | 1158.1 / 1661.9 |
+| 100% | HNSW 강제 ef100 | 30/30 | 20.0 (20~20) | 5.0 | 0/30 | 5.0 | 0.98 | 0.97 (0.80) | 0.99 | 15.1 / 19.3 | 1130.8 / 1693.7 |
+| 100% | HNSW 강제 ef200 | 30/30 | 20.0 (20~20) | 5.0 | 0/30 | 5.0 | 0.98 | 0.98 (0.80) | 0.99 | 15.3 / 23.5 | 1141.2 / 1725.2 |
+| 100% | HNSW 강제 iterative relaxed | 30/30 | 20.0 (20~20) | 5.0 | 0/30 | 5.0 | 0.93 | 0.93 (0.00) | 0.95 | 15.0 / 25.4 | 1194.4 / 1954.7 |
+| 100% | HNSW 강제 iterative strict | 30/30 | 20.0 (20~20) | 5.0 | 0/30 | 5.0 | 0.93 | 0.93 (0.00) | 0.95 | 14.7 / 17.5 | 1125.2 / 1685.0 |
+| 10% | Exact (인덱스 없음) | 0/30 | 20.0 (20~20) | 5.0 | 0/30 | 5.0 | 1.00 | 1.00 (1.00) | 1.00 | 25.4 / 28.3 | 149.7 / 210.7 |
+| 10% | 현재 계획 (Custom Plan) | 0/30 | 20.0 (20~20) | 5.0 | 0/30 | 5.0 | 1.00 | 1.00 (1.00) | 1.00 | 24.3 / 27.6 | 147.1 / 213.3 |
+| 10% | 현재 계획 (Generic Plan) | 0/30 | 20.0 (20~20) | 5.0 | 0/30 | 5.0 | 1.00 | 1.00 (1.00) | 1.00 | 23.4 / 25.0 | 147.0 / 204.5 |
+| 10% | HNSW 강제 ef40 | 30/30 | 2.7 (0~12) | 1.7 | 27/30 | 3.5 | 0.13 | 0.34 (0.00) | 0.54 | 8.3 / 11.1 | 134.2 / 192.0 |
+| 10% | HNSW 강제 ef100 | 30/30 | 7.0 (0~20) | 3.1 | 18/30 | 4.0 | 0.35 | 0.58 (0.00) | 0.72 | 9.2 / 11.6 | 134.1 / 193.8 |
+| 10% | HNSW 강제 ef200 | 30/30 | 12.0 (0~20) | 4.1 | 11/30 | 4.4 | 0.58 | 0.75 (0.00) | 0.81 | 10.7 / 14.1 | 136.8 / 200.8 |
+| 10% | HNSW 강제 iterative relaxed | 30/30 | 20.0 (20~20) | 5.0 | 1/30 | 5.0 | 0.91 | 0.89 (0.40) | 0.95 | 12.3 / 17.0 | 135.9 / 194.7 |
+| 10% | HNSW 강제 iterative strict | 30/30 | 20.0 (20~20) | 5.0 | 1/30 | 5.0 | 0.82 | 0.79 (0.00) | 0.85 | 12.8 / 15.0 | 135.4 / 196.7 |
+| 1% | Exact (인덱스 없음) | 0/30 | 20.0 (20~20) | 4.9 | 1/30 | 4.9 | 1.00 | 1.00 (1.00) | 1.00 | 5.8 / 7.2 | 18.6 / 24.3 |
+| 1% | 현재 계획 (Custom Plan) | 0/30 | 20.0 (20~20) | 4.9 | 1/30 | 4.9 | 1.00 | 1.00 (1.00) | 1.00 | 5.0 / 6.3 | 17.8 / 24.4 |
+| 1% | 현재 계획 (Generic Plan) | 0/30 | 20.0 (20~20) | 4.9 | 1/30 | 4.9 | 1.00 | 1.00 (1.00) | 1.00 | 4.6 / 6.6 | 16.4 / 25.0 |
+| 1% | HNSW 강제 ef40 | 30/30 | 0.2 (0~4) | 0.2 | 30/30 | 2.5 | 0.01 | 0.04 (0.00) | 0.34 | 6.5 / 8.0 | 19.2 / 25.4 |
+| 1% | HNSW 강제 ef100 | 30/30 | 0.6 (0~10) | 0.3 | 30/30 | 2.6 | 0.03 | 0.08 (0.00) | 0.36 | 7.2 / 9.2 | 19.6 / 31.1 |
+| 1% | HNSW 강제 ef200 | 30/30 | 1.6 (0~20) | 0.6 | 30/30 | 2.7 | 0.08 | 0.13 (0.00) | 0.39 | 8.2 / 18.6 | 21.7 / 38.2 |
+| 1% | HNSW 강제 iterative relaxed | 30/30 | 20.0 (20~20) | 4.9 | 1/30 | 4.9 | 0.95 | 0.91 (0.60) | 0.97 | 40.5 / 53.6 | 53.5 / 72.0 |
+| 1% | HNSW 강제 iterative strict | 30/30 | 20.0 (20~20) | 4.9 | 1/30 | 4.9 | 0.83 | 0.75 (0.00) | 0.85 | 42.6 / 52.6 | 54.4 / 82.2 |
+| 0.1% | Exact (인덱스 없음) | 0/30 | 20.0 (20~20) | 5.0 | 1/30 | 4.0 | 1.00 | 1.00 (1.00) | 1.00 | 4.2 / 6.2 | 9.0 / 14.0 |
+| 0.1% | 현재 계획 (Custom Plan) | 0/30 | 20.0 (20~20) | 5.0 | 1/30 | 4.0 | 1.00 | 1.00 (1.00) | 1.00 | 4.5 / 6.1 | 10.2 / 13.3 |
+| 0.1% | 현재 계획 (Generic Plan) | 0/30 | 20.0 (20~20) | 5.0 | 1/30 | 4.0 | 1.00 | 1.00 (1.00) | 1.00 | 5.1 / 6.5 | 8.7 / 10.1 |
+| 0.1% | HNSW 강제 ef40 | 30/30 | 0.0 (0~0) | 0.0 | 30/30 | 1.4 | 0.00 | 0.00 (0.00) | 0.38 | 6.6 / 8.9 | 10.8 / 14.6 |
+| 0.1% | HNSW 강제 ef100 | 30/30 | 0.0 (0~1) | 0.0 | 30/30 | 1.4 | 0.00 | 0.01 (0.00) | 0.38 | 6.7 / 10.3 | 10.9 / 16.7 |
+| 0.1% | HNSW 강제 ef200 | 30/30 | 0.1 (0~1) | 0.1 | 30/30 | 1.4 | 0.00 | 0.01 (0.00) | 0.38 | 7.3 / 9.5 | 11.5 / 14.9 |
+| 0.1% | HNSW 강제 iterative relaxed | 30/30 | 15.8 (10~20) | 4.9 | 3/30 | 3.9 | 0.64 | 0.85 (0.40) | 0.92 | 50.8 / 67.5 | 57.0 / 70.2 |
+| 0.1% | HNSW 강제 iterative strict | 30/30 | 15.6 (10~20) | 4.9 | 3/30 | 3.9 | 0.62 | 0.79 (0.40) | 0.86 | 49.6 / 60.3 | 55.6 / 70.6 |
+
+#### 50000 · topK 10 (SQL 요청 40개)
+
+| 허용 비율 | 방식 | HNSW 사용 | SQL 후보 평균 (최소~최대) | 벡터 최종 평균 | topK 미달 질문 | 하이브리드 최종 평균 | SQL Recall | 벡터 최종 Recall (최소) | 하이브리드 최종 Recall | 벡터 p50 / p95 ms | 하이브리드 p50 / p95 ms |
+|---:|---|---:|---|---:|---:|---:|---:|---|---:|---|---|
+| 100% | Exact (인덱스 없음) | 0/30 | 40.0 (40~40) | 9.9 | 1/30 | 10.0 | 1.00 | 1.00 (1.00) | 1.00 | 217.4 / 327.3 | 1371.1 / 2040.7 |
+| 100% | 현재 계획 (Custom Plan) | 0/30 | 40.0 (40~40) | 9.9 | 1/30 | 10.0 | 1.00 | 1.00 (1.00) | 1.00 | 208.9 / 286.7 | 1328.1 / 1903.5 |
+| 100% | 현재 계획 (Generic Plan) | 0/30 | 40.0 (40~40) | 9.9 | 1/30 | 10.0 | 1.00 | 1.00 (1.00) | 1.00 | 207.4 / 223.8 | 1321.6 / 1856.1 |
+| 100% | HNSW 강제 ef40 | 30/30 | 40.0 (40~40) | 9.8 | 2/30 | 10.0 | 0.93 | 0.91 (0.00) | 0.93 | 16.9 / 20.4 | 1126.8 / 1646.8 |
+| 100% | HNSW 강제 ef100 | 30/30 | 40.0 (40~40) | 9.9 | 1/30 | 10.0 | 0.98 | 0.97 (0.80) | 0.98 | 17.4 / 26.1 | 1160.7 / 1725.3 |
+| 100% | HNSW 강제 ef200 | 30/30 | 40.0 (40~40) | 10.0 | 1/30 | 10.0 | 0.98 | 0.97 (0.80) | 0.98 | 17.4 / 25.9 | 1150.4 / 1728.8 |
+| 100% | HNSW 강제 iterative relaxed | 30/30 | 40.0 (40~40) | 9.8 | 2/30 | 10.0 | 0.93 | 0.91 (0.00) | 0.93 | 15.9 / 22.9 | 1129.2 / 1643.4 |
+| 100% | HNSW 강제 iterative strict | 30/30 | 40.0 (40~40) | 9.8 | 2/30 | 10.0 | 0.93 | 0.91 (0.00) | 0.93 | 16.4 / 23.8 | 1127.0 / 1638.8 |
+| 10% | Exact (인덱스 없음) | 0/30 | 40.0 (40~40) | 10.0 | 0/30 | 10.0 | 1.00 | 1.00 (1.00) | 1.00 | 26.5 / 29.1 | 151.8 / 211.0 |
+| 10% | 현재 계획 (Custom Plan) | 0/30 | 40.0 (40~40) | 10.0 | 0/30 | 10.0 | 1.00 | 1.00 (1.00) | 1.00 | 26.7 / 29.6 | 150.7 / 207.9 |
+| 10% | 현재 계획 (Generic Plan) | 0/30 | 40.0 (40~40) | 10.0 | 0/30 | 10.0 | 1.00 | 1.00 (1.00) | 1.00 | 24.9 / 44.0 | 149.2 / 209.5 |
+| 10% | HNSW 강제 ef40 | 30/30 | 2.7 (0~12) | 1.8 | 30/30 | 5.1 | 0.07 | 0.18 (0.00) | 0.35 | 8.1 / 9.3 | 130.1 / 189.4 |
+| 10% | HNSW 강제 ef100 | 30/30 | 7.1 (0~25) | 3.6 | 30/30 | 6.1 | 0.18 | 0.36 (0.00) | 0.49 | 8.9 / 11.2 | 131.2 / 193.4 |
+| 10% | HNSW 강제 ef200 | 30/30 | 13.9 (0~38) | 5.8 | 25/30 | 7.2 | 0.35 | 0.55 (0.00) | 0.63 | 10.6 / 13.2 | 134.2 / 190.7 |
+| 10% | HNSW 강제 iterative relaxed | 30/30 | 40.0 (40~40) | 10.0 | 0/30 | 10.0 | 0.90 | 0.86 (0.60) | 0.92 | 16.6 / 18.9 | 140.8 / 197.5 |
+| 10% | HNSW 강제 iterative strict | 30/30 | 40.0 (40~40) | 10.0 | 0/30 | 10.0 | 0.78 | 0.73 (0.00) | 0.76 | 16.8 / 19.9 | 141.0 / 200.1 |
+| 1% | Exact (인덱스 없음) | 0/30 | 40.0 (40~40) | 10.0 | 1/30 | 9.4 | 1.00 | 1.00 (1.00) | 1.00 | 7.1 / 8.7 | 21.7 / 28.2 |
+| 1% | 현재 계획 (Custom Plan) | 0/30 | 40.0 (40~40) | 10.0 | 1/30 | 9.4 | 1.00 | 1.00 (1.00) | 1.00 | 7.4 / 9.0 | 22.4 / 28.0 |
+| 1% | 현재 계획 (Generic Plan) | 0/30 | 40.0 (40~40) | 10.0 | 1/30 | 9.4 | 1.00 | 1.00 (1.00) | 1.00 | 6.8 / 9.6 | 19.0 / 23.5 |
+| 1% | HNSW 강제 ef40 | 30/30 | 0.2 (0~4) | 0.2 | 30/30 | 3.2 | 0.01 | 0.02 (0.00) | 0.24 | 6.9 / 16.4 | 20.0 / 31.1 |
+| 1% | HNSW 강제 ef100 | 30/30 | 0.6 (0~10) | 0.3 | 30/30 | 3.3 | 0.02 | 0.03 (0.00) | 0.25 | 8.3 / 13.8 | 22.2 / 32.4 |
+| 1% | HNSW 강제 ef200 | 30/30 | 1.9 (0~28) | 0.6 | 30/30 | 3.4 | 0.05 | 0.06 (0.00) | 0.27 | 8.5 / 11.5 | 22.0 / 29.9 |
+| 1% | HNSW 강제 iterative relaxed | 30/30 | 40.0 (40~40) | 10.0 | 1/30 | 9.4 | 0.92 | 0.95 (0.70) | 0.97 | 48.7 / 62.7 | 63.0 / 76.2 |
+| 1% | HNSW 강제 iterative strict | 30/30 | 40.0 (40~40) | 9.9 | 1/30 | 9.3 | 0.85 | 0.77 (0.20) | 0.83 | 49.5 / 64.6 | 65.0 / 80.1 |
+| 0.1% | Exact (인덱스 없음) | 0/30 | 40.0 (40~40) | 7.9 | 30/30 | 5.1 | 1.00 | 1.00 (1.00) | 1.00 | 4.2 / 6.6 | 9.0 / 13.6 |
+| 0.1% | 현재 계획 (Custom Plan) | 0/30 | 40.0 (40~40) | 7.9 | 30/30 | 5.1 | 1.00 | 1.00 (1.00) | 1.00 | 3.1 / 5.1 | 8.1 / 11.3 |
+| 0.1% | 현재 계획 (Generic Plan) | 0/30 | 40.0 (40~40) | 7.9 | 30/30 | 5.1 | 1.00 | 1.00 (1.00) | 1.00 | 3.7 / 5.7 | 6.6 / 9.3 |
+| 0.1% | HNSW 강제 ef40 | 30/30 | 0.0 (0~0) | 0.0 | 30/30 | 1.5 | 0.00 | 0.00 (0.00) | 0.34 | 6.3 / 8.3 | 10.6 / 14.2 |
+| 0.1% | HNSW 강제 ef100 | 30/30 | 0.0 (0~1) | 0.0 | 30/30 | 1.5 | 0.00 | 0.00 (0.00) | 0.34 | 6.7 / 8.9 | 10.8 / 14.4 |
+| 0.1% | HNSW 강제 ef200 | 30/30 | 0.1 (0~1) | 0.1 | 30/30 | 1.5 | 0.00 | 0.01 (0.00) | 0.34 | 7.1 / 9.0 | 11.0 / 14.3 |
+| 0.1% | HNSW 강제 iterative relaxed | 30/30 | 16.3 (10~26) | 6.6 | 30/30 | 4.5 | 0.41 | 0.64 (0.25) | 0.86 | 49.8 / 60.8 | 56.8 / 66.9 |
+| 0.1% | HNSW 강제 iterative strict | 30/30 | 15.9 (10~26) | 6.6 | 30/30 | 4.5 | 0.40 | 0.60 (0.25) | 0.80 | 49.3 / 59.5 | 55.1 / 63.3 |
+
+#### 50000 · topK 20 (SQL 요청 80개)
+
+| 허용 비율 | 방식 | HNSW 사용 | SQL 후보 평균 (최소~최대) | 벡터 최종 평균 | topK 미달 질문 | 하이브리드 최종 평균 | SQL Recall | 벡터 최종 Recall (최소) | 하이브리드 최종 Recall | 벡터 p50 / p95 ms | 하이브리드 p50 / p95 ms |
+|---:|---|---:|---|---:|---:|---:|---:|---|---:|---|---|
+| 100% | Exact (인덱스 없음) | 0/30 | 80.0 (80~80) | 19.1 | 5/30 | 20.0 | 1.00 | 1.00 (1.00) | 1.00 | 216.6 / 325.3 | 1331.3 / 1892.9 |
+| 100% | 현재 계획 (Custom Plan) | 0/30 | 80.0 (80~80) | 19.1 | 5/30 | 20.0 | 1.00 | 1.00 (1.00) | 1.00 | 221.9 / 292.8 | 1363.0 / 1930.1 |
+| 100% | 현재 계획 (Generic Plan) | 0/30 | 80.0 (80~80) | 19.1 | 5/30 | 20.0 | 1.00 | 1.00 (1.00) | 1.00 | 210.1 / 219.1 | 1311.8 / 1898.2 |
+| 100% | HNSW 강제 ef40 | 30/30 | 40.0 (40~40) | 17.3 | 15/30 | 19.7 | 0.48 | 0.85 (0.00) | 0.87 | 16.2 / 21.2 | 1140.3 / 1701.3 |
+| 100% | HNSW 강제 ef100 | 30/30 | 80.0 (80~80) | 19.0 | 5/30 | 20.0 | 0.96 | 0.97 (0.85) | 0.97 | 19.8 / 22.7 | 1150.1 / 1681.7 |
+| 100% | HNSW 강제 ef200 | 30/30 | 80.0 (80~80) | 19.1 | 5/30 | 20.0 | 0.98 | 0.98 (0.85) | 0.97 | 20.9 / 27.3 | 1141.0 / 1799.3 |
+| 100% | HNSW 강제 iterative relaxed | 30/30 | 80.0 (80~80) | 19.0 | 5/30 | 20.0 | 0.94 | 0.94 (0.60) | 0.96 | 19.5 / 25.5 | 1163.0 / 1711.9 |
+| 100% | HNSW 강제 iterative strict | 30/30 | 80.0 (80~80) | 19.0 | 5/30 | 20.0 | 0.91 | 0.91 (0.00) | 0.92 | 20.2 / 38.4 | 1168.4 / 1715.3 |
+| 10% | Exact (인덱스 없음) | 0/30 | 80.0 (80~80) | 20.0 | 0/30 | 20.0 | 1.00 | 1.00 (1.00) | 1.00 | 30.8 / 35.9 | 157.9 / 216.0 |
+| 10% | 현재 계획 (Custom Plan) | 0/30 | 80.0 (80~80) | 20.0 | 0/30 | 20.0 | 1.00 | 1.00 (1.00) | 1.00 | 30.7 / 35.7 | 157.7 / 220.4 |
+| 10% | 현재 계획 (Generic Plan) | 0/30 | 80.0 (80~80) | 20.0 | 0/30 | 20.0 | 1.00 | 1.00 (1.00) | 1.00 | 28.8 / 30.9 | 151.0 / 212.3 |
+| 10% | HNSW 강제 ef40 | 30/30 | 2.7 (0~12) | 1.8 | 30/30 | 6.6 | 0.03 | 0.09 (0.00) | 0.22 | 8.1 / 9.4 | 131.3 / 190.2 |
+| 10% | HNSW 강제 ef100 | 30/30 | 7.1 (0~25) | 3.6 | 30/30 | 7.9 | 0.09 | 0.18 (0.00) | 0.30 | 9.3 / 11.6 | 134.1 / 195.2 |
+| 10% | HNSW 강제 ef200 | 30/30 | 13.9 (0~38) | 6.1 | 30/30 | 9.4 | 0.17 | 0.30 (0.00) | 0.40 | 10.2 / 13.0 | 133.9 / 190.9 |
+| 10% | HNSW 강제 iterative relaxed | 30/30 | 80.0 (80~80) | 20.0 | 0/30 | 20.0 | 0.94 | 0.85 (0.60) | 0.95 | 22.2 / 29.8 | 150.9 / 219.8 |
+| 10% | HNSW 강제 iterative strict | 30/30 | 80.0 (80~80) | 20.0 | 0/30 | 19.9 | 0.80 | 0.68 (0.35) | 0.72 | 23.6 / 32.6 | 153.3 / 228.3 |
+| 1% | Exact (인덱스 없음) | 0/30 | 80.0 (80~80) | 20.0 | 0/30 | 17.3 | 1.00 | 1.00 (1.00) | 1.00 | 7.2 / 12.1 | 21.9 / 30.3 |
+| 1% | 현재 계획 (Custom Plan) | 0/30 | 80.0 (80~80) | 20.0 | 0/30 | 17.3 | 1.00 | 1.00 (1.00) | 1.00 | 6.2 / 8.8 | 20.7 / 27.6 |
+| 1% | 현재 계획 (Generic Plan) | 0/30 | 80.0 (80~80) | 20.0 | 0/30 | 17.3 | 1.00 | 1.00 (1.00) | 1.00 | 5.5 / 8.0 | 16.7 / 24.6 |
+| 1% | HNSW 강제 ef40 | 30/30 | 0.2 (0~4) | 0.2 | 30/30 | 3.5 | 0.00 | 0.01 (0.00) | 0.21 | 6.8 / 8.3 | 19.4 / 25.5 |
+| 1% | HNSW 강제 ef100 | 30/30 | 0.6 (0~10) | 0.3 | 30/30 | 3.6 | 0.01 | 0.02 (0.00) | 0.21 | 6.9 / 8.3 | 19.4 / 24.9 |
+| 1% | HNSW 강제 ef200 | 30/30 | 1.9 (0~28) | 0.6 | 30/30 | 3.8 | 0.02 | 0.03 (0.00) | 0.22 | 7.8 / 12.4 | 20.7 / 29.7 |
+| 1% | HNSW 강제 iterative relaxed | 30/30 | 80.0 (80~80) | 20.0 | 0/30 | 17.3 | 0.84 | 0.91 (0.70) | 0.93 | 54.4 / 82.3 | 71.5 / 109.4 |
+| 1% | HNSW 강제 iterative strict | 30/30 | 80.0 (80~80) | 20.0 | 0/30 | 17.2 | 0.80 | 0.80 (0.60) | 0.85 | 56.2 / 85.8 | 73.7 / 92.1 |
+| 0.1% | Exact (인덱스 없음) | 0/30 | 40.0 (40~40) | 7.9 | 30/30 | 5.1 | 1.00 | 1.00 (1.00) | 1.00 | 3.8 / 6.0 | 8.8 / 11.9 |
+| 0.1% | 현재 계획 (Custom Plan) | 0/30 | 40.0 (40~40) | 7.9 | 30/30 | 5.1 | 1.00 | 1.00 (1.00) | 1.00 | 4.1 / 6.4 | 9.2 / 13.4 |
+| 0.1% | 현재 계획 (Generic Plan) | 0/30 | 40.0 (40~40) | 7.9 | 30/30 | 5.1 | 1.00 | 1.00 (1.00) | 1.00 | 3.0 / 5.6 | 6.8 / 9.8 |
+| 0.1% | HNSW 강제 ef40 | 30/30 | 0.0 (0~0) | 0.0 | 30/30 | 1.5 | 0.00 | 0.00 (0.00) | 0.34 | 6.7 / 9.8 | 11.0 / 14.7 |
+| 0.1% | HNSW 강제 ef100 | 30/30 | 0.0 (0~1) | 0.0 | 30/30 | 1.5 | 0.00 | 0.00 (0.00) | 0.34 | 6.7 / 8.7 | 10.6 / 13.7 |
+| 0.1% | HNSW 강제 ef200 | 30/30 | 0.1 (0~1) | 0.1 | 30/30 | 1.5 | 0.00 | 0.01 (0.00) | 0.34 | 7.6 / 9.7 | 11.8 / 15.6 |
+| 0.1% | HNSW 강제 iterative relaxed | 30/30 | 16.3 (10~26) | 6.6 | 30/30 | 4.5 | 0.41 | 0.64 (0.25) | 0.86 | 51.5 / 75.5 | 57.7 / 96.2 |
+| 0.1% | HNSW 강제 iterative strict | 30/30 | 15.9 (10~26) | 6.6 | 30/30 | 4.5 | 0.40 | 0.60 (0.25) | 0.80 | 53.1 / 81.9 | 60.8 / 110.3 |
+
diff --git a/docs/test-results/evidence/issue-549/compare_results.py b/docs/test-results/evidence/issue-549/compare_results.py
new file mode 100644
index 00000000..7f4278cd
--- /dev/null
+++ b/docs/test-results/evidence/issue-549/compare_results.py
@@ -0,0 +1,273 @@
+#!/usr/bin/env python3
+"""#549 수정 전후 측정 결과(PermissionRecallBenchmark results.json)를 조합별로 비교한다. 표준 라이브러리만 쓴다.
+
+사용법:
+ python3 compare_results.py <수정 전 results.json[.gz]> <수정 후 results.json[.gz]> <출력 폴더> [접두사]
+
+출력:
+ <접두사>comparison.csv 규모 × 허용 비율 × topK × 방식 조합별 지연 p50·p95 전후값, 개선율, 결과 일치 여부
+ <접두사>comparison.md 결과 문서에 옮겨 쓸 Markdown 표
+ <접두사>before-after-latency.svg 5만 청크·topK 10 현재 계획 Generic Plan 지연 전후(로그 축)
+ <접두사>improvement-percent.svg 같은 조건의 개선율 막대
+
+개선율 = (수정 전 - 수정 후) / 수정 전 × 100. 양수면 빨라진 것이다.
+결과 일치 = 같은 질문의 SQL 후보·벡터 최종·하이브리드 최종 청크 수와 Recall이 전후에 모두 같은지다.
+"""
+import csv
+import gzip
+import json
+import math
+import os
+import sys
+
+METHOD_NAMES = {
+ "exact": "Exact",
+ "planner_default": "현재 계획 Custom",
+ "planner_default_generic": "현재 계획 Generic",
+ "hnsw_ef40": "HNSW 강제 ef40",
+ "hnsw_ef100": "HNSW 강제 ef100",
+ "hnsw_ef200": "HNSW 강제 ef200",
+ "iterative_relaxed": "iterative relaxed",
+ "iterative_strict": "iterative strict",
+}
+RESULT_FIELDS = ["sqlRows", "vectorFinal", "hybridFinal", "sqlRecall", "vectorFinalRecall", "hybridFinalRecall"]
+
+
+def load(path):
+ opener = gzip.open if path.endswith(".gz") else open
+ with opener(path, "rt", encoding="utf-8") as handle:
+ return json.load(handle)
+
+
+def improvement(before, after):
+ return (before - after) / before * 100 if before else None
+
+
+def compare(before, after):
+ before_summary = {(s["scale"], s["ratio"], s["topK"], s["method"]): s for s in before["summaries"]}
+ before_rows = {(r["scale"], r["ratio"], r["topK"], r["method"], r["questionId"]): r for r in before["rows"]}
+ after_rows = {}
+ for row in after["rows"]:
+ after_rows.setdefault((row["scale"], row["ratio"], row["topK"], row["method"]), []).append(row)
+
+ results = []
+ for summary in after["summaries"]:
+ key = (summary["scale"], summary["ratio"], summary["topK"], summary["method"])
+ base = before_summary.get(key)
+ if base is None:
+ continue
+ mismatched = 0
+ for row in after_rows[key]:
+ old = before_rows[key + (row["questionId"],)]
+ if any(old[field] != row[field] for field in RESULT_FIELDS):
+ mismatched += 1
+ results.append({
+ "scale": summary["scale"], "ratio": summary["ratio"], "topK": summary["topK"],
+ "method": summary["method"], "questions": summary["questions"],
+ "vectorP50Before": base["vectorP50Millis"], "vectorP50After": summary["vectorP50Millis"],
+ "vectorP50ImprovementPercent": improvement(base["vectorP50Millis"], summary["vectorP50Millis"]),
+ "vectorP95Before": base["vectorP95Millis"], "vectorP95After": summary["vectorP95Millis"],
+ "vectorP95ImprovementPercent": improvement(base["vectorP95Millis"], summary["vectorP95Millis"]),
+ "hybridP50Before": base["hybridP50Millis"], "hybridP50After": summary["hybridP50Millis"],
+ "hybridP50ImprovementPercent": improvement(base["hybridP50Millis"], summary["hybridP50Millis"]),
+ "hybridP95Before": base["hybridP95Millis"], "hybridP95After": summary["hybridP95Millis"],
+ "hybridP95ImprovementPercent": improvement(base["hybridP95Millis"], summary["hybridP95Millis"]),
+ "hnswPlanBefore": base["hnswPlanQuestions"], "hnswPlanAfter": summary["hnswPlanQuestions"],
+ "resultMismatchQuestions": mismatched,
+ })
+ return results
+
+
+def fmt_percent(value):
+ return "-" if value is None else f"{value:+.1f}%"
+
+
+def markdown(results):
+ lines = ["| 규모 | 허용 비율 | topK | 방식 | 벡터 p50 전 → 후 (ms) | 개선율 | 벡터 p95 전 → 후 (ms) | 개선율 "
+ "| 하이브리드 p50 전 → 후 (ms) | 개선율 | HNSW 사용 전/후 | 결과 다른 질문 |",
+ "|---|---:|---:|---|---|---:|---|---:|---|---:|---|---:|"]
+ for r in results:
+ lines.append(
+ f"| {r['scale']} | {r['ratio'] * 100:g}% | {r['topK']} | {METHOD_NAMES.get(r['method'], r['method'])} | "
+ f"{r['vectorP50Before']:,.1f} → {r['vectorP50After']:,.1f} | {fmt_percent(r['vectorP50ImprovementPercent'])} | "
+ f"{r['vectorP95Before']:,.1f} → {r['vectorP95After']:,.1f} | {fmt_percent(r['vectorP95ImprovementPercent'])} | "
+ f"{r['hybridP50Before']:,.1f} → {r['hybridP50After']:,.1f} | {fmt_percent(r['hybridP50ImprovementPercent'])} | "
+ f"{r['hnswPlanBefore']}/{r['hnswPlanAfter']} | {r['resultMismatchQuestions']}/{r['questions']} |")
+ return "\n".join(lines) + "\n"
+
+
+# 범주형 색은 고정 순서로만 쓴다(dataviz 기준 팔레트 1~3번, 밝은/어두운 화면 각각 검증).
+# 지연 그림: 1번(파랑)=수정 후, 2번(주황)=수정 전. 개선율 그림: 1번=벡터 단독, 3번(청록)=하이브리드(주황과 의미가 겹치지 않게).
+SVG_STYLE = """"""
+
+
+def text_width(text, size=12):
+ """한글은 글자 크기만큼, 그 밖은 0.58배로 어림한 글자 폭."""
+ return sum(size if ord(char) > 0x1100 else size * 0.58 for char in str(text))
+
+
+def esc(text):
+ return str(text).replace("&", "&").replace("<", "<").replace(">", ">")
+
+
+def svg_open(width, height, title, subtitle):
+ return [f'', SVG_STYLE,
+ f' ',
+ f'{esc(title)} ',
+ f'{esc(subtitle)} ']
+
+
+def legend(out, items, y):
+ """items = [(종류, 클래스, 글자)]. 종류는 dot·ring·tick·bar. 글자는 계열 색을 쓰지 않는다."""
+ x = 20
+ for kind, css, label in items:
+ if kind == "dot":
+ out.append(f' ')
+ elif kind == "tick":
+ out.append(f' ')
+ else:
+ out.append(f' ')
+ out.append(f'{esc(label)} ')
+ x += 18 + text_width(label) + 24
+
+
+def log_x(value, x0, width, low=1.0, high=10000.0):
+ value = max(low, min(high, value))
+ return x0 + (math.log10(value) - math.log10(low)) / (math.log10(high) - math.log10(low)) * width
+
+
+def dumbbell_svg(path, title, subtitle, panels):
+ """로그 축 위에 행마다 수정 전(주황)→수정 후(파랑) 지연을 잇고 수정 전 Custom Plan을 회색 눈금으로 둔다.
+
+ panels = [(패널 제목, [(행 이름, 수정 전, 수정 후, 기준값)])]
+ """
+ label_width, plot_width, row_height = 70, 560, 44
+ top = 112
+ rows_total = sum(len(rows) for _, rows in panels)
+ height = top + rows_total * row_height + len(panels) * 40 + 40
+ width = 20 + label_width + plot_width + 90
+ out = svg_open(width, height, title, subtitle)
+ legend(out, [("dot", "d1", "수정 전 Generic Plan"), ("dot", "d0", "수정 후 Generic Plan"),
+ ("tick", "ref", "수정 전 Custom Plan(처음 5번)")], 72)
+ x0 = 20 + label_width
+ y = top
+ for panel_title, rows in panels:
+ out.append(f'{esc(panel_title)} ')
+ y += 14
+ plot_top, plot_bottom = y, y + len(rows) * row_height
+ for tick in (1, 10, 100, 1000, 10000):
+ x = log_x(tick, x0, plot_width)
+ out.append(f' ')
+ out.append(f''
+ f'{tick:,}ms ')
+ for index, (label, before, after, reference) in enumerate(rows):
+ cy = plot_top + index * row_height + row_height / 2
+ xb, xa, xr = log_x(before, x0, plot_width), log_x(after, x0, plot_width), log_x(reference, x0, plot_width)
+ out.append(f'{esc(label)} ')
+ out.append(f' ')
+ out.append(f''
+ f'수정 전 Custom Plan · {esc(label)}: {reference:,.1f}ms ')
+ for css, x, value, name in (("d1", xb, before, "수정 전"), ("d0", xa, after, "수정 후")):
+ out.append(f''
+ f'{name} Generic Plan · {esc(label)}: {value:,.1f}ms ')
+ # 값 글자는 두 점 바깥쪽에 둔다(수정 후는 왼쪽, 수정 전은 오른쪽).
+ out.append(f''
+ f'{after:,.1f} ')
+ out.append(f'{before:,.1f} '
+ f'→ {improvement(before, after):.1f}%↓ ')
+ y = plot_bottom + 40
+ out.append(f'x축: 서비스 호출 지연 p50 (로그 눈금). '
+ '값 글자: 수정 후(왼쪽), 수정 전과 개선율(오른쪽) ')
+ out.append(" ")
+ with open(path, "w", encoding="utf-8") as handle:
+ handle.write("\n".join(out) + "\n")
+
+
+def improvement_bars_svg(path, title, subtitle, rows, series_names, series_classes):
+ """행마다 계열별 개선율(%) 가로 막대. rows = [(행 이름, [계열별 개선율])], 0~100% 선형 축."""
+ label_width, plot_width, bar, gap = 70, 520, 14, 2
+ group = len(series_names) * (bar + gap) + 18
+ top = 104
+ height = top + len(rows) * group + 50
+ width = 20 + label_width + plot_width + 70
+ out = svg_open(width, height, title, subtitle)
+ legend(out, [("bar", css, name) for css, name in zip(series_classes, series_names)], 72)
+ x0 = 20 + label_width
+ plot_bottom = top + len(rows) * group
+ for tick in range(0, 101, 20):
+ x = x0 + tick / 100 * plot_width
+ out.append(f' ')
+ out.append(f'{tick}% ')
+ for row_index, (label, values) in enumerate(rows):
+ y = top + row_index * group
+ out.append(f'{esc(label)} ')
+ for series_index, value in enumerate(values):
+ by = y + series_index * (bar + gap)
+ length = max(0.0, value) / 100 * plot_width
+ # 막대 끝만 둥글게(4px), 기준선 쪽은 각지게 그린다.
+ out.append(f''
+ f'{esc(series_names[series_index])} · {esc(label)}: {value:.1f}% ')
+ out.append(f'{value:.1f}% ')
+ out.append(f'x축: (수정 전 − 수정 후) ÷ 수정 전, p50 기준 ')
+ out.append("")
+ with open(path, "w", encoding="utf-8") as handle:
+ handle.write("\n".join(out) + "\n")
+
+
+def charts(results, output, prefix, scale="50000", top_k=10):
+ """5만 청크·topK 10에서 현재 계획(Generic/Custom)의 전후를 그림으로 남긴다."""
+ index = {(r["scale"], r["ratio"], r["topK"], r["method"]): r for r in results}
+ ratios = sorted({r["ratio"] for r in results if r["scale"] == scale and r["topK"] == top_k}, reverse=True)
+ panels = []
+ for path_name, field in (("벡터 단독 검색", "vector"), ("하이브리드 검색 (기본 경로)", "hybrid")):
+ rows = []
+ for ratio in ratios:
+ generic = index[(scale, ratio, top_k, "planner_default_generic")]
+ custom = index[(scale, ratio, top_k, "planner_default")]
+ rows.append((f"허용 {ratio * 100:g}%", generic[field + "P50Before"], generic[field + "P50After"],
+ custom[field + "P50Before"]))
+ panels.append((path_name, rows))
+ dumbbell_svg(os.path.join(output, prefix + "before-after-latency.svg"),
+ f"수정 전후 검색 지연 · Generic Plan ({int(scale):,} 청크, topK {top_k})",
+ "질문 30개 p50. 같은 SQL을 6번 넘게 실행해 Generic Plan이 쓰일 때의 지연이며, 수정 후에는 처음 5번 수준이 된다.",
+ panels)
+ rows = []
+ for ratio in ratios:
+ generic = index[(scale, ratio, top_k, "planner_default_generic")]
+ rows.append((f"허용 {ratio * 100:g}%", [generic["vectorP50ImprovementPercent"],
+ generic["hybridP50ImprovementPercent"]]))
+ improvement_bars_svg(os.path.join(output, prefix + "improvement-percent.svg"),
+ f"Generic Plan 지연 개선율 ({int(scale):,} 청크, topK {top_k})",
+ "질문 30개 p50. 허용 청크가 많을수록 행마다 하던 변환이 많아 개선 폭이 크다.",
+ rows, ["벡터 단독 검색", "하이브리드 검색"], ["d0", "d2"])
+
+
+def main():
+ if len(sys.argv) not in (4, 5):
+ print(__doc__)
+ sys.exit(2)
+ before, after, output = load(sys.argv[1]), load(sys.argv[2]), sys.argv[3]
+ prefix = sys.argv[4] if len(sys.argv) == 5 else ""
+ results = compare(before, after)
+ os.makedirs(output, exist_ok=True)
+ with open(os.path.join(output, prefix + "comparison.csv"), "w", newline="", encoding="utf-8") as handle:
+ writer = csv.DictWriter(handle, fieldnames=list(results[0].keys()))
+ writer.writeheader()
+ writer.writerows(results)
+ with open(os.path.join(output, prefix + "comparison.md"), "w", encoding="utf-8") as handle:
+ handle.write(markdown(results))
+ charts(results, output, prefix)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/docs/test-results/evidence/issue-549/explain_generic_plan.py b/docs/test-results/evidence/issue-549/explain_generic_plan.py
new file mode 100644
index 00000000..b46ba7d9
--- /dev/null
+++ b/docs/test-results/evidence/issue-549/explain_generic_plan.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python3
+"""#549 수정 전후 SQL의 Custom/Generic Plan 실행 시간을 psql로 비교하는 스크립트를 만든다. 표준 라이브러리만 쓴다.
+
+현재(수정 후) VectorSearchRepository.findTopK SQL을 읽고, 질문 Vector 서브쿼리를 원래의 CAST로 되돌린 SQL을 수정 전으로 쓴다.
+두 SQL을 같은 질문(D01)·허용 문서(5만 청크 100%, 10%)로 PREPARE하고 다음을 EXPLAIN ANALYZE한다.
+ - force_custom_plan / force_generic_plan
+ - 기본 plan_cache_mode(auto)에서 같은 Statement 8회 반복
+
+사용법 (#545 하네스로 5만 청크 데이터가 적재된 상태에서):
+ python3 explain_generic_plan.py > generic.sql
+ docker exec -i <격리 DB 컨테이너> psql -U -d -q < generic.sql > generic-raw.txt
+ python3 explain_generic_plan.py --mask generic-raw.txt > generic-plan-psql.txt
+"""
+import json
+import re
+import sys
+
+SCHEMA = "docgrid_permission_recall_545"
+QUESTION = "재택근무는 한 주에 몇 번까지 할 수 있어?"
+FIXED_CAST = "(SELECT CAST(:queryVector AS vector))"
+ORIGINAL_CAST = "CAST(:queryVector AS vector)"
+
+
+def permitted_ids(plans, ratio):
+ for sample in plans:
+ if sample["scale"] == "50000" and sample["ratio"] == ratio and sample["method"] == "exact":
+ match = re.search(r"document_id = ANY \('\{([0-9,]+)\}'", sample["plan"])
+ if match:
+ return [int(value) for value in match.group(1).split(",")]
+ raise SystemExit(f"허용 비율 {ratio}의 Exact 계획을 찾지 못했습니다.")
+
+
+def prepared(name, sql, ids):
+ placeholders = ", ".join(f"${index + 3}" for index in range(len(ids)))
+ statement = (sql.replace(":queryVector", "$1").replace(":modelId", "$2")
+ .replace("(:permittedIds)", f"({placeholders})").replace(":topK", f"${len(ids) + 3}"))
+ types = "text, bigint, " + ", ".join(["bigint"] * len(ids)) + ", int"
+ return f"PREPARE {name}({types}) AS {statement};"
+
+
+def build(backend, plans_path):
+ with open(f"{backend}/build/reports/permission-recall/question-vectors.json", encoding="utf-8") as handle:
+ vector = "[" + ",".join(repr(float(v)) for v in json.load(handle)["vectors"][QUESTION]) + "]"
+ with open(plans_path, encoding="utf-8") as handle:
+ plans = json.load(handle)
+ source = open(f"{backend}/src/main/java/com/opensource/docgrid/domain/search/repository/VectorSearchRepository.java",
+ encoding="utf-8").read()
+ fixed = re.search(r'@Query\(value = """(.*?)""", nativeQuery = true\)\s*List findTopK',
+ source, re.S).group(1)
+ if FIXED_CAST not in fixed:
+ raise SystemExit("현재 findTopK SQL에 수정(질문 Vector 서브쿼리)이 없습니다.")
+ versions = [("수정 전", fixed.replace(FIXED_CAST, ORIGINAL_CAST)), ("수정 후", fixed)]
+
+ print(f"SET search_path = {SCHEMA}, public;")
+ for ratio, label in ((1.0, "허용 100%"), (0.1, "허용 10%")):
+ ids = permitted_ids(plans, ratio)
+ values = f"'{vector}', 1, " + ", ".join(map(str, ids)) + ", 40"
+ for version_index, (version, sql) in enumerate(versions):
+ name = f"s_{int(ratio * 1000)}_{version_index}"
+ print(prepared(name, sql, ids))
+ for mode in ("force_custom_plan", "force_generic_plan"):
+ print(f"\\echo '=== {version} · {label} · topK 10 · {mode}'")
+ print(f"SET plan_cache_mode = {mode};")
+ print(f"EXPLAIN (ANALYZE, BUFFERS) EXECUTE {name}({values});")
+ print(f"DEALLOCATE {name};")
+ if ratio == 1.0:
+ # 새 Statement를 auto 모드로 8번 반복해 몇 번째부터 Generic Plan으로 바뀌는지 본다.
+ print(prepared(name, sql, ids))
+ print("SET plan_cache_mode = auto;")
+ for run in range(1, 9):
+ print(f"\\echo '=== {version} · {label} · auto 모드 반복 {run}회째'")
+ print(f"EXPLAIN (ANALYZE, SUMMARY) EXECUTE {name}({values});")
+ print(f"DEALLOCATE {name};")
+ print("RESET plan_cache_mode;")
+
+
+def mask(path):
+ text = open(path, encoding="utf-8").read()
+ text = re.sub(r"'\[[-0-9.,eE]+\]'(::vector)?", "'<질문 벡터 1024차원>'", text)
+ text = re.sub(r"'\{[0-9,]+\}'::bigint\[\]", "'<허용 문서 ID 목록>'::bigint[]", text)
+ text = re.sub(r"ARRAY\[\$3(, \$[0-9]+)+\]", "ARRAY[$3, ..., $n]", text)
+ lines = [line.rstrip() for line in text.splitlines()]
+ kept = [line for line in lines if line and not set(line) <= {"-", "+"} and line.strip() != "QUERY PLAN"]
+ print("\n".join(kept))
+
+
+if __name__ == "__main__":
+ if len(sys.argv) == 3 and sys.argv[1] == "--mask":
+ mask(sys.argv[2])
+ elif len(sys.argv) == 3:
+ build(sys.argv[1], sys.argv[2])
+ else:
+ print(__doc__)
+ sys.exit(2)
diff --git a/docs/test-results/evidence/issue-549/full-before-after-latency.svg b/docs/test-results/evidence/issue-549/full-before-after-latency.svg
new file mode 100644
index 00000000..36dfd5db
--- /dev/null
+++ b/docs/test-results/evidence/issue-549/full-before-after-latency.svg
@@ -0,0 +1,99 @@
+
+
+
+수정 전후 검색 지연 · Generic Plan (50,000 청크, topK 10)
+질문 30개 p50. 같은 SQL을 6번 넘게 실행해 Generic Plan이 쓰일 때의 지연이며, 수정 후에는 처음 5번 수준이 된다.
+
+수정 전 Generic Plan
+
+수정 후 Generic Plan
+
+수정 전 Custom Plan(처음 5번)
+벡터 단독 검색
+
+1ms
+
+10ms
+
+100ms
+
+1,000ms
+
+10,000ms
+허용 100%
+
+수정 전 Custom Plan · 허용 100%: 204.2ms
+수정 전 Generic Plan · 허용 100%: 4,226.3ms
+수정 후 Generic Plan · 허용 100%: 207.4ms
+207.4
+4,226.3 → 95.1%↓
+허용 10%
+
+수정 전 Custom Plan · 허용 10%: 28.1ms
+수정 전 Generic Plan · 허용 10%: 480.5ms
+수정 후 Generic Plan · 허용 10%: 24.9ms
+24.9
+480.5 → 94.8%↓
+허용 1%
+
+수정 전 Custom Plan · 허용 1%: 7.3ms
+수정 전 Generic Plan · 허용 1%: 43.2ms
+수정 후 Generic Plan · 허용 1%: 6.8ms
+6.8
+43.2 → 84.4%↓
+허용 0.1%
+
+수정 전 Custom Plan · 허용 0.1%: 3.6ms
+수정 전 Generic Plan · 허용 0.1%: 7.9ms
+수정 후 Generic Plan · 허용 0.1%: 3.7ms
+3.7
+7.9 → 53.4%↓
+하이브리드 검색 (기본 경로)
+
+1ms
+
+10ms
+
+100ms
+
+1,000ms
+
+10,000ms
+허용 100%
+
+수정 전 Custom Plan · 허용 100%: 1,338.4ms
+수정 전 Generic Plan · 허용 100%: 5,444.6ms
+수정 후 Generic Plan · 허용 100%: 1,321.6ms
+1,321.6
+5,444.6 → 75.7%↓
+허용 10%
+
+수정 전 Custom Plan · 허용 10%: 164.1ms
+수정 전 Generic Plan · 허용 10%: 609.4ms
+수정 후 Generic Plan · 허용 10%: 149.2ms
+149.2
+609.4 → 75.5%↓
+허용 1%
+
+수정 전 Custom Plan · 허용 1%: 21.7ms
+수정 전 Generic Plan · 허용 1%: 56.3ms
+수정 후 Generic Plan · 허용 1%: 19.0ms
+19.0
+56.3 → 66.2%↓
+허용 0.1%
+
+수정 전 Custom Plan · 허용 0.1%: 9.1ms
+수정 전 Generic Plan · 허용 0.1%: 10.9ms
+수정 후 Generic Plan · 허용 0.1%: 6.6ms
+6.6
+10.9 → 39.0%↓
+x축: 서비스 호출 지연 p50 (로그 눈금). 값 글자: 수정 후(왼쪽), 수정 전과 개선율(오른쪽)
+
diff --git a/docs/test-results/evidence/issue-549/full-comparison.csv b/docs/test-results/evidence/issue-549/full-comparison.csv
new file mode 100644
index 00000000..aac5be71
--- /dev/null
+++ b/docs/test-results/evidence/issue-549/full-comparison.csv
@@ -0,0 +1,193 @@
+scale,ratio,topK,method,questions,vectorP50Before,vectorP50After,vectorP50ImprovementPercent,vectorP95Before,vectorP95After,vectorP95ImprovementPercent,hybridP50Before,hybridP50After,hybridP50ImprovementPercent,hybridP95Before,hybridP95After,hybridP95ImprovementPercent,hnswPlanBefore,hnswPlanAfter,resultMismatchQuestions
+real,1.0,5,exact,30,15.530542,17.277083,-11.2458470541466,18.703333,24.818333,-32.694707408567226,98.351458,102.59925,-4.318992403752676,143.365083,156.390667,-9.085604198338872,0,0,0
+real,1.0,5,planner_default,30,15.888708,16.15125,-1.6523810494849651,19.100083,18.994209,0.5543117273364729,100.321958,98.227875,2.0873625692193905,150.12325,136.042917,9.379182105370102,0,0,0
+real,1.0,5,planner_default_generic,30,307.084291,15.263708,95.02947286873753,374.938125,18.902292,94.95855696189872,393.650167,97.802083,75.15507646158322,526.828583,134.824792,74.40822378462332,0,0,0
+real,1.0,5,hnsw_ef40,30,5.0245,4.913041,2.2183102796298133,7.034541,7.46425,-6.108557758068364,84.999958,84.916625,0.09803887197216063,121.9795,124.896333,-2.3912485294660146,30,30,5
+real,1.0,5,hnsw_ef100,30,4.917209,5.415458,-10.132760271121288,6.215958,7.4705,-20.182600976390138,86.288959,87.119167,-0.9621254093469813,122.918375,129.494209,-5.349756698296748,30,30,1
+real,1.0,5,hnsw_ef200,30,5.529208,6.223708,-12.560569253318027,7.174625,11.893291,-65.76881718556719,86.566583,94.422792,-9.0753368421623,124.131833,129.561875,-4.374415384649952,30,30,0
+real,1.0,5,iterative_relaxed,30,5.047292,5.241166,-3.8411488774574587,6.941458,8.432625,-21.482043109675228,87.871833,88.842125,-1.1042127686126688,122.902834,166.696667,-35.63289110159982,30,30,5
+real,1.0,5,iterative_strict,30,4.722917,5.098291,-7.947927096749739,6.264333,7.997125,-27.66123703832475,83.850541,88.493958,-5.537730519830515,125.883875,145.057792,-15.231432143314624,30,30,5
+real,1.0,10,exact,30,18.007166,18.184167,-0.9829475665409929,23.836417,19.700417,17.351601123608464,100.576334,100.974708,-0.3960911917907089,146.492875,140.034375,4.408746841783252,0,0,0
+real,1.0,10,planner_default,30,17.767666,18.039125,-1.5278258832645781,23.87325,21.379917,10.444045113254374,102.074458,100.062542,1.9710278549801494,148.92425,141.63625,4.893763104397041,0,0,0
+real,1.0,10,planner_default_generic,30,300.136791,17.818708,94.0631376977706,306.263458,21.481583,92.98591378146067,382.709917,101.928583,73.3666209125174,427.960375,138.2605,67.69315383462781,0,0,0
+real,1.0,10,hnsw_ef40,30,6.414167,7.205042,-12.330127980765075,8.37375,8.979458,-7.233413942379459,87.523375,90.54325,-3.450363974195464,127.447834,134.123666,-5.238089805433638,30,30,8
+real,1.0,10,hnsw_ef100,30,6.938541,7.801583,-12.43837861590787,8.802208,12.42475,-41.15492385546899,90.49525,93.709916,-3.552303573944498,126.522125,131.882667,-4.2368415800793695,30,30,1
+real,1.0,10,hnsw_ef200,30,7.064458,7.352875,-4.0826486617940105,9.009791,9.206042,-2.1781970303195735,89.090125,90.101459,-1.1351808070759863,125.848417,127.192709,-1.0681834798128576,30,30,0
+real,1.0,10,iterative_relaxed,30,6.34025,6.74675,-6.411419108079325,8.148875,9.553,-17.23090610667117,88.15375,87.742792,0.4661832310026606,127.027833,133.722375,-5.270137923237656,30,30,8
+real,1.0,10,iterative_strict,30,6.412625,6.597166,-2.877776261671303,7.64975,8.629667,-12.80979116964606,86.878209,87.339583,-0.5310583693087025,124.161583,125.477334,-1.0597086217884366,30,30,8
+real,1.0,20,exact,30,20.296375,21.259292,-4.744280690517382,22.67725,28.210917,-24.401843257008665,102.272375,105.912375,-3.5591233703138316,140.76575,144.263584,-2.484861551904502,0,0,0
+real,1.0,20,planner_default,30,20.463083,21.294208,-4.061582509341335,22.902209,23.98225,-4.715881337036097,103.484708,104.234125,-0.7241813930614833,141.822459,144.478334,-1.8726758926101965,0,0,0
+real,1.0,20,planner_default_generic,30,301.167209,21.431167,92.88396400419542,312.088917,25.961917,91.68124352201843,383.5855,102.049375,73.39592476775061,421.849834,138.910667,67.07106278012664,0,0,0
+real,1.0,20,hnsw_ef40,30,6.550584,6.919916,-5.638153789036214,7.947583,8.55,-7.579876800280045,87.7295,89.78325,-2.3410027413811703,126.072833,128.1945,-1.6828899212568675,30,30,7
+real,1.0,20,hnsw_ef100,30,10.574333,10.3215,2.391006600605438,12.606166,12.277791,2.6048760582717967,93.095667,93.818834,-0.7767998482679,140.151417,132.528667,5.438938944156373,30,30,5
+real,1.0,20,hnsw_ef200,30,11.299834,11.226834,0.646027189425972,13.188292,13.253375,-0.4934907416365937,93.14675,96.183292,-3.259954856181238,133.986625,136.188042,-1.6430125021807154,30,30,0
+real,1.0,20,iterative_relaxed,30,10.354,10.612333,-2.495006760672208,14.158834,12.622084,10.853647976945004,94.839041,95.122166,-0.29853212033218735,132.157709,134.151417,-1.5085824467492834,30,30,8
+real,1.0,20,iterative_strict,30,10.608833,11.062209,-4.273570900776727,12.126916,14.174625,-16.885653368094587,93.46475,93.926375,-0.49390278152993294,130.819792,133.467458,-2.0239032332355236,30,30,9
+real,0.1,5,exact,30,3.998375,4.360958,-9.06825898021072,5.705208,7.096625,-24.388541136449373,12.766542,13.6065,-6.5793697306600425,17.956959,18.842083,-4.929141955494789,0,0,0
+real,0.1,5,planner_default,30,4.552667,4.366291,4.093776241486567,6.3665,5.918208,7.04141993245897,14.228292,14.01,1.5342108525745741,19.266458,19.40475,-0.7177863206615345,0,0,0
+real,0.1,5,planner_default_generic,30,28.856917,4.336958,84.97081999438818,30.807833,5.995542,80.53890385604207,38.145667,13.073334,65.72786628688391,43.450708,18.101625,58.3398618038629,0,0,0
+real,0.1,5,hnsw_ef40,30,1.96,2.053667,-4.778928571428569,4.6015,3.844334,16.454764750624793,10.792041,10.2895,4.656589054841425,18.465708,14.951916,19.028742358538317,30,30,2
+real,0.1,5,hnsw_ef100,30,3.232875,3.070083,5.035517921354833,5.138667,5.234667,-1.8681887734698528,12.280458,11.783917,4.0433426831474755,16.251291,17.891958,-10.095610250287198,30,30,2
+real,0.1,5,hnsw_ef200,30,4.052541,4.387708,-8.270539397380563,6.572375,7.536708,-14.67251944692748,12.984333,13.410459,-3.281847438755614,19.495541,19.072542,2.1697217840736034,30,30,0
+real,0.1,5,iterative_relaxed,30,4.365625,4.833667,-10.721076592698653,5.577458,7.558875,-35.525449048652625,12.85575,13.829583,-7.575077300040831,17.096583,18.817875,-10.068046930781444,30,30,0
+real,0.1,5,iterative_strict,30,5.376667,5.401125,-0.4548914783080315,7.034208,7.925625,-12.672599388587894,15.072042,15.112125,-0.26594273025513715,20.592042,18.668416,9.341599050740081,30,30,3
+real,0.1,10,exact,30,5.514958,5.647541,-2.4040618260374846,6.808917,7.119958,-4.568142040797376,14.814333,15.177709,-2.452867773392164,21.191042,20.180959,4.766556547809202,0,0,0
+real,0.1,10,planner_default,30,5.570958,5.461208,1.9700381873279247,7.179166,6.690125,6.811947237325342,15.340208,14.756416,3.8056328832047175,21.618,19.479958,9.89010084189101,0,0,0
+real,0.1,10,planner_default_generic,30,31.789375,5.605083,82.36806165582054,33.457834,7.856584,76.51795391178042,41.730209,14.731333,64.69863594500569,46.3735,19.893,57.102655611502264,0,0,0
+real,0.1,10,hnsw_ef40,30,2.235291,2.114167,5.418712820836303,4.414792,3.985916,9.714523356932789,10.95,10.412,4.913242009132406,15.896167,15.934625,-0.24193253631520306,30,30,2
+real,0.1,10,hnsw_ef100,30,3.109917,2.918916,6.141675163678002,5.181,4.86575,6.084732677089361,11.999667,12.333792,-2.7844522685504542,17.42975,17.571667,-0.8142228087035269,30,30,2
+real,0.1,10,hnsw_ef200,30,4.555125,4.604,-1.0729672621497715,6.427833,7.157875,-11.357513488604948,14.010084,14.44075,-3.0739715764730504,18.450833,19.252583,-4.345332267654268,30,30,0
+real,0.1,10,iterative_relaxed,30,6.779458,8.001708,-18.028727370241114,8.628833,11.62425,-34.71404534077783,15.289,18.582792,-21.543541107986144,22.471292,25.474083,-13.362787506833174,30,30,0
+real,0.1,10,iterative_strict,30,7.28375,7.549916,-3.654244036382347,10.88925,9.130084,16.155070367564345,16.825959,17.280667,-2.7024195173659944,24.136958,23.882292,1.0550873892227857,30,30,3
+real,0.1,20,exact,30,4.998833,4.71825,5.612970067213688,6.189458,7.954083,-28.510170034274402,14.170042,13.742625,3.0163425062536877,21.003334,18.655375,11.178982346326539,0,0,0
+real,0.1,20,planner_default,30,4.822333,4.904458,-1.7030138731605544,6.82075,6.744208,1.1221933071876242,13.492125,14.625875,-8.403049927272397,21.323375,22.035667,-3.340428051375552,0,0,0
+real,0.1,20,planner_default_generic,30,35.685417,4.492875,87.40977301736449,37.947542,6.487583,82.90381232070314,47.334625,12.666834,73.23981335016386,56.23725,17.078167,69.63193079320202,0,0,0
+real,0.1,20,hnsw_ef40,30,2.2555,2.155792,4.420660607404129,4.446375,4.17175,6.176379635095993,10.79575,11.153084,-3.3099506750341554,15.34675,15.808,-3.005522341863911,30,30,2
+real,0.1,20,hnsw_ef100,30,3.175334,3.285792,-3.4786261854658425,4.901584,5.094667,-3.93919598236,12.44375,12.222416,1.7786760421898449,17.260708,17.164208,0.5590732431137961,30,30,2
+real,0.1,20,hnsw_ef200,30,4.491375,4.714958,-4.978052378169281,6.241667,7.01425,-12.377831114668567,13.719542,14.37025,-4.74292800736351,19.28375,20.055208,-4.000560057042843,30,30,0
+real,0.1,20,iterative_relaxed,30,7.820167,8.860209,-13.299485803819788,11.015333,14.286,-29.691948486713926,17.735417,18.475833,-4.1747876579389125,23.173708,27.342084,-17.987522756392714,30,30,0
+real,0.1,20,iterative_strict,30,7.702625,9.0245,-17.161357329481824,10.200125,14.375417,-40.93373365522482,16.666166,20.601333,-23.611711295807325,24.730375,26.009583,-5.17261869259969,30,30,3
+real,0.01,5,exact,30,2.674375,3.67575,-37.44332788034587,4.666916,5.206542,-11.562796502015468,5.3935,7.156584,-32.689051636228776,10.027667,10.347916,-3.1936541171540744,0,0,0
+real,0.01,5,planner_default,30,3.251,3.145333,3.250292217779144,5.036334,4.237041,15.870532017932101,6.324584,6.112125,3.3592565139462134,9.005333,8.293416,7.905504438314493,0,0,0
+real,0.01,5,planner_default_generic,30,5.302291,3.349792,36.823686214128955,8.189292,4.324709,47.190685104402185,7.227459,5.74225,20.549532000112343,10.235292,7.898208,22.83358403453462,0,0,0
+real,0.01,5,hnsw_ef40,30,1.495709,1.556542,-4.067168145675405,2.533542,2.73425,-7.922031685284855,3.852333,4.030583,-4.627066247907444,5.720833,5.540292,3.155851604128277,30,30,0
+real,0.01,5,hnsw_ef100,30,1.723875,1.880417,-9.080820825175836,3.034875,3.57125,-17.673709790353804,3.837292,4.169209,-8.649771766130913,5.814834,6.384959,-9.804665103079468,30,30,0
+real,0.01,5,hnsw_ef200,30,2.193,2.580084,-17.650889192886446,3.745667,4.159542,-11.04943391924589,4.512875,4.887708,-8.305858238927508,6.74675,7.876708,-16.74818245822063,30,30,0
+real,0.01,5,iterative_relaxed,30,8.866708,8.813875,0.5958581245711452,11.094667,10.804417,2.616121781753329,11.56,11.394166,1.434550173010383,13.995292,15.569334,-11.246939327882552,30,30,0
+real,0.01,5,iterative_strict,30,9.148166,9.289,-1.539477967496435,10.713791,11.384833,-6.26334786631548,12.092334,12.155125,-0.5192628652169277,15.070458,15.199625,-0.8570874222933299,30,30,0
+real,0.01,10,exact,30,3.631709,3.292583,9.337917768191224,4.829958,5.300208,-9.736109506542274,6.717292,6.555042,2.415407875673699,9.662042,9.797833,-1.4054068487800104,0,0,0
+real,0.01,10,planner_default,30,3.397667,2.670541,21.400743510179193,5.048417,5.310834,-5.198005632260571,5.610334,5.56675,0.7768521446316756,9.267458,9.497208,-2.479104841910274,0,0,0
+real,0.01,10,planner_default_generic,30,7.103792,2.637542,62.871350962978646,10.485208,4.364833,58.37151728415879,9.579041,4.289792,55.21689488540659,13.541417,7.304875,46.05531311826524,0,0,0
+real,0.01,10,hnsw_ef40,30,1.698917,1.748166,-2.8988467358911647,2.712958,2.634208,2.902735685550601,4.189208,4.287875,-2.355266198288551,6.932875,6.210959,10.412938355299934,30,30,0
+real,0.01,10,hnsw_ef100,30,1.841666,2.000917,-8.647116252349765,2.99075,3.00225,-0.38451893337792487,4.343959,4.575917,-5.3397833635170056,6.46275,6.449167,0.21017368767165182,30,30,0
+real,0.01,10,hnsw_ef200,30,2.265792,2.748,-21.282094737734113,3.457875,4.54475,-31.431876513754826,4.733042,4.997167,-5.580449106515427,6.6945,7.0285,-4.989170214355076,30,30,0
+real,0.01,10,iterative_relaxed,30,13.104792,13.397667,-2.234869504224107,14.618541,17.224125,-17.823830709234254,15.830917,16.4445,-3.875852548528945,18.541,20.704292,-11.667612318645155,30,30,0
+real,0.01,10,iterative_strict,30,12.756042,13.139,-3.0021694817248057,14.52775,17.083667,-17.59334377312384,15.903209,16.788917,-5.569366534766669,18.745875,20.454375,-9.114005081117831,30,30,0
+real,0.01,20,exact,30,3.828208,3.602625,5.892652645833243,4.990208,5.108167,-2.363809284101984,6.957209,6.490542,6.707675448588654,9.1925,9.334875,-1.5488169703562629,0,0,0
+real,0.01,20,planner_default,30,2.350833,4.213542,-79.23612608807176,4.137,5.789042,-39.93333333333335,5.363041,7.228042,-34.77506511697375,8.233,12.456,-51.29357463864932,0,0,0
+real,0.01,20,planner_default_generic,30,4.91125,3.959292,19.383212013234917,8.219,5.263958,35.95378999878331,6.681541,6.395958,4.274208599483262,10.182792,8.762292,13.950005067372473,0,0,0
+real,0.01,20,hnsw_ef40,30,1.514917,2.18175,-44.017791073702384,3.258917,4.344334,-33.30606456071143,3.952916,4.980667,-25.999818867893985,5.7115,8.60375,-50.63906154250196,30,30,0
+real,0.01,20,hnsw_ef100,30,1.810209,2.377958,-31.363726508927982,3.198667,4.730958,-47.90404878032006,4.161333,5.102792,-22.623976499837912,6.069375,8.542625,-40.74966532797857,30,30,0
+real,0.01,20,hnsw_ef200,30,2.567166,3.3945,-32.22752248978056,4.361334,5.948958,-36.402256740712815,5.078167,6.066833,-19.46895405369694,7.48375,10.67825,-42.68581927509605,30,30,0
+real,0.01,20,iterative_relaxed,30,12.065667,14.137834,-17.17407748780072,26.146625,17.002583,34.97216944825575,14.725083,17.633958,-19.754557580422468,30.98025,23.545167,23.999428668264468,30,30,0
+real,0.01,20,iterative_strict,30,13.147417,13.92275,-5.897226808885728,15.115584,15.64125,-3.4776426765912536,16.816916,17.299375,-2.8688910618332293,19.193917,20.019958,-4.303660373231791,30,30,0
+real,0.001,5,exact,30,2.222792,2.429167,-9.284494455621576,3.304834,6.250333,-89.12698792132979,3.892125,3.9205,-0.7290361948806895,6.933667,11.022542,-58.97132065903944,0,0,0
+real,0.001,5,planner_default,30,2.106,1.607084,23.690218423551755,3.352875,3.268542,2.5152443798232857,3.736125,2.614625,30.01773227608819,5.945625,4.61075,22.451382318931977,0,0,0
+real,0.001,5,planner_default_generic,30,3.6415,0.799084,78.05618563778663,4.423416,1.545417,65.06281570623247,4.650667,1.630416,64.94231902649662,6.082125,2.739458,54.95886717224654,0,0,0
+real,0.001,5,hnsw_ef40,30,1.516584,1.654375,-9.08561609511903,3.063708,2.856333,6.768758641489342,2.789958,3.017625,-8.160230369059313,4.846084,4.416416,8.86629286657021,30,30,0
+real,0.001,5,hnsw_ef100,30,1.682459,1.788458,-6.300242680505153,2.841792,3.249625,-14.351261457559177,2.97775,2.953583,0.8115859289732124,5.557542,5.4345,2.2139643748981093,30,30,0
+real,0.001,5,hnsw_ef200,30,2.121666,2.502375,-17.943870524389798,3.579542,3.756209,-4.935463810733332,3.2825,3.819875,-16.370906321401367,5.514416,6.141083,-11.364158960803834,30,30,0
+real,0.001,5,iterative_relaxed,30,9.599291,9.801875,-2.1104058622663033,11.065792,11.462541,-3.585364698703896,10.709541,11.092708,-3.577809730594432,13.544042,14.934791,-10.268345298988303,30,30,0
+real,0.001,5,iterative_strict,30,9.787833,10.254584,-4.768685775492904,11.279917,12.044333,-6.776787453311941,11.09725,12.036125,-8.460429385658603,13.076667,14.9105,-14.023703440639732,30,30,0
+real,0.001,10,exact,30,2.171292,2.512083,-15.69530952078301,3.195292,4.411583,-38.06509702399657,3.713709,4.474125,-20.475917741535476,6.117708,7.933667,-29.683649497491533,0,0,0
+real,0.001,10,planner_default,30,1.845375,2.312083,-25.2906861748967,3.32875,4.012417,-20.538250093879093,3.076708,3.820458,-24.173564732174775,6.034125,7.142833,-18.37396474219543,0,0,0
+real,0.001,10,planner_default_generic,30,4.178583,1.733,58.52661057588182,5.495542,3.173542,42.252429332720965,4.675375,3.373625,27.842686415528163,7.119792,4.815916,32.358754300687444,0,0,0
+real,0.001,10,hnsw_ef40,30,1.487333,1.537917,-3.400986867097009,3.02375,2.941417,2.722877221992566,2.809167,2.983708,-6.21326535588664,4.675833,5.475875,-17.11014914347883,30,30,0
+real,0.001,10,hnsw_ef100,30,1.741,1.95075,-12.047673750717971,3.091666,3.208333,-3.77359650104507,2.837583,3.45075,-21.608777611086627,5.742708,5.633708,1.8980592431305923,30,30,0
+real,0.001,10,hnsw_ef200,30,2.188875,2.582375,-17.977271429387244,3.376709,3.968792,-17.5343211393105,3.421791,3.978875,-16.2804800176282,5.620084,6.948625,-23.63916624733722,30,30,0
+real,0.001,10,iterative_relaxed,30,9.557542,10.357875,-8.373837122557246,11.897708,12.355167,-3.8449338309529875,11.122,12.118667,-8.961221003416656,15.490625,15.595833,-0.6791720798870354,30,30,0
+real,0.001,10,iterative_strict,30,9.400458,10.153583,-8.011577733765726,10.935333,11.652583,-6.559013795007431,10.861875,11.497333,-5.85035272455262,13.696792,14.263,-4.133873099627997,30,30,0
+real,0.001,20,exact,30,2.153041,2.514583,-16.792155839113143,3.901458,4.014542,-2.8985061482143277,3.570084,3.997791,-11.980306345733037,6.808333,6.869709,-0.9014835202684723,0,0,0
+real,0.001,20,planner_default,30,2.057375,2.317708,-12.65364845980923,3.55225,3.779,-6.383278203955239,3.546,4.162542,-17.386971235194597,6.328333,8.023375,-26.784968490122125,0,0,0
+real,0.001,20,planner_default_generic,30,2.757875,2.28,17.32765263110185,4.211083,3.755833,10.810758182633787,3.717709,3.711292,0.17260630135388055,6.289125,5.656416,10.060366108162903,0,0,0
+real,0.001,20,hnsw_ef40,30,1.514458,1.741167,-14.96964590632423,3.020625,3.206375,-6.1493896130767665,2.965542,2.847584,3.9776202798679035,5.65925,5.358875,5.3076821133542404,30,30,0
+real,0.001,20,hnsw_ef100,30,1.819792,1.929584,-6.0332169830398135,3.010917,3.498417,-16.191080657487397,3.0775,3.204375,-4.1226645004061755,4.790083,5.035958,-5.133000826916775,30,30,0
+real,0.001,20,hnsw_ef200,30,2.353708,2.72425,-15.742904387460122,3.918542,4.387541,-11.968711832104892,3.844417,4.21175,-9.554972834632673,5.353834,5.868833,-9.619256032219162,30,30,0
+real,0.001,20,iterative_relaxed,30,9.919292,9.646375,2.751375803837609,11.769959,12.358917,-5.003908679715876,11.236417,11.704958,-4.1698434652256156,13.24525,14.857458,-12.171971083973492,30,30,0
+real,0.001,20,iterative_strict,30,10.062084,10.009917,0.5184512472764166,12.234,13.899417,-13.613021088769,11.460333,11.684417,-1.9553009498066025,13.984292,15.659875,-11.981893684714247,30,30,0
+50000,1.0,5,exact,30,209.081541,214.92475,-2.7947034310408116,258.515958,290.64025,-12.426425141615423,1304.960333,1340.441917,-2.7189779721833207,1848.363625,1891.37825,-2.3271733125563983,0,0,0
+50000,1.0,5,planner_default,30,223.17325,220.969416,0.9874991738481205,282.183417,380.901041,-34.983495858652816,1352.653208,1350.275625,0.1757718080242751,1906.70975,2001.463625,-4.969496537163042,0,0,0
+50000,1.0,5,planner_default_generic,30,4280.916292,204.443917,95.22429538316233,4793.333708,245.759417,94.87289156209108,5479.385958,1305.707833,76.17054460101241,6034.354,1847.093041,69.39037648437596,0,0,0
+50000,1.0,5,hnsw_ef40,30,14.236916,14.808291,-4.013334067574745,18.979833,19.1645,-0.9729643037428255,1154.444292,1158.122208,-0.3185875685372695,1682.005291,1661.9185,1.1942168736019718,30,30,8
+50000,1.0,5,hnsw_ef100,30,14.508208,15.061,-3.8102017837075413,17.10875,19.252666,-12.531108350989994,1119.739916,1130.768416,-0.9849162151329339,1665.420292,1693.656667,-1.6954503998561794,30,30,9
+50000,1.0,5,hnsw_ef200,30,14.6045,15.268625,-4.547399774042249,17.378834,23.537125,-35.43558215700776,1147.5325,1141.161834,0.5551621413772619,1675.310292,1725.2065,-2.9783263577061647,30,30,10
+50000,1.0,5,iterative_relaxed,30,14.035167,14.97225,-6.676678660111428,16.583167,25.426459,-53.32691879663276,1088.348,1194.428708,-9.746947483709251,1657.876542,1954.731333,-17.90572358553825,30,30,8
+50000,1.0,5,iterative_strict,30,13.674083,14.657583,-7.192438425304287,16.110834,17.537708,-8.856611644065094,1088.203833,1125.166125,-3.3966331379389594,1623.530042,1685.045667,-3.7890044168335755,30,30,8
+50000,1.0,10,exact,30,212.874792,217.44475,-2.14678213285112,268.302541,327.269625,-21.97783285250362,1328.271875,1371.06275,-3.221544911503915,1913.334167,2040.724125,-6.658008841170711,0,0,0
+50000,1.0,10,planner_default,30,204.235,208.930417,-2.2990266115014526,264.684083,286.687583,-8.313117944459107,1338.411875,1328.082708,0.7717480091843998,1869.184042,1903.460417,-1.83376137554249,0,0,0
+50000,1.0,10,planner_default_generic,30,4226.314583,207.391375,95.09285523055442,4674.723458,223.840042,95.21169446682593,5444.565375,1321.618625,75.72591136349428,6157.011625,1856.109958,69.85372009915606,0,0,0
+50000,1.0,10,hnsw_ef40,30,15.741709,16.946917,-7.656144577440727,20.40025,20.405542,-0.02594085856791334,1128.395083,1126.786375,0.14256602357068282,1730.92125,1646.843125,4.857420578781386,30,30,11
+50000,1.0,10,hnsw_ef100,30,16.284,17.388125,-6.780428641611397,18.804083,26.09375,-38.76640514722256,1120.31375,1160.747042,-3.6091043245697856,1629.551083,1725.289333,-5.87513033489849,30,30,12
+50000,1.0,10,hnsw_ef200,30,17.030875,17.408625,-2.2180304887446995,23.662667,25.869166,-9.324811104344244,1137.446583,1150.442542,-1.142555544518268,1633.041,1728.755083,-5.8610949143346724,30,30,12
+50000,1.0,10,iterative_relaxed,30,15.935625,15.909958,0.1610667921716301,18.824792,22.854375,-21.40572389856952,1123.101583,1129.156583,-0.5391319976440693,1676.057,1643.395709,1.9486981051360497,30,30,11
+50000,1.0,10,iterative_strict,30,16.057542,16.412875,-2.2128729291195257,20.988084,23.764875,-13.230321548169902,1101.91725,1127.018208,-2.2779349356768845,1687.79425,1638.785167,2.903735630098273,30,30,11
+50000,1.0,20,exact,30,211.061625,216.616875,-2.6320511841032217,295.217333,325.262041,-10.177149049713831,1319.523083,1331.252375,-0.8889038889212055,1890.254292,1892.89875,-0.13989958976376632,0,0,0
+50000,1.0,20,planner_default,30,213.134459,221.876916,-4.101850559979136,276.680709,292.835667,-5.838845092738295,1332.686833,1362.955625,-2.2712606780891096,1908.47,1930.059833,-1.1312639444162076,0,0,0
+50000,1.0,20,planner_default_generic,30,4340.192417,210.061417,95.16008976520914,4562.290375,219.132292,95.1968797689691,5586.952334,1311.778291,76.52068225073208,6081.294542,1898.185416,68.78649105235198,0,0,0
+50000,1.0,20,hnsw_ef40,30,16.05625,16.246917,-1.1874939665239472,19.633,21.177083,-7.864732847756331,1132.688333,1140.250209,-0.6676042985250721,1678.640166,1701.289583,-1.3492717175933513,30,30,10
+50000,1.0,20,hnsw_ef100,30,18.494791,19.800209,-7.058300902129684,21.288459,22.682792,-6.54971315678603,1098.970625,1150.098791,-4.652368756444238,1628.331834,1681.740458,-3.279959458189891,30,30,19
+50000,1.0,20,hnsw_ef200,30,19.148416,20.933833,-9.324097617265046,21.959792,27.3155,-24.388700949444328,1113.599584,1140.979542,-2.458689675659935,1626.7695,1799.34675,-10.608586526855822,30,30,17
+50000,1.0,20,iterative_relaxed,30,18.256083,19.536084,-7.011367115278773,20.321666,25.500709,-25.485326842789362,1094.774958,1163.030584,-6.234671838374305,1631.103875,1711.913916,-4.954316045628912,30,30,19
+50000,1.0,20,iterative_strict,30,18.529333,20.16925,-8.850383335439007,22.218666,38.388417,-72.77552576738854,1110.784166,1168.385041,-5.185604617270003,1627.702125,1715.336792,-5.383949904224647,30,30,19
+50000,0.1,5,exact,30,24.714167,25.397958,-2.7667976832882912,30.958625,28.298833,8.59144099584527,153.44125,149.721333,2.424326574503277,222.179333,210.678541,5.176355444365304,0,0,0
+50000,0.1,5,planner_default,30,24.842834,24.257917,2.3544697034162883,30.2445,27.599875,8.744151829258206,151.410125,147.070417,2.8661940540634268,213.507792,213.261833,0.11519907432699186,0,0,0
+50000,0.1,5,planner_default_generic,30,464.400625,23.430666,94.9546437410587,570.493916,25.03425,95.61182875086085,586.129209,146.993125,74.92137864093377,800.502958,204.453625,74.45935421515331,0,0,0
+50000,0.1,5,hnsw_ef40,30,8.049958,8.250542,-2.491739708455612,11.958708,11.070166,7.430083584280169,140.929125,134.179834,4.789138512000269,203.598541,192.017959,5.687949404313276,30,30,3
+50000,0.1,5,hnsw_ef100,30,8.930209,9.20475,-3.074295349638525,13.05775,11.569167,11.39999617085639,141.886958,134.116333,5.4766309106436655,216.120333,193.831709,10.313062029198337,30,30,7
+50000,0.1,5,hnsw_ef200,30,10.78575,10.6655,1.1148969705398364,14.956333,14.081125,5.85175523973691,137.871042,136.798375,0.7780219721556871,210.239542,200.817708,4.481475706411113,30,30,12
+50000,0.1,5,iterative_relaxed,30,12.092958,12.344292,-2.0783500612505224,15.118416,16.997209,-12.427181524837005,139.802958,135.855625,2.8234974827928796,208.7085,194.682333,6.7204579593068745,30,30,15
+50000,0.1,5,iterative_strict,30,12.690542,12.847,-1.2328709049621274,17.190958,15.019,12.634304615251798,143.945667,135.417917,5.924283917486727,229.016583,196.722667,14.101125594036128,30,30,18
+50000,0.1,10,exact,30,27.656291,26.522125,4.100933129464108,40.099125,29.085209,27.466724024526723,160.693416,151.783417,5.544719392859274,250.182042,211.010458,15.657232504321792,0,0,0
+50000,0.1,10,planner_default,30,28.115583,26.651584,5.207073244755413,37.808792,29.573708,21.780870438812215,164.114416,150.73075,8.155082488304991,241.357042,207.877,13.871582831214843,0,0,0
+50000,0.1,10,planner_default_generic,30,480.472042,24.856542,94.82664133868585,597.380541,43.983,92.63735642838759,609.368833,149.160125,75.52219330521619,777.374083,209.5005,73.05023352572972,0,0,0
+50000,0.1,10,hnsw_ef40,30,8.078541,8.06475,0.17071151832984996,14.552875,9.283542,36.20819253927488,132.9595,130.06075,2.1801751661219986,197.1145,189.446833,3.8899558378505867,30,30,3
+50000,0.1,10,hnsw_ef100,30,9.303875,8.894167,4.40362752079107,13.1655,11.244625,14.590216854658012,137.244667,131.180625,4.418417219810808,221.512042,193.390375,12.69532199969517,30,30,7
+50000,0.1,10,hnsw_ef200,30,10.647875,10.55775,0.8464130166817364,14.777083,13.175541,10.838011805171552,138.325333,134.226958,2.962852075693181,198.641542,190.701167,3.9973385828831263,30,30,12
+50000,0.1,10,iterative_relaxed,30,15.837292,16.568625,-4.6177907182616895,23.2545,18.918708,18.64495904018578,151.478292,140.824958,7.032911356037736,215.356125,197.513167,8.285326456352232,30,30,21
+50000,0.1,10,iterative_strict,30,16.081042,16.789916,-4.408134746492184,23.687333,19.866292,16.131157526260967,147.873208,141.042833,4.619075417637523,212.223875,200.075666,5.724242383190855,30,30,25
+50000,0.1,20,exact,30,29.974584,30.791333,-2.724805121565662,34.680916,35.930958,-3.604408833953501,157.272209,157.919292,-0.41144141365751985,219.341333,215.984792,1.5302820285130632,0,0,0
+50000,0.1,20,planner_default,30,30.78725,30.718333,0.2238491583366459,36.18425,35.650709,1.474511700532689,157.770167,157.718583,0.032695661658259635,217.077209,220.441667,-1.5498900209279844,0,0,0
+50000,0.1,20,planner_default_generic,30,474.088791,28.758083,93.93403017621651,561.39025,30.865042,94.50203454726903,614.371708,151.001209,75.42184852691817,692.685167,212.300375,69.3511013207534,0,0,0
+50000,0.1,20,hnsw_ef40,30,7.491875,8.14175,-8.67439726370234,8.858167,9.448208,-6.660983022785633,132.12125,131.296167,0.624489247566161,192.23575,190.195667,1.0612401699475824,30,30,3
+50000,0.1,20,hnsw_ef100,30,8.362458,9.284042,-11.020491821902116,10.718833,11.64675,-8.65688456943028,131.951,134.073791,-1.6087721957393324,196.909084,195.192,0.8720186824900368,30,30,7
+50000,0.1,20,hnsw_ef200,30,10.107625,10.242166,-1.3310842062304318,13.147375,12.992541,1.1776799551241295,135.49625,133.9065,1.173279703312829,202.151,190.911958,5.559726145307226,30,30,12
+50000,0.1,20,iterative_relaxed,30,22.514708,22.247125,1.1884808810311835,27.124458,29.83625,-9.997589629256368,151.860041,150.880833,0.6448095190491882,219.177959,219.821834,-0.2937681338660556,30,30,27
+50000,0.1,20,iterative_strict,30,23.427167,23.615209,-0.802666408618675,31.773708,32.636542,-2.7155596696488793,150.53775,153.254042,-1.8043925859128427,230.899208,228.340708,1.108059235958913,30,30,28
+50000,0.01,5,exact,30,5.593083,5.787542,-3.4767765827898525,7.669792,7.234541,5.6748735819693685,18.938291,18.62375,1.660873201283043,26.03825,24.320458,6.597186830912226,0,0,0
+50000,0.01,5,planner_default,30,5.251625,5.047,3.8964130150191623,7.395208,6.315417,14.60122555038344,19.088666,17.795625,6.773867801972116,24.803,24.427625,1.5134257952667087,0,0,0
+50000,0.01,5,planner_default_generic,30,40.804958,4.559709,88.82560055569718,44.090208,6.576459,85.08408261535078,52.70575,16.388417,68.90582716307046,57.577208,25.014875,56.554206310246926,0,0,0
+50000,0.01,5,hnsw_ef40,30,6.617791,6.516167,1.5356181541544627,8.155125,8.01175,1.7580969022547257,19.020791,19.168542,-0.7767868328924887,27.098917,25.433167,6.146924616950556,30,30,0
+50000,0.01,5,hnsw_ef100,30,7.085125,7.187166,-1.4402145339708292,8.373708,9.218917,-10.093604888061522,19.496,19.572917,-0.3945270824784658,26.148667,31.091209,-18.9016977423744,30,30,2
+50000,0.01,5,hnsw_ef200,30,7.436083,8.202916,-10.312324378305085,9.613292,18.5625,-93.09202300315023,19.94875,21.749916,-9.02896672723854,26.426042,38.184083,-44.49414331514346,30,30,5
+50000,0.01,5,iterative_relaxed,30,41.656292,40.458667,2.8750158559480092,68.46075,53.624291,21.671481834481806,51.65825,53.474458,-3.515814027769032,82.875625,72.042208,13.071897798659132,30,30,16
+50000,0.01,5,iterative_strict,30,41.736208,42.593917,-2.0550717017703186,58.7895,52.616417,10.50031553253557,53.811458,54.350125,-1.0010265843382216,73.3225,82.17975,-12.079852705513304,30,30,19
+50000,0.01,10,exact,30,7.2575,7.110917,2.0197450912848853,9.25225,8.74925,5.436515442189739,21.075,21.717916,-3.0506097271648858,28.108666,28.2245,-0.41209355150471944,0,0,0
+50000,0.01,10,planner_default,30,7.263167,7.391625,-1.7686224204950842,11.905542,9.00525,24.36085648179646,21.702208,22.425667,-3.3335732474778688,37.675208,28.042375,25.568095071963498,0,0,0
+50000,0.01,10,planner_default_generic,30,43.209042,6.753667,84.36978306531304,48.96925,9.618417,80.3582513516135,56.310625,19.021625,66.22018491181726,65.478125,23.522375,64.0759795733308,0,0,0
+50000,0.01,10,hnsw_ef40,30,6.832375,6.916042,-1.2245668599864639,10.94925,16.368541,-49.4946320524237,19.912833,20.0005,-0.4402537800623333,29.673834,31.139667,-4.939816674852329,30,30,0
+50000,0.01,10,hnsw_ef100,30,7.053792,8.288167,-17.499452776605832,9.429417,13.775709,-46.09290266831979,19.413792,22.241625,-14.566103314592008,27.388208,32.444334,-18.46095954872257,30,30,2
+50000,0.01,10,hnsw_ef200,30,8.10825,8.530833,-5.211765794098596,12.585208,11.498125,8.63778334056934,20.928708,22.031792,-5.270674138126439,31.438,29.910375,4.859167249825054,30,30,5
+50000,0.01,10,iterative_relaxed,30,48.992167,48.669916,0.6577602497150236,73.208959,62.732833,14.309896142629203,64.468291,62.962042,2.336418379696147,85.159167,76.220959,10.495884723719765,30,30,21
+50000,0.01,10,iterative_strict,30,48.270459,49.521334,-2.59138824430901,55.367625,64.581666,-16.64156806436975,62.575625,65.040667,-3.9393006462180713,70.961334,80.076875,-12.845785847261565,30,30,24
+50000,0.01,20,exact,30,10.253167,7.204917,29.729838595235986,14.148334,12.052791,14.811235018907535,26.039125,21.851416,16.082372199526667,38.648291,30.307333,21.581699434006023,0,0,0
+50000,0.01,20,planner_default,30,7.7645,6.209041,20.032957692060016,11.456583,8.846708,22.780570786245782,23.02975,20.653208,10.31944332873783,31.691709,27.576542,12.984995539369617,0,0,0
+50000,0.01,20,planner_default_generic,30,46.787542,5.507834,88.22799026287808,52.707167,7.990209,84.84037474448209,59.863042,16.679333,72.13751182240287,66.559916,24.604291,63.03437191837802,0,0,0
+50000,0.01,20,hnsw_ef40,30,6.913,6.775458,1.9896137711557909,8.261584,8.337083,-0.913856229023401,19.78225,19.370083,2.083519316559037,24.835667,25.477125,-2.582809634224843,30,30,0
+50000,0.01,20,hnsw_ef100,30,7.250416,6.934875,4.352039938122178,8.92525,8.337458,6.58572028794712,20.306084,19.433416,4.297569142331911,26.594583,24.902125,6.363920050936684,30,30,2
+50000,0.01,20,hnsw_ef200,30,7.436667,7.7565,-4.300757315071389,11.1885,12.425667,-11.057487598873855,19.94075,20.693041,-3.772631420583476,26.666,29.749333,-11.562787819695492,30,30,5
+50000,0.01,20,iterative_relaxed,30,56.124167,54.417708,3.0405065967393377,70.719291,82.304958,-16.382611924092963,72.862291,71.502584,1.8661326474074227,84.718333,109.41625,-29.15297802188814,30,30,25
+50000,0.01,20,iterative_strict,30,57.260666,56.24425,1.7750684213138554,72.858541,85.793041,-17.75289461258907,73.847625,73.696583,0.20453196700637252,88.272792,92.093459,-4.328249864352314,30,30,28
+50000,0.001,5,exact,30,3.923375,4.16425,-6.139484499952208,5.384709,6.18825,-14.922644844874627,8.466542,9.019167,-6.527163037754953,12.156458,13.986667,-15.055446249228188,0,0,0
+50000,0.001,5,planner_default,30,3.864167,4.475,-15.807624256405054,5.080917,6.137667,-20.798410995495498,8.133333,10.231292,-25.79457892600733,11.453042,13.253125,-15.717073245693161,0,0,0
+50000,0.001,5,planner_default_generic,30,6.93875,5.0505,27.21311475409835,7.940291,6.490208,18.262340763077827,9.884875,8.727916,11.704336170158944,12.459084,10.070625,19.17042215944608,0,0,0
+50000,0.001,5,hnsw_ef40,30,6.229875,6.5675,-5.419450631031924,7.70425,8.931667,-15.931687055845787,10.470167,10.808917,-3.2353829695361997,13.443625,14.614542,-8.70983086779049,30,30,0
+50000,0.001,5,hnsw_ef100,30,6.189542,6.731084,-8.749306491498073,7.900166,10.277708,-30.09483598192748,10.467875,10.946833,-4.5755036241835185,13.491292,16.684709,-23.67020890215705,30,30,0
+50000,0.001,5,hnsw_ef200,30,6.643041,7.313542,-10.093284084803928,8.034417,9.484417,-18.04735800992158,10.915125,11.488583,-5.253792329451111,13.652584,14.931042,-9.364219989417393,30,30,1
+50000,0.001,5,iterative_relaxed,30,49.534791,50.756292,-2.465945601748887,78.147583,67.455625,13.68175136011564,55.116917,57.024375,-3.460748720760267,87.850292,70.2045,20.086207567756293,30,30,23
+50000,0.001,5,iterative_strict,30,48.003584,49.604333,-3.3346447631910165,53.490416,60.309459,-12.748158473846965,53.884416,55.598292,-3.18065245432,61.700083,70.601208,-14.426439264271332,30,30,27
+50000,0.001,10,exact,30,4.287959,4.167125,2.8179840338958333,5.940875,6.638833,-11.748404065057754,9.445625,9.036625,4.330046979421678,13.776458,13.575125,1.461427893875189,0,0,0
+50000,0.001,10,planner_default,30,3.591541,3.090709,13.944766327323006,5.494375,5.125875,6.706859287908089,9.066,8.07275,10.955768806529907,12.006334,11.287625,5.986082013044118,0,0,0
+50000,0.001,10,planner_default_generic,30,7.866542,3.667125,53.383265480563125,8.883167,5.7295,35.50160657792429,10.896542,6.64525,39.015056336221164,12.463625,9.30125,25.372834949703645,0,0,0
+50000,0.001,10,hnsw_ef40,30,5.998708,6.339166,-5.675522129098464,7.170625,8.328958,-16.153863854266536,10.268458,10.5565,-2.8051144582760044,12.913209,14.1955,-9.930072377826448,30,30,0
+50000,0.001,10,hnsw_ef100,30,6.18675,6.7325,-8.821271265203864,7.765459,8.925625,-14.940082743338163,10.319209,10.84375,-5.08315123765784,12.798791,14.439375,-12.818273225963301,30,30,0
+50000,0.001,10,hnsw_ef200,30,6.581083,7.124,-8.249660428230431,8.224041,8.967375,-9.038549297115624,10.647,11.036334,-3.6567483798253018,13.14325,14.299541,-8.797603332509079,30,30,1
+50000,0.001,10,iterative_relaxed,30,50.086042,49.838166,0.4949003556719415,71.956417,60.833209,15.458257183650492,56.070166,56.800041,-1.3017172091125961,84.618916,66.936833,20.896135091118403,30,30,25
+50000,0.001,10,iterative_strict,30,48.381333,49.261208,-1.8186249643018424,70.138084,59.511917,15.150352553115093,55.068291,55.067792,0.000906147604989071,66.323125,63.279958,4.588395073362427,30,30,27
+50000,0.001,20,exact,30,4.121875,3.834875,6.962850644427605,6.11725,5.9895,2.0883566962278914,9.324708,8.756917,6.089102200304822,12.957333,11.864208,8.436342571422689,0,0,0
+50000,0.001,20,planner_default,30,4.509417,4.132667,8.354738539372171,6.727791,6.434917,4.353197059777859,9.695834,9.170209,5.421142729960103,14.543167,13.358959,8.142710593916716,0,0,0
+50000,0.001,20,planner_default_generic,30,8.243209,2.951416,64.19578831496327,10.982375,5.590333,49.09723079024345,11.282,6.782,39.88654493884063,16.613084,9.7685,41.19996022412215,0,0,0
+50000,0.001,20,hnsw_ef40,30,6.086583,6.654916,-9.337472273030697,8.330875,9.752041,-17.05902441220159,10.65625,11.036459,-3.5679436950146695,12.457084,14.653916,-17.63520258834251,30,30,0
+50000,0.001,20,hnsw_ef100,30,6.254583,6.724417,-7.511835721102424,7.517833,8.72375,-16.040752700944545,10.4125,10.629541,-2.08442737094838,13.416167,13.715334,-2.229899195500478,30,30,0
+50000,0.001,20,hnsw_ef200,30,6.572958,7.647666,-16.35044678514605,7.849167,9.693709,-23.499843996184573,11.088458,11.778792,-6.22569883026116,13.651792,15.649375,-14.632386722563592,30,30,1
+50000,0.001,20,iterative_relaxed,30,48.980583,51.472916,-5.088410237991645,53.874625,75.506625,-40.15248365997907,54.910417,57.670583,-5.026670986672707,62.776709,96.20425,-53.24831698329393,30,30,25
+50000,0.001,20,iterative_strict,30,48.364875,53.060459,-9.708665638027606,51.982917,81.853708,-57.4627064502748,54.253042,60.847,-12.154079765702356,59.072,110.290375,-86.70499559859154,30,30,27
diff --git a/docs/test-results/evidence/issue-549/full-comparison.md b/docs/test-results/evidence/issue-549/full-comparison.md
new file mode 100644
index 00000000..5a4763f8
--- /dev/null
+++ b/docs/test-results/evidence/issue-549/full-comparison.md
@@ -0,0 +1,194 @@
+| 규모 | 허용 비율 | topK | 방식 | 벡터 p50 전 → 후 (ms) | 개선율 | 벡터 p95 전 → 후 (ms) | 개선율 | 하이브리드 p50 전 → 후 (ms) | 개선율 | HNSW 사용 전/후 | 결과 다른 질문 |
+|---|---:|---:|---|---|---:|---|---:|---|---:|---|---:|
+| real | 100% | 5 | Exact | 15.5 → 17.3 | -11.2% | 18.7 → 24.8 | -32.7% | 98.4 → 102.6 | -4.3% | 0/0 | 0/30 |
+| real | 100% | 5 | 현재 계획 Custom | 15.9 → 16.2 | -1.7% | 19.1 → 19.0 | +0.6% | 100.3 → 98.2 | +2.1% | 0/0 | 0/30 |
+| real | 100% | 5 | 현재 계획 Generic | 307.1 → 15.3 | +95.0% | 374.9 → 18.9 | +95.0% | 393.7 → 97.8 | +75.2% | 0/0 | 0/30 |
+| real | 100% | 5 | HNSW 강제 ef40 | 5.0 → 4.9 | +2.2% | 7.0 → 7.5 | -6.1% | 85.0 → 84.9 | +0.1% | 30/30 | 5/30 |
+| real | 100% | 5 | HNSW 강제 ef100 | 4.9 → 5.4 | -10.1% | 6.2 → 7.5 | -20.2% | 86.3 → 87.1 | -1.0% | 30/30 | 1/30 |
+| real | 100% | 5 | HNSW 강제 ef200 | 5.5 → 6.2 | -12.6% | 7.2 → 11.9 | -65.8% | 86.6 → 94.4 | -9.1% | 30/30 | 0/30 |
+| real | 100% | 5 | iterative relaxed | 5.0 → 5.2 | -3.8% | 6.9 → 8.4 | -21.5% | 87.9 → 88.8 | -1.1% | 30/30 | 5/30 |
+| real | 100% | 5 | iterative strict | 4.7 → 5.1 | -7.9% | 6.3 → 8.0 | -27.7% | 83.9 → 88.5 | -5.5% | 30/30 | 5/30 |
+| real | 100% | 10 | Exact | 18.0 → 18.2 | -1.0% | 23.8 → 19.7 | +17.4% | 100.6 → 101.0 | -0.4% | 0/0 | 0/30 |
+| real | 100% | 10 | 현재 계획 Custom | 17.8 → 18.0 | -1.5% | 23.9 → 21.4 | +10.4% | 102.1 → 100.1 | +2.0% | 0/0 | 0/30 |
+| real | 100% | 10 | 현재 계획 Generic | 300.1 → 17.8 | +94.1% | 306.3 → 21.5 | +93.0% | 382.7 → 101.9 | +73.4% | 0/0 | 0/30 |
+| real | 100% | 10 | HNSW 강제 ef40 | 6.4 → 7.2 | -12.3% | 8.4 → 9.0 | -7.2% | 87.5 → 90.5 | -3.5% | 30/30 | 8/30 |
+| real | 100% | 10 | HNSW 강제 ef100 | 6.9 → 7.8 | -12.4% | 8.8 → 12.4 | -41.2% | 90.5 → 93.7 | -3.6% | 30/30 | 1/30 |
+| real | 100% | 10 | HNSW 강제 ef200 | 7.1 → 7.4 | -4.1% | 9.0 → 9.2 | -2.2% | 89.1 → 90.1 | -1.1% | 30/30 | 0/30 |
+| real | 100% | 10 | iterative relaxed | 6.3 → 6.7 | -6.4% | 8.1 → 9.6 | -17.2% | 88.2 → 87.7 | +0.5% | 30/30 | 8/30 |
+| real | 100% | 10 | iterative strict | 6.4 → 6.6 | -2.9% | 7.6 → 8.6 | -12.8% | 86.9 → 87.3 | -0.5% | 30/30 | 8/30 |
+| real | 100% | 20 | Exact | 20.3 → 21.3 | -4.7% | 22.7 → 28.2 | -24.4% | 102.3 → 105.9 | -3.6% | 0/0 | 0/30 |
+| real | 100% | 20 | 현재 계획 Custom | 20.5 → 21.3 | -4.1% | 22.9 → 24.0 | -4.7% | 103.5 → 104.2 | -0.7% | 0/0 | 0/30 |
+| real | 100% | 20 | 현재 계획 Generic | 301.2 → 21.4 | +92.9% | 312.1 → 26.0 | +91.7% | 383.6 → 102.0 | +73.4% | 0/0 | 0/30 |
+| real | 100% | 20 | HNSW 강제 ef40 | 6.6 → 6.9 | -5.6% | 7.9 → 8.6 | -7.6% | 87.7 → 89.8 | -2.3% | 30/30 | 7/30 |
+| real | 100% | 20 | HNSW 강제 ef100 | 10.6 → 10.3 | +2.4% | 12.6 → 12.3 | +2.6% | 93.1 → 93.8 | -0.8% | 30/30 | 5/30 |
+| real | 100% | 20 | HNSW 강제 ef200 | 11.3 → 11.2 | +0.6% | 13.2 → 13.3 | -0.5% | 93.1 → 96.2 | -3.3% | 30/30 | 0/30 |
+| real | 100% | 20 | iterative relaxed | 10.4 → 10.6 | -2.5% | 14.2 → 12.6 | +10.9% | 94.8 → 95.1 | -0.3% | 30/30 | 8/30 |
+| real | 100% | 20 | iterative strict | 10.6 → 11.1 | -4.3% | 12.1 → 14.2 | -16.9% | 93.5 → 93.9 | -0.5% | 30/30 | 9/30 |
+| real | 10% | 5 | Exact | 4.0 → 4.4 | -9.1% | 5.7 → 7.1 | -24.4% | 12.8 → 13.6 | -6.6% | 0/0 | 0/30 |
+| real | 10% | 5 | 현재 계획 Custom | 4.6 → 4.4 | +4.1% | 6.4 → 5.9 | +7.0% | 14.2 → 14.0 | +1.5% | 0/0 | 0/30 |
+| real | 10% | 5 | 현재 계획 Generic | 28.9 → 4.3 | +85.0% | 30.8 → 6.0 | +80.5% | 38.1 → 13.1 | +65.7% | 0/0 | 0/30 |
+| real | 10% | 5 | HNSW 강제 ef40 | 2.0 → 2.1 | -4.8% | 4.6 → 3.8 | +16.5% | 10.8 → 10.3 | +4.7% | 30/30 | 2/30 |
+| real | 10% | 5 | HNSW 강제 ef100 | 3.2 → 3.1 | +5.0% | 5.1 → 5.2 | -1.9% | 12.3 → 11.8 | +4.0% | 30/30 | 2/30 |
+| real | 10% | 5 | HNSW 강제 ef200 | 4.1 → 4.4 | -8.3% | 6.6 → 7.5 | -14.7% | 13.0 → 13.4 | -3.3% | 30/30 | 0/30 |
+| real | 10% | 5 | iterative relaxed | 4.4 → 4.8 | -10.7% | 5.6 → 7.6 | -35.5% | 12.9 → 13.8 | -7.6% | 30/30 | 0/30 |
+| real | 10% | 5 | iterative strict | 5.4 → 5.4 | -0.5% | 7.0 → 7.9 | -12.7% | 15.1 → 15.1 | -0.3% | 30/30 | 3/30 |
+| real | 10% | 10 | Exact | 5.5 → 5.6 | -2.4% | 6.8 → 7.1 | -4.6% | 14.8 → 15.2 | -2.5% | 0/0 | 0/30 |
+| real | 10% | 10 | 현재 계획 Custom | 5.6 → 5.5 | +2.0% | 7.2 → 6.7 | +6.8% | 15.3 → 14.8 | +3.8% | 0/0 | 0/30 |
+| real | 10% | 10 | 현재 계획 Generic | 31.8 → 5.6 | +82.4% | 33.5 → 7.9 | +76.5% | 41.7 → 14.7 | +64.7% | 0/0 | 0/30 |
+| real | 10% | 10 | HNSW 강제 ef40 | 2.2 → 2.1 | +5.4% | 4.4 → 4.0 | +9.7% | 10.9 → 10.4 | +4.9% | 30/30 | 2/30 |
+| real | 10% | 10 | HNSW 강제 ef100 | 3.1 → 2.9 | +6.1% | 5.2 → 4.9 | +6.1% | 12.0 → 12.3 | -2.8% | 30/30 | 2/30 |
+| real | 10% | 10 | HNSW 강제 ef200 | 4.6 → 4.6 | -1.1% | 6.4 → 7.2 | -11.4% | 14.0 → 14.4 | -3.1% | 30/30 | 0/30 |
+| real | 10% | 10 | iterative relaxed | 6.8 → 8.0 | -18.0% | 8.6 → 11.6 | -34.7% | 15.3 → 18.6 | -21.5% | 30/30 | 0/30 |
+| real | 10% | 10 | iterative strict | 7.3 → 7.5 | -3.7% | 10.9 → 9.1 | +16.2% | 16.8 → 17.3 | -2.7% | 30/30 | 3/30 |
+| real | 10% | 20 | Exact | 5.0 → 4.7 | +5.6% | 6.2 → 8.0 | -28.5% | 14.2 → 13.7 | +3.0% | 0/0 | 0/30 |
+| real | 10% | 20 | 현재 계획 Custom | 4.8 → 4.9 | -1.7% | 6.8 → 6.7 | +1.1% | 13.5 → 14.6 | -8.4% | 0/0 | 0/30 |
+| real | 10% | 20 | 현재 계획 Generic | 35.7 → 4.5 | +87.4% | 37.9 → 6.5 | +82.9% | 47.3 → 12.7 | +73.2% | 0/0 | 0/30 |
+| real | 10% | 20 | HNSW 강제 ef40 | 2.3 → 2.2 | +4.4% | 4.4 → 4.2 | +6.2% | 10.8 → 11.2 | -3.3% | 30/30 | 2/30 |
+| real | 10% | 20 | HNSW 강제 ef100 | 3.2 → 3.3 | -3.5% | 4.9 → 5.1 | -3.9% | 12.4 → 12.2 | +1.8% | 30/30 | 2/30 |
+| real | 10% | 20 | HNSW 강제 ef200 | 4.5 → 4.7 | -5.0% | 6.2 → 7.0 | -12.4% | 13.7 → 14.4 | -4.7% | 30/30 | 0/30 |
+| real | 10% | 20 | iterative relaxed | 7.8 → 8.9 | -13.3% | 11.0 → 14.3 | -29.7% | 17.7 → 18.5 | -4.2% | 30/30 | 0/30 |
+| real | 10% | 20 | iterative strict | 7.7 → 9.0 | -17.2% | 10.2 → 14.4 | -40.9% | 16.7 → 20.6 | -23.6% | 30/30 | 3/30 |
+| real | 1% | 5 | Exact | 2.7 → 3.7 | -37.4% | 4.7 → 5.2 | -11.6% | 5.4 → 7.2 | -32.7% | 0/0 | 0/30 |
+| real | 1% | 5 | 현재 계획 Custom | 3.3 → 3.1 | +3.3% | 5.0 → 4.2 | +15.9% | 6.3 → 6.1 | +3.4% | 0/0 | 0/30 |
+| real | 1% | 5 | 현재 계획 Generic | 5.3 → 3.3 | +36.8% | 8.2 → 4.3 | +47.2% | 7.2 → 5.7 | +20.5% | 0/0 | 0/30 |
+| real | 1% | 5 | HNSW 강제 ef40 | 1.5 → 1.6 | -4.1% | 2.5 → 2.7 | -7.9% | 3.9 → 4.0 | -4.6% | 30/30 | 0/30 |
+| real | 1% | 5 | HNSW 강제 ef100 | 1.7 → 1.9 | -9.1% | 3.0 → 3.6 | -17.7% | 3.8 → 4.2 | -8.6% | 30/30 | 0/30 |
+| real | 1% | 5 | HNSW 강제 ef200 | 2.2 → 2.6 | -17.7% | 3.7 → 4.2 | -11.0% | 4.5 → 4.9 | -8.3% | 30/30 | 0/30 |
+| real | 1% | 5 | iterative relaxed | 8.9 → 8.8 | +0.6% | 11.1 → 10.8 | +2.6% | 11.6 → 11.4 | +1.4% | 30/30 | 0/30 |
+| real | 1% | 5 | iterative strict | 9.1 → 9.3 | -1.5% | 10.7 → 11.4 | -6.3% | 12.1 → 12.2 | -0.5% | 30/30 | 0/30 |
+| real | 1% | 10 | Exact | 3.6 → 3.3 | +9.3% | 4.8 → 5.3 | -9.7% | 6.7 → 6.6 | +2.4% | 0/0 | 0/30 |
+| real | 1% | 10 | 현재 계획 Custom | 3.4 → 2.7 | +21.4% | 5.0 → 5.3 | -5.2% | 5.6 → 5.6 | +0.8% | 0/0 | 0/30 |
+| real | 1% | 10 | 현재 계획 Generic | 7.1 → 2.6 | +62.9% | 10.5 → 4.4 | +58.4% | 9.6 → 4.3 | +55.2% | 0/0 | 0/30 |
+| real | 1% | 10 | HNSW 강제 ef40 | 1.7 → 1.7 | -2.9% | 2.7 → 2.6 | +2.9% | 4.2 → 4.3 | -2.4% | 30/30 | 0/30 |
+| real | 1% | 10 | HNSW 강제 ef100 | 1.8 → 2.0 | -8.6% | 3.0 → 3.0 | -0.4% | 4.3 → 4.6 | -5.3% | 30/30 | 0/30 |
+| real | 1% | 10 | HNSW 강제 ef200 | 2.3 → 2.7 | -21.3% | 3.5 → 4.5 | -31.4% | 4.7 → 5.0 | -5.6% | 30/30 | 0/30 |
+| real | 1% | 10 | iterative relaxed | 13.1 → 13.4 | -2.2% | 14.6 → 17.2 | -17.8% | 15.8 → 16.4 | -3.9% | 30/30 | 0/30 |
+| real | 1% | 10 | iterative strict | 12.8 → 13.1 | -3.0% | 14.5 → 17.1 | -17.6% | 15.9 → 16.8 | -5.6% | 30/30 | 0/30 |
+| real | 1% | 20 | Exact | 3.8 → 3.6 | +5.9% | 5.0 → 5.1 | -2.4% | 7.0 → 6.5 | +6.7% | 0/0 | 0/30 |
+| real | 1% | 20 | 현재 계획 Custom | 2.4 → 4.2 | -79.2% | 4.1 → 5.8 | -39.9% | 5.4 → 7.2 | -34.8% | 0/0 | 0/30 |
+| real | 1% | 20 | 현재 계획 Generic | 4.9 → 4.0 | +19.4% | 8.2 → 5.3 | +36.0% | 6.7 → 6.4 | +4.3% | 0/0 | 0/30 |
+| real | 1% | 20 | HNSW 강제 ef40 | 1.5 → 2.2 | -44.0% | 3.3 → 4.3 | -33.3% | 4.0 → 5.0 | -26.0% | 30/30 | 0/30 |
+| real | 1% | 20 | HNSW 강제 ef100 | 1.8 → 2.4 | -31.4% | 3.2 → 4.7 | -47.9% | 4.2 → 5.1 | -22.6% | 30/30 | 0/30 |
+| real | 1% | 20 | HNSW 강제 ef200 | 2.6 → 3.4 | -32.2% | 4.4 → 5.9 | -36.4% | 5.1 → 6.1 | -19.5% | 30/30 | 0/30 |
+| real | 1% | 20 | iterative relaxed | 12.1 → 14.1 | -17.2% | 26.1 → 17.0 | +35.0% | 14.7 → 17.6 | -19.8% | 30/30 | 0/30 |
+| real | 1% | 20 | iterative strict | 13.1 → 13.9 | -5.9% | 15.1 → 15.6 | -3.5% | 16.8 → 17.3 | -2.9% | 30/30 | 0/30 |
+| real | 0.1% | 5 | Exact | 2.2 → 2.4 | -9.3% | 3.3 → 6.3 | -89.1% | 3.9 → 3.9 | -0.7% | 0/0 | 0/30 |
+| real | 0.1% | 5 | 현재 계획 Custom | 2.1 → 1.6 | +23.7% | 3.4 → 3.3 | +2.5% | 3.7 → 2.6 | +30.0% | 0/0 | 0/30 |
+| real | 0.1% | 5 | 현재 계획 Generic | 3.6 → 0.8 | +78.1% | 4.4 → 1.5 | +65.1% | 4.7 → 1.6 | +64.9% | 0/0 | 0/30 |
+| real | 0.1% | 5 | HNSW 강제 ef40 | 1.5 → 1.7 | -9.1% | 3.1 → 2.9 | +6.8% | 2.8 → 3.0 | -8.2% | 30/30 | 0/30 |
+| real | 0.1% | 5 | HNSW 강제 ef100 | 1.7 → 1.8 | -6.3% | 2.8 → 3.2 | -14.4% | 3.0 → 3.0 | +0.8% | 30/30 | 0/30 |
+| real | 0.1% | 5 | HNSW 강제 ef200 | 2.1 → 2.5 | -17.9% | 3.6 → 3.8 | -4.9% | 3.3 → 3.8 | -16.4% | 30/30 | 0/30 |
+| real | 0.1% | 5 | iterative relaxed | 9.6 → 9.8 | -2.1% | 11.1 → 11.5 | -3.6% | 10.7 → 11.1 | -3.6% | 30/30 | 0/30 |
+| real | 0.1% | 5 | iterative strict | 9.8 → 10.3 | -4.8% | 11.3 → 12.0 | -6.8% | 11.1 → 12.0 | -8.5% | 30/30 | 0/30 |
+| real | 0.1% | 10 | Exact | 2.2 → 2.5 | -15.7% | 3.2 → 4.4 | -38.1% | 3.7 → 4.5 | -20.5% | 0/0 | 0/30 |
+| real | 0.1% | 10 | 현재 계획 Custom | 1.8 → 2.3 | -25.3% | 3.3 → 4.0 | -20.5% | 3.1 → 3.8 | -24.2% | 0/0 | 0/30 |
+| real | 0.1% | 10 | 현재 계획 Generic | 4.2 → 1.7 | +58.5% | 5.5 → 3.2 | +42.3% | 4.7 → 3.4 | +27.8% | 0/0 | 0/30 |
+| real | 0.1% | 10 | HNSW 강제 ef40 | 1.5 → 1.5 | -3.4% | 3.0 → 2.9 | +2.7% | 2.8 → 3.0 | -6.2% | 30/30 | 0/30 |
+| real | 0.1% | 10 | HNSW 강제 ef100 | 1.7 → 2.0 | -12.0% | 3.1 → 3.2 | -3.8% | 2.8 → 3.5 | -21.6% | 30/30 | 0/30 |
+| real | 0.1% | 10 | HNSW 강제 ef200 | 2.2 → 2.6 | -18.0% | 3.4 → 4.0 | -17.5% | 3.4 → 4.0 | -16.3% | 30/30 | 0/30 |
+| real | 0.1% | 10 | iterative relaxed | 9.6 → 10.4 | -8.4% | 11.9 → 12.4 | -3.8% | 11.1 → 12.1 | -9.0% | 30/30 | 0/30 |
+| real | 0.1% | 10 | iterative strict | 9.4 → 10.2 | -8.0% | 10.9 → 11.7 | -6.6% | 10.9 → 11.5 | -5.9% | 30/30 | 0/30 |
+| real | 0.1% | 20 | Exact | 2.2 → 2.5 | -16.8% | 3.9 → 4.0 | -2.9% | 3.6 → 4.0 | -12.0% | 0/0 | 0/30 |
+| real | 0.1% | 20 | 현재 계획 Custom | 2.1 → 2.3 | -12.7% | 3.6 → 3.8 | -6.4% | 3.5 → 4.2 | -17.4% | 0/0 | 0/30 |
+| real | 0.1% | 20 | 현재 계획 Generic | 2.8 → 2.3 | +17.3% | 4.2 → 3.8 | +10.8% | 3.7 → 3.7 | +0.2% | 0/0 | 0/30 |
+| real | 0.1% | 20 | HNSW 강제 ef40 | 1.5 → 1.7 | -15.0% | 3.0 → 3.2 | -6.1% | 3.0 → 2.8 | +4.0% | 30/30 | 0/30 |
+| real | 0.1% | 20 | HNSW 강제 ef100 | 1.8 → 1.9 | -6.0% | 3.0 → 3.5 | -16.2% | 3.1 → 3.2 | -4.1% | 30/30 | 0/30 |
+| real | 0.1% | 20 | HNSW 강제 ef200 | 2.4 → 2.7 | -15.7% | 3.9 → 4.4 | -12.0% | 3.8 → 4.2 | -9.6% | 30/30 | 0/30 |
+| real | 0.1% | 20 | iterative relaxed | 9.9 → 9.6 | +2.8% | 11.8 → 12.4 | -5.0% | 11.2 → 11.7 | -4.2% | 30/30 | 0/30 |
+| real | 0.1% | 20 | iterative strict | 10.1 → 10.0 | +0.5% | 12.2 → 13.9 | -13.6% | 11.5 → 11.7 | -2.0% | 30/30 | 0/30 |
+| 50000 | 100% | 5 | Exact | 209.1 → 214.9 | -2.8% | 258.5 → 290.6 | -12.4% | 1,305.0 → 1,340.4 | -2.7% | 0/0 | 0/30 |
+| 50000 | 100% | 5 | 현재 계획 Custom | 223.2 → 221.0 | +1.0% | 282.2 → 380.9 | -35.0% | 1,352.7 → 1,350.3 | +0.2% | 0/0 | 0/30 |
+| 50000 | 100% | 5 | 현재 계획 Generic | 4,280.9 → 204.4 | +95.2% | 4,793.3 → 245.8 | +94.9% | 5,479.4 → 1,305.7 | +76.2% | 0/0 | 0/30 |
+| 50000 | 100% | 5 | HNSW 강제 ef40 | 14.2 → 14.8 | -4.0% | 19.0 → 19.2 | -1.0% | 1,154.4 → 1,158.1 | -0.3% | 30/30 | 8/30 |
+| 50000 | 100% | 5 | HNSW 강제 ef100 | 14.5 → 15.1 | -3.8% | 17.1 → 19.3 | -12.5% | 1,119.7 → 1,130.8 | -1.0% | 30/30 | 9/30 |
+| 50000 | 100% | 5 | HNSW 강제 ef200 | 14.6 → 15.3 | -4.5% | 17.4 → 23.5 | -35.4% | 1,147.5 → 1,141.2 | +0.6% | 30/30 | 10/30 |
+| 50000 | 100% | 5 | iterative relaxed | 14.0 → 15.0 | -6.7% | 16.6 → 25.4 | -53.3% | 1,088.3 → 1,194.4 | -9.7% | 30/30 | 8/30 |
+| 50000 | 100% | 5 | iterative strict | 13.7 → 14.7 | -7.2% | 16.1 → 17.5 | -8.9% | 1,088.2 → 1,125.2 | -3.4% | 30/30 | 8/30 |
+| 50000 | 100% | 10 | Exact | 212.9 → 217.4 | -2.1% | 268.3 → 327.3 | -22.0% | 1,328.3 → 1,371.1 | -3.2% | 0/0 | 0/30 |
+| 50000 | 100% | 10 | 현재 계획 Custom | 204.2 → 208.9 | -2.3% | 264.7 → 286.7 | -8.3% | 1,338.4 → 1,328.1 | +0.8% | 0/0 | 0/30 |
+| 50000 | 100% | 10 | 현재 계획 Generic | 4,226.3 → 207.4 | +95.1% | 4,674.7 → 223.8 | +95.2% | 5,444.6 → 1,321.6 | +75.7% | 0/0 | 0/30 |
+| 50000 | 100% | 10 | HNSW 강제 ef40 | 15.7 → 16.9 | -7.7% | 20.4 → 20.4 | -0.0% | 1,128.4 → 1,126.8 | +0.1% | 30/30 | 11/30 |
+| 50000 | 100% | 10 | HNSW 강제 ef100 | 16.3 → 17.4 | -6.8% | 18.8 → 26.1 | -38.8% | 1,120.3 → 1,160.7 | -3.6% | 30/30 | 12/30 |
+| 50000 | 100% | 10 | HNSW 강제 ef200 | 17.0 → 17.4 | -2.2% | 23.7 → 25.9 | -9.3% | 1,137.4 → 1,150.4 | -1.1% | 30/30 | 12/30 |
+| 50000 | 100% | 10 | iterative relaxed | 15.9 → 15.9 | +0.2% | 18.8 → 22.9 | -21.4% | 1,123.1 → 1,129.2 | -0.5% | 30/30 | 11/30 |
+| 50000 | 100% | 10 | iterative strict | 16.1 → 16.4 | -2.2% | 21.0 → 23.8 | -13.2% | 1,101.9 → 1,127.0 | -2.3% | 30/30 | 11/30 |
+| 50000 | 100% | 20 | Exact | 211.1 → 216.6 | -2.6% | 295.2 → 325.3 | -10.2% | 1,319.5 → 1,331.3 | -0.9% | 0/0 | 0/30 |
+| 50000 | 100% | 20 | 현재 계획 Custom | 213.1 → 221.9 | -4.1% | 276.7 → 292.8 | -5.8% | 1,332.7 → 1,363.0 | -2.3% | 0/0 | 0/30 |
+| 50000 | 100% | 20 | 현재 계획 Generic | 4,340.2 → 210.1 | +95.2% | 4,562.3 → 219.1 | +95.2% | 5,587.0 → 1,311.8 | +76.5% | 0/0 | 0/30 |
+| 50000 | 100% | 20 | HNSW 강제 ef40 | 16.1 → 16.2 | -1.2% | 19.6 → 21.2 | -7.9% | 1,132.7 → 1,140.3 | -0.7% | 30/30 | 10/30 |
+| 50000 | 100% | 20 | HNSW 강제 ef100 | 18.5 → 19.8 | -7.1% | 21.3 → 22.7 | -6.5% | 1,099.0 → 1,150.1 | -4.7% | 30/30 | 19/30 |
+| 50000 | 100% | 20 | HNSW 강제 ef200 | 19.1 → 20.9 | -9.3% | 22.0 → 27.3 | -24.4% | 1,113.6 → 1,141.0 | -2.5% | 30/30 | 17/30 |
+| 50000 | 100% | 20 | iterative relaxed | 18.3 → 19.5 | -7.0% | 20.3 → 25.5 | -25.5% | 1,094.8 → 1,163.0 | -6.2% | 30/30 | 19/30 |
+| 50000 | 100% | 20 | iterative strict | 18.5 → 20.2 | -8.9% | 22.2 → 38.4 | -72.8% | 1,110.8 → 1,168.4 | -5.2% | 30/30 | 19/30 |
+| 50000 | 10% | 5 | Exact | 24.7 → 25.4 | -2.8% | 31.0 → 28.3 | +8.6% | 153.4 → 149.7 | +2.4% | 0/0 | 0/30 |
+| 50000 | 10% | 5 | 현재 계획 Custom | 24.8 → 24.3 | +2.4% | 30.2 → 27.6 | +8.7% | 151.4 → 147.1 | +2.9% | 0/0 | 0/30 |
+| 50000 | 10% | 5 | 현재 계획 Generic | 464.4 → 23.4 | +95.0% | 570.5 → 25.0 | +95.6% | 586.1 → 147.0 | +74.9% | 0/0 | 0/30 |
+| 50000 | 10% | 5 | HNSW 강제 ef40 | 8.0 → 8.3 | -2.5% | 12.0 → 11.1 | +7.4% | 140.9 → 134.2 | +4.8% | 30/30 | 3/30 |
+| 50000 | 10% | 5 | HNSW 강제 ef100 | 8.9 → 9.2 | -3.1% | 13.1 → 11.6 | +11.4% | 141.9 → 134.1 | +5.5% | 30/30 | 7/30 |
+| 50000 | 10% | 5 | HNSW 강제 ef200 | 10.8 → 10.7 | +1.1% | 15.0 → 14.1 | +5.9% | 137.9 → 136.8 | +0.8% | 30/30 | 12/30 |
+| 50000 | 10% | 5 | iterative relaxed | 12.1 → 12.3 | -2.1% | 15.1 → 17.0 | -12.4% | 139.8 → 135.9 | +2.8% | 30/30 | 15/30 |
+| 50000 | 10% | 5 | iterative strict | 12.7 → 12.8 | -1.2% | 17.2 → 15.0 | +12.6% | 143.9 → 135.4 | +5.9% | 30/30 | 18/30 |
+| 50000 | 10% | 10 | Exact | 27.7 → 26.5 | +4.1% | 40.1 → 29.1 | +27.5% | 160.7 → 151.8 | +5.5% | 0/0 | 0/30 |
+| 50000 | 10% | 10 | 현재 계획 Custom | 28.1 → 26.7 | +5.2% | 37.8 → 29.6 | +21.8% | 164.1 → 150.7 | +8.2% | 0/0 | 0/30 |
+| 50000 | 10% | 10 | 현재 계획 Generic | 480.5 → 24.9 | +94.8% | 597.4 → 44.0 | +92.6% | 609.4 → 149.2 | +75.5% | 0/0 | 0/30 |
+| 50000 | 10% | 10 | HNSW 강제 ef40 | 8.1 → 8.1 | +0.2% | 14.6 → 9.3 | +36.2% | 133.0 → 130.1 | +2.2% | 30/30 | 3/30 |
+| 50000 | 10% | 10 | HNSW 강제 ef100 | 9.3 → 8.9 | +4.4% | 13.2 → 11.2 | +14.6% | 137.2 → 131.2 | +4.4% | 30/30 | 7/30 |
+| 50000 | 10% | 10 | HNSW 강제 ef200 | 10.6 → 10.6 | +0.8% | 14.8 → 13.2 | +10.8% | 138.3 → 134.2 | +3.0% | 30/30 | 12/30 |
+| 50000 | 10% | 10 | iterative relaxed | 15.8 → 16.6 | -4.6% | 23.3 → 18.9 | +18.6% | 151.5 → 140.8 | +7.0% | 30/30 | 21/30 |
+| 50000 | 10% | 10 | iterative strict | 16.1 → 16.8 | -4.4% | 23.7 → 19.9 | +16.1% | 147.9 → 141.0 | +4.6% | 30/30 | 25/30 |
+| 50000 | 10% | 20 | Exact | 30.0 → 30.8 | -2.7% | 34.7 → 35.9 | -3.6% | 157.3 → 157.9 | -0.4% | 0/0 | 0/30 |
+| 50000 | 10% | 20 | 현재 계획 Custom | 30.8 → 30.7 | +0.2% | 36.2 → 35.7 | +1.5% | 157.8 → 157.7 | +0.0% | 0/0 | 0/30 |
+| 50000 | 10% | 20 | 현재 계획 Generic | 474.1 → 28.8 | +93.9% | 561.4 → 30.9 | +94.5% | 614.4 → 151.0 | +75.4% | 0/0 | 0/30 |
+| 50000 | 10% | 20 | HNSW 강제 ef40 | 7.5 → 8.1 | -8.7% | 8.9 → 9.4 | -6.7% | 132.1 → 131.3 | +0.6% | 30/30 | 3/30 |
+| 50000 | 10% | 20 | HNSW 강제 ef100 | 8.4 → 9.3 | -11.0% | 10.7 → 11.6 | -8.7% | 132.0 → 134.1 | -1.6% | 30/30 | 7/30 |
+| 50000 | 10% | 20 | HNSW 강제 ef200 | 10.1 → 10.2 | -1.3% | 13.1 → 13.0 | +1.2% | 135.5 → 133.9 | +1.2% | 30/30 | 12/30 |
+| 50000 | 10% | 20 | iterative relaxed | 22.5 → 22.2 | +1.2% | 27.1 → 29.8 | -10.0% | 151.9 → 150.9 | +0.6% | 30/30 | 27/30 |
+| 50000 | 10% | 20 | iterative strict | 23.4 → 23.6 | -0.8% | 31.8 → 32.6 | -2.7% | 150.5 → 153.3 | -1.8% | 30/30 | 28/30 |
+| 50000 | 1% | 5 | Exact | 5.6 → 5.8 | -3.5% | 7.7 → 7.2 | +5.7% | 18.9 → 18.6 | +1.7% | 0/0 | 0/30 |
+| 50000 | 1% | 5 | 현재 계획 Custom | 5.3 → 5.0 | +3.9% | 7.4 → 6.3 | +14.6% | 19.1 → 17.8 | +6.8% | 0/0 | 0/30 |
+| 50000 | 1% | 5 | 현재 계획 Generic | 40.8 → 4.6 | +88.8% | 44.1 → 6.6 | +85.1% | 52.7 → 16.4 | +68.9% | 0/0 | 0/30 |
+| 50000 | 1% | 5 | HNSW 강제 ef40 | 6.6 → 6.5 | +1.5% | 8.2 → 8.0 | +1.8% | 19.0 → 19.2 | -0.8% | 30/30 | 0/30 |
+| 50000 | 1% | 5 | HNSW 강제 ef100 | 7.1 → 7.2 | -1.4% | 8.4 → 9.2 | -10.1% | 19.5 → 19.6 | -0.4% | 30/30 | 2/30 |
+| 50000 | 1% | 5 | HNSW 강제 ef200 | 7.4 → 8.2 | -10.3% | 9.6 → 18.6 | -93.1% | 19.9 → 21.7 | -9.0% | 30/30 | 5/30 |
+| 50000 | 1% | 5 | iterative relaxed | 41.7 → 40.5 | +2.9% | 68.5 → 53.6 | +21.7% | 51.7 → 53.5 | -3.5% | 30/30 | 16/30 |
+| 50000 | 1% | 5 | iterative strict | 41.7 → 42.6 | -2.1% | 58.8 → 52.6 | +10.5% | 53.8 → 54.4 | -1.0% | 30/30 | 19/30 |
+| 50000 | 1% | 10 | Exact | 7.3 → 7.1 | +2.0% | 9.3 → 8.7 | +5.4% | 21.1 → 21.7 | -3.1% | 0/0 | 0/30 |
+| 50000 | 1% | 10 | 현재 계획 Custom | 7.3 → 7.4 | -1.8% | 11.9 → 9.0 | +24.4% | 21.7 → 22.4 | -3.3% | 0/0 | 0/30 |
+| 50000 | 1% | 10 | 현재 계획 Generic | 43.2 → 6.8 | +84.4% | 49.0 → 9.6 | +80.4% | 56.3 → 19.0 | +66.2% | 0/0 | 0/30 |
+| 50000 | 1% | 10 | HNSW 강제 ef40 | 6.8 → 6.9 | -1.2% | 10.9 → 16.4 | -49.5% | 19.9 → 20.0 | -0.4% | 30/30 | 0/30 |
+| 50000 | 1% | 10 | HNSW 강제 ef100 | 7.1 → 8.3 | -17.5% | 9.4 → 13.8 | -46.1% | 19.4 → 22.2 | -14.6% | 30/30 | 2/30 |
+| 50000 | 1% | 10 | HNSW 강제 ef200 | 8.1 → 8.5 | -5.2% | 12.6 → 11.5 | +8.6% | 20.9 → 22.0 | -5.3% | 30/30 | 5/30 |
+| 50000 | 1% | 10 | iterative relaxed | 49.0 → 48.7 | +0.7% | 73.2 → 62.7 | +14.3% | 64.5 → 63.0 | +2.3% | 30/30 | 21/30 |
+| 50000 | 1% | 10 | iterative strict | 48.3 → 49.5 | -2.6% | 55.4 → 64.6 | -16.6% | 62.6 → 65.0 | -3.9% | 30/30 | 24/30 |
+| 50000 | 1% | 20 | Exact | 10.3 → 7.2 | +29.7% | 14.1 → 12.1 | +14.8% | 26.0 → 21.9 | +16.1% | 0/0 | 0/30 |
+| 50000 | 1% | 20 | 현재 계획 Custom | 7.8 → 6.2 | +20.0% | 11.5 → 8.8 | +22.8% | 23.0 → 20.7 | +10.3% | 0/0 | 0/30 |
+| 50000 | 1% | 20 | 현재 계획 Generic | 46.8 → 5.5 | +88.2% | 52.7 → 8.0 | +84.8% | 59.9 → 16.7 | +72.1% | 0/0 | 0/30 |
+| 50000 | 1% | 20 | HNSW 강제 ef40 | 6.9 → 6.8 | +2.0% | 8.3 → 8.3 | -0.9% | 19.8 → 19.4 | +2.1% | 30/30 | 0/30 |
+| 50000 | 1% | 20 | HNSW 강제 ef100 | 7.3 → 6.9 | +4.4% | 8.9 → 8.3 | +6.6% | 20.3 → 19.4 | +4.3% | 30/30 | 2/30 |
+| 50000 | 1% | 20 | HNSW 강제 ef200 | 7.4 → 7.8 | -4.3% | 11.2 → 12.4 | -11.1% | 19.9 → 20.7 | -3.8% | 30/30 | 5/30 |
+| 50000 | 1% | 20 | iterative relaxed | 56.1 → 54.4 | +3.0% | 70.7 → 82.3 | -16.4% | 72.9 → 71.5 | +1.9% | 30/30 | 25/30 |
+| 50000 | 1% | 20 | iterative strict | 57.3 → 56.2 | +1.8% | 72.9 → 85.8 | -17.8% | 73.8 → 73.7 | +0.2% | 30/30 | 28/30 |
+| 50000 | 0.1% | 5 | Exact | 3.9 → 4.2 | -6.1% | 5.4 → 6.2 | -14.9% | 8.5 → 9.0 | -6.5% | 0/0 | 0/30 |
+| 50000 | 0.1% | 5 | 현재 계획 Custom | 3.9 → 4.5 | -15.8% | 5.1 → 6.1 | -20.8% | 8.1 → 10.2 | -25.8% | 0/0 | 0/30 |
+| 50000 | 0.1% | 5 | 현재 계획 Generic | 6.9 → 5.1 | +27.2% | 7.9 → 6.5 | +18.3% | 9.9 → 8.7 | +11.7% | 0/0 | 0/30 |
+| 50000 | 0.1% | 5 | HNSW 강제 ef40 | 6.2 → 6.6 | -5.4% | 7.7 → 8.9 | -15.9% | 10.5 → 10.8 | -3.2% | 30/30 | 0/30 |
+| 50000 | 0.1% | 5 | HNSW 강제 ef100 | 6.2 → 6.7 | -8.7% | 7.9 → 10.3 | -30.1% | 10.5 → 10.9 | -4.6% | 30/30 | 0/30 |
+| 50000 | 0.1% | 5 | HNSW 강제 ef200 | 6.6 → 7.3 | -10.1% | 8.0 → 9.5 | -18.0% | 10.9 → 11.5 | -5.3% | 30/30 | 1/30 |
+| 50000 | 0.1% | 5 | iterative relaxed | 49.5 → 50.8 | -2.5% | 78.1 → 67.5 | +13.7% | 55.1 → 57.0 | -3.5% | 30/30 | 23/30 |
+| 50000 | 0.1% | 5 | iterative strict | 48.0 → 49.6 | -3.3% | 53.5 → 60.3 | -12.7% | 53.9 → 55.6 | -3.2% | 30/30 | 27/30 |
+| 50000 | 0.1% | 10 | Exact | 4.3 → 4.2 | +2.8% | 5.9 → 6.6 | -11.7% | 9.4 → 9.0 | +4.3% | 0/0 | 0/30 |
+| 50000 | 0.1% | 10 | 현재 계획 Custom | 3.6 → 3.1 | +13.9% | 5.5 → 5.1 | +6.7% | 9.1 → 8.1 | +11.0% | 0/0 | 0/30 |
+| 50000 | 0.1% | 10 | 현재 계획 Generic | 7.9 → 3.7 | +53.4% | 8.9 → 5.7 | +35.5% | 10.9 → 6.6 | +39.0% | 0/0 | 0/30 |
+| 50000 | 0.1% | 10 | HNSW 강제 ef40 | 6.0 → 6.3 | -5.7% | 7.2 → 8.3 | -16.2% | 10.3 → 10.6 | -2.8% | 30/30 | 0/30 |
+| 50000 | 0.1% | 10 | HNSW 강제 ef100 | 6.2 → 6.7 | -8.8% | 7.8 → 8.9 | -14.9% | 10.3 → 10.8 | -5.1% | 30/30 | 0/30 |
+| 50000 | 0.1% | 10 | HNSW 강제 ef200 | 6.6 → 7.1 | -8.2% | 8.2 → 9.0 | -9.0% | 10.6 → 11.0 | -3.7% | 30/30 | 1/30 |
+| 50000 | 0.1% | 10 | iterative relaxed | 50.1 → 49.8 | +0.5% | 72.0 → 60.8 | +15.5% | 56.1 → 56.8 | -1.3% | 30/30 | 25/30 |
+| 50000 | 0.1% | 10 | iterative strict | 48.4 → 49.3 | -1.8% | 70.1 → 59.5 | +15.2% | 55.1 → 55.1 | +0.0% | 30/30 | 27/30 |
+| 50000 | 0.1% | 20 | Exact | 4.1 → 3.8 | +7.0% | 6.1 → 6.0 | +2.1% | 9.3 → 8.8 | +6.1% | 0/0 | 0/30 |
+| 50000 | 0.1% | 20 | 현재 계획 Custom | 4.5 → 4.1 | +8.4% | 6.7 → 6.4 | +4.4% | 9.7 → 9.2 | +5.4% | 0/0 | 0/30 |
+| 50000 | 0.1% | 20 | 현재 계획 Generic | 8.2 → 3.0 | +64.2% | 11.0 → 5.6 | +49.1% | 11.3 → 6.8 | +39.9% | 0/0 | 0/30 |
+| 50000 | 0.1% | 20 | HNSW 강제 ef40 | 6.1 → 6.7 | -9.3% | 8.3 → 9.8 | -17.1% | 10.7 → 11.0 | -3.6% | 30/30 | 0/30 |
+| 50000 | 0.1% | 20 | HNSW 강제 ef100 | 6.3 → 6.7 | -7.5% | 7.5 → 8.7 | -16.0% | 10.4 → 10.6 | -2.1% | 30/30 | 0/30 |
+| 50000 | 0.1% | 20 | HNSW 강제 ef200 | 6.6 → 7.6 | -16.4% | 7.8 → 9.7 | -23.5% | 11.1 → 11.8 | -6.2% | 30/30 | 1/30 |
+| 50000 | 0.1% | 20 | iterative relaxed | 49.0 → 51.5 | -5.1% | 53.9 → 75.5 | -40.2% | 54.9 → 57.7 | -5.0% | 30/30 | 25/30 |
+| 50000 | 0.1% | 20 | iterative strict | 48.4 → 53.1 | -9.7% | 52.0 → 81.9 | -57.5% | 54.3 → 60.8 | -12.2% | 30/30 | 27/30 |
diff --git a/docs/test-results/evidence/issue-549/full-improvement-percent.svg b/docs/test-results/evidence/issue-549/full-improvement-percent.svg
new file mode 100644
index 00000000..4b610454
--- /dev/null
+++ b/docs/test-results/evidence/issue-549/full-improvement-percent.svg
@@ -0,0 +1,51 @@
+
+
+
+Generic Plan 지연 개선율 (50,000 청크, topK 10)
+질문 30개 p50. 허용 청크가 많을수록 행마다 하던 변환이 많아 개선 폭이 크다.
+
+벡터 단독 검색
+
+하이브리드 검색
+
+0%
+
+20%
+
+40%
+
+60%
+
+80%
+
+100%
+허용 100%
+벡터 단독 검색 · 허용 100%: 95.1%
+95.1%
+하이브리드 검색 · 허용 100%: 75.7%
+75.7%
+허용 10%
+벡터 단독 검색 · 허용 10%: 94.8%
+94.8%
+하이브리드 검색 · 허용 10%: 75.5%
+75.5%
+허용 1%
+벡터 단독 검색 · 허용 1%: 84.4%
+84.4%
+하이브리드 검색 · 허용 1%: 66.2%
+66.2%
+허용 0.1%
+벡터 단독 검색 · 허용 0.1%: 53.4%
+53.4%
+하이브리드 검색 · 허용 0.1%: 39.0%
+39.0%
+x축: (수정 전 − 수정 후) ÷ 수정 전, p50 기준
+
diff --git a/docs/test-results/evidence/issue-549/generic-plan-psql.txt b/docs/test-results/evidence/issue-549/generic-plan-psql.txt
new file mode 100644
index 00000000..c98e131e
--- /dev/null
+++ b/docs/test-results/evidence/issue-549/generic-plan-psql.txt
@@ -0,0 +1,584 @@
+=== 수정 전 · 허용 100% · topK 10 · force_custom_plan
+ Limit (cost=2087.01..2087.03 rows=9 width=1037) (actual time=356.321..356.328 rows=40 loops=1)
+ Buffers: shared hit=327843 read=40339
+ -> Sort (cost=2087.01..2087.03 rows=9 width=1037) (actual time=356.320..356.325 rows=40 loops=1)
+ Sort Key: ((e.vector <=> '<질문 벡터 1024차원>'))
+ Sort Method: top-N heapsort Memory: 100kB
+ Buffers: shared hit=327843 read=40339
+ -> Nested Loop (cost=254.45..2086.86 rows=9 width=1037) (actual time=2.213..347.996 rows=50008 loops=1)
+ Buffers: shared hit=327840 read=40339
+ -> Hash Join (cost=254.16..2076.04 rows=9 width=117) (actual time=2.027..22.844 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ Buffers: shared hit=357 read=1072
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1654.38 rows=31960 width=50) (actual time=0.044..14.444 rows=50008 loops=1)
+ Index Cond: (document_id = ANY ('<허용 문서 ID 목록>'::bigint[]))
+ Filter: ((embedding_model_id = '1'::bigint) AND ((status)::text = 'ACTIVE'::text))
+ Buffers: shared hit=201 read=1072
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=1.966..1.966 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ Buffers: shared hit=156
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.015..0.985 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ Buffers: shared hit=156
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Buffers: shared hit=143267 read=6757
+ Planning:
+ Buffers: shared hit=553 read=88
+ Planning Time: 11.135 ms
+ Execution Time: 356.365 ms
+(28 rows)
+=== 수정 전 · 허용 100% · topK 10 · force_generic_plan
+ Limit (cost=2089.24..2089.25 rows=1 width=1037) (actual time=2293.092..2293.105 rows=40 loops=1)
+ Buffers: shared hit=325849 read=42305
+ -> Sort (cost=2089.24..2089.27 rows=9 width=1037) (actual time=2293.091..2293.102 rows=40 loops=1)
+ Sort Key: ((e.vector <=> ($1)::vector))
+ Sort Method: top-N heapsort Memory: 100kB
+ Buffers: shared hit=325849 read=42305
+ -> Nested Loop (cost=254.45..2089.10 rows=9 width=1037) (actual time=0.940..2285.073 rows=50008 loops=1)
+ Buffers: shared hit=325849 read=42305
+ -> Hash Join (cost=254.16..2078.24 rows=9 width=117) (actual time=0.863..16.125 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ Buffers: shared hit=2 read=1427
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1656.34 rows=32006 width=50) (actual time=0.066..8.994 rows=50008 loops=1)
+ Index Cond: (document_id = ANY (ARRAY[$3, ..., $n]))
+ Filter: ((embedding_model_id = $2) AND ((status)::text = 'ACTIVE'::text))
+ Buffers: shared hit=2 read=1271
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.792..0.792 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ Buffers: shared read=156
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.014..0.534 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ Buffers: shared read=156
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Buffers: shared hit=142896 read=7128
+ Planning:
+ Buffers: shared hit=19 read=19
+ Planning Time: 2.539 ms
+ Execution Time: 2293.306 ms
+(28 rows)
+=== 수정 전 · 허용 100% · auto 모드 반복 1회째
+ Limit (cost=2087.01..2087.03 rows=9 width=1037) (actual time=152.158..152.165 rows=40 loops=1)
+ -> Sort (cost=2087.01..2087.03 rows=9 width=1037) (actual time=152.158..152.163 rows=40 loops=1)
+ Sort Key: ((e.vector <=> '<질문 벡터 1024차원>'))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2086.86 rows=9 width=1037) (actual time=0.770..145.549 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2076.04 rows=9 width=117) (actual time=0.750..12.253 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1654.38 rows=31960 width=50) (actual time=0.035..6.889 rows=50008 loops=1)
+ Index Cond: (document_id = ANY ('<허용 문서 ID 목록>'::bigint[]))
+ Filter: ((embedding_model_id = '1'::bigint) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.706..0.707 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.010..0.463 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 3.158 ms
+ Execution Time: 152.184 ms
+(18 rows)
+=== 수정 전 · 허용 100% · auto 모드 반복 2회째
+ Limit (cost=2087.01..2087.03 rows=9 width=1037) (actual time=146.939..146.947 rows=40 loops=1)
+ -> Sort (cost=2087.01..2087.03 rows=9 width=1037) (actual time=146.938..146.945 rows=40 loops=1)
+ Sort Key: ((e.vector <=> '<질문 벡터 1024차원>'))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2086.86 rows=9 width=1037) (actual time=0.642..140.518 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2076.04 rows=9 width=117) (actual time=0.631..11.876 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1654.38 rows=31960 width=50) (actual time=0.027..6.662 rows=50008 loops=1)
+ Index Cond: (document_id = ANY ('<허용 문서 ID 목록>'::bigint[]))
+ Filter: ((embedding_model_id = '1'::bigint) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.602..0.603 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.008..0.424 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 2.952 ms
+ Execution Time: 146.964 ms
+(18 rows)
+=== 수정 전 · 허용 100% · auto 모드 반복 3회째
+ Limit (cost=2087.01..2087.03 rows=9 width=1037) (actual time=146.595..146.602 rows=40 loops=1)
+ -> Sort (cost=2087.01..2087.03 rows=9 width=1037) (actual time=146.594..146.600 rows=40 loops=1)
+ Sort Key: ((e.vector <=> '<질문 벡터 1024차원>'))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2086.86 rows=9 width=1037) (actual time=0.620..140.134 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2076.04 rows=9 width=117) (actual time=0.609..11.839 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1654.38 rows=31960 width=50) (actual time=0.027..6.682 rows=50008 loops=1)
+ Index Cond: (document_id = ANY ('<허용 문서 ID 목록>'::bigint[]))
+ Filter: ((embedding_model_id = '1'::bigint) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.580..0.581 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.008..0.407 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 2.824 ms
+ Execution Time: 146.619 ms
+(18 rows)
+=== 수정 전 · 허용 100% · auto 모드 반복 4회째
+ Limit (cost=2087.01..2087.03 rows=9 width=1037) (actual time=145.296..145.302 rows=40 loops=1)
+ -> Sort (cost=2087.01..2087.03 rows=9 width=1037) (actual time=145.295..145.300 rows=40 loops=1)
+ Sort Key: ((e.vector <=> '<질문 벡터 1024차원>'))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2086.86 rows=9 width=1037) (actual time=0.644..138.846 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2076.04 rows=9 width=117) (actual time=0.634..11.690 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1654.38 rows=31960 width=50) (actual time=0.026..6.592 rows=50008 loops=1)
+ Index Cond: (document_id = ANY ('<허용 문서 ID 목록>'::bigint[]))
+ Filter: ((embedding_model_id = '1'::bigint) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.606..0.606 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.008..0.409 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.000..0.000 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 2.822 ms
+ Execution Time: 145.317 ms
+(18 rows)
+=== 수정 전 · 허용 100% · auto 모드 반복 5회째
+ Limit (cost=2087.01..2087.03 rows=9 width=1037) (actual time=145.496..145.503 rows=40 loops=1)
+ -> Sort (cost=2087.01..2087.03 rows=9 width=1037) (actual time=145.496..145.500 rows=40 loops=1)
+ Sort Key: ((e.vector <=> '<질문 벡터 1024차원>'))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2086.86 rows=9 width=1037) (actual time=0.621..139.045 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2076.04 rows=9 width=117) (actual time=0.611..11.704 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1654.38 rows=31960 width=50) (actual time=0.027..6.660 rows=50008 loops=1)
+ Index Cond: (document_id = ANY ('<허용 문서 ID 목록>'::bigint[]))
+ Filter: ((embedding_model_id = '1'::bigint) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.582..0.582 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.007..0.407 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 2.888 ms
+ Execution Time: 145.519 ms
+(18 rows)
+=== 수정 전 · 허용 100% · auto 모드 반복 6회째
+ Limit (cost=2089.24..2089.25 rows=1 width=1037) (actual time=2215.904..2215.911 rows=40 loops=1)
+ -> Sort (cost=2089.24..2089.27 rows=9 width=1037) (actual time=2215.903..2215.908 rows=40 loops=1)
+ Sort Key: ((e.vector <=> ($1)::vector))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2089.10 rows=9 width=1037) (actual time=0.703..2208.707 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2078.24 rows=9 width=117) (actual time=0.644..13.945 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1656.34 rows=32006 width=50) (actual time=0.054..7.681 rows=50008 loops=1)
+ Index Cond: (document_id = ANY (ARRAY[$3, ..., $n]))
+ Filter: ((embedding_model_id = $2) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.587..0.588 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.007..0.401 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 2.011 ms
+ Execution Time: 2216.037 ms
+(18 rows)
+=== 수정 전 · 허용 100% · auto 모드 반복 7회째
+ Limit (cost=2089.24..2089.25 rows=1 width=1037) (actual time=2245.389..2245.400 rows=40 loops=1)
+ -> Sort (cost=2089.24..2089.27 rows=9 width=1037) (actual time=2245.388..2245.397 rows=40 loops=1)
+ Sort Key: ((e.vector <=> ($1)::vector))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2089.10 rows=9 width=1037) (actual time=0.714..2237.673 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2078.24 rows=9 width=117) (actual time=0.660..15.338 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1656.34 rows=32006 width=50) (actual time=0.066..8.703 rows=50008 loops=1)
+ Index Cond: (document_id = ANY (ARRAY[$3, ..., $n]))
+ Filter: ((embedding_model_id = $2) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.592..0.592 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.009..0.421 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 1.243 ms
+ Execution Time: 2245.551 ms
+(18 rows)
+=== 수정 전 · 허용 100% · auto 모드 반복 8회째
+ Limit (cost=2089.24..2089.25 rows=1 width=1037) (actual time=2346.021..2346.034 rows=40 loops=1)
+ -> Sort (cost=2089.24..2089.27 rows=9 width=1037) (actual time=2346.020..2346.031 rows=40 loops=1)
+ Sort Key: ((e.vector <=> ($1)::vector))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2089.10 rows=9 width=1037) (actual time=0.907..2337.740 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2078.24 rows=9 width=117) (actual time=0.839..16.850 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1656.34 rows=32006 width=50) (actual time=0.077..9.816 rows=50008 loops=1)
+ Index Cond: (document_id = ANY (ARRAY[$3, ..., $n]))
+ Filter: ((embedding_model_id = $2) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.759..0.763 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.011..0.556 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 1.648 ms
+ Execution Time: 2346.231 ms
+(18 rows)
+=== 수정 후 · 허용 100% · topK 10 · force_custom_plan
+ Limit (cost=2087.02..2087.04 rows=9 width=1037) (actual time=147.046..147.051 rows=40 loops=1)
+ Buffers: shared hit=325849 read=42305
+ InitPlan 1
+ -> Result (cost=0.00..0.01 rows=1 width=32) (actual time=0.003..0.004 rows=1 loops=1)
+ -> Sort (cost=2087.01..2087.03 rows=9 width=1037) (actual time=147.045..147.049 rows=40 loops=1)
+ Sort Key: ((e.vector <=> (InitPlan 1).col1))
+ Sort Method: top-N heapsort Memory: 100kB
+ Buffers: shared hit=325849 read=42305
+ -> Nested Loop (cost=254.45..2086.86 rows=9 width=1037) (actual time=0.811..140.433 rows=50008 loops=1)
+ Buffers: shared hit=325849 read=42305
+ -> Hash Join (cost=254.16..2076.04 rows=9 width=117) (actual time=0.762..12.124 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ Buffers: shared hit=2 read=1427
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1654.38 rows=31960 width=50) (actual time=0.031..6.629 rows=50008 loops=1)
+ Index Cond: (document_id = ANY ('<허용 문서 ID 목록>'::bigint[]))
+ Filter: ((embedding_model_id = '1'::bigint) AND ((status)::text = 'ACTIVE'::text))
+ Buffers: shared hit=2 read=1271
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.729..0.729 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ Buffers: shared read=156
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.009..0.486 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ Buffers: shared read=156
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Buffers: shared hit=142896 read=7128
+ Planning:
+ Buffers: shared hit=19 read=19
+ Planning Time: 3.623 ms
+ Execution Time: 147.091 ms
+(30 rows)
+=== 수정 후 · 허용 100% · topK 10 · force_generic_plan
+ Limit (cost=2089.21..2089.22 rows=1 width=1037) (actual time=149.440..149.446 rows=40 loops=1)
+ Buffers: shared hit=325849 read=42305
+ InitPlan 1
+ -> Result (cost=0.00..0.01 rows=1 width=32) (actual time=0.050..0.050 rows=1 loops=1)
+ -> Sort (cost=2089.20..2089.22 rows=9 width=1037) (actual time=149.439..149.443 rows=40 loops=1)
+ Sort Key: ((e.vector <=> (InitPlan 1).col1))
+ Sort Method: top-N heapsort Memory: 100kB
+ Buffers: shared hit=325849 read=42305
+ -> Nested Loop (cost=254.45..2089.05 rows=9 width=1037) (actual time=0.852..142.671 rows=50008 loops=1)
+ Buffers: shared hit=325849 read=42305
+ -> Hash Join (cost=254.16..2078.24 rows=9 width=117) (actual time=0.774..12.762 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ Buffers: shared hit=2 read=1427
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1656.34 rows=32006 width=50) (actual time=0.070..7.080 rows=50008 loops=1)
+ Index Cond: (document_id = ANY (ARRAY[$3, ..., $n]))
+ Filter: ((embedding_model_id = $2) AND ((status)::text = 'ACTIVE'::text))
+ Buffers: shared hit=2 read=1271
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.697..0.697 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ Buffers: shared read=156
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.009..0.420 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ Buffers: shared read=156
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Buffers: shared hit=142896 read=7128
+ Planning:
+ Buffers: shared hit=19 read=19
+ Planning Time: 2.125 ms
+ Execution Time: 149.585 ms
+(30 rows)
+=== 수정 후 · 허용 100% · auto 모드 반복 1회째
+ Limit (cost=2087.02..2087.04 rows=9 width=1037) (actual time=143.837..143.843 rows=40 loops=1)
+ InitPlan 1
+ -> Result (cost=0.00..0.01 rows=1 width=32) (actual time=0.000..0.001 rows=1 loops=1)
+ -> Sort (cost=2087.01..2087.03 rows=9 width=1037) (actual time=143.836..143.841 rows=40 loops=1)
+ Sort Key: ((e.vector <=> (InitPlan 1).col1))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2086.86 rows=9 width=1037) (actual time=0.607..137.425 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2076.04 rows=9 width=117) (actual time=0.589..11.870 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1654.38 rows=31960 width=50) (actual time=0.027..6.639 rows=50008 loops=1)
+ Index Cond: (document_id = ANY ('<허용 문서 ID 목록>'::bigint[]))
+ Filter: ((embedding_model_id = '1'::bigint) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.560..0.560 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.008..0.391 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.000..0.000 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 2.905 ms
+ Execution Time: 143.866 ms
+(20 rows)
+=== 수정 후 · 허용 100% · auto 모드 반복 2회째
+ Limit (cost=2087.02..2087.04 rows=9 width=1037) (actual time=142.049..142.055 rows=40 loops=1)
+ InitPlan 1
+ -> Result (cost=0.00..0.01 rows=1 width=32) (actual time=0.000..0.000 rows=1 loops=1)
+ -> Sort (cost=2087.01..2087.03 rows=9 width=1037) (actual time=142.049..142.053 rows=40 loops=1)
+ Sort Key: ((e.vector <=> (InitPlan 1).col1))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2086.86 rows=9 width=1037) (actual time=0.602..135.703 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2076.04 rows=9 width=117) (actual time=0.590..11.703 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1654.38 rows=31960 width=50) (actual time=0.028..6.570 rows=50008 loops=1)
+ Index Cond: (document_id = ANY ('<허용 문서 ID 목록>'::bigint[]))
+ Filter: ((embedding_model_id = '1'::bigint) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.560..0.561 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.006..0.388 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.000..0.000 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 2.871 ms
+ Execution Time: 142.070 ms
+(20 rows)
+=== 수정 후 · 허용 100% · auto 모드 반복 3회째
+ Limit (cost=2087.02..2087.04 rows=9 width=1037) (actual time=150.145..150.154 rows=40 loops=1)
+ InitPlan 1
+ -> Result (cost=0.00..0.01 rows=1 width=32) (actual time=0.000..0.001 rows=1 loops=1)
+ -> Sort (cost=2087.01..2087.03 rows=9 width=1037) (actual time=150.145..150.151 rows=40 loops=1)
+ Sort Key: ((e.vector <=> (InitPlan 1).col1))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2086.86 rows=9 width=1037) (actual time=0.612..143.483 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2076.04 rows=9 width=117) (actual time=0.599..12.459 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1654.38 rows=31960 width=50) (actual time=0.026..7.062 rows=50008 loops=1)
+ Index Cond: (document_id = ANY ('<허용 문서 ID 목록>'::bigint[]))
+ Filter: ((embedding_model_id = '1'::bigint) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.570..0.571 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.007..0.391 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 2.865 ms
+ Execution Time: 150.173 ms
+(20 rows)
+=== 수정 후 · 허용 100% · auto 모드 반복 4회째
+ Limit (cost=2087.02..2087.04 rows=9 width=1037) (actual time=154.784..154.791 rows=40 loops=1)
+ InitPlan 1
+ -> Result (cost=0.00..0.01 rows=1 width=32) (actual time=0.001..0.001 rows=1 loops=1)
+ -> Sort (cost=2087.01..2087.03 rows=9 width=1037) (actual time=154.783..154.788 rows=40 loops=1)
+ Sort Key: ((e.vector <=> (InitPlan 1).col1))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2086.86 rows=9 width=1037) (actual time=0.699..147.928 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2076.04 rows=9 width=117) (actual time=0.682..12.838 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1654.38 rows=31960 width=50) (actual time=0.028..7.219 rows=50008 loops=1)
+ Index Cond: (document_id = ANY ('<허용 문서 ID 목록>'::bigint[]))
+ Filter: ((embedding_model_id = '1'::bigint) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.651..0.652 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.009..0.466 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 3.019 ms
+ Execution Time: 154.818 ms
+(20 rows)
+=== 수정 후 · 허용 100% · auto 모드 반복 5회째
+ Limit (cost=2087.02..2087.04 rows=9 width=1037) (actual time=163.048..163.055 rows=40 loops=1)
+ InitPlan 1
+ -> Result (cost=0.00..0.01 rows=1 width=32) (actual time=0.000..0.001 rows=1 loops=1)
+ -> Sort (cost=2087.01..2087.03 rows=9 width=1037) (actual time=163.047..163.052 rows=40 loops=1)
+ Sort Key: ((e.vector <=> (InitPlan 1).col1))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2086.86 rows=9 width=1037) (actual time=0.745..156.060 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2076.04 rows=9 width=117) (actual time=0.725..13.345 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1654.38 rows=31960 width=50) (actual time=0.030..7.532 rows=50008 loops=1)
+ Index Cond: (document_id = ANY ('<허용 문서 ID 목록>'::bigint[]))
+ Filter: ((embedding_model_id = '1'::bigint) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.692..0.692 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.009..0.488 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 3.275 ms
+ Execution Time: 163.076 ms
+(20 rows)
+=== 수정 후 · 허용 100% · auto 모드 반복 6회째
+ Limit (cost=2089.21..2089.22 rows=1 width=1037) (actual time=147.767..147.774 rows=40 loops=1)
+ InitPlan 1
+ -> Result (cost=0.00..0.01 rows=1 width=32) (actual time=0.047..0.048 rows=1 loops=1)
+ -> Sort (cost=2089.20..2089.22 rows=9 width=1037) (actual time=147.765..147.771 rows=40 loops=1)
+ Sort Key: ((e.vector <=> (InitPlan 1).col1))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2089.05 rows=9 width=1037) (actual time=0.822..141.079 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2078.24 rows=9 width=117) (actual time=0.753..12.497 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1656.34 rows=32006 width=50) (actual time=0.067..7.123 rows=50008 loops=1)
+ Index Cond: (document_id = ANY (ARRAY[$3, ..., $n]))
+ Filter: ((embedding_model_id = $2) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.682..0.683 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.010..0.465 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 2.208 ms
+ Execution Time: 147.914 ms
+(20 rows)
+=== 수정 후 · 허용 100% · auto 모드 반복 7회째
+ Limit (cost=2089.21..2089.22 rows=1 width=1037) (actual time=142.095..142.101 rows=40 loops=1)
+ InitPlan 1
+ -> Result (cost=0.00..0.01 rows=1 width=32) (actual time=0.045..0.046 rows=1 loops=1)
+ -> Sort (cost=2089.20..2089.22 rows=9 width=1037) (actual time=142.094..142.099 rows=40 loops=1)
+ Sort Key: ((e.vector <=> (InitPlan 1).col1))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2089.05 rows=9 width=1037) (actual time=0.717..135.588 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2078.24 rows=9 width=117) (actual time=0.657..12.049 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1656.34 rows=32006 width=50) (actual time=0.063..6.789 rows=50008 loops=1)
+ Index Cond: (document_id = ANY (ARRAY[$3, ..., $n]))
+ Filter: ((embedding_model_id = $2) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.592..0.592 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.009..0.419 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.000..0.000 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 1.049 ms
+ Execution Time: 142.237 ms
+(20 rows)
+=== 수정 후 · 허용 100% · auto 모드 반복 8회째
+ Limit (cost=2089.21..2089.22 rows=1 width=1037) (actual time=148.356..148.362 rows=40 loops=1)
+ InitPlan 1
+ -> Result (cost=0.00..0.01 rows=1 width=32) (actual time=0.043..0.043 rows=1 loops=1)
+ -> Sort (cost=2089.20..2089.22 rows=9 width=1037) (actual time=148.355..148.359 rows=40 loops=1)
+ Sort Key: ((e.vector <=> (InitPlan 1).col1))
+ Sort Method: top-N heapsort Memory: 100kB
+ -> Nested Loop (cost=254.45..2089.05 rows=9 width=1037) (actual time=0.698..141.596 rows=50008 loops=1)
+ -> Hash Join (cost=254.16..2078.24 rows=9 width=117) (actual time=0.644..12.621 rows=50008 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..1656.34 rows=32006 width=50) (actual time=0.061..7.144 rows=50008 loops=1)
+ Index Cond: (document_id = ANY (ARRAY[$3, ..., $n]))
+ Filter: ((embedding_model_id = $2) AND ((status)::text = 'ACTIVE'::text))
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.581..0.582 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.008..0.408 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..1.20 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=50008)
+ Index Cond: (id = e.chunk_id)
+ Planning Time: 1.061 ms
+ Execution Time: 148.495 ms
+(20 rows)
+=== 수정 전 · 허용 10% · topK 10 · force_custom_plan
+ Limit (cost=607.75..607.76 rows=1 width=1037) (actual time=16.812..16.817 rows=40 loops=1)
+ Buffers: shared hit=35181 read=3944
+ -> Sort (cost=607.75..607.76 rows=1 width=1037) (actual time=16.811..16.815 rows=40 loops=1)
+ Sort Key: ((e.vector <=> '<질문 벡터 1024차원>'))
+ Sort Method: top-N heapsort Memory: 125kB
+ Buffers: shared hit=35181 read=3944
+ -> Nested Loop (cost=254.45..607.74 rows=1 width=1037) (actual time=0.665..16.023 rows=5244 loops=1)
+ Buffers: shared hit=35181 read=3944
+ -> Hash Join (cost=254.16..604.41 rows=1 width=117) (actual time=0.652..2.281 rows=5244 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ Buffers: shared hit=105 read=553
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..323.24 rows=5200 width=50) (actual time=0.008..1.130 rows=5244 loops=1)
+ Index Cond: (document_id = ANY ('<허용 문서 ID 목록>'::bigint[]))
+ Filter: ((embedding_model_id = '1'::bigint) AND ((status)::text = 'ACTIVE'::text))
+ Buffers: shared hit=105 read=397
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.643..0.643 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ Buffers: shared read=156
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.007..0.426 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ Buffers: shared read=156
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..3.33 rows=1 width=938) (actual time=0.001..0.001 rows=1 loops=5244)
+ Index Cond: (id = e.chunk_id)
+ Buffers: shared hit=15261 read=471
+ Planning:
+ Buffers: shared hit=19 read=19
+ Planning Time: 0.543 ms
+ Execution Time: 16.831 ms
+(28 rows)
+=== 수정 전 · 허용 10% · topK 10 · force_generic_plan
+ Limit (cost=602.27..602.28 rows=1 width=1037) (actual time=231.147..231.154 rows=40 loops=1)
+ Buffers: shared hit=39125
+ -> Sort (cost=602.27..602.28 rows=1 width=1037) (actual time=231.147..231.151 rows=40 loops=1)
+ Sort Key: ((e.vector <=> ($1)::vector))
+ Sort Method: top-N heapsort Memory: 125kB
+ Buffers: shared hit=39125
+ -> Nested Loop (cost=254.45..602.26 rows=1 width=1037) (actual time=0.560..230.246 rows=5244 loops=1)
+ Buffers: shared hit=39125
+ -> Hash Join (cost=254.16..598.91 rows=1 width=117) (actual time=0.508..1.978 rows=5244 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ Buffers: shared hit=658
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..318.21 rows=5110 width=50) (actual time=0.016..0.960 rows=5244 loops=1)
+ Index Cond: (document_id = ANY (ARRAY[$3, ..., $n]))
+ Filter: ((embedding_model_id = $2) AND ((status)::text = 'ACTIVE'::text))
+ Buffers: shared hit=502
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.490..0.490 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ Buffers: shared hit=156
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.003..0.291 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ Buffers: shared hit=156
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..3.35 rows=1 width=938) (actual time=0.000..0.000 rows=1 loops=5244)
+ Index Cond: (id = e.chunk_id)
+ Buffers: shared hit=15732
+ Planning:
+ Buffers: shared hit=38
+ Planning Time: 0.375 ms
+ Execution Time: 231.181 ms
+(28 rows)
+=== 수정 후 · 허용 10% · topK 10 · force_custom_plan
+ Limit (cost=607.76..607.77 rows=1 width=1037) (actual time=15.006..15.011 rows=40 loops=1)
+ Buffers: shared hit=39125
+ InitPlan 1
+ -> Result (cost=0.00..0.01 rows=1 width=32) (actual time=0.000..0.000 rows=1 loops=1)
+ -> Sort (cost=607.75..607.76 rows=1 width=1037) (actual time=15.005..15.009 rows=40 loops=1)
+ Sort Key: ((e.vector <=> (InitPlan 1).col1))
+ Sort Method: top-N heapsort Memory: 125kB
+ Buffers: shared hit=39125
+ -> Nested Loop (cost=254.45..607.74 rows=1 width=1037) (actual time=0.475..14.175 rows=5244 loops=1)
+ Buffers: shared hit=39125
+ -> Hash Join (cost=254.16..604.41 rows=1 width=117) (actual time=0.468..1.878 rows=5244 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ Buffers: shared hit=658
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..323.24 rows=5200 width=50) (actual time=0.005..0.876 rows=5244 loops=1)
+ Index Cond: (document_id = ANY ('<허용 문서 ID 목록>'::bigint[]))
+ Filter: ((embedding_model_id = '1'::bigint) AND ((status)::text = 'ACTIVE'::text))
+ Buffers: shared hit=502
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.460..0.461 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ Buffers: shared hit=156
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.003..0.265 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ Buffers: shared hit=156
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..3.33 rows=1 width=938) (actual time=0.000..0.000 rows=1 loops=5244)
+ Index Cond: (id = e.chunk_id)
+ Buffers: shared hit=15732
+ Planning:
+ Buffers: shared hit=38
+ Planning Time: 0.518 ms
+ Execution Time: 15.026 ms
+(30 rows)
+=== 수정 후 · 허용 10% · topK 10 · force_generic_plan
+ Limit (cost=602.28..602.29 rows=1 width=1037) (actual time=13.559..13.564 rows=40 loops=1)
+ Buffers: shared hit=39125
+ InitPlan 1
+ -> Result (cost=0.00..0.01 rows=1 width=32) (actual time=0.039..0.039 rows=1 loops=1)
+ -> Sort (cost=602.27..602.27 rows=1 width=1037) (actual time=13.558..13.561 rows=40 loops=1)
+ Sort Key: ((e.vector <=> (InitPlan 1).col1))
+ Sort Method: top-N heapsort Memory: 125kB
+ Buffers: shared hit=39125
+ -> Nested Loop (cost=254.45..602.26 rows=1 width=1037) (actual time=0.498..12.809 rows=5244 loops=1)
+ Buffers: shared hit=39125
+ -> Hash Join (cost=254.16..598.91 rows=1 width=117) (actual time=0.453..1.715 rows=5244 loops=1)
+ Hash Cond: ((e.document_id = d.id) AND (e.document_version_id = d.current_version_id))
+ Buffers: shared hit=658
+ -> Index Scan using idx_embeddings_document_id on embeddings e (cost=0.29..318.21 rows=5110 width=50) (actual time=0.011..0.768 rows=5244 loops=1)
+ Index Cond: (document_id = ANY (ARRAY[$3, ..., $n]))
+ Filter: ((embedding_model_id = $2) AND ((status)::text = 'ACTIVE'::text))
+ Buffers: shared hit=502
+ -> Hash (cost=200.49..200.49 rows=3559 width=91) (actual time=0.441..0.441 rows=3559 loops=1)
+ Buckets: 4096 Batches: 1 Memory Usage: 473kB
+ Buffers: shared hit=156
+ -> Seq Scan on documents d (cost=0.00..200.49 rows=3559 width=91) (actual time=0.002..0.257 rows=3559 loops=1)
+ Filter: ((deleted_at IS NULL) AND ((status)::text = 'INDEXED'::text))
+ Buffers: shared hit=156
+ -> Index Scan using document_chunks_pkey on document_chunks dc (cost=0.29..3.35 rows=1 width=938) (actual time=0.000..0.000 rows=1 loops=5244)
+ Index Cond: (id = e.chunk_id)
+ Buffers: shared hit=15732
+ Planning:
+ Buffers: shared hit=38
+ Planning Time: 0.376 ms
+ Execution Time: 13.590 ms
+(30 rows)
diff --git a/docs/test-results/evidence/issue-549/same-index-check.md b/docs/test-results/evidence/issue-549/same-index-check.md
new file mode 100644
index 00000000..c11eff29
--- /dev/null
+++ b/docs/test-results/evidence/issue-549/same-index-check.md
@@ -0,0 +1,18 @@
+### 같은 Index에서 결과 비교 (질문 30개)
+
+| 허용 비율 | 방식 | 수정 전/후 결과가 같은 질문 |
+|---:|---|---:|
+| 100% | hnsw_ef40 | 30/30 |
+| 100% | iterative_relaxed | 30/30 |
+| 10% | hnsw_ef40 | 30/30 |
+| 10% | iterative_relaxed | 30/30 |
+
+### 같은 Index에서 실행 시간 비교 (수정 전/후 번갈아 15회, Execution Time 중앙값)
+
+| 경우 | 수정 전 (ms) | 수정 후 (ms) | 차이 (ms) | 변화율 |
+|---|---:|---:|---:|---:|
+| 현재 계획 Custom · 허용 100% | 141.398 | 139.644 | -1.754 | -1.2% |
+| 현재 계획 Custom · 허용 10% | 12.551 | 12.651 | +0.100 | +0.8% |
+| 현재 계획 Custom · 허용 0.1% | 0.375 | 0.376 | +0.001 | +0.3% |
+| HNSW 강제 ef40 · 허용 100% | 5.205 | 5.311 | +0.106 | +2.0% |
+| HNSW 강제 iterative relaxed · 허용 10% | 8.144 | 8.264 | +0.120 | +1.5% |
diff --git a/docs/test-results/evidence/issue-549/same_index_check.py b/docs/test-results/evidence/issue-549/same_index_check.py
new file mode 100644
index 00000000..d93a8c27
--- /dev/null
+++ b/docs/test-results/evidence/issue-549/same_index_check.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python3
+"""#549 같은 HNSW Index 위에서 수정 전/후 findTopK SQL을 번갈아 실행해 결과와 실행 시간을 비교한다. 표준 라이브러리만 쓴다.
+
+전체 재측정은 실행마다 HNSW Index를 새로 만들므로, HNSW 강제 방식의 결과 차이가 SQL 수정 때문인지 Index 재생성 때문인지
+구분할 수 없다. 이 스크립트는 한 Index에서 두 SQL을 비교한다.
+ 1) 결과: 질문 30개 × 허용 10%·100% × {HNSW 강제 ef40, iterative relaxed} × {수정 전, 수정 후}의 청크 ID 순서
+ 2) 시간: Custom Plan·HNSW 강제 계획을 수정 전/후 번갈아 15회씩 EXPLAIN (ANALYZE, TIMING OFF)한 Execution Time 중앙값
+
+사용법 (#545 하네스로 5만 청크 데이터가 적재된 상태에서):
+ python3 same_index_check.py build > check.sql
+ docker exec -i <격리 DB 컨테이너> psql -U -d -qAt < check.sql > check-raw.txt
+ python3 same_index_check.py report check-raw.txt > same-index-check.md
+"""
+import json
+import re
+import statistics
+import sys
+from collections import defaultdict
+
+SCHEMA = "docgrid_permission_recall_545"
+FIXED_CAST = "(SELECT CAST(:queryVector AS vector))"
+ORIGINAL_CAST = "CAST(:queryVector AS vector)"
+MODES = {
+ "hnsw_ef40": ["enable_sort = off", "hnsw.ef_search = 40"],
+ "iterative_relaxed": ["enable_sort = off", "hnsw.ef_search = 40", "hnsw.iterative_scan = relaxed_order"],
+}
+TIMING_CASES = [
+ ("현재 계획 Custom · 허용 100%", 1.0, []),
+ ("현재 계획 Custom · 허용 10%", 0.1, []),
+ ("현재 계획 Custom · 허용 0.1%", 0.001, []),
+ ("HNSW 강제 ef40 · 허용 100%", 1.0, MODES["hnsw_ef40"]),
+ ("HNSW 강제 iterative relaxed · 허용 10%", 0.1, MODES["iterative_relaxed"]),
+]
+TIMING_RUNS = 15
+LIMIT = 40
+
+
+def permitted_ids(plans, ratio):
+ for sample in plans:
+ if sample["scale"] == "50000" and sample["ratio"] == ratio and sample["method"] == "exact":
+ match = re.search(r"document_id = ANY \('\{([0-9,]+)\}'", sample["plan"])
+ if match:
+ return [int(value) for value in match.group(1).split(",")]
+ raise SystemExit(f"허용 비율 {ratio}의 Exact 계획을 찾지 못했습니다.")
+
+
+def literal_sql(sql, vector, ids):
+ return (sql.replace(":queryVector", f"'{vector}'").replace(":modelId", "1")
+ .replace("(:permittedIds)", "(" + ",".join(map(str, ids)) + ")").replace(":topK", str(LIMIT)))
+
+
+def build(backend, plans_path):
+ with open(f"{backend}/build/reports/permission-recall/question-vectors.json", encoding="utf-8") as handle:
+ vectors = json.load(handle)["vectors"]
+ with open(f"{backend}/../docs/test-results/evidence/issue-545/questions.json", encoding="utf-8") as handle:
+ questions = json.load(handle)["questions"]
+ with open(plans_path, encoding="utf-8") as handle:
+ plans = json.load(handle)
+ source = open(f"{backend}/src/main/java/com/opensource/docgrid/domain/search/repository/VectorSearchRepository.java",
+ encoding="utf-8").read()
+ fixed = re.search(r'@Query\(value = """(.*?)""", nativeQuery = true\)\s*List findTopK',
+ source, re.S).group(1)
+ if FIXED_CAST not in fixed:
+ raise SystemExit("현재 findTopK SQL에 수정(질문 Vector 서브쿼리)이 없습니다.")
+ forms = {"before": fixed.replace(FIXED_CAST, ORIGINAL_CAST), "after": fixed}
+
+ print(f"SET search_path = {SCHEMA}, public;")
+ print("SET plan_cache_mode = force_custom_plan;")
+ # 1) 결과 비교: 같은 Index에서 수정 전/후 SQL의 청크 ID 순서
+ for ratio in (1.0, 0.1):
+ ids = permitted_ids(plans, ratio)
+ for question in questions:
+ vector = "[" + ",".join(repr(float(v)) for v in vectors[question["question"]]) + "]"
+ for mode, settings in MODES.items():
+ for form, sql in forms.items():
+ print("BEGIN;")
+ for setting in settings:
+ print(f"SET LOCAL {setting};")
+ print(f"SELECT 'R|{ratio}|{question['id']}|{mode}|{form}|' || "
+ f"coalesce(string_agg(chunk_id::text, ',' ORDER BY distance, chunk_id), '') "
+ f"FROM ({literal_sql(sql, vector, ids)}) t;")
+ print("ROLLBACK;")
+ # 2) 시간 비교: 질문 D01, Custom Plan, 수정 전/후를 번갈아 실행
+ vector = "[" + ",".join(repr(float(v)) for v in vectors[questions[0]["question"]]) + "]"
+ for case_index, (title, ratio, settings) in enumerate(TIMING_CASES):
+ ids = permitted_ids(plans, ratio)
+ for run in range(TIMING_RUNS):
+ for form, sql in forms.items():
+ print("BEGIN;")
+ for setting in settings:
+ print(f"SET LOCAL {setting};")
+ print(f"SELECT 'T|{case_index}|{form}|{run}';")
+ print(f"EXPLAIN (ANALYZE, TIMING OFF, SUMMARY ON) {literal_sql(sql, vector, ids)};")
+ print("ROLLBACK;")
+
+
+def report(path):
+ results = defaultdict(dict)
+ timings = defaultdict(lambda: defaultdict(list))
+ current = None
+ for line in open(path, encoding="utf-8"):
+ line = line.rstrip("\n")
+ if line.startswith("R|"):
+ _, ratio, qid, mode, form, ids = line.split("|", 5)
+ results[(ratio, qid, mode)][form] = ids
+ elif line.startswith("T|"):
+ _, case_index, form, _ = line.split("|")
+ current = (int(case_index), form)
+ elif line.startswith("Execution Time:") and current:
+ timings[current[0]][current[1]].append(float(line.split(":")[1].split()[0]))
+ current = None
+
+ print("### 같은 Index에서 결과 비교 (질문 30개)\n")
+ print("| 허용 비율 | 방식 | 수정 전/후 결과가 같은 질문 |")
+ print("|---:|---|---:|")
+ grouped = defaultdict(lambda: [0, 0])
+ for (ratio, _, mode), forms in results.items():
+ grouped[(ratio, mode)][1] += 1
+ if forms.get("before") == forms.get("after"):
+ grouped[(ratio, mode)][0] += 1
+ for (ratio, mode), (same, total) in sorted(grouped.items(), key=lambda item: (-float(item[0][0]), item[0][1])):
+ print(f"| {float(ratio) * 100:g}% | {mode} | {same}/{total} |")
+
+ print(f"\n### 같은 Index에서 실행 시간 비교 (수정 전/후 번갈아 {TIMING_RUNS}회, Execution Time 중앙값)\n")
+ print("| 경우 | 수정 전 (ms) | 수정 후 (ms) | 차이 (ms) | 변화율 |")
+ print("|---|---:|---:|---:|---:|")
+ for case_index, (title, _, _) in enumerate(TIMING_CASES):
+ before = statistics.median(timings[case_index]["before"])
+ after = statistics.median(timings[case_index]["after"])
+ print(f"| {title} | {before:.3f} | {after:.3f} | {after - before:+.3f} | {(after - before) / before * 100:+.1f}% |")
+
+
+if __name__ == "__main__":
+ if len(sys.argv) == 4 and sys.argv[1] == "build":
+ build(sys.argv[2], sys.argv[3])
+ elif len(sys.argv) == 3 and sys.argv[1] == "report":
+ report(sys.argv[2])
+ else:
+ print(__doc__)
+ sys.exit(2)
diff --git a/docs/test-results/gimin-#549-generic-plan-query-vector-cast.md b/docs/test-results/gimin-#549-generic-plan-query-vector-cast.md
new file mode 100644
index 00000000..460749ab
--- /dev/null
+++ b/docs/test-results/gimin-#549-generic-plan-query-vector-cast.md
@@ -0,0 +1,198 @@
+# Issue #549 Generic Plan 질문 Vector 변환 수정 전후 성능 비교
+
+- 측정일: 2026-10-17 (KST)
+- 관련 이슈: #549
+- 설계 문서: [gimin-#549-generic-plan-query-vector-cast.md](../design/gimin-%23549-generic-plan-query-vector-cast.md)
+- 기준(수정 전) 측정: [#545 결과](gimin-%23545-permission-filter-vector-recall.md)
+- 원시 데이터·재현 스크립트: [evidence/issue-549/](evidence/issue-549/)
+
+## 1. 결론
+
+벡터 검색 SQL에서 질문 Vector 변환을 `CAST(:queryVector AS vector)` → `(SELECT CAST(:queryVector AS vector))`로 바꿨다. **같은 SQL을 6번 넘게 실행해 Generic Plan이 쓰일 때의 지연이 처음 5번(Custom Plan) 수준으로 내려왔다.** 검색 결과는 바뀌지 않았다.
+
+5만 청크·topK 10, 질문 30개 p50이다. 수정 전 값은 #545 기준 측정이다.
+
+| 허용 비율 | 벡터 단독 검색: 수정 전 → 수정 후 | 개선율 | 하이브리드 검색(기본 경로): 수정 전 → 수정 후 | 개선율 |
+|---:|---|---:|---|---:|
+| 100% | 4,226.3 → **207.4ms** | **95.1%** (20.4배) | 5,444.6 → **1,321.6ms** | **75.7%** |
+| 10% | 480.5 → **24.9ms** | **94.8%** | 609.4 → **149.2ms** | **75.5%** |
+| 1% | 43.2 → **6.8ms** | **84.4%** | 56.3 → **19.0ms** | **66.2%** |
+| 0.1% | 7.9 → **3.7ms** | **53.4%** | 10.9 → **6.6ms** | **39.0%** |
+
+
+
+
+
+- **Generic Plan 개선율 (벡터 단독 p50, 24개 조합):** 5만 청크는 중앙값 91.4%(27.2~95.2%), 실제 3,572 청크는 중앙값 80.2%(17.3~95.0%)다. 허용 청크가 많을수록 행마다 하던 변환이 많아 개선 폭이 크다.
+- **수정 후 Generic Plan은 Custom Plan과 같은 속도다.** 5만 청크·100%·topK 10에서 Generic 207.4ms, Custom 208.9ms다.
+- **psql 반복 실행:** 수정 전에는 같은 Statement를 6회째 실행할 때부터 145ms → 2,216ms로 뛰었다. 수정 후에는 1~8회 내내 142~163ms다.
+- **검색 결과는 같다.** Exact·현재 계획(Custom·Generic) 2,160개 질문·조합에서 SQL 후보·최종 결과·Recall이 수정 전과 모두 같았다.
+ - HNSW 강제 방식에서 보인 차이는 측정마다 HNSW Index를 새로 만들기 때문이다. **같은 Index 위에서는 수정 전/후 결과가 120/120 같았다.**
+- **Custom Plan과 HNSW 강제 계획은 사실상 변화가 없다.** 같은 Index에서 번갈아 15회 잰 실행 시간 변화가 -1.2~+2.0%(HNSW 계획은 +0.1ms)다.
+- **하이브리드는 개선 후에도 5만 청크·100%에서 p50 1.3초다.** 남은 시간은 단어 갈래 조회 비용이며, 이 수정의 범위 밖이다(#545 4.6).
+
+## 2. 무엇을 바꿨나
+
+`VectorSearchRepository`의 SQL 세 곳이다. `findTopK`의 SELECT 거리 계산과 ORDER BY, 그리고 `findLexicalCandidates`의 거리 계산이다. 스칼라 서브쿼리는 PostgreSQL이 InitPlan으로 실행마다 한 번만 계산한다. 원인과 다른 방법을 고르지 않은 이유는 [설계 문서](../design/gimin-%23549-generic-plan-query-vector-cast.md)에 있다.
+
+## 3. 측정 환경과 방법
+
+### 3.1 #545와 같은 환경
+
+| 항목 | 값 |
+|---|---|
+| DB | 새 격리 컨테이너 `pgvector/pgvector:0.8.1-pg17@sha256:3e8b…b21`(CI·#545와 같은 digest). PostgreSQL 17.8, pgvector 0.8.1 |
+| DB 설정 | 이미지 기본값(`shared_buffers` 128MB, `work_mem` 4MB, `maintenance_work_mem` 64MB, `jit` on, `plan_cache_mode` auto). #545와 같음 |
+| 데이터 | #545와 같은 원본 251건(`develop` `ed5d601`의 문서를 따로 풀어 지정)과 같은 BGE-M3 Embedding 캐시, Seed 545. 5만 청크 HNSW Index 크기가 409,673,728바이트로 #545와 바이트까지 같다 |
+| 하네스·질문 | #545(PR #547)의 `PermissionRecallBenchmark`, 질문 30개, 예열 1회·측정 3회(Generic Plan은 0회·1회) |
+| Application | Java 17.0.18, PostgreSQL JDBC 42.7.11, `aarch64` |
+
+### 3.2 측정 세 가지
+
+| 측정 | 내용 | 시각 |
+|---|---|---|
+| **A/B** | 5만 청크·topK 10·허용 4단계 × Exact·현재 계획(Custom·Generic)을 수정 전 코드와 수정 후 코드로 **연달아** 측정. 시간대에 따른 흔들림을 줄이려는 것이다 | 22:18~22:45 |
+| **전체 재측정** | 수정 후 코드로 #545와 같은 192개 조합(2개 규모 × 허용 4단계 × topK 3개 × 방식 8개 × 질문 30개)을 다시 측정해 #545 기준과 비교 | 22:45~00:04 |
+| **psql** | ① 같은 Prepared Statement를 `auto` 모드로 8회 반복 ② 같은 HNSW Index 위에서 수정 전/후 SQL의 결과·실행 시간 비교 | 00:06 이후 |
+
+개선율은 `(수정 전 − 수정 후) ÷ 수정 전 × 100`이다. 양수면 빨라진 것이다.
+
+## 4. 결과
+
+### 4.1 A/B (수정 전·후 연달아 측정)
+
+5만 청크·topK 10, 질문 30개다. 표기는 p50 / p95, 단위는 ms다. 전체 표는 [ab-comparison.md](evidence/issue-549/ab-comparison.md)에 있다.
+
+| 허용 비율 | 방식 | 벡터: 수정 전 → 수정 후 | 개선율 (p50) | 하이브리드 p50: 수정 전 → 수정 후 | 개선율 | 결과 다른 질문 |
+|---:|---|---|---:|---|---:|---:|
+| 100% | 현재 계획 Generic | 4,465.7 / 5,178.2 → **218.5 / 255.9** | **95.1%** | 5,799.9 → 1,380.6 | 76.2% | 0/30 |
+| 100% | 현재 계획 Custom | 234.2 / 376.1 → 231.0 / 341.3 | 1.3% | 1,403.7 → 1,379.8 | 1.7% | 0/30 |
+| 10% | 현재 계획 Generic | 461.1 / 545.5 → **27.3 / 28.6** | **94.1%** | 596.4 → 152.8 | 74.4% | 0/30 |
+| 10% | 현재 계획 Custom | 27.2 / 29.6 → 29.2 / 38.3 | -7.6% | 152.1 → 158.7 | -4.4% | 0/30 |
+| 1% | 현재 계획 Generic | 44.6 / 46.8 → **7.3 / 9.1** | **83.7%** | 58.0 → 20.1 | 65.3% | 0/30 |
+| 1% | 현재 계획 Custom | 6.8 / 8.5 → 6.7 / 9.6 | 1.4% | 19.4 → 20.4 | -5.5% | 0/30 |
+| 0.1% | 현재 계획 Generic | 7.8 / 9.0 → **2.6 / 3.8** | **66.5%** | 11.6 → 5.5 | 53.1% | 0/30 |
+| 0.1% | 현재 계획 Custom | 2.8 / 4.2 → 3.2 / 5.7 | -13.6% | 7.1 → 7.8 | -9.5% | 0/30 |
+
+전체 재측정(#545 기준 대비)과 A/B의 Generic Plan 개선율 차이는 0~13%p였다(가장 큰 차이는 0.1%: 53.4% vs 66.5%). 원래 빨랐던 Custom Plan의 2ms 이내(-13.6~+1.4%) 차이는 측정 흔들림이다. 아래 4.4에서 같은 Index로 다시 잰 결과가 근거다.
+
+### 4.2 전체 192개 조합 (#545 기준 대비)
+
+현재 계획(운영 경로)의 벡터 단독 p50이다. 단위는 ms이며 모든 조합은 [full-comparison.md](evidence/issue-549/full-comparison.md)·[full-comparison.csv](evidence/issue-549/full-comparison.csv)에 있다.
+
+| 규모 | 허용 비율 | Generic: 수정 전 → 수정 후 (topK 10) | 개선율 | Custom: 수정 전 → 수정 후 (topK 10) | 수정 후 Generic ÷ Custom |
+|---|---:|---|---:|---|---:|
+| 5만 청크 | 100% | 4,226.3 → 207.4 | **95.1%** | 204.2 → 208.9 | 0.99 |
+| 5만 청크 | 10% | 480.5 → 24.9 | **94.8%** | 28.1 → 26.7 | 0.93 |
+| 5만 청크 | 1% | 43.2 → 6.8 | **84.4%** | 7.3 → 7.4 | 0.91 |
+| 5만 청크 | 0.1% | 7.9 → 3.7 | **53.4%** | 3.6 → 3.1 | 1.19 |
+| 실제 3,572 | 100% | 300.1 → 17.8 | **94.1%** | 17.8 → 18.0 | 0.99 |
+| 실제 3,572 | 10% | 31.8 → 5.6 | **82.4%** | 5.6 → 5.5 | 1.03 |
+| 실제 3,572 | 1% | 7.1 → 2.6 | **62.9%** | 3.4 → 2.7 | 0.99 |
+| 실제 3,572 | 0.1% | 4.2 → 1.7 | **58.5%** | 1.8 → 2.3 | 0.75 |
+
+| 방식 | 벡터 단독 p50 개선율 중앙값 (24개 조합) | 결과 다른 질문 |
+|---|---:|---:|
+| 현재 계획 Generic | 5만 청크 91.4% · 실제 80.2% | 0/720 |
+| 현재 계획 Custom | 5만 청크 1.7% · 실제 -1.6% | 0/720 |
+| Exact | 5만 청크 -2.4% · 실제 -6.9% | 0/720 |
+| HNSW 강제(ef40·100·200, iterative 2종) | -14.2~-1.0% (차이 -0.9~-0.1ms) | 64~292/720 (Index 재생성 영향, 4.4) |
+
+- **Generic Plan 이외의 방식은 1ms 미만 차이다.** 전체 재측정은 #545 기준보다 약 6시간 늦게 실행했다. 그래서 수정과 무관한 Exact도 0.2~0.3ms 느리게 나왔다. 같은 Index·같은 시각에 번갈아 잰 4.4 결과로 판단했다.
+- **수정 후에도 HNSW 사용 여부는 같았다.** 현재 계획은 720개 질문·조합 모두 HNSW를 쓰지 않고, HNSW 강제 방식은 모두 쓴다.
+
+### 4.3 psql: 같은 Statement 반복 실행
+
+5만 청크·허용 100%·topK 10, 질문 D01이다. `EXPLAIN (ANALYZE) EXECUTE`의 Execution Time이며, 전문은 [generic-plan-psql.txt](evidence/issue-549/generic-plan-psql.txt)에 있다.
+
+| 실행 | 수정 전 | 수정 후 |
+|---|---:|---:|
+| `auto` 모드 1~5회째 | 145.3~152.2ms | 142.1~163.1ms |
+| `auto` 모드 **6~8회째** | **2,216.0~2,346.2ms** | **142.2~148.5ms** |
+| `force_custom_plan` | 356.4ms (첫 실행, 디스크 읽기 포함) | 147.1ms |
+| `force_generic_plan` | 2,293.3ms | **149.6ms** |
+| 허용 10% `force_generic_plan` | 231.2ms | **13.6ms** |
+
+수정 후 Generic Plan의 정렬 키는 `(InitPlan 1).col1`을 참조한다. 수정 전처럼 `($1)::vector`를 행마다 계산하지 않는다.
+
+### 4.4 같은 HNSW Index 위에서 수정 전/후 SQL 비교
+
+전체 재측정은 실행마다 HNSW Index를 새로 만든다. 그래서 HNSW 강제 방식의 결과 차이가 SQL 수정 때문인지 Index 재생성 때문인지 구분할 수 없었다. 한 Index 위에서 두 SQL을 비교했다([same-index-check.md](evidence/issue-549/same-index-check.md)).
+
+| 비교 | 결과 |
+|---|---|
+| HNSW 강제 ef40·iterative relaxed × 허용 100%·10% × 질문 30개의 청크 ID 순서 | **120/120 같음** |
+| 현재 계획 Custom 허용 100% / 10% / 0.1% (번갈아 15회 중앙값) | 141.40 → 139.64ms (-1.2%) / 12.55 → 12.65ms (+0.8%) / 0.375 → 0.376ms (+0.3%) |
+| HNSW 강제 ef40 허용 100% / iterative relaxed 허용 10% | 5.21 → 5.31ms (+2.0%) / 8.14 → 8.26ms (+1.5%) |
+
+따라서 다음과 같이 판단한다.
+- HNSW 강제 방식의 결과 차이는 HNSW Index 재생성에서 온다. 같은 Index에서는 수정 전/후 결과가 같다.
+- 스칼라 서브쿼리 자체의 비용은 HNSW 계획에서 약 0.1ms이고, Custom Plan에서는 측정되지 않을 만큼 작다.
+
+### 4.5 수정 후 결과 (#545와 같은 형식)
+
+수정 후에도 권한 필터 결과는 #545와 같다. 현재 계획은 HNSW를 쓰지 않아 결과가 빠지지 않고, HNSW를 강제하면 허용 비율이 낮을수록 결과가 빠진다.
+
+
+
+
+
+전체 표는 [after-fix/tables.md](evidence/issue-549/after-fix/tables.md)에 있다. 원시 데이터는 [after-fix/results.json.gz](evidence/issue-549/after-fix/results.json.gz), [rows.csv.gz](evidence/issue-549/after-fix/rows.csv.gz), [summary.csv](evidence/issue-549/after-fix/summary.csv)다.
+
+## 5. 자동 테스트 결과
+
+### 5.1 회귀 통합 테스트
+
+`VectorSearchRepositoryGenericPlanIntegrationTest`에 운영 `@Query` SQL을 그대로 읽어 검사하는 테스트 3건을 추가했다.
+
+| 테스트 | 수정 전 SQL | 수정 후 SQL |
+|---|---|---|
+| Generic Plan 결과가 Custom Plan과 같은 청크·거리 순서 | 통과 | 통과 |
+| Generic Plan 정렬 키가 `$1`을 직접 쓰지 않고 InitPlan 결과를 씀 | **실패** (`((e.vector <=> ($1)::vector))`) | 통과 |
+| `enable_sort=off`일 때 HNSW 정렬 Index Scan을 쓰고 정렬 키에 `$1`이 없음 | **실패** (`(e.vector <=> ($1)::vector)`) | 통과 |
+
+같은 SQL 파일의 단어 갈래 쿼리 테스트(`LexicalSearchRepositoryIntegrationTest`) 12건도 수정 후 모두 통과했다.
+
+### 5.2 전체 테스트
+
+격리 DB와 격리 Valkey 컨테이너, CI와 같은 설정(`SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE=3`, CI 전용 `JWT_SECRET`)으로 실행했다.
+
+```text
+./backend/gradlew -p backend test # develop a24d3e8(PR #547 포함) + 이번 수정
+BUILD SUCCESSFUL in 1m 26s
+테스트 클래스 253개, tests=1,511, skipped=5, failures=0, errors=0
+```
+
+## 6. 한계
+
+- **Generic Plan 측정은 `plan_cache_mode = force_generic_plan`으로 강제한 값이다.** 기본 `auto`에서 실제로 6회째부터 바뀌는 것은 psql 반복 실행으로 확인했다. 앱의 Connection Pool에서 언제 바뀌는지는 Connection별 실행 횟수에 따라 다르다.
+- **수 ms 이하 조합의 개선율은 흔들림이 크다.** 예를 들어 실제 3,572 청크·허용 0.1%의 Custom Plan은 1.8 → 2.3ms였다. 결론은 100%·10% 조합과 같은 Index 비교를 근거로 한다.
+- **한 대의 로컬 장비, PostgreSQL 기본 설정이다.** #545와 같은 한계를 가진다(합성 92.9%, 무작위 권한, 질문 30개).
+- **하이브리드 검색의 단어 갈래 비용은 그대로다.** 5만 청크·100%에서 p50 1.3초가 남는다.
+
+## 7. 재현 방법
+
+#545 [재현 방법](gimin-%23545-permission-filter-vector-recall.md#8-재현-방법)으로 격리 DB와 Embedding 캐시를 준비한다. 원본은 #545와 같은 `ed5d601`의 문서를 따로 풀어 지정한다. 지금 저장소의 `docs/`가 달라졌기 때문이다.
+
+```bash
+git archive ed5d601 -- docs/design docs/test-results examples/demo-corpus | tar -x -C
+
+./backend/gradlew -p backend vectorSearchPerformanceTest --tests '*PermissionRecallBenchmark' \
+ -Dvector.search.performance.permission.phase=measure -Dvector.search.performance.permission.drop-schema=false \
+ -Dvector.search.performance.permission.repository-root= \
+ -Dvector.search.performance.permission.output=build/reports/permission-recall/after-full-results.json \
+ -Dvector.search.performance.permission.plan-output=build/reports/permission-recall/after-full-plans.json
+
+# 수정 후 표·그래프 (#545 스크립트)
+python3 docs/test-results/evidence/issue-545/make_report.py backend/build/reports/permission-recall/after-full-results.json docs/test-results/evidence/issue-549/after-fix
+
+# 수정 전(#545 기준) 대비 비교 표·그래프
+python3 docs/test-results/evidence/issue-549/compare_results.py docs/test-results/evidence/issue-545/results.json.gz \
+ docs/test-results/evidence/issue-549/after-fix/results.json.gz docs/test-results/evidence/issue-549 full-
+
+# psql 증빙 (5만 청크가 남아 있을 때)
+python3 docs/test-results/evidence/issue-549/explain_generic_plan.py backend backend/build/reports/permission-recall/after-full-plans.json > generic.sql
+python3 docs/test-results/evidence/issue-549/same_index_check.py build backend backend/build/reports/permission-recall/after-full-plans.json > check.sql
+```
+
+A/B는 같은 명령에 `-Dvector.search.performance.permission.scales=50000 -Dvector.search.performance.permission.top-ks=10 -Dvector.search.performance.permission.methods=exact,planner_default,planner_default_generic`를 붙여 수정 전 코드와 수정 후 코드에서 한 번씩 실행했다.