Cache prepared statements per pooled connection - #18
Merged
Merged
Conversation
Laravel prepares every query fresh: one COM_STMT_PREPARE round trip plus client and server parse work per query. Profiling put PDO::prepare at 7.5% of gpulab's PHP CPU and 4% of the frontend's, and with EMULATE_PREPARES=false every one of those is a synchronous network round trip on the request path. MySqlStringBindingConnection now keeps an LRU cache (default 64) of PDOStatements keyed by PDO instance + SQL text. select, statement, affectingStatement, and MySqlConnection's own insert override (which bypasses statement() to capture lastInsertId) all reuse cached handles; cursor() and selectResultSets() stay uncached by design. On a hit the statement is closeCursor'd and re-run through prepared() so fetch mode and the StatementPrepared event behave exactly as a fresh prepare. Safety: the cache lives on the Connection, which the pool hands to one coroutine at a time; setPdo/setReadPdo flush it (covers disconnect and the reconnector swap); MySQL error 1615 evicts and re-prepares once; results are freed eagerly since cached statements no longer die with their request. Escape hatches: OCTANE_MYSQL_STMT_CACHE=false, OCTANE_MYSQL_STMT_CACHE_SIZE.
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.
Laravel prepares every query fresh — one synchronous COM_STMT_PREPARE round trip plus parse work per query (PDO::prepare was 7.5% of gpulab PHP CPU, 4% frontend). An LRU cache (default 64) of PDOStatements per Connection, keyed by PDO instance + SQL, now sits in MySqlStringBindingConnection.
Covered paths: select, statement, affectingStatement, and MySqlConnection's own insert override (bypasses statement() to capture lastInsertId — every Eloquent create()). cursor()/selectResultSets() uncached by design.
Safety (all adversarially reviewed and test-pinned):
Evidence: 190 tests green; 11 cache tests incl. lastInsertId-per-execution, stale-results, LRU, read/write segmentation, 1615/1461 heal, hatch independence; 6 mutation tests kill guards. E2E on real Octane+Swoole+MySQL: 200 → 2 prepares per 100 requests, 18-probe battery byte-identical in BOTH buffered and unbuffered modes, 10 concurrency rounds with zero errors, statements bounded and freed on connection close.
Rollout: staggered per app, watching
Prepared_stmt_count(cap 65,536; steady state ~10.6k today).🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.