@@ -578,7 +578,7 @@ pub struct RefSpec {
578578 pub name : String ,
579579}
580580
581- #[ derive( Serialize , Deserialize , Debug , Default , Clone ) ]
581+ #[ derive( Serialize , Deserialize , Debug , Clone ) ]
582582#[ serde( deny_unknown_fields) ]
583583pub struct FilesystemSpec {
584584 /// Path on the system where to store the actual content. This is where
@@ -599,7 +599,7 @@ pub struct FilesystemSpec {
599599
600600 /// Buffer size to use when reading files. Generally this should be left
601601 /// to the default value except for testing.
602- /// Default: 32k .
602+ /// Default: 256k .
603603 #[ serde( default , deserialize_with = "convert_data_size_with_shellexpand" ) ]
604604 pub read_buffer_size : u32 ,
605605
@@ -624,6 +624,41 @@ pub struct FilesystemSpec {
624624 /// Default: 0
625625 #[ serde( default , deserialize_with = "convert_numeric_with_shellexpand" ) ]
626626 pub max_concurrent_writes : usize ,
627+
628+ /// If true, use sync_data() instead of sync_all() when flushing writes
629+ /// to disk. sync_data() only syncs the file data without metadata
630+ /// (timestamps, permissions), which is faster. For content-addressed
631+ /// storage where the content is verified by hash, metadata sync is
632+ /// unnecessary and this significantly reduces write latency.
633+ /// Default: true
634+ #[ serde( default = "default_sync_data_only" ) ]
635+ pub sync_data_only : bool ,
636+
637+ /// If true, skip writes when a blob with the same key already exists
638+ /// in the store. This is safe for content-addressed storage (CAS) where
639+ /// identical keys guarantee identical content. Do NOT enable this for
640+ /// stores where the same key can hold different content (e.g. action
641+ /// cache).
642+ /// When a duplicate write is skipped, the existing entry's access time
643+ /// is updated in the LRU to prevent premature eviction.
644+ /// Default: false
645+ #[ serde( default ) ]
646+ pub content_is_immutable : bool ,
647+ }
648+
649+ impl Default for FilesystemSpec {
650+ fn default ( ) -> Self {
651+ Self {
652+ content_path : String :: new ( ) ,
653+ temp_path : String :: new ( ) ,
654+ read_buffer_size : 0 ,
655+ eviction_policy : None ,
656+ block_size : 0 ,
657+ max_concurrent_writes : 0 ,
658+ sync_data_only : true ,
659+ content_is_immutable : false ,
660+ }
661+ }
627662}
628663
629664// NetApp ONTAP S3 Spec
@@ -1095,6 +1130,32 @@ pub struct GrpcEndpoint {
10951130 /// If not set or 0, defaults to 20 seconds.
10961131 #[ serde( default , deserialize_with = "convert_duration_with_shellexpand" ) ]
10971132 pub http2_keepalive_timeout_s : u64 ,
1133+
1134+ /// Whether to set TCP_NODELAY on the connection socket.
1135+ /// Disables Nagle's algorithm, reducing latency for small writes.
1136+ /// Default: true
1137+ #[ serde( default = "default_tcp_nodelay" ) ]
1138+ pub tcp_nodelay : bool ,
1139+ }
1140+
1141+ fn default_sync_data_only ( ) -> bool {
1142+ true
1143+ }
1144+
1145+ fn default_tcp_nodelay ( ) -> bool {
1146+ true
1147+ }
1148+
1149+ fn default_batch_update_threshold_bytes ( ) -> u64 {
1150+ 1_048_576
1151+ }
1152+
1153+ fn default_batch_coalesce_delay_ms ( ) -> u64 {
1154+ 10
1155+ }
1156+
1157+ const fn default_connections_per_endpoint ( ) -> usize {
1158+ 32
10981159}
10991160
11001161#[ derive( Serialize , Deserialize , Debug , Clone ) ]
@@ -1121,8 +1182,8 @@ pub struct GrpcSpec {
11211182 pub max_concurrent_requests : usize ,
11221183
11231184 /// The number of connections to make to each specified endpoint to balance
1124- /// the load over multiple TCP connections. Default 1 .
1125- #[ serde( default , deserialize_with = "convert_numeric_with_shellexpand" ) ]
1185+ /// the load over multiple TCP connections. Default 16 .
1186+ #[ serde( default = "default_connections_per_endpoint" , deserialize_with = "convert_numeric_with_shellexpand" ) ]
11261187 pub connections_per_endpoint : usize ,
11271188
11281189 /// Maximum time (seconds) allowed for a single RPC request (e.g. a
@@ -1132,6 +1193,35 @@ pub struct GrpcSpec {
11321193 /// Default: 120 (seconds)
11331194 #[ serde( default , deserialize_with = "convert_duration_with_shellexpand" ) ]
11341195 pub rpc_timeout_s : u64 ,
1196+
1197+ /// Maximum blob size (in bytes) for using BatchUpdateBlobs instead of
1198+ /// ByteStream.Write. Blobs at or below this size skip per-blob streaming
1199+ /// overhead (UUID generation, resource_name, streaming setup). Only
1200+ /// applies to CAS stores, not AC.
1201+ ///
1202+ /// Set to 0 to disable (all uploads use ByteStream.Write).
1203+ ///
1204+ /// Default: 1048576 (1 MiB)
1205+ #[ serde(
1206+ default = "default_batch_update_threshold_bytes" ,
1207+ deserialize_with = "convert_numeric_with_shellexpand"
1208+ ) ]
1209+ pub batch_update_threshold_bytes : u64 ,
1210+
1211+ /// Time window (in milliseconds) to coalesce multiple small blob uploads
1212+ /// into a single BatchUpdateBlobs RPC. Requires
1213+ /// `batch_update_threshold_bytes > 0`.
1214+ ///
1215+ /// When > 0, incoming small uploads are buffered for up to this duration
1216+ /// before being sent as one batch. When 0, each small upload is sent
1217+ /// immediately as a single-element BatchUpdateBlobs RPC.
1218+ ///
1219+ /// Default: 10 (milliseconds)
1220+ #[ serde(
1221+ default = "default_batch_coalesce_delay_ms" ,
1222+ deserialize_with = "convert_numeric_with_shellexpand"
1223+ ) ]
1224+ pub batch_coalesce_delay_ms : u64 ,
11351225}
11361226
11371227/// The possible error codes that might occur on an upstream request.
0 commit comments