Problem
to_search_results() in src/basic_memory/api/v2/utils.py makes a separate get_entities_by_id() call per search result, creating an N+1 query pattern. On Basic Memory Cloud where each DB round-trip goes to Neon PostgreSQL (~300ms network latency), this adds up fast.
For 10 results: 10 sequential round-trips × ~300ms = ~3s just to hydrate.
Evidence
Logfire data from today (60 search calls):
| Phase |
Avg |
P50 |
P95 |
Max |
| execute_query |
1.0s |
0.96s |
1.81s |
5.76s |
| hydrate_results |
0.98s |
0.75s |
2.88s |
3.37s |
| paginate_results |
0.001s |
0s |
0.001s |
0.003s |
| build_response |
0.001s |
0.001s |
0.001s |
0.003s |
Drilling into the slowest trace (019d49b0b62a1d112df71e20ec93aeb3):
hydrate_results: 3.371s total
fetch_entities: 3.367s (10 sequential DB calls)
shape_results: 0.001s
Root Cause
src/basic_memory/api/v2/utils.py lines 188-192:
for result in results:
entity_batches.append(
await entity_service.get_entities_by_id(
[result.entity_id, result.from_id, result.to_id]
)
)
Each iteration awaits a separate DB query. With Neon's network latency this is ~300ms per result.
Proposed Fix
Collect all unique entity IDs upfront, fetch them in a single find_by_ids call, then index into the result dict:
# Collect all entity IDs across all results
all_ids = set()
for result in results:
for eid in (result.entity_id, result.from_id, result.to_id):
if eid is not None:
all_ids.add(eid)
# Single batch fetch
entities_by_id = {e.id: e for e in await entity_service.get_entities_by_id(list(all_ids))}
# Build results using the lookup dict
for result in results:
entities = [entities_by_id[eid] for eid in (result.entity_id, result.from_id, result.to_id) if eid in entities_by_id]
...
This turns 10 round-trips into 1 (~300ms instead of ~3s).
Impact
Search is the slowest MCP tool on cloud (P50 ~3-6s). This fix would roughly halve search latency by eliminating ~1s avg / ~3s worst-case from hydration.
Problem
to_search_results()insrc/basic_memory/api/v2/utils.pymakes a separateget_entities_by_id()call per search result, creating an N+1 query pattern. On Basic Memory Cloud where each DB round-trip goes to Neon PostgreSQL (~300ms network latency), this adds up fast.For 10 results: 10 sequential round-trips × ~300ms = ~3s just to hydrate.
Evidence
Logfire data from today (60 search calls):
Drilling into the slowest trace (
019d49b0b62a1d112df71e20ec93aeb3):hydrate_results: 3.371s totalfetch_entities: 3.367s (10 sequential DB calls)shape_results: 0.001sRoot Cause
src/basic_memory/api/v2/utils.pylines 188-192:Each iteration awaits a separate DB query. With Neon's network latency this is ~300ms per result.
Proposed Fix
Collect all unique entity IDs upfront, fetch them in a single
find_by_idscall, then index into the result dict:This turns 10 round-trips into 1 (~300ms instead of ~3s).
Impact
Search is the slowest MCP tool on cloud (P50 ~3-6s). This fix would roughly halve search latency by eliminating ~1s avg / ~3s worst-case from hydration.