perf: reduce shallow search work and reuse exact counts across pages
Browse files
app.py
CHANGED
|
@@ -38,6 +38,7 @@ source_name_order: dict[str, list[int]] = {}
|
|
| 38 |
source_size_order: dict[str, list[int]] = {}
|
| 39 |
record_map: dict[str, dict] = {}
|
| 40 |
record_map_index: dict[str, int] = {}
|
|
|
|
| 41 |
sources: list[dict] = []
|
| 42 |
source_counts: dict[str, int] = {}
|
| 43 |
folder_tree_data: dict[str, list[dict]] = {}
|
|
@@ -790,17 +791,26 @@ def load_json_gz(path: Path):
|
|
| 790 |
return json.loads(gzip.decompress(path.read_bytes()).decode("utf-8"))
|
| 791 |
|
| 792 |
def build_indexes() -> None:
|
| 793 |
-
global word_index, latin_all_index, latin_file_index, latin_vocabulary_all, source_records_map, extension_counts, source_extension_counts, name_order, size_order, source_name_order, source_size_order
|
| 794 |
clear_metadata_match_cache()
|
| 795 |
word_index = {}
|
| 796 |
latin_all_index = {}
|
| 797 |
latin_file_index = {}
|
| 798 |
latin_vocabulary_all = set()
|
| 799 |
source_records_map = {}
|
|
|
|
| 800 |
extension_counts = {}
|
| 801 |
source_extension_counts = {}
|
| 802 |
for idx, rec in enumerate(records):
|
| 803 |
source = rec["source"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 804 |
extension = rec["extension"]
|
| 805 |
source_records_map.setdefault(source, []).append(idx)
|
| 806 |
extension_counts[extension] = extension_counts.get(extension, 0) + 1
|
|
@@ -870,10 +880,26 @@ def relevance_page(indices, query_tokens: list[str], search_paths: bool, start:
|
|
| 870 |
limit = start + page_size
|
| 871 |
if limit <= 0:
|
| 872 |
return []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 873 |
if start >= 1000:
|
| 874 |
-
indices = list(indices)
|
| 875 |
-
if start >= len(indices):
|
| 876 |
-
return []
|
| 877 |
if len(indices) >= 10000:
|
| 878 |
scores = {idx: score_record(idx, query_tokens, search_paths) for idx in indices}
|
| 879 |
if len(scores) == len(indices):
|
|
@@ -1177,13 +1203,16 @@ def fulltext_search(q="", sources_filter=None, folders=None, min_size=None, max_
|
|
| 1177 |
source_slugs = sources_filter if sources_filter is not None else list(source_counts)
|
| 1178 |
if not source_slugs:
|
| 1179 |
return {"results": [], "total": 0, "page": max(1, page), "page_size": page_size}
|
| 1180 |
-
|
| 1181 |
sqlite_started = time.perf_counter()
|
| 1182 |
for source_slug in source_slugs:
|
| 1183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1184 |
if timings is not None:
|
| 1185 |
timings["sqlite"] = (time.perf_counter() - sqlite_started) * 1000
|
| 1186 |
-
content_indices = {idx for doc_id in matched_doc_ids if (idx := record_map_index.get(doc_id)) is not None}
|
| 1187 |
indices = set(content_indices)
|
| 1188 |
metadata_started = time.perf_counter()
|
| 1189 |
indices.update(metadata_matches(q, exact, search_paths))
|
|
|
|
| 38 |
source_size_order: dict[str, list[int]] = {}
|
| 39 |
record_map: dict[str, dict] = {}
|
| 40 |
record_map_index: dict[str, int] = {}
|
| 41 |
+
source_doc_number_index: dict[str, dict[int, int]] = {}
|
| 42 |
sources: list[dict] = []
|
| 43 |
source_counts: dict[str, int] = {}
|
| 44 |
folder_tree_data: dict[str, list[dict]] = {}
|
|
|
|
| 791 |
return json.loads(gzip.decompress(path.read_bytes()).decode("utf-8"))
|
| 792 |
|
| 793 |
def build_indexes() -> None:
|
| 794 |
+
global word_index, latin_all_index, latin_file_index, latin_vocabulary_all, source_records_map, source_doc_number_index, extension_counts, source_extension_counts, name_order, size_order, source_name_order, source_size_order
|
| 795 |
clear_metadata_match_cache()
|
| 796 |
word_index = {}
|
| 797 |
latin_all_index = {}
|
| 798 |
latin_file_index = {}
|
| 799 |
latin_vocabulary_all = set()
|
| 800 |
source_records_map = {}
|
| 801 |
+
source_doc_number_index = {}
|
| 802 |
extension_counts = {}
|
| 803 |
source_extension_counts = {}
|
| 804 |
for idx, rec in enumerate(records):
|
| 805 |
source = rec["source"]
|
| 806 |
+
id_source, separator, number_text = rec["doc_id"].partition(":")
|
| 807 |
+
try:
|
| 808 |
+
number = int(number_text)
|
| 809 |
+
except ValueError:
|
| 810 |
+
pass
|
| 811 |
+
else:
|
| 812 |
+
if separator and str(number) == number_text:
|
| 813 |
+
source_doc_number_index.setdefault(id_source, {})[number] = idx
|
| 814 |
extension = rec["extension"]
|
| 815 |
source_records_map.setdefault(source, []).append(idx)
|
| 816 |
extension_counts[extension] = extension_counts.get(extension, 0) + 1
|
|
|
|
| 880 |
limit = start + page_size
|
| 881 |
if limit <= 0:
|
| 882 |
return []
|
| 883 |
+
indices = list(indices)
|
| 884 |
+
if start >= len(indices):
|
| 885 |
+
return []
|
| 886 |
+
if start < 1000 and len(indices) >= 10000 and query_tokens and all(
|
| 887 |
+
re.fullmatch(r"[\u4e00-\u9fff\u3400-\u4dbf]{1,2}", token) for token in query_tokens
|
| 888 |
+
):
|
| 889 |
+
potential = metadata_prefilter(query_tokens, search_paths)
|
| 890 |
+
if potential is not None and len(potential) >= limit:
|
| 891 |
+
candidates = set(indices)
|
| 892 |
+
potential.intersection_update(candidates)
|
| 893 |
+
if len(candidates) == len(indices) and len(potential) >= limit:
|
| 894 |
+
# Only maximum-score hits can make unseen candidates irrelevant.
|
| 895 |
+
maximum = (5 if search_paths else 3) * len(query_tokens)
|
| 896 |
+
highest = []
|
| 897 |
+
for idx in name_order:
|
| 898 |
+
if idx in potential and score_record(idx, query_tokens, search_paths) == maximum:
|
| 899 |
+
highest.append(idx)
|
| 900 |
+
if len(highest) == limit:
|
| 901 |
+
return highest[start:limit]
|
| 902 |
if start >= 1000:
|
|
|
|
|
|
|
|
|
|
| 903 |
if len(indices) >= 10000:
|
| 904 |
scores = {idx: score_record(idx, query_tokens, search_paths) for idx in indices}
|
| 905 |
if len(scores) == len(indices):
|
|
|
|
| 1203 |
source_slugs = sources_filter if sources_filter is not None else list(source_counts)
|
| 1204 |
if not source_slugs:
|
| 1205 |
return {"results": [], "total": 0, "page": max(1, page), "page_size": page_size}
|
| 1206 |
+
content_indices = set()
|
| 1207 |
sqlite_started = time.perf_counter()
|
| 1208 |
for source_slug in source_slugs:
|
| 1209 |
+
number_index = source_doc_number_index.get(source_slug, {})
|
| 1210 |
+
content_indices.update(
|
| 1211 |
+
idx for doc_number in fulltext_databases.search_source(source_slug, q, exact)
|
| 1212 |
+
if (idx := number_index.get(doc_number)) is not None
|
| 1213 |
+
)
|
| 1214 |
if timings is not None:
|
| 1215 |
timings["sqlite"] = (time.perf_counter() - sqlite_started) * 1000
|
|
|
|
| 1216 |
indices = set(content_indices)
|
| 1217 |
metadata_started = time.perf_counter()
|
| 1218 |
indices.update(metadata_matches(q, exact, search_paths))
|