Add multi_thread_streams/cutoff support and Samba performance tuning
- Add multi_thread_streams (default 4) and multi_thread_cutoff (default "50M") fields to ReadConfig, wired into rclone mount args - Expose both fields in Web UI config editor under Read Tuning section - Add Samba performance options: TCP_NODELAY, large readwrite, max xmit - Update config.toml.default with new fields and sftp_connections guidance Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
078ab4505e
commit
5efef83a90
@ -118,6 +118,12 @@ pub struct ReadConfig {
|
|||||||
/// In-memory buffer size (e.g. "256M").
|
/// In-memory buffer size (e.g. "256M").
|
||||||
#[serde(default = "default_buffer_size")]
|
#[serde(default = "default_buffer_size")]
|
||||||
pub buffer_size: String,
|
pub buffer_size: String,
|
||||||
|
/// Number of parallel SFTP streams for single-file downloads (rclone --multi-thread-streams).
|
||||||
|
#[serde(default = "default_multi_thread_streams")]
|
||||||
|
pub multi_thread_streams: u32,
|
||||||
|
/// Minimum file size to trigger multi-thread download (e.g. "50M").
|
||||||
|
#[serde(default = "default_multi_thread_cutoff")]
|
||||||
|
pub multi_thread_cutoff: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bandwidth control.
|
/// Bandwidth control.
|
||||||
@ -292,6 +298,12 @@ fn default_read_ahead() -> String {
|
|||||||
fn default_buffer_size() -> String {
|
fn default_buffer_size() -> String {
|
||||||
"256M".into()
|
"256M".into()
|
||||||
}
|
}
|
||||||
|
fn default_multi_thread_streams() -> u32 {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
fn default_multi_thread_cutoff() -> String {
|
||||||
|
"50M".into()
|
||||||
|
}
|
||||||
fn default_bw_zero() -> String {
|
fn default_bw_zero() -> String {
|
||||||
"0".into()
|
"0".into()
|
||||||
}
|
}
|
||||||
@ -417,6 +429,8 @@ impl Config {
|
|||||||
writeln!(out, "chunk_limit = {:?}", self.read.chunk_limit).unwrap();
|
writeln!(out, "chunk_limit = {:?}", self.read.chunk_limit).unwrap();
|
||||||
writeln!(out, "read_ahead = {:?}", self.read.read_ahead).unwrap();
|
writeln!(out, "read_ahead = {:?}", self.read.read_ahead).unwrap();
|
||||||
writeln!(out, "buffer_size = {:?}", self.read.buffer_size).unwrap();
|
writeln!(out, "buffer_size = {:?}", self.read.buffer_size).unwrap();
|
||||||
|
writeln!(out, "multi_thread_streams = {}", self.read.multi_thread_streams).unwrap();
|
||||||
|
writeln!(out, "multi_thread_cutoff = {:?}", self.read.multi_thread_cutoff).unwrap();
|
||||||
writeln!(out).unwrap();
|
writeln!(out).unwrap();
|
||||||
|
|
||||||
// --- Bandwidth ---
|
// --- Bandwidth ---
|
||||||
@ -690,6 +704,8 @@ mount_point = "/mnt/photos"
|
|||||||
assert_eq!(config.read.chunk_limit, "1G");
|
assert_eq!(config.read.chunk_limit, "1G");
|
||||||
assert_eq!(config.read.read_ahead, "512M");
|
assert_eq!(config.read.read_ahead, "512M");
|
||||||
assert_eq!(config.read.buffer_size, "256M");
|
assert_eq!(config.read.buffer_size, "256M");
|
||||||
|
assert_eq!(config.read.multi_thread_streams, 4);
|
||||||
|
assert_eq!(config.read.multi_thread_cutoff, "50M");
|
||||||
|
|
||||||
assert_eq!(config.bandwidth.limit_up, "0");
|
assert_eq!(config.bandwidth.limit_up, "0");
|
||||||
assert_eq!(config.bandwidth.limit_down, "0");
|
assert_eq!(config.bandwidth.limit_down, "0");
|
||||||
@ -994,6 +1010,8 @@ mount_point = "/mnt/photos"
|
|||||||
assert_eq!(config.cache.dir, config2.cache.dir);
|
assert_eq!(config.cache.dir, config2.cache.dir);
|
||||||
assert_eq!(config.cache.max_size, config2.cache.max_size);
|
assert_eq!(config.cache.max_size, config2.cache.max_size);
|
||||||
assert_eq!(config.read.chunk_size, config2.read.chunk_size);
|
assert_eq!(config.read.chunk_size, config2.read.chunk_size);
|
||||||
|
assert_eq!(config.read.multi_thread_streams, config2.read.multi_thread_streams);
|
||||||
|
assert_eq!(config.read.multi_thread_cutoff, config2.read.multi_thread_cutoff);
|
||||||
assert_eq!(config.bandwidth.adaptive, config2.bandwidth.adaptive);
|
assert_eq!(config.bandwidth.adaptive, config2.bandwidth.adaptive);
|
||||||
assert_eq!(config.writeback.transfers, config2.writeback.transfers);
|
assert_eq!(config.writeback.transfers, config2.writeback.transfers);
|
||||||
assert_eq!(config.protocols.enable_smb, config2.protocols.enable_smb);
|
assert_eq!(config.protocols.enable_smb, config2.protocols.enable_smb);
|
||||||
|
|||||||
@ -66,6 +66,13 @@ pub fn build_mount_args(config: &Config, share: &ShareConfig, rc_port: u16) -> V
|
|||||||
args.push("--vfs-read-ahead".into());
|
args.push("--vfs-read-ahead".into());
|
||||||
args.push(config.read.read_ahead.clone());
|
args.push(config.read.read_ahead.clone());
|
||||||
|
|
||||||
|
// Multi-thread download: splits large files across N parallel SFTP streams
|
||||||
|
args.push("--multi-thread-streams".into());
|
||||||
|
args.push(config.read.multi_thread_streams.to_string());
|
||||||
|
|
||||||
|
args.push("--multi-thread-cutoff".into());
|
||||||
|
args.push(config.read.multi_thread_cutoff.clone());
|
||||||
|
|
||||||
// Concurrent transfers for write-back
|
// Concurrent transfers for write-back
|
||||||
args.push("--transfers".into());
|
args.push("--transfers".into());
|
||||||
args.push(config.writeback.transfers.to_string());
|
args.push(config.writeback.transfers.to_string());
|
||||||
@ -240,6 +247,8 @@ mount_point = "/mnt/photos"
|
|||||||
assert!(args.contains(&"--dir-cache-time".to_string()));
|
assert!(args.contains(&"--dir-cache-time".to_string()));
|
||||||
assert!(args.contains(&"1h".to_string()));
|
assert!(args.contains(&"1h".to_string()));
|
||||||
assert!(args.contains(&"--buffer-size".to_string()));
|
assert!(args.contains(&"--buffer-size".to_string()));
|
||||||
|
assert!(args.contains(&"--multi-thread-streams".to_string()));
|
||||||
|
assert!(args.contains(&"--multi-thread-cutoff".to_string()));
|
||||||
assert!(args.contains(&"--transfers".to_string()));
|
assert!(args.contains(&"--transfers".to_string()));
|
||||||
assert!(args.contains(&"4".to_string()));
|
assert!(args.contains(&"4".to_string()));
|
||||||
assert!(args.contains(&"--rc".to_string()));
|
assert!(args.contains(&"--rc".to_string()));
|
||||||
|
|||||||
@ -53,6 +53,13 @@ pub fn generate(config: &Config) -> Result<String> {
|
|||||||
writeln!(conf, " printcap name = /dev/null")?;
|
writeln!(conf, " printcap name = /dev/null")?;
|
||||||
writeln!(conf, " disable spoolss = yes")?;
|
writeln!(conf, " disable spoolss = yes")?;
|
||||||
writeln!(conf)?;
|
writeln!(conf)?;
|
||||||
|
writeln!(conf, " # Performance tuning")?;
|
||||||
|
writeln!(conf, " socket options = TCP_NODELAY IPTOS_THROUGHPUT SO_RCVBUF=131072 SO_SNDBUF=131072")?;
|
||||||
|
writeln!(conf, " read raw = yes")?;
|
||||||
|
writeln!(conf, " write raw = yes")?;
|
||||||
|
writeln!(conf, " large readwrite = yes")?;
|
||||||
|
writeln!(conf, " max xmit = 65535")?;
|
||||||
|
writeln!(conf)?;
|
||||||
|
|
||||||
// Share sections — each share points at its own mount_point
|
// Share sections — each share points at its own mount_point
|
||||||
for share in &config.shares {
|
for share in &config.shares {
|
||||||
@ -274,6 +281,9 @@ mount_point = "/mnt/photos"
|
|||||||
assert!(content.contains("server min protocol = SMB2_02"));
|
assert!(content.contains("server min protocol = SMB2_02"));
|
||||||
assert!(content.contains("map to guest = Bad User"));
|
assert!(content.contains("map to guest = Bad User"));
|
||||||
assert!(content.contains("load printers = no"));
|
assert!(content.contains("load printers = no"));
|
||||||
|
assert!(content.contains("socket options = TCP_NODELAY"));
|
||||||
|
assert!(content.contains("large readwrite = yes"));
|
||||||
|
assert!(content.contains("max xmit = 65535"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -18,7 +18,7 @@ nas_user = "admin"
|
|||||||
# nas_key_file = "/root/.ssh/id_ed25519"
|
# nas_key_file = "/root/.ssh/id_ed25519"
|
||||||
# SFTP port
|
# SFTP port
|
||||||
sftp_port = 22
|
sftp_port = 22
|
||||||
# SFTP connection pool size
|
# SFTP connection pool size (if multi_thread_streams=4, recommend >= 16)
|
||||||
sftp_connections = 8
|
sftp_connections = 8
|
||||||
|
|
||||||
# --- Additional NAS (uncomment to add) ---
|
# --- Additional NAS (uncomment to add) ---
|
||||||
@ -49,6 +49,11 @@ chunk_limit = "1G"
|
|||||||
read_ahead = "512M"
|
read_ahead = "512M"
|
||||||
# In-memory buffer size
|
# In-memory buffer size
|
||||||
buffer_size = "256M"
|
buffer_size = "256M"
|
||||||
|
# Number of parallel SFTP streams for single-file downloads (improves cold-read speed)
|
||||||
|
# If using multi_thread_streams=4, set sftp_connections >= 16 for multi-file concurrency
|
||||||
|
multi_thread_streams = 4
|
||||||
|
# Minimum file size to trigger multi-thread download
|
||||||
|
multi_thread_cutoff = "50M"
|
||||||
|
|
||||||
[bandwidth]
|
[bandwidth]
|
||||||
# Upload (write-back) speed limit ("0" = unlimited)
|
# Upload (write-back) speed limit ("0" = unlimited)
|
||||||
|
|||||||
@ -295,6 +295,14 @@ if (window.Alpine) {
|
|||||||
<label>Buffer Size</label>
|
<label>Buffer Size</label>
|
||||||
<input type="text" x-model="config.read.buffer_size" placeholder="e.g. 256M">
|
<input type="text" x-model="config.read.buffer_size" placeholder="e.g. 256M">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field-row">
|
||||||
|
<label>Multi-Thread Streams</label>
|
||||||
|
<input type="number" x-model.number="config.read.multi_thread_streams" min="1" max="64" placeholder="e.g. 4">
|
||||||
|
</div>
|
||||||
|
<div class="field-row">
|
||||||
|
<label>Multi-Thread Cutoff</label>
|
||||||
|
<input type="text" x-model="config.read.multi_thread_cutoff" placeholder="e.g. 50M">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user