From d03f85287e225f390a28993fce6c0c1238d0eb1a Mon Sep 17 00:00:00 2001
From: sunheyi <50973219+sunheyi6@users.noreply.github.com>
Date: Thu, 14 Aug 2025 16:19:10 +0800
Subject: [PATCH] feat: mysql add prepared_stmt_cache_capacity (#6639)
* feat: your clear and concise commit message
Signed-off-by: sunheyi <1061867552@qq.com>
* fix error
Signed-off-by: sunheyi <1061867552@qq.com>
* add param
Signed-off-by: sunheyi <1061867552@qq.com>
* fix
Signed-off-by: sunheyi <1061867552@qq.com>
* fix doc error
Signed-off-by: sunheyi <1061867552@qq.com>
---------
Signed-off-by: sunheyi <1061867552@qq.com>
---
config/config.md | 2 ++
config/frontend.example.toml | 2 ++
config/standalone.example.toml | 3 +-
src/frontend/src/server.rs | 1 +
src/frontend/src/service_config/mysql.rs | 2 ++
src/servers/src/mysql/handler.rs | 32 ++++++++++++++++++--
src/servers/src/mysql/server.rs | 5 +++
src/servers/tests/mysql/mysql_server_test.rs | 1 +
tests-integration/src/test_util.rs | 1 +
tests-integration/tests/http.rs | 1 +
tests/conf/standalone-test.toml.template | 1 +
11 files changed, 47 insertions(+), 4 deletions(-)
diff --git a/config/config.md b/config/config.md
index 8f17a6cf61..075378d025 100644
--- a/config/config.md
+++ b/config/config.md
@@ -41,6 +41,7 @@
| `mysql.addr` | String | `127.0.0.1:4002` | The addr to bind the MySQL server. |
| `mysql.runtime_size` | Integer | `2` | The number of server worker threads. |
| `mysql.keep_alive` | String | `0s` | Server-side keep-alive time.
Set to 0 (default) to disable. |
+| `mysql.prepared_stmt_cache_size` | Integer | `10000` | Maximum entries in the MySQL prepared statement cache; default is 10,000. |
| `mysql.tls` | -- | -- | -- |
| `mysql.tls.mode` | String | `disable` | TLS mode, refer to https://www.postgresql.org/docs/current/libpq-ssl.html
- `disable` (default value)
- `prefer`
- `require`
- `verify-ca`
- `verify-full` |
| `mysql.tls.cert_path` | String | Unset | Certificate file path. |
@@ -248,6 +249,7 @@
| `mysql.addr` | String | `127.0.0.1:4002` | The addr to bind the MySQL server. |
| `mysql.runtime_size` | Integer | `2` | The number of server worker threads. |
| `mysql.keep_alive` | String | `0s` | Server-side keep-alive time.
Set to 0 (default) to disable. |
+| `mysql.prepared_stmt_cache_size` | Integer | `10000` | Maximum entries in the MySQL prepared statement cache; default is 10,000. |
| `mysql.tls` | -- | -- | -- |
| `mysql.tls.mode` | String | `disable` | TLS mode, refer to https://www.postgresql.org/docs/current/libpq-ssl.html
- `disable` (default value)
- `prefer`
- `require`
- `verify-ca`
- `verify-full` |
| `mysql.tls.cert_path` | String | Unset | Certificate file path. |
diff --git a/config/frontend.example.toml b/config/frontend.example.toml
index 621eddc8a7..6241d7346a 100644
--- a/config/frontend.example.toml
+++ b/config/frontend.example.toml
@@ -90,6 +90,8 @@ runtime_size = 2
## Server-side keep-alive time.
## Set to 0 (default) to disable.
keep_alive = "0s"
+## Maximum entries in the MySQL prepared statement cache; default is 10,000.
+prepared_stmt_cache_size = 10000
# MySQL server TLS options.
[mysql.tls]
diff --git a/config/standalone.example.toml b/config/standalone.example.toml
index a315ca75d2..9d82d884c6 100644
--- a/config/standalone.example.toml
+++ b/config/standalone.example.toml
@@ -85,7 +85,8 @@ runtime_size = 2
## Server-side keep-alive time.
## Set to 0 (default) to disable.
keep_alive = "0s"
-
+## Maximum entries in the MySQL prepared statement cache; default is 10,000.
+prepared_stmt_cache_size= 10000
# MySQL server TLS options.
[mysql.tls]
diff --git a/src/frontend/src/server.rs b/src/frontend/src/server.rs
index 3e91395fde..0c6493f79b 100644
--- a/src/frontend/src/server.rs
+++ b/src/frontend/src/server.rs
@@ -230,6 +230,7 @@ where
tls_server_config,
opts.keep_alive.as_secs(),
opts.reject_no_database.unwrap_or(false),
+ opts.prepared_stmt_cache_size,
)),
Some(instance.process_manager().clone()),
);
diff --git a/src/frontend/src/service_config/mysql.rs b/src/frontend/src/service_config/mysql.rs
index 20753554ff..f55dd78de8 100644
--- a/src/frontend/src/service_config/mysql.rs
+++ b/src/frontend/src/service_config/mysql.rs
@@ -29,6 +29,7 @@ pub struct MysqlOptions {
#[serde(default = "Default::default")]
#[serde(with = "humantime_serde")]
pub keep_alive: std::time::Duration,
+ pub prepared_stmt_cache_size: usize,
}
impl Default for MysqlOptions {
@@ -40,6 +41,7 @@ impl Default for MysqlOptions {
tls: TlsOption::default(),
reject_no_database: None,
keep_alive: std::time::Duration::from_secs(0),
+ prepared_stmt_cache_size: 10000,
}
}
}
diff --git a/src/servers/src/mysql/handler.rs b/src/servers/src/mysql/handler.rs
index 0a5e7909a0..8cc858507c 100644
--- a/src/servers/src/mysql/handler.rs
+++ b/src/servers/src/mysql/handler.rs
@@ -83,6 +83,7 @@ pub struct MysqlInstanceShim {
prepared_stmts: Arc>>,
prepared_stmts_counter: AtomicU32,
process_id: u32,
+ prepared_stmt_cache_size: usize,
}
impl MysqlInstanceShim {
@@ -91,6 +92,7 @@ impl MysqlInstanceShim {
user_provider: Option,
client_addr: SocketAddr,
process_id: u32,
+ prepared_stmt_cache_size: usize,
) -> MysqlInstanceShim {
// init a random salt
let mut bs = vec![0u8; 20];
@@ -118,6 +120,7 @@ impl MysqlInstanceShim {
prepared_stmts: Default::default(),
prepared_stmts_counter: AtomicU32::new(1),
process_id,
+ prepared_stmt_cache_size,
}
}
@@ -159,9 +162,24 @@ impl MysqlInstanceShim {
}
/// Save query and logical plan with a given statement key
- fn save_plan(&self, plan: SqlPlan, stmt_key: String) {
+ fn save_plan(&self, plan: SqlPlan, stmt_key: String) -> Result<()> {
let mut prepared_stmts = self.prepared_stmts.write();
+ let max_capacity = self.prepared_stmt_cache_size;
+
+ let is_update = prepared_stmts.contains_key(&stmt_key);
+
+ if !is_update && prepared_stmts.len() >= max_capacity {
+ return error::InternalSnafu {
+ err_msg: format!(
+ "Prepared statement cache is full, max capacity: {}",
+ max_capacity
+ ),
+ }
+ .fail();
+ }
+
let _ = prepared_stmts.insert(stmt_key, plan);
+ Ok(())
}
/// Retrieve the query and logical plan by a given statement key
@@ -237,7 +255,11 @@ impl MysqlInstanceShim {
schema: None,
},
stmt_key,
- );
+ )
+ .map_err(|e| {
+ error!(e; "Failed to save prepared statement");
+ e
+ })?;
} else {
self.save_plan(
SqlPlan {
@@ -247,7 +269,11 @@ impl MysqlInstanceShim {
schema,
},
stmt_key,
- );
+ )
+ .map_err(|e| {
+ error!(e; "Failed to save prepared statement");
+ e
+ })?;
}
Ok((params, columns))
diff --git a/src/servers/src/mysql/server.rs b/src/servers/src/mysql/server.rs
index 57aef8796f..9f042de765 100644
--- a/src/servers/src/mysql/server.rs
+++ b/src/servers/src/mysql/server.rs
@@ -77,6 +77,8 @@ pub struct MysqlSpawnConfig {
keep_alive_secs: u64,
// other shim config
reject_no_database: bool,
+ // prepared statement cache capacity
+ prepared_stmt_cache_size: usize,
}
impl MysqlSpawnConfig {
@@ -85,12 +87,14 @@ impl MysqlSpawnConfig {
tls: Arc,
keep_alive_secs: u64,
reject_no_database: bool,
+ prepared_stmt_cache_size: usize,
) -> MysqlSpawnConfig {
MysqlSpawnConfig {
force_tls,
tls,
keep_alive_secs,
reject_no_database,
+ prepared_stmt_cache_size,
}
}
@@ -201,6 +205,7 @@ impl MysqlServer {
spawn_ref.user_provider(),
stream.peer_addr()?,
process_id,
+ spawn_config.prepared_stmt_cache_size,
);
let (mut r, w) = stream.into_split();
let mut w = BufWriter::with_capacity(DEFAULT_RESULT_SET_WRITE_BUFFER_SIZE, w);
diff --git a/src/servers/tests/mysql/mysql_server_test.rs b/src/servers/tests/mysql/mysql_server_test.rs
index e6abdf22d2..f89da0043d 100644
--- a/src/servers/tests/mysql/mysql_server_test.rs
+++ b/src/servers/tests/mysql/mysql_server_test.rs
@@ -72,6 +72,7 @@ fn create_mysql_server(table: TableRef, opts: MysqlOpts<'_>) -> Result