Skip to content

perf: optimize ParserSQL digest and classification overhead - #6137

Open
Snehil-Shah wants to merge 1 commit into
sysown:v3.0from
Snehil-Shah:digest-perf
Open

perf: optimize ParserSQL digest and classification overhead#6137
Snehil-Shah wants to merge 1 commit into
sysown:v3.0from
Snehil-Shah:digest-perf

Conversation

@Snehil-Shah

@Snehil-Shah Snehil-Shah commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

This implements two performance improvements in the digest computation and command-type classification path with the ParserSQL parser:

  • we were parsing twice just to get the command types. Instead now we reuse the results of the first parse (captured at parser init).
  • reduce heap allocations by directly copying and hashing from parser's arena instead of creating an intermediate std::string instance, and by reusing the struct's existing buffer for the digest text (instead of reallocating).

NOTE: This does NOT affect the legacy parser path in any way.

Measurements

All numbers in the below tables are in ns. They are calculated by arming clock_gettime(CLOCK_THREAD_CPUTIME_ID, ...) around the code under bench.

The numbers are recorded for the following four configurations:

  • legacy (baseline): Legacy parser path
  • legacy (patched): unchanged
  • parser=1 (baseline): ParserSQL path before PR
  • parser=1 (patched): ParserSQL path after this PR

Machine specs: Ubuntu 24.04.4 (colima), aarch64 (ARM64), 4 vCPUs, 8 GiB RAM using jemalloc allocator correctly.

Function-level benchmark

Benchmarked the following two functions in the hot-path:

Before:

parsersql_digest_init_mysql(&qp, ...);
parsersql_command_type_mysql(qp.digest_text, ...);

After:

parsersql_digest_init_mysql(&qp, ...);
parsersql_stmt_type_to_mysql_command(qp.parsersql_stmt_type);  // cached lookup

taking median of 35 iterations with 200,000 calls per iteration (both funcs serially) on one single core.

case legacy (baseline) legacy (patched) parser=1 (baseline) parser=1 (patched)
point-select 279.9 277.2 744.1 375.7
in-list 457.8 457.1 1023.7 666.8
join-orderby 581.4 581.2 1647.2 885.5
insert 309.9 311.3 972.6 483.5
begin 34.9 35.1 124.9 71.5
union 197.8 198.5 545.6 265.3
  • ~45% speedup.

Full proxy synchronous path

CPU time from query packet received to just before the query is handed to the backend, excluding all network/backend IO.

Setup: Running a full ProxySQL server with single MySQL and a single PgSQL worker thread (on a real MySQL backend), with timers attached in code (just calculating the proxy synchronous path), and ran sysbench for 20 seconds and saved all measurements. Took the median of 10 such 20-second iterations.

legacy (baseline) legacy (patched) parser=1 (baseline) parser=1 (patched)
fullpath 16104.4 16048.6 16791.8 16061.0
  • ~4.35% speedup.

Summary by CodeRabbit

  • Improvements
    • Improved SQL statement classification for MySQL and PostgreSQL queries.
    • Query command types are now reused from parser results when available, providing more consistent classification.
    • Improved handling of normalized query digest text, including more efficient storage when possible.
    • Added fallback behavior to preserve existing text-based classification when parser results are unavailable.

Signed-off-by: Snehil Shah <snehilshah.989@gmail.com>
@gitar-bot

gitar-bot Bot commented Aug 29, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6977d8e4-3cd8-447b-9149-a11a99e890e2

📥 Commits

Reviewing files that changed from the base of the PR and between 7c91137 and 7b6d29b.

📒 Files selected for processing (6)
  • include/Query_Processor_ParserSQL.h
  • include/proxysql_structs.h
  • lib/MySQL_Query_Processor.cpp
  • lib/PgSQL_Query_Processor.cpp
  • lib/Query_Processor.cpp
  • lib/Query_Processor_ParserSQL.cpp

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

📜 Recent review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: cubic · AI code reviewer
🧰 Additional context used
📓 Path-based instructions (2)
Header include guards use the `#ifndef __CLASS_*_H` convention.

📄 CodeRabbit inference engine (CLAUDE.md)

Files:

  • include/proxysql_structs.h
  • include/Query_Processor_ParserSQL.h
Class names must use `PascalCase` with protocol prefixes such as `MySQL_`, `PgSQL_`, and `ProxySQL_`.

📄 CodeRabbit inference engine (CLAUDE.md)

Files:

  • lib/MySQL_Query_Processor.cpp
  • include/proxysql_structs.h
  • include/Query_Processor_ParserSQL.h
  • lib/Query_Processor_ParserSQL.cpp
  • lib/Query_Processor.cpp
  • lib/PgSQL_Query_Processor.cpp
🔇 Additional comments (1)
lib/MySQL_Query_Processor.cpp (1)

167-170: 🎯 Functional Correctness

No issue in the prepared-statement path.

stmt_exec_qp is passed only to Query_Processor::process_query(), which does not call query_parser_command_type(). The classification calls operate on QueryParserArgs, which query_parser_init() initializes before use.


