Repository: risingwavelabs/risingwave
Pull request #21406: feat(foyer): bump foyer version for advanced io throttling
Author: login: MrCroxx
Labels: name: type/feature
description: Type: New feature.
Created: 2025-04-15T07:18:49Z | Merged: 2025-04-21T15:40:12Z

I hereby agree to the terms of the [RisingWave Labs, Inc. Contributor License Agreement](https://raw.githubusercontent.com/risingwavelabs/risingwave/17af8a747593ebdbfa826691daf75bdab7d14fa0/.github/contributor-license-agreement.txt).

## What's changed and what's your intention?

Use more precise advanced io throttling for disk cache.

e.g.

```toml
[storage.data_file_cache.throttle]
write_iops = 100000
read_iops = 100000
write_throughput = 1073741824
read_throughput = 1073741824

[storage.data_file_cache.throttle.iops_counter]
PerIoSize = 131072
```

TODO:

- [x] longevity test
- [x] io throttling test
- [ ] document

<!--

**Please do not leave this empty!**

Please explain **IN DETAIL** what the changes are in this PR and why they are needed:

- Summarize your change (**mandatory**)
- How does this PR work? Need a brief introduction for the changed logic (optional)
- Describe clearly one logical change and avoid lazy messages (optional)
- Describe any limitations of the current code (optional)
- Refer to a related PR or issue link (optional)

-->

## Checklist

- [ ] I have written necessary rustdoc comments.
- [ ] <!-- OPTIONAL --> I have added necessary unit tests and integration tests.
- [ ] <!-- OPTIONAL --> I have added test labels as necessary. <!-- See https://github.com/risingwavelabs/risingwave/blob/main/docs/developer-guide.md#ci-labels-guide) -->
- [ ] <!-- OPTIONAL --> I have added fuzzing tests or opened an issue to track them. <!-- Recommended for new SQL features, see #7934 -->
- [ ] <!-- OPTIONAL --> My PR contains breaking changes. <!-- If it deprecates some features, please create a tracking issue to remove them in the future -->
- [ ] <!-- OPTIONAL --> My PR changes performance-critical code, so I will run (micro) benchmarks and present the results. <!-- To manually trigger a benchmark, please check out [Notion](https://www.notion.so/risingwave-labs/Manually-trigger-nexmark-performance-dashboard-test-b784f1eae1cf48889b2645d020b6b7d3). -->
- [ ] <!-- OPTIONAL --> I have checked the [Release Timeline](https://github.com/risingwavelabs/rw-commits-history/blob/main/release_timeline.md) and [Currently Supported Versions](https://docs.risingwave.com/changelog/release-support-policy#support-end-dates-for-recent-releases) to determine which release branches I need to cherry-pick this PR into. <!-- Please check out the [details](https://github.com/risingwavelabs/risingwave/blob/main/CONTRIBUTING.md) -->


## Documentation

- [ ] <!-- OPTIONAL --> My PR needs documentation updates. <!-- Please use the **Release note** section below to summarize the impact on users -->

<details>
<summary><b>Release note</b></summary>

<!--
If this PR includes changes that directly affect users or other significant modifications relevant to the community, kindly draft a release note to provide a concise summary of these changes.

Please prioritize highlighting the impact these changes will have on users.
Discuss technical details in the "What's changed" section, and focus on the impact on users in the release note.

You should also mention the environment or conditions where the impact may occur.
-->

</details>



Diff:
diff --git a/Cargo.lock b/Cargo.lock
index 2922938dea038..c78cd1acdbf05 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4948,8 +4948,7 @@ dependencies = [
 [[package]]
 name = "foyer"
 version = "0.16.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59819ed1a4be90fe66aa13df94adff2365bbb94929ad1052fd977e06b478974e"
+source = "git+https://github.com/foyer-rs/foyer?rev=82168c1beb68c0835e82fe956ab6fcea73eef18b#82168c1beb68c0835e82fe956ab6fcea73eef18b"
 dependencies = [
  "ahash 0.8.11",
  "anyhow",
@@ -4969,8 +4968,7 @@ dependencies = [
 [[package]]
 name = "foyer-common"
 version = "0.16.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06d425cd90d221e1c2cad27717260414cc1d55fa325fd8d02bbd928e4930d7d8"
+source = "git+https://github.com/foyer-rs/foyer?rev=82168c1beb68c0835e82fe956ab6fcea73eef18b#82168c1beb68c0835e82fe956ab6fcea73eef18b"
 dependencies = [
  "ahash 0.8.11",
  "bincode 1.3.3",
@@ -4999,8 +4997,7 @@ dependencies = [
 [[package]]
 name = "foyer-memory"
 version = "0.16.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd2582199b44afa6f617464335cc50debdff3caaf9a93972369bbf7e42cc630d"
+source = "git+https://github.com/foyer-rs/foyer?rev=82168c1beb68c0835e82fe956ab6fcea73eef18b#82168c1beb68c0835e82fe956ab6fcea73eef18b"
 dependencies = [
  "ahash 0.8.11",
  "arc-swap",
@@ -5025,8 +5022,7 @@ dependencies = [
 [[package]]
 name = "foyer-storage"
 version = "0.16.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba8dbd31e2caa79d3816b6924c30cc7eb292eac731d8a2af9c59bec911c27924"
+source = "git+https://github.com/foyer-rs/foyer?rev=82168c1beb68c0835e82fe956ab6fcea73eef18b#82168c1beb68c0835e82fe956ab6fcea73eef18b"
 dependencies = [
  "ahash 0.8.11",
  "allocator-api2",
@@ -7550,9 +7546,9 @@ dependencies = [
 
 [[package]]
 name = "mixtrics"
-version = "0.0.6"
+version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41434672bd2f5df1b8ed58e0cee0a7a1000bf8dd711cea5a5540afcf41f7d7fb"
+checksum = "749ed12bab176c8a42c13a679dd2de12876d5ad4abe7525548e31ae001a9ebbf"
 dependencies = [
  "itertools 0.14.0",
  "parking_lot 0.12.1",
diff --git a/Cargo.toml b/Cargo.toml
index 7f58c7c8cc51e..6355fdce81d78 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -100,8 +100,14 @@ normal = ["workspace-hack"]
 development = ["expect-test", "pretty_assertions"]
 
 [workspace.dependencies]
-foyer = { version = "0.16.1", features = ["serde", "tracing", "nightly"] }
-mixtrics = { version = "0.0.6", features = ["prometheus"] }
+# Switch to a crate-io version after new foyer version is released.
+# foyer = { version = "0.16.1", features = ["serde", "tracing", "nightly"] }
+foyer = { git = "https://github.com/foyer-rs/foyer", rev = "82168c1beb68c0835e82fe956ab6fcea73eef18b", features = [
+    "serde",
+    "tracing",
+    "nightly",
+] }
+mixtrics = { version = "0.1", features = ["prometheus"] }
 apache-avro = { git = "https://github.com/risingwavelabs/avro", rev = "25113ba88234a9ae23296e981d8302c290fdaa4b", features = [
     "snappy",
     "zstandard",
diff --git a/src/common/src/config.rs b/src/common/src/config.rs
index 33a635048cd83..8cd26b0606e86 100644
--- a/src/common/src/config.rs
+++ b/src/common/src/config.rs
@@ -24,7 +24,9 @@ use std::num::NonZeroUsize;
 use anyhow::Context;
 use clap::ValueEnum;
 use educe::Educe;
-use foyer::{Compression, LfuConfig, LruConfig, RecoverMode, RuntimeOptions, S3FifoConfig};
+use foyer::{
+    Compression, LfuConfig, LruConfig, RecoverMode, RuntimeOptions, S3FifoConfig, Throttle,
+};
 use risingwave_common_proc_macro::ConfigDoc;
 pub use risingwave_common_proc_macro::OverrideConfig;
 use risingwave_pb::meta::SystemParams;
@@ -1025,6 +1027,7 @@ pub struct FileCacheConfig {
     #[serde(default = "default::file_cache::recover_concurrency")]
     pub recover_concurrency: usize,
 
+    /// Deprecated soon. Please use `throttle` to do I/O throttling instead.
     #[serde(default = "default::file_cache::insert_rate_limit_mb")]
     pub insert_rate_limit_mb: usize,
 
@@ -1037,6 +1040,9 @@ pub struct FileCacheConfig {
     #[serde(default = "default::file_cache::flush_buffer_threshold_mb")]
     pub flush_buffer_threshold_mb: Option<usize>,
 
+    #[serde(default = "default::file_cache::throttle")]
+    pub throttle: Throttle,
+
     /// Recover mode.
     ///
     /// Options:
@@ -1975,7 +1981,9 @@ pub mod default {
     }
 
     pub mod file_cache {
-        use foyer::{Compression, RecoverMode, RuntimeOptions, TokioRuntimeOptions};
+        use std::num::NonZeroUsize;
+
+        use foyer::{Compression, RecoverMode, RuntimeOptions, Throttle, TokioRuntimeOptions};
 
         pub fn dir() -> String {
             "".to_owned()
@@ -2024,6 +2032,17 @@ pub mod default {
         pub fn runtime_config() -> RuntimeOptions {
             RuntimeOptions::Unified(TokioRuntimeOptions::default())
         }
+
+        pub fn throttle() -> Throttle {
+            Throttle::new()
+                .with_iops_counter(foyer::IopsCounter::PerIoSize(
+                    NonZeroUsize::new(128 * 1024).unwrap(),
+                ))
+                .with_read_iops(100000)
+                .with_write_iops(100000)
+                .with_write_throughput(1024 * 1024 * 1024)
+                .with_read_throughput(1024 * 1024 * 1024)
+        }
     }
 
     pub mod cache_refill {
diff --git a/src/config/docs.md b/src/config/docs.md
index 17eebd1b2fa8d..a02b929cce87e 100644
--- a/src/config/docs.md
+++ b/src/config/docs.md
@@ -208,11 +208,12 @@ This page is automatically generated by `./risedev generate-example-config`
 | flush_buffer_threshold_mb |  |  |
 | flushers |  | 4 |
 | indexer_shards |  | 64 |
-| insert_rate_limit_mb |  | 0 |
+| insert_rate_limit_mb | Deprecated soon. Please use `throttle` to do I/O throttling instead. | 0 |
 | reclaimers |  | 4 |
 | recover_concurrency |  | 8 |
 | recover_mode | Recover mode. Options: - "None": Do not recover disk cache. - "Quiet": Recover disk cache and skip errors. - "Strict": Recover disk cache and panic on errors. More details, see [`RecoverMode::None`], [`RecoverMode::Quiet`] and [`RecoverMode::Strict`], | "Quiet" |
 | runtime_config |  |  |
+| throttle |  |  |
 
 ## storage.meta_file_cache
 
@@ -225,11 +226,12 @@ This page is automatically generated by `./risedev generate-example-config`
 | flush_buffer_threshold_mb |  |  |
 | flushers |  | 4 |
 | indexer_shards |  | 64 |
-| insert_rate_limit_mb |  | 0 |
+| insert_rate_limit_mb | Deprecated soon. Please use `throttle` to do I/O throttling instead. | 0 |
 | reclaimers |  | 4 |
 | recover_concurrency |  | 8 |
 | recover_mode | Recover mode. Options: - "None": Do not recover disk cache. - "Quiet": Recover disk cache and skip errors. - "Strict": Recover disk cache and panic on errors. More details, see [`RecoverMode::None`], [`RecoverMode::Quiet`] and [`RecoverMode::Strict`], | "Quiet" |
 | runtime_config |  |  |
+| throttle |  |  |
 
 ## streaming
 
diff --git a/src/config/example.toml b/src/config/example.toml
index f177fa56a46ca..e59f6731b5bdc 100644
--- a/src/config/example.toml
+++ b/src/config/example.toml
@@ -243,6 +243,15 @@ indexer_shards = 64
 compression = "None"
 recover_mode = "Quiet"
 
+[storage.data_file_cache.throttle]
+write_iops = 100000
+read_iops = 100000
+write_throughput = 1073741824
+read_throughput = 1073741824
+
+[storage.data_file_cache.throttle.iops_counter]
+PerIoSize = 131072
+
 [storage.data_file_cache.runtime_config.Unified]
 worker_threads = 0
 max_blocking_threads = 0
@@ -259,6 +268,15 @@ indexer_shards = 64
 compression = "None"
 recover_mode = "Quiet"
 
+[storage.meta_file_cache.throttle]
+write_iops = 100000
+read_iops = 100000
+write_throughput = 1073741824
+read_throughput = 1073741824
+
+[storage.meta_file_cache.throttle.iops_counter]
+PerIoSize = 131072
+
 [storage.meta_file_cache.runtime_config.Unified]
 worker_threads = 0
 max_blocking_threads = 0
diff --git a/src/storage/src/hummock/event_handler/refiller.rs b/src/storage/src/hummock/event_handler/refiller.rs
index 8cfd7ac96e1a3..b9f80fd4eab21 100644
--- a/src/storage/src/hummock/event_handler/refiller.rs
+++ b/src/storage/src/hummock/event_handler/refiller.rs
@@ -607,7 +607,7 @@ impl CacheRefillTask {
 
             let mut writer = sstable_store.block_cache().storage_writer(key);
 
-            if writer.pick() {
+            if writer.pick().admitted() {
                 admits += 1;
             }
 
diff --git a/src/storage/src/opts.rs b/src/storage/src/opts.rs
index 8bddb3e4c499b..520fffaec934d 100644
--- a/src/storage/src/opts.rs
+++ b/src/storage/src/opts.rs
@@ -90,11 +90,11 @@ pub struct StorageOpts {
     pub data_file_cache_reclaimers: usize,
     pub data_file_cache_recover_mode: foyer::RecoverMode,
     pub data_file_cache_recover_concurrency: usize,
-    pub data_file_cache_insert_rate_limit_mb: usize,
     pub data_file_cache_indexer_shards: usize,
     pub data_file_cache_compression: foyer::Compression,
     pub data_file_cache_flush_buffer_threshold_mb: usize,
     pub data_file_cache_runtime_config: foyer::RuntimeOptions,
+    pub data_file_cache_throttle: foyer::Throttle,
 
     pub cache_refill_data_refill_levels: Vec<u32>,
     pub cache_refill_timeout_ms: u64,
@@ -111,11 +111,11 @@ pub struct StorageOpts {
     pub meta_file_cache_reclaimers: usize,
     pub meta_file_cache_recover_mode: foyer::RecoverMode,
     pub meta_file_cache_recover_concurrency: usize,
-    pub meta_file_cache_insert_rate_limit_mb: usize,
     pub meta_file_cache_indexer_shards: usize,
     pub meta_file_cache_compression: foyer::Compression,
     pub meta_file_cache_flush_buffer_threshold_mb: usize,
     pub meta_file_cache_runtime_config: foyer::RuntimeOptions,
+    pub meta_file_cache_throttle: foyer::Throttle,
 
     /// The storage url for storing backups.
     pub backup_storage_url: String,
@@ -158,6 +158,19 @@ impl Default for StorageOpts {
 
 impl From<(&RwConfig, &SystemParamsReader, &StorageMemoryConfig)> for StorageOpts {
     fn from((c, p, s): (&RwConfig, &SystemParamsReader, &StorageMemoryConfig)) -> Self {
+        let mut data_file_cache_throttle = c.storage.data_file_cache.throttle.clone();
+        if data_file_cache_throttle.write_throughput.is_none() {
+            data_file_cache_throttle = data_file_cache_throttle.with_write_throughput(
+                c.storage.data_file_cache.insert_rate_limit_mb * 1024 * 1024,
+            );
+        }
+        let mut meta_file_cache_throttle = c.storage.meta_file_cache.throttle.clone();
+        if meta_file_cache_throttle.write_throughput.is_none() {
+            meta_file_cache_throttle = meta_file_cache_throttle.with_write_throughput(
+                c.storage.meta_file_cache.insert_rate_limit_mb * 1024 * 1024,
+            );
+        }
+
         Self {
             parallel_compact_size_mb: p.parallel_compact_size_mb(),
             sstable_size_mb: p.sstable_size_mb(),
@@ -196,11 +209,11 @@ impl From<(&RwConfig, &SystemParamsReader, &StorageMemoryConfig)> for StorageOpt
             data_file_cache_reclaimers: c.storage.data_file_cache.reclaimers,
             data_file_cache_recover_mode: c.storage.data_file_cache.recover_mode,
             data_file_cache_recover_concurrency: c.storage.data_file_cache.recover_concurrency,
-            data_file_cache_insert_rate_limit_mb: c.storage.data_file_cache.insert_rate_limit_mb,
             data_file_cache_indexer_shards: c.storage.data_file_cache.indexer_shards,
             data_file_cache_compression: c.storage.data_file_cache.compression,
             data_file_cache_flush_buffer_threshold_mb: s.block_file_cache_flush_buffer_threshold_mb,
             data_file_cache_runtime_config: c.storage.data_file_cache.runtime_config.clone(),
+            data_file_cache_throttle,
             meta_file_cache_dir: c.storage.meta_file_cache.dir.clone(),
             meta_file_cache_capacity_mb: c.storage.meta_file_cache.capacity_mb,
             meta_file_cache_file_capacity_mb: c.storage.meta_file_cache.file_capacity_mb,
@@ -208,11 +221,11 @@ impl From<(&RwConfig, &SystemParamsReader, &StorageMemoryConfig)> for StorageOpt
             meta_file_cache_reclaimers: c.storage.meta_file_cache.reclaimers,
             meta_file_cache_recover_mode: c.storage.meta_file_cache.recover_mode,
             meta_file_cache_recover_concurrency: c.storage.meta_file_cache.recover_concurrency,
-            meta_file_cache_insert_rate_limit_mb: c.storage.meta_file_cache.insert_rate_limit_mb,
             meta_file_cache_indexer_shards: c.storage.meta_file_cache.indexer_shards,
             meta_file_cache_compression: c.storage.meta_file_cache.compression,
             meta_file_cache_flush_buffer_threshold_mb: s.meta_file_cache_flush_buffer_threshold_mb,
             meta_file_cache_runtime_config: c.storage.meta_file_cache.runtime_config.clone(),
+            meta_file_cache_throttle,
             cache_refill_data_refill_levels: c.storage.cache_refill.data_refill_levels.clone(),
             cache_refill_timeout_ms: c.storage.cache_refill.timeout_ms,
             cache_refill_concurrency: c.storage.cache_refill.concurrency,
diff --git a/src/storage/src/store_impl.rs b/src/storage/src/store_impl.rs
index d7ec9b3f33f1a..ebf9fc123dd17 100644
--- a/src/storage/src/store_impl.rs
+++ b/src/storage/src/store_impl.rs
@@ -18,9 +18,7 @@ use std::sync::{Arc, LazyLock};
 use std::time::Duration;
 
 use enum_as_inner::EnumAsInner;
-use foyer::{
-    DirectFsDeviceOptions, Engine, HybridCacheBuilder, LargeEngineOptions, RateLimitPicker,
-};
+use foyer::{DirectFsDeviceOptions, Engine, HybridCacheBuilder, LargeEngineOptions};
 use futures::FutureExt;
 use futures::future::BoxFuture;
 use mixtrics::registry::prometheus::PrometheusMetricsRegistry;
@@ -683,7 +681,8 @@ impl StateStoreImpl {
                         .with_device_options(
                             DirectFsDeviceOptions::new(&opts.meta_file_cache_dir)
                                 .with_capacity(opts.meta_file_cache_capacity_mb * MB)
-                                .with_file_size(opts.meta_file_cache_file_capacity_mb * MB),
+                                .with_file_size(opts.meta_file_cache_file_capacity_mb * MB)
+                                .with_throttle(opts.meta_file_cache_throttle.clone()),
                         )
                         .with_recover_mode(opts.meta_file_cache_recover_mode)
                         .with_compression(opts.meta_file_cache_compression)
@@ -703,11 +702,6 @@ impl StateStoreImpl {
                                 .with_recover_concurrency(opts.meta_file_cache_recover_concurrency)
                                 .with_blob_index_size(16 * KB),
                         );
-                    if opts.meta_file_cache_insert_rate_limit_mb > 0 {
-                        builder = builder.with_admission_picker(Arc::new(RateLimitPicker::new(
-                            opts.meta_file_cache_insert_rate_limit_mb * MB,
-                        )));
-                    }
                 }
             }
 
@@ -738,7 +732,8 @@ impl StateStoreImpl {
                         .with_device_options(
                             DirectFsDeviceOptions::new(&opts.data_file_cache_dir)
                                 .with_capacity(opts.data_file_cache_capacity_mb * MB)
-                                .with_file_size(opts.data_file_cache_file_capacity_mb * MB),
+                                .with_file_size(opts.data_file_cache_file_capacity_mb * MB)
+                                .with_throttle(opts.data_file_cache_throttle.clone()),
                         )
                         .with_recover_mode(opts.data_file_cache_recover_mode)
                         .with_compression(opts.data_file_cache_compression)
@@ -758,11 +753,6 @@ impl StateStoreImpl {
                                 .with_recover_concurrency(opts.data_file_cache_recover_concurrency)
                                 .with_blob_index_size(16 * KB),
                         );
-                    if opts.data_file_cache_insert_rate_limit_mb > 0 {
-                        builder = builder.with_admission_picker(Arc::new(RateLimitPicker::new(
-                            opts.data_file_cache_insert_rate_limit_mb * MB,
-                        )));
-                    }
                 }
             }
 


Reviews:
user: login: stdrc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T07:19:44Z
user: login: stdrc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T07:20:23Z
user: login: MrCroxx
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T07:21:40Z
user: login: hzxa21
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-21T10:41:30Z
user: login: Li0k
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T13:50:59Z

Review comments:
diff_hunk: @@ -100,7 +100,12 @@ normal = ["workspace-hack"]
 development = ["expect-test", "pretty_assertions"]
 
 [workspace.dependencies]
-foyer = { version = "0.16.1", features = ["serde", "tracing", "nightly"] }
+# foyer = { version = "0.16.1", features = ["serde", "tracing", "nightly"] }
+foyer = { git = "https://github.com/foyer-rs/foyer", rev = "fb8030ead6570d62f115e9a7fc442bc874aadcea", features = [
+    "serde",
+    "tracing",
+    "nightly",
+] }
path: Cargo.toml
user: login: stdrc
body: why not publish
created_at: 2025-04-15T07:19:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -224,11 +225,12 @@ This page is automatically generated by `./risedev generate-example-config`
 | flush_buffer_threshold_mb |  |  |
 | flushers |  | 4 |
 | indexer_shards |  | 64 |
-| insert_rate_limit_mb |  | 0 |
+| insert_rate_limit_mb | Deprecated soon. Please use `throttle` instead io throttling. | 0 |
path: src/config/docs.md
user: login: stdrc
body: ```suggestion
| insert_rate_limit_mb | Deprecated soon. Please use `throttle` to do I/O throttling instead. | 0 |
```
created_at: 2025-04-15T07:20:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -100,7 +100,12 @@ normal = ["workspace-hack"]
 development = ["expect-test", "pretty_assertions"]
 
 [workspace.dependencies]
-foyer = { version = "0.16.1", features = ["serde", "tracing", "nightly"] }
+# foyer = { version = "0.16.1", features = ["serde", "tracing", "nightly"] }
+foyer = { git = "https://github.com/foyer-rs/foyer", rev = "fb8030ead6570d62f115e9a7fc442bc874aadcea", features = [
+    "serde",
+    "tracing",
+    "nightly",
+] }
path: Cargo.toml
user: login: MrCroxx
body: My usual workflow is to first conduct longevity testing in RisingWave, resolve some obvious bugs, and then release the foyer version, followed by bumping the foyer version on the RW side. This is just a temporary placeholder. I will modify the release version before merging. Let me leave a comment here. 🙏
created_at: 2025-04-15T07:21:40Z
author_association: CONTRIBUTOR

PR comments:
user: login: MrCroxx
created_at: 2025-04-21T03:39:14Z
body: <img width="1907" alt="image" src="https://github.com/user-attachments/assets/aec7cab1-3e22-4879-b1e1-99abeb5a7c9b" />

No significant performance affects.
author_association: CONTRIBUTOR