Skip to content

Search hydrate_results N+1 query causes 3s+ latency on cloud #707

@phernandez

Description

@phernandez

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.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingcloudBasic Memory CloudenhancementNew feature or request

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions