document-dataply 0.0.10-alpha.6 → 0.0.10-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -10482,7 +10482,7 @@ var SELECTIVITY = {
10482
10482
  /** FTS 통계 없을 때 보수적 추정 */
10483
10483
  FTS_DEFAULT: 0.5,
10484
10484
  /** 정렬 비용 가중치 (orderBy 미지원 시) */
10485
- SORT_PENALTY: 0.3,
10485
+ SORT_PENALTY: 0.5,
10486
10486
  /** 인메모리 정렬이 유의미해지는 임계 문서 수 */
10487
10487
  SORT_THRESHOLD: 1e4
10488
10488
  };
@@ -10668,9 +10668,12 @@ var Optimizer = class {
10668
10668
  * 비용 계산: effectiveScanCost + sortPenalty
10669
10669
  * - effectiveScanCost: 인덱스 순서 지원 + limit 존재 시 조기 종료 이점 반영
10670
10670
  * - sortPenalty: 인메모리 정렬의 절대 문서 수 기반 비용
10671
+ * - hasUncoveredFilters: 드라이버가 커버하지 못하는 비-FTS 필터 존재 여부
10672
+ * true일 경우 topK/N 조기 종료를 적용하지 않음
10673
+ * (uncovered 필터가 행을 탈락시킬 수 있어 topK개 이상 스캔 필요)
10671
10674
  */
10672
- calculateCost(selectivity, isIndexOrderSupported, orderByField, N, topK) {
10673
- const effectiveScanCost = isIndexOrderSupported && isFinite(topK) && N > 0 ? Math.min(topK / N, selectivity) : selectivity;
10675
+ calculateCost(selectivity, isIndexOrderSupported, orderByField, N, topK, hasUncoveredFilters = false) {
10676
+ const effectiveScanCost = isIndexOrderSupported && isFinite(topK) && N > 0 && !hasUncoveredFilters ? Math.min(topK / N, selectivity) : selectivity;
10674
10677
  const estimatedSortDocs = selectivity * N;
10675
10678
  const sortPenalty = orderByField && !isIndexOrderSupported ? Math.min(estimatedSortDocs / SELECTIVITY.SORT_THRESHOLD, 1) * SELECTIVITY.SORT_PENALTY : 0;
10676
10679
  return effectiveScanCost + sortPenalty;
@@ -10699,9 +10702,19 @@ var Optimizer = class {
10699
10702
  orderByField
10700
10703
  );
10701
10704
  if (candidate) {
10705
+ const hasUncoveredFilters = ![...queryFields].every(
10706
+ (f) => candidate.coveredFields.includes(f)
10707
+ );
10702
10708
  candidates.push({
10703
10709
  ...candidate,
10704
- cost: this.calculateCost(candidate.selectivity, candidate.isIndexOrderSupported, orderByField, N, topK)
10710
+ cost: this.calculateCost(
10711
+ candidate.selectivity,
10712
+ candidate.isIndexOrderSupported,
10713
+ orderByField,
10714
+ N,
10715
+ topK,
10716
+ hasUncoveredFilters
10717
+ )
10705
10718
  });
10706
10719
  }
10707
10720
  } else if (config.type === "fts") {
@@ -10716,7 +10729,14 @@ var Optimizer = class {
10716
10729
  if (candidate) {
10717
10730
  candidates.push({
10718
10731
  ...candidate,
10719
- cost: this.calculateCost(candidate.selectivity, candidate.isIndexOrderSupported, orderByField, N, topK)
10732
+ cost: this.calculateCost(
10733
+ candidate.selectivity,
10734
+ candidate.isIndexOrderSupported,
10735
+ orderByField,
10736
+ N,
10737
+ topK,
10738
+ true
10739
+ )
10720
10740
  });
10721
10741
  }
10722
10742
  }
@@ -11035,12 +11055,24 @@ var QueryManager = class {
11035
11055
  }
11036
11056
  return true;
11037
11057
  }
11038
- adjustChunkSize(currentChunkSize, chunkTotalSize) {
11039
- if (chunkTotalSize <= 0) return currentChunkSize;
11040
- const { verySmallChunkSize, smallChunkSize } = this.getFreeMemoryChunkSize();
11041
- if (chunkTotalSize < verySmallChunkSize) return currentChunkSize * 2;
11042
- if (chunkTotalSize > smallChunkSize) return Math.max(Math.floor(currentChunkSize / 2), 20);
11043
- return currentChunkSize;
11058
+ /**
11059
+ * 최적화 공식: x = x * Math.sqrt(z / n)
11060
+ * @param currentChunkSize 현재 청크 크기
11061
+ * @param matchedCount 매칭된 문서 개수
11062
+ * @param limit 최대 문서 개수
11063
+ * @param chunkTotalSize 청크 내 문서 총 크기
11064
+ * @returns
11065
+ */
11066
+ adjustChunkSize(currentChunkSize, matchedCount, limit, chunkTotalSize) {
11067
+ if (matchedCount <= 0 || chunkTotalSize <= 0) return currentChunkSize;
11068
+ const n = Math.max(matchedCount, 1);
11069
+ const z = isFinite(limit) ? limit : currentChunkSize * 10;
11070
+ const nextChunkSize = Math.ceil(currentChunkSize * Math.sqrt(z / n));
11071
+ const { smallChunkSize } = this.getFreeMemoryChunkSize();
11072
+ const avgDocSize = chunkTotalSize / currentChunkSize;
11073
+ const maxSafeChunkSize = Math.max(Math.floor(smallChunkSize / avgDocSize), 20);
11074
+ const finalChunkSize = Math.max(Math.min(nextChunkSize, maxSafeChunkSize), 20);
11075
+ return finalChunkSize;
11044
11076
  }
11045
11077
  async *processChunkedKeysWithVerify(keysStream, startIdx, initialChunkSize, limit, ftsConditions, compositeVerifyConditions, others, tx) {
11046
11078
  const verifyOthers = others.filter((o) => !o.isFtsMatch);
@@ -11056,6 +11088,7 @@ var QueryManager = class {
11056
11088
  let chunk = [];
11057
11089
  let chunkSize = 0;
11058
11090
  let dropped = 0;
11091
+ let nAccumulated = 0;
11059
11092
  const processChunk = async (pks) => {
11060
11093
  const docs = [];
11061
11094
  const rawResults = await this.api.selectMany(new Float64Array(pks), false, tx);
@@ -11107,8 +11140,9 @@ var QueryManager = class {
11107
11140
  }
11108
11141
  docs.push(doc);
11109
11142
  }
11143
+ nAccumulated += docs.length;
11110
11144
  if (!isReadQuotaLimited) {
11111
- currentChunkSize = this.adjustChunkSize(currentChunkSize, chunkTotalSize);
11145
+ currentChunkSize = this.adjustChunkSize(currentChunkSize, nAccumulated, limit, chunkTotalSize);
11112
11146
  }
11113
11147
  return docs;
11114
11148
  };
@@ -38,6 +38,9 @@ export declare class Optimizer<T extends Record<string, any>> {
38
38
  * 비용 계산: effectiveScanCost + sortPenalty
39
39
  * - effectiveScanCost: 인덱스 순서 지원 + limit 존재 시 조기 종료 이점 반영
40
40
  * - sortPenalty: 인메모리 정렬의 절대 문서 수 기반 비용
41
+ * - hasUncoveredFilters: 드라이버가 커버하지 못하는 비-FTS 필터 존재 여부
42
+ * true일 경우 topK/N 조기 종료를 적용하지 않음
43
+ * (uncovered 필터가 행을 탈락시킬 수 있어 topK개 이상 스캔 필요)
41
44
  */
42
45
  private calculateCost;
43
46
  /**
@@ -45,7 +45,15 @@ export declare class QueryManager<T extends DocumentJSON> {
45
45
  condition: any;
46
46
  }[]): boolean;
47
47
  verifyValue(value: Primitive, condition: any): boolean;
48
- adjustChunkSize(currentChunkSize: number, chunkTotalSize: number): number;
48
+ /**
49
+ * 최적화 공식: x = x * Math.sqrt(z / n)
50
+ * @param currentChunkSize 현재 청크 크기
51
+ * @param matchedCount 매칭된 문서 개수
52
+ * @param limit 최대 문서 개수
53
+ * @param chunkTotalSize 청크 내 문서 총 크기
54
+ * @returns
55
+ */
56
+ adjustChunkSize(currentChunkSize: number, matchedCount: number, limit: number, chunkTotalSize: number): number;
49
57
  processChunkedKeysWithVerify(keysStream: AsyncIterableIterator<number>, startIdx: number, initialChunkSize: number, limit: number, ftsConditions: {
50
58
  field: string;
51
59
  matchTokens: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document-dataply",
3
- "version": "0.0.10-alpha.6",
3
+ "version": "0.0.10-alpha.7",
4
4
  "description": "Simple and powerful JSON document database supporting complex queries and flexible indexing policies.",
5
5
  "license": "MIT",
6
6
  "author": "izure <admin@izure.org>",