JingsongLi commented on code in PR #1072:
URL: https://github.com/apache/paimon-rust/pull/1072#discussion_r4216558835


##########
crates/paimon/src/arrow/format/parquet.rs:
##########
@@ -445,7 +458,70 @@ fn parquet_writer_properties(
             );
         }
     }
-    Ok(props.build())
+    Ok(parquet_sizing_properties(props, options)?.build())
+}
+
+/// Java's parquet writer sizing keys; `file.block-size` overrides 
`parquet.block.size` as in
+/// Java's `ParquetFileFormat`.
+fn parquet_sizing_properties(
+    mut props: parquet::file::properties::WriterPropertiesBuilder,
+    options: &HashMap<String, String>,
+) -> crate::Result<parquet::file::properties::WriterPropertiesBuilder> {
+    const BLOCK_SIZE: &str = "parquet.block.size";
+    const PAGE_SIZE: &str = "parquet.page.size";
+    const DICTIONARY_PAGE_SIZE: &str = "parquet.dictionary.page.size";
+    const PAGE_ROW_COUNT_LIMIT: &str = "parquet.page.row.count.limit";
+    const PAGE_SIZE_ROW_CHECK_MIN: &str = "parquet.page.size.row.check.min";
+    let positive = |key: &str| -> crate::Result<Option<usize>> {
+        options
+            .get(key)
+            .map(|value| {
+                value
+                    .trim()
+                    .parse::<usize>()
+                    .ok()
+                    .filter(|parsed| *parsed > 0)
+                    .ok_or_else(|| crate::Error::ConfigInvalid {
+                        message: format!("Option '{key}' must be a positive 
integer, got: {value}"),
+                    })
+            })
+            .transpose()
+    };
+    let file_block_size = CoreOptions::new(options)
+        .file_block_size()?
+        .map(|bytes| {
+            usize::try_from(bytes)
+                .ok()
+                .filter(|bytes| *bytes > 0)
+                .ok_or_else(|| crate::Error::ConfigInvalid {
+                    message: format!("Option 'file.block-size' must be 
positive, got: {bytes}"),
+                })
+        })
+        .transpose()?;
+    let block_size = match file_block_size {
+        Some(bytes) => Some(bytes),
+        None => positive(BLOCK_SIZE)?,
+    };
+    if let Some(bytes) = block_size {
+        props = props.set_max_row_group_bytes(Some(bytes));
+    }
+    if let Some(bytes) = positive(PAGE_SIZE)? {
+        props = props.set_data_page_size_limit(bytes);
+    }
+    if let Some(bytes) = positive(DICTIONARY_PAGE_SIZE)? {
+        props = props.set_dictionary_page_size_limit(bytes);
+    }
+    // parquet-rs checks page limits between write batches, so the batch 
bounds the check interval.
+    let mut write_batch_size = positive(PAGE_SIZE_ROW_CHECK_MIN)?;
+    if let Some(rows) = positive(PAGE_ROW_COUNT_LIMIT)? {
+        props = props.set_data_page_row_count_limit(rows);
+        let batch = 
write_batch_size.unwrap_or(parquet::file::properties::DEFAULT_WRITE_BATCH_SIZE);
+        write_batch_size = Some(batch.min(rows));

Review Comment:
   [P2] Clamp the check interval against the effective default page row limit
   
   The clamp only runs when `parquet.page.row.count.limit` is explicitly set. 
With only `parquet.page.size.row.check.min=65536`, the effective page row limit 
remains 20,000, but `write_batch_size` becomes 65,536. A filesystem TableWrite 
probe writing 131,072 INT32 rows with dictionary and compression disabled 
produces two consecutive pages of 65,536 rows, as confirmed by the offset 
index. Since parquet-rs checks the limit after each write batch, the larger 
interval defeats the default row limit and increases page-index read 
granularity. This also contradicts the documented promise that the interval 
never exceeds the page row limit. Please clamp against the effective limit even 
when it is omitted, using `DEFAULT_DATA_PAGE_ROW_COUNT_LIMIT`, and add a 
behavior test that configures only the check interval above that default.



##########
crates/paimon/src/arrow/format/parquet.rs:
##########
@@ -524,13 +600,21 @@ impl FormatFileWriter for ParquetFormatWriter {
             physical_batch = cast_record_batch_to_schema(batch, &self.schema)?;
             &physical_batch
         };
-        self.inner
-            .write(batch)
-            .await
-            .map_err(|e| crate::Error::DataInvalid {
-                message: format!("Failed to write parquet batch: {e}"),
-                source: None,
-            })
+        // parquet-rs splits by bytes only once a row group holds rows, so 
bound each write
+        // by the batch's in-memory row size, which is not smaller than its 
encoded size.
+        let rows = batch.num_rows();
+        let step = self.max_row_group_bytes.map_or(rows, |limit| {
+            let row_bytes = 
batch.get_array_memory_size().div_ceil(rows.max(1)).max(1);
+            (limit / row_bytes).max(1)

Review Comment:
   [P2] Account for row-size skew when bounding the first slice
   
   The whole-batch average does not bound the size of a particular slice. I 
reproduced on this head through filesystem `TableWrite`: one 65,536-row `(INT, 
VARCHAR)` batch, with the first 1,000 strings each 8 KiB and the remainder each 
1 byte, `file.block-size=1 mb`, dictionary disabled, and compression disabled. 
The append path produces a first row group of 8,259,966 bytes; the primary-key 
path also produces oversized groups, including one of 4,262,119 bytes. Every 
individual row is well below the 1 MiB limit, and both tables commit and read 
back correctly. The average permits a first slice containing too many large 
values, and parquet-rs accepts that slice into an empty group before checking 
the byte limit. This leaves point-read granularity dependent on value 
distribution despite the new bounding logic. Please base the bound on the 
actual rows in each slice, accounting for physical encoding, and check the 
writer's encoded size at sufficiently small intervals. Add a skewed variable-
 width TableWrite regression alongside the uniform-INT case.



-- 
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]

Reply via email to