📝 Walkthrough

Walkthrough

ParserSQL now records statement types during digest parsing, exposes protocol-specific conversion functions, and reuses stored classifications in MySQL and PostgreSQL command detection. Unclassified statements continue through text-based parsing.

Changes

ParserSQL command classification

Layer / File(s) Summary
Classification contract and mappings
include/proxysql_structs.h, include/Query_Processor_ParserSQL.h, lib/Query_Processor_ParserSQL.cpp
The query parser state stores parsersql_stmt_type. Public functions map ParserSQL statement types to MySQL and PostgreSQL command enums.
Digest statement capture
lib/Query_Processor.cpp, lib/Query_Processor_ParserSQL.cpp
Parser initialization sets the statement type to -1. MySQL and PostgreSQL digest initialization records the parsed statement type and stores normalized digest text in the preallocated buffer when it fits.
Protocol command integration
lib/MySQL_Query_Processor.cpp, lib/PgSQL_Query_Processor.cpp
Command detection uses the stored statement type when it is nonnegative. Text-based classification remains the fallback.
Estimated code review effort: 3 (Moderate) ~20 minutes

Merge Risk: ⚪ Minimal · up to 7b6d2

This change reuses ParserSQL results and reduces digest allocations while preserving normal MySQL and PostgreSQL classification behavior. No actionable merge-blocking risk remains beyond normal checks and review.

Suggested reviewers: renecannao

Poem

A rabbit mapped each query's course
From ParserSQL to protocol source.

Digests now carry the type they found,
Fast command paths turn them around.
Unknown statements keep their text trail,
While carrots and fallbacks never fail.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 6 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main changes: reducing ParserSQL digest and command-classification overhead.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/Query_Processor_ParserSQL.cpp">

<violation number="1" location="lib/Query_Processor_ParserSQL.cpp:445">
P1: When callers use either ParserSQL digest adapter directly, short normalized output now makes `digest_text` point to inline storage, but existing callers free that field and abort. Keep `digest_text` heap-allocated for this public adapter contract, or update the API and every caller to use the ownership-aware cleanup path in both MySQL and PostgreSQL branches.</violation>
</file>

<file name="include/proxysql_structs.h">

<violation number="1" location="include/proxysql_structs.h:902">
P2: When a zero-initialized `SQP_par_t` reaches command lookup with ParserSQL enabled, `parsersql_stmt_type` is `0`, so the lookup treats it as a cached type and never parses `digest_text`. Initialize this field to `-1` for every manually constructed parser state, or make the lookup distinguish an absent cache from a valid type.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

if (normalized_len < QUERY_DIGEST_BUF) {
memcpy(qp->buf, normalized, normalized_len);
qp->buf[normalized_len] = '\0';
qp->digest_text = qp->buf;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When callers use either ParserSQL digest adapter directly, short normalized output now makes digest_text point to inline storage, but existing callers free that field and abort. Keep digest_text heap-allocated for this public adapter contract, or update the API and every caller to use the ownership-aware cleanup path in both MySQL and PostgreSQL branches.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/Query_Processor_ParserSQL.cpp, line 445:

<comment>When callers use either ParserSQL digest adapter directly, short normalized output now makes `digest_text` point to inline storage, but existing callers free that field and abort. Keep `digest_text` heap-allocated for this public adapter contract, or update the API and every caller to use the ownership-aware cleanup path in both MySQL and PostgreSQL branches.</comment>

<file context>
@@ -413,26 +413,39 @@ void parsersql_digest_init_mysql(SQP_par_t* qp, const char* query, int query_len
+        if (normalized_len < QUERY_DIGEST_BUF) {
+            memcpy(qp->buf, normalized, normalized_len);
+            qp->buf[normalized_len] = '\0';
+            qp->digest_text = qp->buf;
+        } else {
+            qp->digest_text = strndup(normalized, normalized_len);
</file context>

char *digest_text;
char *first_comment;
char *query_prefix;
int parsersql_stmt_type;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a zero-initialized SQP_par_t reaches command lookup with ParserSQL enabled, parsersql_stmt_type is 0, so the lookup treats it as a cached type and never parses digest_text. Initialize this field to -1 for every manually constructed parser state, or make the lookup distinguish an absent cache from a valid type.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At include/proxysql_structs.h, line 902:

<comment>When a zero-initialized `SQP_par_t` reaches command lookup with ParserSQL enabled, `parsersql_stmt_type` is `0`, so the lookup treats it as a cached type and never parses `digest_text`. Initialize this field to `-1` for every manually constructed parser state, or make the lookup distinguish an absent cache from a valid type.</comment>

<file context>
@@ -899,6 +899,7 @@ struct __SQP_query_parser_t {
 	char *digest_text;
 	char *first_comment;
 	char *query_prefix;
+	int parsersql_stmt_type;
 };
 
</file context>

@sonarqubecloud

Copy link
Copy Markdown

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant