fix: postgres/neon connection settings and search index dedupe · basicmachines-co/basic-memory@b5d4fb5 · GitHub
Skip to content

Commit b5d4fb5

Browse files
phernandezclaude
andcommitted
fix: postgres/neon connection settings and search index dedupe
- Reduce db_pool_recycle from 3600s to 180s for Neon scale-to-zero - Add connect_args for Neon serverless (statement cache, timeouts, app name) - Dedupe observation permalinks in search indexing to avoid unique constraint violations - Add tests for duplicate observation permalink handling 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 8307752 commit b5d4fb5

4 files changed

Lines changed: 205 additions & 4 deletions

File tree

src/basic_memory/config.py

Lines changed: 2 additions & 2 deletions

src/basic_memory/db.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,27 @@ def _create_postgres_engine(db_url: str, config: BasicMemoryConfig) -> AsyncEngi
201201
Configured async engine for Postgres
202202
"""
203203
# Postgres with asyncpg - pool sized for concurrent operations
204+
# connect_args tuned for Neon serverless which scales to zero after ~5 minutes
204205
engine = create_async_engine(
205206
db_url,
206207
echo=False,
207208
pool_pre_ping=True, # Verify connections before using them
208209
pool_size=config.db_pool_size,
209210
max_overflow=config.db_pool_overflow,
210211
pool_recycle=config.db_pool_recycle,
212+
connect_args={
213+
# Disable statement cache to avoid issues with prepared statements on reconnect
214+
"statement_cache_size": 0,
215+
# Allow 10s for commands (Neon cold start can take 2-5s)
216+
"command_timeout": 10,
217+
# Allow 10s for initial connection (Neon wake-up time)
218+
"timeout": 10,
219+
"server_settings": {
220+
"application_name": "basic-memory",
221+
# Statement timeout for queries (10s to allow for cold start)
222+
"statement_timeout": "10s",
223+
},
224+
},
211225
)
212226
logger.debug(
213227
f"Created Postgres engine with pool_size={config.db_pool_size}, "

src/basic_memory/services/search_service.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,16 @@ async def index_entity_markdown(
284284
)
285285
)
286286

287-
# Add observation rows
287+
# Add observation rows - dedupe by permalink to avoid unique constraint violations
288+
# Two observations with same entity/category/content generate identical permalinks
289+
seen_permalinks: set[str] = {entity.permalink} if entity.permalink else set()
288290
for obs in entity.observations:
291+
obs_permalink = obs.permalink
292+
if obs_permalink in seen_permalinks:
293+
logger.debug(f"Skipping duplicate observation permalink: {obs_permalink}")
294+
continue
295+
seen_permalinks.add(obs_permalink)
296+
289297
# Index with parent entity's file path since that's where it's defined
290298
obs_content_stems = "\n".join(
291299
p for p in self._generate_variants(obs.content) if p and p.strip()
@@ -297,7 +305,7 @@ async def index_entity_markdown(
297305
title=f"{obs.category}: {obs.content[:100]}...",
298306
content_stems=obs_content_stems,
299307
content_snippet=obs.content,
300-
permalink=obs.permalink,
308+
permalink=obs_permalink,
301309
file_path=entity.file_path,
302310
category=obs.category,
303311
entity_id=entity.id,

tests/services/test_search_service.py

Lines changed: 179 additions & 0 deletions

0 commit comments

Comments
 (0)