jayzhan211 commented on PR #24392:
URL: https://github.com/apache/datafusion/pull/24392#issuecomment-5619174254

   @lyne7-sc , here is a suggestion:
   
   **Retained payload pins the source batch's whole view/dictionary buffer**
   
   `store_batch` replaces `ScalarValue::compacted()` with `copy_array_data`:
   
   ```rust
   // Detach the stored payload from potentially oversized backing buffers.
   let values = make_array(copy_array_data(&values.to_data()));
   ```
   
   `copy_array_data` (`MutableArrayData`) compacts an offset-sliced array, but 
it does **not** compact `Utf8View`/`BinaryView` variadic buffers or dictionary 
value buffers. `ScalarValue::compacted()` did both — `compact_view_buffers()` 
calls `.gc()`, and `try_from_array` on a dictionary yields a single unpacked 
value rather than the dictionary.
   
   Measured `acc.size()` after retaining **one row**, this branch vs `main`:
   
   | payload | main | this PR |
   |---|---|---|
   | `Utf8View`, 1 row from a 10-row batch | 1,012 | 17,494 |
   | `Utf8View`, 1 row from a 10,000-row batch | 1,012 | 509,014 |
   | `Dictionary(Int32,Utf8)`, dict of 1,000 | 1,131 | 38,110 |
   | `Dictionary(Int32,Utf8)`, dict of 10,000 | 1,131 | 303,518 |
   
   That cost is per stored batch per accumulator, and ordered `ARRAY_AGG` gets 
one accumulator **per group** through `GroupsAccumulatorAdapter` 
(`groups_accumulator_supported` is false when `order_bys` is non-empty). 
`Utf8View` is the default for Parquet string columns, so `SELECT g, 
array_agg(str_col ORDER BY k) FROM parquet GROUP BY g` now reports hundreds of 
KB per group to the memory pool and keeps the source batches alive for the 
whole aggregation — spurious spills / `ResourcesExhausted` in exactly the 
workload shape #20788 is about.
   
   Suggested fix — compact the two lossy encodings after the copy:
   
   ```rust
    // Detach the stored payload from potentially oversized backing buffers.
    let values = make_array(copy_array_data(&values.to_data()));
   +let values = compact_payload(values)?;
   ```
   
   ```rust
   /// `copy_array_data` compacts offset-sliced buffers but keeps view variadic
   /// buffers and dictionary values whole, so a single retained row can pin an
   /// entire input batch. Compact those explicitly.
   fn compact_payload(array: ArrayRef) -> Result<ArrayRef> {
       match array.data_type() {
           DataType::Utf8View => Ok(Arc::new(array.as_string_view().gc())),
           DataType::BinaryView => Ok(Arc::new(array.as_binary_view().gc())),
           DataType::Dictionary(_, value_type) => {
               // Round-tripping rebuilds the dictionary from only the 
referenced values.
               let target = array.data_type().clone();
               let flat = cast(array.as_ref(), value_type)?;
               Ok(cast(flat.as_ref(), &target)?)
           }
           // List/LargeList/Struct/FixedSizeList need to recurse into children;
           // `ScalarValue::compact_view_buffers` already implements exactly 
this
           // walk — consider making it `pub` and reusing it here instead.
           _ => Ok(array),
       }
   }
   ```
   
   Please also restore a guard for this. `does_not_over_account_memory_ordered` 
used to carry `// without compaction, the size is 17112`; that comment was 
dropped and the test still only covers `List<Utf8>`. Something like:
   
   ```rust
   #[test]
   fn stored_payload_does_not_pin_source_buffers() -> Result<()> {
       for n in [10usize, 10_000] {
           let vals: Vec<String> =
               (0..n).map(|i| 
format!("this-is-a-long-string-value-{i:08}")).collect();
           let arr = StringViewArray::from(vals);
           let mut acc = ordered_accumulator(
               DataType::Utf8View, DataType::Int64,
               SortOptions::new(false, false), false, false,
           )?;
           acc.update_batch(&[
               Arc::new(arr.slice(3, 1)),
               Arc::new(Int64Array::from(vec![1i64])),
           ])?;
           // size must not scale with the source batch
           assert!(acc.size() < 2_000, "n={n}: size={}", acc.size());
       }
       Ok(())
   }
   ```
   
   and the same shape for `Dictionary(Int32, Utf8)` with dictionaries of 100 
and 10,000 entries.


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

Reply via email to