perf: accelerate complete wildcard and literal candidate scans
Browse files
app.py
CHANGED
|
@@ -61,12 +61,15 @@ FULLTEXT_MATCH_CACHE_TTL_SECONDS = 300
|
|
| 61 |
FULLTEXT_MATCH_CACHE_MAX_ENTRIES = 16
|
| 62 |
FULLTEXT_MATCH_CACHE_MAX_TOTAL_IDS = 250000
|
| 63 |
WILDCARD_FTS_SCAN_MIN_CANDIDATES = 5000
|
|
|
|
| 64 |
PREVIEW_CACHE_MAX_ENTRIES = 64
|
| 65 |
PREVIEW_CACHE_MAX_ENTRY_BYTES = 2 * 1024 * 1024
|
| 66 |
api_response_cache: dict[tuple, tuple[float, object]] = {}
|
| 67 |
api_response_cache_lock = threading.Lock()
|
| 68 |
search_response_cache: dict[tuple, tuple[float, object]] = {}
|
| 69 |
search_response_cache_lock = threading.Lock()
|
|
|
|
|
|
|
| 70 |
preview_text_cache: dict[str, str] = {}
|
| 71 |
preview_text_cache_lock = threading.Lock()
|
| 72 |
cache_key_locks: dict[tuple, threading.Lock] = {}
|
|
@@ -247,6 +250,8 @@ class FulltextDatabases:
|
|
| 247 |
self.file_signatures[source] = signature
|
| 248 |
self.generation += 1
|
| 249 |
self.match_cache.clear()
|
|
|
|
|
|
|
| 250 |
if old_connection is not None:
|
| 251 |
if self.connection_readers.get(old_connection, 0):
|
| 252 |
self.retired_connections.add(old_connection)
|
|
@@ -287,6 +292,8 @@ class FulltextDatabases:
|
|
| 287 |
with self.lock:
|
| 288 |
self.closed = True
|
| 289 |
self.match_cache.clear()
|
|
|
|
|
|
|
| 290 |
for event in self.match_inflight.values():
|
| 291 |
event.set()
|
| 292 |
self.match_inflight.clear()
|
|
@@ -778,6 +785,8 @@ def load_json_gz(path: Path):
|
|
| 778 |
|
| 779 |
def build_indexes() -> None:
|
| 780 |
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
|
|
|
|
|
|
|
| 781 |
word_index = {}
|
| 782 |
latin_all_index = {}
|
| 783 |
latin_file_index = {}
|
|
@@ -1041,7 +1050,7 @@ def metadata_prefilter(terms: list[str], search_paths: bool) -> set[int] | None:
|
|
| 1041 |
break
|
| 1042 |
return candidates
|
| 1043 |
|
| 1044 |
-
def
|
| 1045 |
field = "_search_text" if search_paths else "_file_search_text"
|
| 1046 |
if exact:
|
| 1047 |
terms = query_terms(q)
|
|
@@ -1077,6 +1086,20 @@ def metadata_matches(q: str, exact: bool, search_paths: bool) -> set[int]:
|
|
| 1077 |
if all(term in records[idx][field] for term in terms)
|
| 1078 |
}
|
| 1079 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1080 |
def search(q="", sources_filter=None, folders=None, min_size=None, max_size=None, page=1, page_size=100, sort="relevance", exact=False, search_paths=True, timings=None):
|
| 1081 |
started = time.perf_counter()
|
| 1082 |
q = q.strip()
|
|
|
|
| 61 |
FULLTEXT_MATCH_CACHE_MAX_ENTRIES = 16
|
| 62 |
FULLTEXT_MATCH_CACHE_MAX_TOTAL_IDS = 250000
|
| 63 |
WILDCARD_FTS_SCAN_MIN_CANDIDATES = 5000
|
| 64 |
+
METADATA_MATCH_CACHE_MAX_ENTRIES = 128
|
| 65 |
PREVIEW_CACHE_MAX_ENTRIES = 64
|
| 66 |
PREVIEW_CACHE_MAX_ENTRY_BYTES = 2 * 1024 * 1024
|
| 67 |
api_response_cache: dict[tuple, tuple[float, object]] = {}
|
| 68 |
api_response_cache_lock = threading.Lock()
|
| 69 |
search_response_cache: dict[tuple, tuple[float, object]] = {}
|
| 70 |
search_response_cache_lock = threading.Lock()
|
| 71 |
+
metadata_match_cache: dict[tuple, frozenset[int]] = {}
|
| 72 |
+
metadata_match_cache_lock = threading.Lock()
|
| 73 |
preview_text_cache: dict[str, str] = {}
|
| 74 |
preview_text_cache_lock = threading.Lock()
|
| 75 |
cache_key_locks: dict[tuple, threading.Lock] = {}
|
|
|
|
| 250 |
self.file_signatures[source] = signature
|
| 251 |
self.generation += 1
|
| 252 |
self.match_cache.clear()
|
| 253 |
+
with metadata_match_cache_lock:
|
| 254 |
+
metadata_match_cache.clear()
|
| 255 |
if old_connection is not None:
|
| 256 |
if self.connection_readers.get(old_connection, 0):
|
| 257 |
self.retired_connections.add(old_connection)
|
|
|
|
| 292 |
with self.lock:
|
| 293 |
self.closed = True
|
| 294 |
self.match_cache.clear()
|
| 295 |
+
with metadata_match_cache_lock:
|
| 296 |
+
metadata_match_cache.clear()
|
| 297 |
for event in self.match_inflight.values():
|
| 298 |
event.set()
|
| 299 |
self.match_inflight.clear()
|
|
|
|
| 785 |
|
| 786 |
def build_indexes() -> None:
|
| 787 |
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
|
| 788 |
+
with metadata_match_cache_lock:
|
| 789 |
+
metadata_match_cache.clear()
|
| 790 |
word_index = {}
|
| 791 |
latin_all_index = {}
|
| 792 |
latin_file_index = {}
|
|
|
|
| 1050 |
break
|
| 1051 |
return candidates
|
| 1052 |
|
| 1053 |
+
def _metadata_matches_uncached(q: str, exact: bool, search_paths: bool) -> set[int]:
|
| 1054 |
field = "_search_text" if search_paths else "_file_search_text"
|
| 1055 |
if exact:
|
| 1056 |
terms = query_terms(q)
|
|
|
|
| 1086 |
if all(term in records[idx][field] for term in terms)
|
| 1087 |
}
|
| 1088 |
|
| 1089 |
+
|
| 1090 |
+
def metadata_matches(q: str, exact: bool, search_paths: bool) -> set[int]:
|
| 1091 |
+
key = (q, exact, search_paths)
|
| 1092 |
+
with metadata_match_cache_lock:
|
| 1093 |
+
cached = metadata_match_cache.get(key)
|
| 1094 |
+
if cached is not None:
|
| 1095 |
+
return set(cached)
|
| 1096 |
+
result = _metadata_matches_uncached(q, exact, search_paths)
|
| 1097 |
+
with metadata_match_cache_lock:
|
| 1098 |
+
metadata_match_cache[key] = frozenset(result)
|
| 1099 |
+
while len(metadata_match_cache) > METADATA_MATCH_CACHE_MAX_ENTRIES:
|
| 1100 |
+
metadata_match_cache.pop(next(iter(metadata_match_cache)), None)
|
| 1101 |
+
return set(result)
|
| 1102 |
+
|
| 1103 |
def search(q="", sources_filter=None, folders=None, min_size=None, max_size=None, page=1, page_size=100, sort="relevance", exact=False, search_paths=True, timings=None):
|
| 1104 |
started = time.perf_counter()
|
| 1105 |
q = q.strip()
|