kosiew commented on code in PR #24924:
URL: https://github.com/apache/datafusion/pull/24924#discussion_r4060636211
##########
datafusion/common/src/config.rs:
##########
@@ -1065,6 +1065,13 @@ config_namespace! {
/// number of rows written is not roughly divisible by the soft max
pub soft_max_rows_per_output_file: ConfigNonZeroUsize, default =
non_zero_usize_default(50000000)
+ /// Target encoded size in bytes of output files when writing multiple.
+ /// Writers asynchronously report the cumulative encoded size as they
+ /// process RecordBatches. The final file size may exceed this limit
due
+ /// to batches buffered before the limit is observed, the size of a
batch,
+ /// and file metadata written when the file is finalized.
+ pub soft_max_bytes_per_output_file: ConfigNonZeroUsize, default =
non_zero_usize_default(4294967295)
Review Comment:
I think the existing SemVer issue still needs to be resolved before merge.
`ExecutionOptions` is publicly and exhaustively constructible, so adding this
field breaks downstream struct literals. The posted `cargo-semver-checks`
result also reports `constructible_struct_adds_field` while the workspace
version remains `55.1.0`.
Could we either follow the project's approved breaking API process,
including the `api-change` label and any required upgrade or release
documentation, or use a compatible design?
##########
datafusion/core/src/datasource/listing/table.rs:
##########
@@ -1763,6 +1784,154 @@ mod tests {
Ok(())
}
+ #[tokio::test]
+ async fn test_copy_respects_soft_max_bytes_per_output_file() -> Result<()>
{
+ struct TestCase {
+ format: &'static str,
+ options: &'static str,
+ // Arrow, Avro, and the parallel Parquet writer buffer about 1 MB
+ // before forwarding bytes to the object writer (and therefore the
+ // demuxer). Their batches must be larger than that buffer for
+ // rotation to be observable.
+ payload_size: usize,
+ }
+
+ let test_cases = [
+ TestCase {
+ format: "csv",
+ options: "",
+ payload_size: 32 * 1024,
+ },
+ TestCase {
+ format: "csv",
+ options: "OPTIONS ('format.compression' 'gzip')",
+ payload_size: 32 * 1024,
+ },
+ TestCase {
+ format: "json",
+ options: "",
+ payload_size: 32 * 1024,
+ },
+ TestCase {
+ format: "json",
+ options: "OPTIONS ('format.compression' 'zstd')",
+ payload_size: 32 * 1024,
+ },
+ TestCase {
+ format: "arrow",
+ options: "",
+ payload_size: 1536 * 1024,
+ },
+ #[cfg(feature = "parquet")]
+ TestCase {
+ format: "parquet",
+ options: "OPTIONS ('format.compression' 'uncompressed',
'format.max_row_group_size' '1', 'format.allow_single_file_parallelism'
'false')",
+ payload_size: 32 * 1024,
+ },
+ #[cfg(feature = "parquet")]
+ TestCase {
+ format: "parquet",
+ options: "OPTIONS ('format.compression' 'zstd(3)',
'format.max_row_group_size' '1')",
+ payload_size: 1536 * 1024,
+ },
+ #[cfg(feature = "avro")]
+ TestCase {
+ format: "avro",
+ options: "",
+ payload_size: 1536 * 1024,
+ },
+ ];
+
+ for test_case in test_cases {
+ let mut config_map = HashMap::new();
+ config_map.insert(
+ "datafusion.execution.minimum_parallel_output_files".into(),
+ "1".into(),
+ );
+ config_map.insert(
+ "datafusion.execution.soft_max_rows_per_output_file".into(),
+ "1000000".into(),
+ );
+ config_map.insert(
+ "datafusion.execution.soft_max_bytes_per_output_file".into(),
+ "1024".into(),
+ );
+ // Use the smallest supported buffer so the writer cannot get far
+ // ahead of the demuxer. There is still an intentional delay, so
the
+ // test only requires that rotation eventually occurs.
+ config_map.insert(
+
"datafusion.execution.max_buffered_batches_per_output_file".into(),
+ "2".into(),
+ );
+ let config = SessionConfig::from_string_hash_map(&config_map)?;
+ let ctx = SessionContext::new_with_config(config);
+
+ // A deterministic high-entropy payload avoids compression reducing
Review Comment:
Could we add a regression case that specifically verifies compressed-size
accounting here?
The gzip/zstd cases use high-entropy payloads and only check that
`files.len() > 1`. The previous implementation based on
`RecordBatch::get_array_memory_size()` would also rotate for this data, so
these cases would still pass with the behavior we are trying to prevent.
A deterministic, highly compressible CSV or JSON case would cover this
better. Ideally it would assert an exact or bounded file count, or otherwise
verify emitted size, so that it fails with the previous batch-memory accounting
but passes when rotation is based on the compressed bytes actually written.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]