From 33fac2c5e2b9130fe939754a8c0308d014476ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Thu, 8 Jan 2026 16:37:08 +0100 Subject: [PATCH] feat: add configs for database connection (#128) --- hindsight-api/hindsight_api/config.py | 38 +++++++++++++++++++ .../hindsight_api/engine/memory_engine.py | 32 +++++++++++----- hindsight-api/hindsight_api/main.py | 6 +++ .../docs/developer/configuration.md | 20 ++++++++++ 4 files changed, 87 insertions(+), 9 deletions(-) diff --git a/hindsight-api/hindsight_api/config.py b/hindsight-api/hindsight_api/config.py index 06902f0b..bdfd3fef 100644 --- a/hindsight-api/hindsight_api/config.py +++ b/hindsight-api/hindsight_api/config.py @@ -73,6 +73,16 @@ ENV_LAZY_RERANKER = "HINDSIGHT_API_LAZY_RERANKER" # Database migrations ENV_RUN_MIGRATIONS_ON_STARTUP = "HINDSIGHT_API_RUN_MIGRATIONS_ON_STARTUP" +# Database connection pool +ENV_DB_POOL_MIN_SIZE = "HINDSIGHT_API_DB_POOL_MIN_SIZE" +ENV_DB_POOL_MAX_SIZE = "HINDSIGHT_API_DB_POOL_MAX_SIZE" +ENV_DB_COMMAND_TIMEOUT = "HINDSIGHT_API_DB_COMMAND_TIMEOUT" +ENV_DB_ACQUIRE_TIMEOUT = "HINDSIGHT_API_DB_ACQUIRE_TIMEOUT" + +# Background task processing +ENV_TASK_BATCH_SIZE = "HINDSIGHT_API_TASK_BATCH_SIZE" +ENV_TASK_BATCH_INTERVAL = "HINDSIGHT_API_TASK_BATCH_INTERVAL" + # Default values DEFAULT_DATABASE_URL = "pg0" DEFAULT_LLM_PROVIDER = "openai" @@ -109,6 +119,16 @@ DEFAULT_RETAIN_CHUNK_SIZE = 3000 # Max chars per chunk for fact extraction # Database migrations DEFAULT_RUN_MIGRATIONS_ON_STARTUP = True +# Database connection pool +DEFAULT_DB_POOL_MIN_SIZE = 5 +DEFAULT_DB_POOL_MAX_SIZE = 100 +DEFAULT_DB_COMMAND_TIMEOUT = 60 # seconds +DEFAULT_DB_ACQUIRE_TIMEOUT = 30 # seconds + +# Background task processing +DEFAULT_TASK_BATCH_SIZE = 10 +DEFAULT_TASK_BATCH_INTERVAL = 1.0 # seconds + # Default MCP tool descriptions (can be customized via env vars) DEFAULT_MCP_RETAIN_DESCRIPTION = """Store important information to long-term memory. @@ -193,6 +213,16 @@ class HindsightConfig: # Database migrations run_migrations_on_startup: bool + # Database connection pool + db_pool_min_size: int + db_pool_max_size: int + db_command_timeout: int + db_acquire_timeout: int + + # Background task processing + task_batch_size: int + task_batch_interval: float + @classmethod def from_env(cls) -> "HindsightConfig": """Create configuration from environment variables.""" @@ -245,6 +275,14 @@ class HindsightConfig: retain_chunk_size=int(os.getenv(ENV_RETAIN_CHUNK_SIZE, str(DEFAULT_RETAIN_CHUNK_SIZE))), # Database migrations run_migrations_on_startup=os.getenv(ENV_RUN_MIGRATIONS_ON_STARTUP, "true").lower() == "true", + # Database connection pool + db_pool_min_size=int(os.getenv(ENV_DB_POOL_MIN_SIZE, str(DEFAULT_DB_POOL_MIN_SIZE))), + db_pool_max_size=int(os.getenv(ENV_DB_POOL_MAX_SIZE, str(DEFAULT_DB_POOL_MAX_SIZE))), + db_command_timeout=int(os.getenv(ENV_DB_COMMAND_TIMEOUT, str(DEFAULT_DB_COMMAND_TIMEOUT))), + db_acquire_timeout=int(os.getenv(ENV_DB_ACQUIRE_TIMEOUT, str(DEFAULT_DB_ACQUIRE_TIMEOUT))), + # Background task processing + task_batch_size=int(os.getenv(ENV_TASK_BATCH_SIZE, str(DEFAULT_TASK_BATCH_SIZE))), + task_batch_interval=float(os.getenv(ENV_TASK_BATCH_INTERVAL, str(DEFAULT_TASK_BATCH_INTERVAL))), ) def get_llm_base_url(self) -> str: diff --git a/hindsight-api/hindsight_api/engine/memory_engine.py b/hindsight-api/hindsight_api/engine/memory_engine.py index 684c5877..2d11fb6e 100644 --- a/hindsight-api/hindsight_api/engine/memory_engine.py +++ b/hindsight-api/hindsight_api/engine/memory_engine.py @@ -215,9 +215,13 @@ class MemoryEngine(MemoryEngineInterface): embeddings: Embeddings | None = None, cross_encoder: CrossEncoderModel | None = None, query_analyzer: QueryAnalyzer | None = None, - pool_min_size: int = 5, - pool_max_size: int = 100, + pool_min_size: int | None = None, + pool_max_size: int | None = None, + db_command_timeout: int | None = None, + db_acquire_timeout: int | None = None, task_backend: TaskBackend | None = None, + task_batch_size: int | None = None, + task_batch_interval: float | None = None, run_migrations: bool = True, operation_validator: "OperationValidatorExtension | None" = None, tenant_extension: "TenantExtension | None" = None, @@ -248,9 +252,13 @@ class MemoryEngine(MemoryEngineInterface): embeddings: Embeddings implementation. If not provided, created from env vars. cross_encoder: Cross-encoder model. If not provided, created from env vars. query_analyzer: Query analyzer implementation. If not provided, uses DateparserQueryAnalyzer. - pool_min_size: Minimum number of connections in the pool (default: 5) - pool_max_size: Maximum number of connections in the pool (default: 100) + pool_min_size: Minimum number of connections in the pool. Defaults to HINDSIGHT_API_DB_POOL_MIN_SIZE. + pool_max_size: Maximum number of connections in the pool. Defaults to HINDSIGHT_API_DB_POOL_MAX_SIZE. + db_command_timeout: PostgreSQL command timeout in seconds. Defaults to HINDSIGHT_API_DB_COMMAND_TIMEOUT. + db_acquire_timeout: Connection acquisition timeout in seconds. Defaults to HINDSIGHT_API_DB_ACQUIRE_TIMEOUT. task_backend: Custom task backend. If not provided, uses AsyncIOQueueBackend. + task_batch_size: Background task batch size. Defaults to HINDSIGHT_API_TASK_BATCH_SIZE. + task_batch_interval: Background task batch interval in seconds. Defaults to HINDSIGHT_API_TASK_BATCH_INTERVAL. run_migrations: Whether to run database migrations during initialize(). Default: True operation_validator: Optional extension to validate operations before execution. If provided, retain/recall/reflect operations will be validated. @@ -306,8 +314,10 @@ class MemoryEngine(MemoryEngineInterface): # Connection pool (will be created in initialize()) self._pool = None self._initialized = False - self._pool_min_size = pool_min_size - self._pool_max_size = pool_max_size + self._pool_min_size = pool_min_size if pool_min_size is not None else config.db_pool_min_size + self._pool_max_size = pool_max_size if pool_max_size is not None else config.db_pool_max_size + self._db_command_timeout = db_command_timeout if db_command_timeout is not None else config.db_command_timeout + self._db_acquire_timeout = db_acquire_timeout if db_acquire_timeout is not None else config.db_acquire_timeout self._run_migrations = run_migrations # Initialize entity resolver (will be created in initialize()) @@ -386,7 +396,11 @@ class MemoryEngine(MemoryEngineInterface): self._cross_encoder_reranker = CrossEncoderReranker(cross_encoder=cross_encoder) # Initialize task backend - self._task_backend = task_backend or AsyncIOQueueBackend(batch_size=100, batch_interval=1.0) + _task_batch_size = task_batch_size if task_batch_size is not None else config.task_batch_size + _task_batch_interval = task_batch_interval if task_batch_interval is not None else config.task_batch_interval + self._task_backend = task_backend or AsyncIOQueueBackend( + batch_size=_task_batch_size, batch_interval=_task_batch_interval + ) # Backpressure mechanism: limit concurrent searches to prevent overwhelming the database # Limit concurrent searches to prevent connection pool exhaustion @@ -731,9 +745,9 @@ class MemoryEngine(MemoryEngineInterface): self.db_url, min_size=self._pool_min_size, max_size=self._pool_max_size, - command_timeout=60, + command_timeout=self._db_command_timeout, statement_cache_size=0, # Disable prepared statement cache - timeout=30, # Connection acquisition timeout (seconds) + timeout=self._db_acquire_timeout, # Connection acquisition timeout (seconds) ) # Initialize entity resolver with pool diff --git a/hindsight-api/hindsight_api/main.py b/hindsight-api/hindsight_api/main.py index 4c74170e..e71ed1a5 100644 --- a/hindsight-api/hindsight_api/main.py +++ b/hindsight-api/hindsight_api/main.py @@ -197,6 +197,12 @@ def main(): skip_llm_verification=config.skip_llm_verification, lazy_reranker=config.lazy_reranker, run_migrations_on_startup=config.run_migrations_on_startup, + db_pool_min_size=config.db_pool_min_size, + db_pool_max_size=config.db_pool_max_size, + db_command_timeout=config.db_command_timeout, + db_acquire_timeout=config.db_acquire_timeout, + task_batch_size=config.task_batch_size, + task_batch_interval=config.task_batch_interval, ) config.configure_logging() if not args.daemon: diff --git a/hindsight-docs/docs/developer/configuration.md b/hindsight-docs/docs/developer/configuration.md index 1ed70e61..75e74e9b 100644 --- a/hindsight-docs/docs/developer/configuration.md +++ b/hindsight-docs/docs/developer/configuration.md @@ -24,6 +24,17 @@ The API service handles all memory operations (retain, recall, reflect). If not provided, the server uses embedded `pg0` — convenient for development but not recommended for production. +### Database Connection Pool + +| Variable | Description | Default | +|----------|-------------|---------| +| `HINDSIGHT_API_DB_POOL_MIN_SIZE` | Minimum connections in the pool | `5` | +| `HINDSIGHT_API_DB_POOL_MAX_SIZE` | Maximum connections in the pool | `100` | +| `HINDSIGHT_API_DB_COMMAND_TIMEOUT` | PostgreSQL command timeout in seconds | `60` | +| `HINDSIGHT_API_DB_ACQUIRE_TIMEOUT` | Connection acquisition timeout in seconds | `30` | + +For high-concurrency workloads, increase `DB_POOL_MAX_SIZE`. Each concurrent recall/think operation can use 2-4 connections. + To run migrations manually (e.g., before starting the API), use the admin CLI: ```bash @@ -266,6 +277,15 @@ Configuration for the local MCP server (`hindsight-local-mcp` command). export HINDSIGHT_API_MCP_INSTRUCTIONS="Also store every action you take, including tool calls and decisions made." ``` +### Background Tasks + +Controls background task processing for async operations like opinion formation and entity observations. + +| Variable | Description | Default | +|----------|-------------|---------| +| `HINDSIGHT_API_TASK_BATCH_SIZE` | Max tasks to process in one batch | `10` | +| `HINDSIGHT_API_TASK_BATCH_INTERVAL` | Interval between batch processing in seconds | `1.0` | + ### Performance Optimization | Variable | Description | Default |