apache / apache/fluss

[c++] Avoid redundant STRING and BYTES copies during row type resolution

Open
#4,239 0 comments 0 reactions 1 assignee Claimed by @loserwang1024 View on GitHub
Dominant language
Java
Stars
2.1k
Forks
625
Avg merge
3d 14h
Merged PRs (30d)
97

Description

### Search before asking

- [x] I searched in the [issues](https://github.com/apache/fluss/issues) and found nothing similar.

### Motivation

The C++ row-based append path copies STRING and BYTES values into a second owned row during schema resolution, even when those values require no conversion.

The binding already owns the STRING value after `gr_set_str`:

```rust
fcore::row::Datum::String(std::borrow::Cow::Owned(val.to_string()))
```

Before invoking the core append operation, [AppendWriter::append](https://github.com/apache/fluss/blob/44e1cdd225c9f31ec26d62f25be0468f2cd89187/fluss-rust/bindings/cpp/src/lib.rs#L2155) calls `resolve_row_types`. That function constructs another row and returns `GenericRow<'static>`. Its pass-through STRING and Blob branches allocate and copy again:

```rust
// STRING when the target is not DECIMAL:
_ => Datum::String(Cow::Owned(cow.to_string())),

Datum::Blob(cow) => Datum::Blob(Cow::Owned(cow.to_vec())),
```

See [resolve_row_types and resolve_datum](https://github.com/apache/fluss/blob/44e1cdd225c9f31ec26d62f25be0468f2cd89187/fluss-rust/bindings/cpp/src/types.rs#L461).

For a row containing 13 non-empty STRING fields with STRING target columns, this resolution step creates a new row vector and 13 additional owned strings without changing the field values. These copies are additional to the storage established by the setters and to the subsequent encoding needed for writing.

This is a performance improvement opportunity in the C++ binding, not a correctness failure or a claim that all Rust append callers copy their input this way. Scalar numeric fields are copied by value and do not incur the same string allocation behavior.

### Solution

Consider either or both of the following:

- Return a row borrowing unchanged values from the input, while retaining owned storage for values that genuinely require conversion.
- For schemas that require no resolution, pass the existing row directly to the core writer instead of constructing a second row.

The core append interface accepts a borrowed row and returns a `WriteResultFuture` without a row lifetime parameter. This supports investigating a borrowed resolution path, but changes to the shared helper should also check its upsert, delete, lookup, and prefix-lookup callers. A schema-based fast path must preserve existing validation rather than bypassing it accidentally.

### Anything else?

_No response_

### Willingness to contribute

- [ ] I'm willing to submit a PR!

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.