Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 2 additions & 25 deletions hive/server/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,6 @@

CACHE_NAMESPACE = "hivemind"

# Per-query execution timeout (milliseconds). PostgreSQL cancels any statement
# running longer than this, immediately releasing the connection back to the
# pool. This is the safety net that prevents a single pathological query (e.g.
# 160s get_discussion lookups) from exhausting the aiopg pool. The pool's
# acquire `timeout` only bounds how long we wait for a free connection, NOT
# query execution time. See connection-pool-exhaustion incident.
STATEMENT_TIMEOUT_MS = 30000 # 30 seconds

# Sentinel value to represent 'record not found' in cache.
# Using a string marker that can be easily serialized/deserialized.
# This allows us to distinguish between:
Expand Down Expand Up @@ -105,29 +97,14 @@ def __init__(self):
async def init(self, url, redis_url, pool_size=20):
"""Initialize the aiopg.sa engine."""
conf = make_url(url)
# statement_timeout (ms) cancels runaway queries server-side so a single
# slow query cannot hold a pool connection indefinitely. See note on
# STATEMENT_TIMEOUT_MS above. Passed via the standard libpq `options`
# string (every psycopg2/libpq version accepts this); the newer
# `server_settings` dict is rejected as an invalid DSN option by the
# older psycopg2 shipped in the runtime image. Merge into conf.query
# (rather than a separate kwarg) so a DATABASE_URL that already carries
# an `options=...` query param does not cause a duplicate-keyword error;
# any pre-existing options string is preserved and appended to.
query = dict(conf.query)
timeout_opt = '-c statement_timeout=%d' % STATEMENT_TIMEOUT_MS
if 'options' in query and query['options']:
query['options'] = query['options'] + ' ' + timeout_opt
else:
query['options'] = timeout_opt
self.db = await create_engine(user=conf.username,
database=conf.database,
password=conf.password,
host=conf.host,
port=conf.port,
maxsize=pool_size,
timeout=10,
**query)
**conf.query)
# Lightweight isolated engine (1 connection) for health checks. A short
# acquire timeout keeps health checks responsive instead of blocking.
self.health_db = await create_engine(user=conf.username,
Expand All @@ -137,7 +114,7 @@ async def init(self, url, redis_url, pool_size=20):
port=conf.port,
maxsize=1,
timeout=2,
**query)
**conf.query)
if redis_url is not None:
self.redis_cache = Cache.from_url(redis_url)
self.redis_cache.serializer = SafeUniversalSerializer()
Expand Down
Loading