diff --git a/config/config.md b/config/config.md
index a05c08f770..90c346d09e 100644
--- a/config/config.md
+++ b/config/config.md
@@ -24,7 +24,8 @@
| `enable_telemetry` | Bool | `true` | Enable telemetry to collect anonymous usage data. Enabled by default. |
| `runtime` | -- | -- | The runtime options. |
| `runtime.global_rt_size` | Integer | `8` | The number of threads to execute the runtime for global read operations. |
-| `runtime.compact_rt_size` | Integer | `4` | The number of threads to execute the runtime for global write operations. |
+| `runtime.compact_rt_size` | Integer | `4` | The number of threads to execute compact operations. |
+| `runtime.compact_rt_max_blocking_threads` | Integer | `4` | The maximum number of blocking threads for compact operations. Defaults to max(num_cpus / 2, 1). |
| `http` | -- | -- | The HTTP server options. |
| `http.addr` | String | `127.0.0.1:4000` | The address to bind the HTTP server. |
| `http.timeout` | String | `0s` | HTTP request timeout. Set to 0 to disable timeout. When Prometheus pending-row batching is enabled, a nonzero timeout less than or equal to the `prom_store.pending_rows_flush_interval` plus 1 second is adjusted to that value. |
@@ -251,7 +252,8 @@
| `write_bytes_exhausted_policy` | String | Unset | Policy when write bytes quota is exhausted. Options: "wait" (default, 10s timeout), "wait()" (e.g., "wait(30s)"), "fail" |
| `runtime` | -- | -- | The runtime options. |
| `runtime.global_rt_size` | Integer | `8` | The number of threads to execute the runtime for global read operations. |
-| `runtime.compact_rt_size` | Integer | `4` | The number of threads to execute the runtime for global write operations. |
+| `runtime.compact_rt_size` | Integer | `4` | The number of threads to execute compact operations. |
+| `runtime.compact_rt_max_blocking_threads` | Integer | `4` | The maximum number of blocking threads for compact operations. Defaults to max(num_cpus / 2, 1). |
| `http` | -- | -- | The HTTP server options. |
| `http.addr` | String | `127.0.0.1:4000` | The address to bind the HTTP server. |
| `http.timeout` | String | `0s` | HTTP request timeout. Set to 0 to disable timeout. When Prometheus pending-row batching is enabled, a nonzero timeout less than or equal to the `prom_store.pending_rows_flush_interval` plus 1 second is adjusted to that value. |
@@ -391,7 +393,8 @@
| `enable_telemetry` | Bool | `true` | Whether to enable greptimedb telemetry. Enabled by default. |
| `runtime` | -- | -- | The runtime options. |
| `runtime.global_rt_size` | Integer | `8` | The number of threads to execute the runtime for global read operations. |
-| `runtime.compact_rt_size` | Integer | `4` | The number of threads to execute the runtime for global write operations. |
+| `runtime.compact_rt_size` | Integer | `4` | The number of threads to execute compact operations. |
+| `runtime.compact_rt_max_blocking_threads` | Integer | `4` | The maximum number of blocking threads for compact operations. Defaults to max(num_cpus / 2, 1). |
| `backend_tls` | -- | -- | TLS configuration for kv store backend (applicable for etcd, PostgreSQL, and MySQL backends) When using etcd, PostgreSQL, or MySQL as metadata store, you can configure TLS here
Note: if TLS is configured in both this section and the `store_addrs` connection string, the settings here will override the TLS settings in `store_addrs`. |
| `backend_tls.mode` | String | `prefer` | TLS mode, refer to https://www.postgresql.org/docs/current/libpq-ssl.html - "disable" - No TLS - "prefer" (default) - Try TLS, fallback to plain - "require" - Require TLS - "verify_ca" - Require TLS and verify CA - "verify_full" - Require TLS and verify hostname |
| `backend_tls.cert_path` | String | `""` | Path to client certificate file (for client authentication) Like "/path/to/client.crt" |
@@ -500,7 +503,8 @@
| `grpc.tls.watch` | Bool | `false` | Watch for Certificate and key file change and auto reload. For now, gRPC tls config does not support auto reload. |
| `runtime` | -- | -- | The runtime options. |
| `runtime.global_rt_size` | Integer | `8` | The number of threads to execute the runtime for global read operations. |
-| `runtime.compact_rt_size` | Integer | `4` | The number of threads to execute the runtime for global write operations. |
+| `runtime.compact_rt_size` | Integer | `4` | The number of threads to execute compact operations. |
+| `runtime.compact_rt_max_blocking_threads` | Integer | `4` | The maximum number of blocking threads for compact operations. Defaults to max(num_cpus / 2, 1). |
| `runtime.query_rt_size` | Integer | `7` | The number of threads to execute datanode query operations. Defaults to max(num_cpus - 1, 1). |
| `runtime.ingest_rt_size` | Integer | `8` | The number of threads to execute datanode ingestion operations. |
| `meta_client` | -- | -- | The metasrv client options. |
diff --git a/config/datanode.example.toml b/config/datanode.example.toml
index 9b5fc2e9a4..9b7b427254 100644
--- a/config/datanode.example.toml
+++ b/config/datanode.example.toml
@@ -81,8 +81,11 @@ watch = false
#+ [runtime]
## The number of threads to execute the runtime for global read operations.
#+ global_rt_size = 8
-## The number of threads to execute the runtime for global write operations.
+## The number of threads to execute compact operations.
#+ compact_rt_size = 4
+## The maximum number of blocking threads for compact operations.
+## Defaults to max(num_cpus / 2, 1).
+#+ compact_rt_max_blocking_threads = 4
## The number of threads to execute datanode query operations.
## Defaults to max(num_cpus - 1, 1).
#+ query_rt_size = 7
diff --git a/config/frontend.example.toml b/config/frontend.example.toml
index d9df1f0969..f495f3b19f 100644
--- a/config/frontend.example.toml
+++ b/config/frontend.example.toml
@@ -41,8 +41,11 @@ default_column_prefix = "greptime"
#+ [runtime]
## The number of threads to execute the runtime for global read operations.
#+ global_rt_size = 8
-## The number of threads to execute the runtime for global write operations.
+## The number of threads to execute compact operations.
#+ compact_rt_size = 4
+## The maximum number of blocking threads for compact operations.
+## Defaults to max(num_cpus / 2, 1).
+#+ compact_rt_max_blocking_threads = 4
## The HTTP server options.
[http]
diff --git a/config/metasrv.example.toml b/config/metasrv.example.toml
index c4b44ca38e..ff7f68037f 100644
--- a/config/metasrv.example.toml
+++ b/config/metasrv.example.toml
@@ -89,8 +89,11 @@ node_max_idle_time = "24hours"
#+ [runtime]
## The number of threads to execute the runtime for global read operations.
#+ global_rt_size = 8
-## The number of threads to execute the runtime for global write operations.
+## The number of threads to execute compact operations.
#+ compact_rt_size = 4
+## The maximum number of blocking threads for compact operations.
+## Defaults to max(num_cpus / 2, 1).
+#+ compact_rt_max_blocking_threads = 4
## TLS configuration for kv store backend (applicable for etcd, PostgreSQL, and MySQL backends)
## When using etcd, PostgreSQL, or MySQL as metadata store, you can configure TLS here
diff --git a/config/standalone.example.toml b/config/standalone.example.toml
index fccfe28695..4abbf815c0 100644
--- a/config/standalone.example.toml
+++ b/config/standalone.example.toml
@@ -54,8 +54,11 @@ max_concurrent_queries = 0
#+ [runtime]
## The number of threads to execute the runtime for global read operations.
#+ global_rt_size = 8
-## The number of threads to execute the runtime for global write operations.
+## The number of threads to execute compact operations.
#+ compact_rt_size = 4
+## The maximum number of blocking threads for compact operations.
+## Defaults to max(num_cpus / 2, 1).
+#+ compact_rt_max_blocking_threads = 4
## The HTTP server options.
[http]
diff --git a/src/cmd/tests/load_config_test.rs b/src/cmd/tests/load_config_test.rs
index d55a3f233b..bdbaf720b7 100644
--- a/src/cmd/tests/load_config_test.rs
+++ b/src/cmd/tests/load_config_test.rs
@@ -20,6 +20,7 @@ use common_base::memory_limit::MemoryLimit;
use common_base::readable_size::ReadableSize;
use common_config::{Configurable, DEFAULT_DATA_HOME, ENV_VAR_SEP};
use common_options::datanode::{ClientOptions, DatanodeClientOptions};
+use common_runtime::global::RuntimeOptions;
use common_telemetry::logging::{DEFAULT_LOGGING_DIR, DEFAULT_OTLP_HTTP_ENDPOINT, LoggingOptions};
use common_test_util::temp_dir::create_named_temp_file;
use common_wal::config::DatanodeWalConfig;
@@ -46,6 +47,7 @@ fn test_load_datanode_runtime_options_from_runtime_section() {
[runtime]
global_rt_size = 8
compact_rt_size = 4
+ compact_rt_max_blocking_threads = 6
ingest_rt_size = 8
query_rt_size = 7
"#;
@@ -54,10 +56,29 @@ fn test_load_datanode_runtime_options_from_runtime_section() {
assert_eq!(8, options.runtime.global_rt_size);
assert_eq!(4, options.runtime.compact_rt_size);
+ assert_eq!(6, options.runtime.compact_rt_max_blocking_threads);
assert_eq!(8, options.runtime.ingest_rt_size);
assert_eq!(7, options.runtime.query_rt_size);
}
+#[test]
+fn test_load_runtime_options_without_max_blocking_threads() {
+ let toml = r#"
+ [runtime]
+ global_rt_size = 8
+ compact_rt_size = 4
+ ingest_rt_size = 8
+ query_rt_size = 7
+ "#;
+
+ let options: GreptimeOptions = toml::from_str(toml).unwrap();
+
+ assert_eq!(
+ RuntimeOptions::default().compact_rt_max_blocking_threads,
+ options.runtime.compact_rt_max_blocking_threads
+ );
+}
+
#[allow(deprecated)]
#[test]
fn test_load_datanode_example_config() {
diff --git a/src/common/runtime/src/global.rs b/src/common/runtime/src/global.rs
index e3f9b93280..e28eaf61d1 100644
--- a/src/common/runtime/src/global.rs
+++ b/src/common/runtime/src/global.rs
@@ -36,6 +36,8 @@ pub struct RuntimeOptions {
pub global_rt_size: usize,
/// The number of threads to execute the runtime for compact operations.
pub compact_rt_size: usize,
+ /// The maximum number of blocking threads for compact operations.
+ pub compact_rt_max_blocking_threads: usize,
/// The number of threads to execute datanode query operations.
pub query_rt_size: usize,
/// The number of threads to execute datanode ingestion operations.
@@ -48,6 +50,7 @@ impl Default for RuntimeOptions {
Self {
global_rt_size: cpus,
compact_rt_size: usize::max(cpus / 2, 1),
+ compact_rt_max_blocking_threads: usize::max(cpus / 2, 1),
query_rt_size: usize::max(cpus.saturating_sub(1), 1),
ingest_rt_size: cpus,
}
@@ -66,6 +69,25 @@ pub fn create_runtime(runtime_name: &str, thread_name: &str, worker_threads: usi
.expect("Fail to create runtime")
}
+fn create_compact_runtime(
+ runtime_name: &str,
+ thread_name: &str,
+ worker_threads: usize,
+ max_blocking_threads: usize,
+) -> Runtime {
+ let max_blocking_threads = max_blocking_threads.max(1);
+ info!(
+ "Creating compact runtime with runtime_name: {runtime_name}, thread_name: {thread_name}, work_threads: {worker_threads}, max_blocking_threads: {max_blocking_threads}."
+ );
+ Builder::default()
+ .runtime_name(runtime_name)
+ .thread_name(thread_name)
+ .worker_threads(worker_threads)
+ .max_blocking_threads(max_blocking_threads)
+ .build()
+ .expect("Fail to create runtime")
+}
+
struct GlobalRuntimes {
global_runtime: Runtime,
compact_runtime: Runtime,
@@ -122,8 +144,16 @@ impl GlobalRuntimes {
Self {
global_runtime,
- compact_runtime: compact
- .unwrap_or_else(|| create_runtime("compact", "compact-worker", COMPACT_WORKERS)),
+ compact_runtime: compact.unwrap_or_else(|| {
+ let max_blocking_threads =
+ RuntimeOptions::default().compact_rt_max_blocking_threads;
+ create_compact_runtime(
+ "compact",
+ "compact-worker",
+ COMPACT_WORKERS,
+ max_blocking_threads,
+ )
+ }),
hb_runtime: heartbeat
.unwrap_or_else(|| create_runtime("heartbeat", "hb-worker", HB_WORKERS)),
query_runtime,
@@ -172,10 +202,11 @@ pub fn init_global_runtimes(options: &RuntimeOptions) {
"global-worker",
options.global_rt_size,
));
- c.compact_runtime = Some(create_runtime(
+ c.compact_runtime = Some(create_compact_runtime(
"compact",
"compact-worker",
options.compact_rt_size,
+ options.compact_rt_max_blocking_threads,
));
c.hb_runtime = Some(create_runtime("heartbeat", "hb-worker", HB_WORKERS));
});
@@ -246,6 +277,9 @@ define_global_runtime_spawn!(ingest);
#[cfg(test)]
mod tests {
+ use std::sync::mpsc;
+ use std::time::Duration;
+
use tokio_test::assert_ok;
use super::*;
@@ -257,6 +291,10 @@ mod tests {
assert_eq!(cpus, options.global_rt_size);
assert_eq!(usize::max(cpus / 2, 1), options.compact_rt_size);
+ assert_eq!(
+ usize::max(cpus / 2, 1),
+ options.compact_rt_max_blocking_threads
+ );
assert_eq!(usize::max(cpus.saturating_sub(1), 1), options.query_rt_size);
assert_eq!(cpus, options.ingest_rt_size);
}
@@ -276,6 +314,45 @@ mod tests {
assert_eq!("test-global", runtimes.ingest_runtime.name());
}
+ #[test]
+ fn test_create_compact_runtime_with_zero_max_blocking_threads() {
+ let runtime = create_compact_runtime("test-compact", "test-compact-worker", 1, 0);
+ let handle = runtime.spawn_blocking(|| 1 + 1);
+
+ assert_eq!(2, runtime.block_on(handle).unwrap());
+ }
+
+ #[test]
+ fn test_compact_runtime_limits_blocking_threads() {
+ let runtime = create_compact_runtime("test-compact", "test-compact-worker", 1, 1);
+ let (first_started_tx, first_started_rx) = mpsc::channel();
+ let (release_first_tx, release_first_rx) = mpsc::channel();
+ let first = runtime.spawn_blocking(move || {
+ first_started_tx.send(()).unwrap();
+ release_first_rx.recv().unwrap();
+ });
+ first_started_rx
+ .recv_timeout(Duration::from_secs(5))
+ .unwrap();
+
+ let (second_started_tx, second_started_rx) = mpsc::channel();
+ let second = runtime.spawn_blocking(move || second_started_tx.send(()).unwrap());
+ assert!(
+ second_started_rx
+ .recv_timeout(Duration::from_secs(1))
+ .is_err()
+ );
+
+ release_first_tx.send(()).unwrap();
+ second_started_rx
+ .recv_timeout(Duration::from_secs(5))
+ .unwrap();
+ runtime.block_on(async {
+ first.await.unwrap();
+ second.await.unwrap();
+ });
+ }
+
#[test]
fn test_datanode_runtime_spawn_block_on() {
let handle = spawn_query(async { 1 + 1 });
diff --git a/src/mito2/src/compaction/picker.rs b/src/mito2/src/compaction/picker.rs
index be82730507..a207a390a4 100644
--- a/src/mito2/src/compaction/picker.rs
+++ b/src/mito2/src/compaction/picker.rs
@@ -24,6 +24,7 @@ use crate::compaction::compactor::CompactionRegion;
use crate::compaction::twcs::TwcsPicker;
use crate::compaction::window::WindowedCompactionPicker;
use crate::compaction::{CompactionOutput, SerializedCompactionOutput};
+use crate::error::Result;
use crate::region::options::CompactionOptions;
use crate::sst::file::{FileHandle, FileMeta};
use crate::sst::file_purger::FilePurger;
@@ -36,9 +37,10 @@ pub(crate) trait CompactionTask: Debug + Send + Sync + 'static {
/// Picker picks input SST files for compaction.
/// Different compaction strategy may implement different pickers.
+#[async_trait::async_trait]
pub trait Picker: Debug + Send + Sync + 'static {
/// Picks input SST files for compaction.
- fn pick(&self, compaction_region: &CompactionRegion) -> Option;
+ async fn pick(&self, compaction_region: &CompactionRegion) -> Result