feat: #401 background connection keep-alive for relational sink#448
Merged
Sadeequ merged 1 commit intoJun 2, 2026
Merged
Conversation
|
@Fury03 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat: #401 background connection keep-alive for relational sink
Summary
Adds
src/database/connection.pywith aConnectionKeepAliveclass that runs a low-overhead background heartbeat (SELECT 1;) on a fixed interval (default 30s) to keep a long-lived relational connection warm during quiet, low-volume market windows.Motivation
Closes #401. Serverless / autoscaled Postgres sinks and intermediary poolers (e.g. PgBouncer) commonly drop idle TCP connections after a short timeout. When the next price record arrives, the first write stalls waiting for a fresh TCP handshake and re-authentication. A periodic heartbeat keeps the channel active so the write path never pays the reconnect cost.
Implementation
ConnectionKeepAlivemirrors the conventions already in this directory: the threading idiom (daemon thread + lock +threading.Event) followsbatch_sink.py, and the connection style follows thepsycopg2/PostgreSQL usage incleanup_job.py.intervalseconds and issuesSELECT 1;.stop()signals the thread via athreading.Event, so shutdown is prompt and does not wait out the full interval.False) so a transient drop never kills the background loop — the next tick simply retries.start()is idempotent; invalid arguments (Noneconnection, non-positive interval) are rejected.Note on the SQLite vs. Postgres mismatch
The keep-alive is written connection-agnostic: it accepts any DB-API 2.0 connection exposing
cursor(). This is deliberate, because the database layer in this repo is currently inconsistent —cleanup_job.pyusespsycopg2/PostgreSQL (where idle-connection drops are a real phenomenon and this heartbeat is meaningful), whilemodels.pyandbatch_sink.pyusesqlite3(a local file with no socket to keep open, where the ping is harmless but inert). The class and its docstring make this explicit so the behaviour is meaningful for the networked sink the issue describes without misrepresenting what it does against SQLite.Changes
src/database/connection.py(new):ConnectionKeepAliveclass plusDEFAULT_PING_INTERVALandHEARTBEAT_QUERYconstants.src/database/__init__.py(new): package marker sofrom database.connection import ...resolves (the directory previously had none, unlikesrc/analytics/).tests/test_connection_keepalive.py(new): 7 mock-based unit tests.Testing
7 new unit tests, all passing:
They cover: the heartbeat query is executed, failures are swallowed rather than raised, the background thread starts/stops cleanly, the loop pings on its interval,
stop()returns promptly instead of blocking for the full interval, double-start()is a no-op, and invalid arguments are rejected. Tests useunittest.mockconnections, consistent with the existing Python tests, so no live database is required.Note: the full Python suite has two pre-existing failures unrelated to this change, reproducible on a clean checkout (verified by stashing these files):
tests/test_window.pyfails to import on Python 3.12 (Dequewas removed fromcollections.abc), andtests/test_signer.py::TestExceptionPathCleanup::test_signing_error_does_not_abort_cleanupfails on a read-only-attribute mock. Neither touches the files in this PR.