← 输入框|github.com|刷新
Skip to content

fix: Dispose SQL registry connection pools after Gunicorn fork - #7065

Open
casaar97 wants to merge 1 commit into
feast-dev:masterfrom
casaar97:casaar97/sql-registry-postfork-dispose
Open

casaar97 wants to merge 1 commit into
feast-dev:masterfrom
casaar97:casaar97/sql-registry-postfork-dispose

Conversation

@casaar97

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

feast serve --workers N builds the FastAPI app, and with it the FeatureStore and its registry, in the Gunicorn master before it forks the workers. get_app() creates VectorStoreRegistry(store), whose first refresh() lists feature views, which creates the SqlRegistry and runs queries in the master. SQLAlchemy's QueuePool keeps that connection idle in the master, and every forked worker inherits the pool and checks out the same DBAPI connection, so two processes talk over one socket.

With MySQL over TLS this fails with (2013, 'Lost connection to MySQL server during query ([SSL] record layer failure ...)'), either as a sqlalchemy.pool "Exception during reset or similar", or, when it hits async_refresh() in the lifespan, as "Application startup failed", after which Gunicorn halts the whole server ("Worker failed to boot."). Without TLS the same sharing gives other errors (see #5784 for a Postgres ResourceClosedError). Workers re-forked through --max-requests inherit the master's pool again. Details and a deterministic reproduction are in #5784.

This applies SQLAlchemy's documented recipe ("Using Connection Pools with Multiprocessing or os.fork()"): engine.dispose(close=False) in the child. close=False matters: a plain dispose() would close the inherited connections in the child and send a disconnect on a socket the master still shares (sqlalchemy/sqlalchemy#7815).

  • BaseRegistry gains a no-op reset_after_fork(), so file, remote and other registries are unaffected.
  • SqlRegistry.reset_after_fork() disposes write_engine, and read_engine when it is a separate engine, with close=False.
  • FeastServeApplication.load_config always sets a Gunicorn post_fork hook, _gunicorn_post_fork, which calls it. Unlike the metrics post_worker_init / child_exit hooks, it is set for every run, because the bug does not depend on metrics. post_fork runs in the child right after the fork, before the worker loads the app or runs the lifespan, and on every re-fork. The hook does nothing when no registry was built, and never builds one.

#5795 targets the same issue; this PR keeps the change to the registry engines and uses close=False.

Which issue(s) this PR fixes:

Fixes #5784

Misc

Tests:

  • test_sql_registry.py: test_forked_child_does_not_reuse_parent_pooled_connection uses the file-backed SQLite SqlRegistry fixture (a real QueuePool) and os.fork(). Without the fix, the child checks out the parent's DBAPI connection object. With it, the child gets its own. Two more tests check that reset_after_fork() disposes the write engine, and the read engine when read_path is set, with close=False.
  • test_feature_server.py: the post_fork hook is registered with and without metrics, it calls reset_after_fork(), and it does not build a registry when none exists.
  • Manual: MySQL 8.4 with TLS in Docker, feast serve --workers 2 behind a 10 ms latency proxy, 10 runs on each side. Before (master): "Exception during reset or similar" / "Lost connection to MySQL server during query" in 2 of 10 runs, one of them with [SSL] record layer failure. After (this PR): 0 of 10. Both workers report "Application startup complete" in every run on both sides.

ruff check, ruff format --check and mypy feast pass. The touched unit test files pass (206 tests).

@casaar97
casaar97 requested a review from a team as a code owner October 23, 2026 10:36
@codecov-commenter

codecov-commenter commented Oct 23, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 51.02%. Comparing base (1e5e90c) to head (b891917).
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #7065      +/-   ##
==========================================
+ Coverage   50.97%   51.02%   +0.04%     
==========================================
  Files         455      455              
  Lines       56994    57003       +9     
  Branches     8321     8323       +2     
==========================================
+ Hits        29055    29083      +28     
+ Misses      25954    25934      -20     
- Partials     1985     1986       +1     
Flag Coverage Δ
go-feature-server 30.58% <ø> (+0.20%) ⬆️
python-unit 52.48% <100.00%> (+0.02%) ⬆️
Files with missing lines Coverage Δ
sdk/python/feast/feature_server.py 66.26% <100.00%> (+1.81%) ⬆️
sdk/python/feast/infra/registry/base_registry.py 74.16% <100.00%> (+0.09%) ⬆️
sdk/python/feast/infra/registry/sql.py 61.25% <100.00%> (+0.21%) ⬆️

... and 2 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1e5e90c...b891917. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Gunicorn workers inherited the parent's pooled SQL registry connections,
so several workers shared one socket and the server crashed on startup.
A post_fork hook, set for every run, now disposes the pools with
close=False, so the parent's connections are left open.

Fixes feast-dev#5784

Signed-off-by: Carlos Sánchez <carlos.sancheza@cabify.com>
@casaar97
casaar97 force-pushed the casaar97/sql-registry-postfork-dispose branch from 26042ff to b891917 Compare October 27, 2026 11:04

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The feast server crashes when starting multiple workers with SQL registry

3 participants