mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
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>
This commit is contained in:
@@ -112,8 +112,8 @@ class BasicMemoryConfig(BaseSettings):
|
||||
gt=0,
|
||||
)
|
||||
db_pool_recycle: int = Field(
|
||||
default=3600,
|
||||
description="Recycle connections after N seconds to prevent stale connections (Postgres only)",
|
||||
default=180,
|
||||
description="Recycle connections after N seconds to prevent stale connections. Default 180s works well with Neon's ~5 minute scale-to-zero (Postgres only)",
|
||||
gt=0,
|
||||
)
|
||||
|
||||
|
||||
@@ -201,6 +201,7 @@ def _create_postgres_engine(db_url: str, config: BasicMemoryConfig) -> AsyncEngi
|
||||
Configured async engine for Postgres
|
||||
"""
|
||||
# Postgres with asyncpg - pool sized for concurrent operations
|
||||
# connect_args tuned for Neon serverless which scales to zero after ~5 minutes
|
||||
engine = create_async_engine(
|
||||
db_url,
|
||||
echo=False,
|
||||
@@ -208,6 +209,19 @@ def _create_postgres_engine(db_url: str, config: BasicMemoryConfig) -> AsyncEngi
|
||||
pool_size=config.db_pool_size,
|
||||
max_overflow=config.db_pool_overflow,
|
||||
pool_recycle=config.db_pool_recycle,
|
||||
connect_args={
|
||||
# Disable statement cache to avoid issues with prepared statements on reconnect
|
||||
"statement_cache_size": 0,
|
||||
# Allow 10s for commands (Neon cold start can take 2-5s)
|
||||
"command_timeout": 10,
|
||||
# Allow 10s for initial connection (Neon wake-up time)
|
||||
"timeout": 10,
|
||||
"server_settings": {
|
||||
"application_name": "basic-memory",
|
||||
# Statement timeout for queries (10s to allow for cold start)
|
||||
"statement_timeout": "10s",
|
||||
},
|
||||
},
|
||||
)
|
||||
logger.debug(
|
||||
f"Created Postgres engine with pool_size={config.db_pool_size}, "
|
||||
|
||||
@@ -284,8 +284,16 @@ class SearchService:
|
||||
)
|
||||
)
|
||||
|
||||
# Add observation rows
|
||||
# Add observation rows - dedupe by permalink to avoid unique constraint violations
|
||||
# Two observations with same entity/category/content generate identical permalinks
|
||||
seen_permalinks: set[str] = {entity.permalink} if entity.permalink else set()
|
||||
for obs in entity.observations:
|
||||
obs_permalink = obs.permalink
|
||||
if obs_permalink in seen_permalinks:
|
||||
logger.debug(f"Skipping duplicate observation permalink: {obs_permalink}")
|
||||
continue
|
||||
seen_permalinks.add(obs_permalink)
|
||||
|
||||
# Index with parent entity's file path since that's where it's defined
|
||||
obs_content_stems = "\n".join(
|
||||
p for p in self._generate_variants(obs.content) if p and p.strip()
|
||||
@@ -297,7 +305,7 @@ class SearchService:
|
||||
title=f"{obs.category}: {obs.content[:100]}...",
|
||||
content_stems=obs_content_stems,
|
||||
content_snippet=obs.content,
|
||||
permalink=obs.permalink,
|
||||
permalink=obs_permalink,
|
||||
file_path=entity.file_path,
|
||||
category=obs.category,
|
||||
entity_id=entity.id,
|
||||
|
||||
Reference in New Issue
Block a user