-
Notifications
You must be signed in to change notification settings - Fork 2.2k
perf(arrow codec): drop serde_json::Value round-trip in Arrow encoding #25773
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| The `clickhouse` sink's `arrow_stream` batch encoding now serializes events directly into the Arrow record batch instead of first materializing each event as an intermediate JSON value. This removes one allocation and one serialization pass per event on the encode hot path, reducing CPU by roughly 30% on wide or deeply nested events. Encoding behavior is otherwise unchanged, with one exception: non-finite floats (`inf`/`-inf`) are now passed through to the destination, whereas previously they were encoded as null. | ||
|
|
||
| authors: benjamin-awd |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,9 +27,9 @@ use vector_common::internal_event::{ | |
| ComponentEventsDropped, Count, InternalEventHandle, Registered, UNINTENTIONAL, emit, register, | ||
| }; | ||
| use vector_config::configurable_component; | ||
| use vector_core::event::Event; | ||
| use vector_core::event::{Event, LogEvent}; | ||
|
|
||
| use super::arrow::{ArrowEncodingError, build_record_batch}; | ||
| use super::arrow::{ArrowEncodingError, build_record_batch, decode_rows_to_record_batch}; | ||
| use crate::encoding::format::arrow::vector_log_events_to_json_values; | ||
| use crate::internal_events::{ArrowWriterError, JsonSerializationError, SchemaGenerationError}; | ||
|
|
||
|
|
@@ -319,15 +319,9 @@ impl tokio_util::codec::Encoder<Vec<Event>> for ParquetSerializer { | |
| return Ok(()); | ||
| } | ||
|
|
||
| let json_values = match vector_log_events_to_json_values(&events) { | ||
| Ok(values) => values, | ||
| Err(e) => { | ||
| emit(JsonSerializationError { error: &e }); | ||
| return Err(Box::new(e)); | ||
| } | ||
| }; | ||
| let logs: Vec<&LogEvent> = events.iter().filter_map(Event::maybe_as_log).collect(); | ||
|
|
||
| let non_log_count = events.len() - json_values.len(); | ||
| let non_log_count = events.len() - logs.len(); | ||
|
|
||
| if non_log_count > 0 { | ||
| warn!( | ||
|
|
@@ -338,17 +332,15 @@ impl tokio_util::codec::Encoder<Vec<Event>> for ParquetSerializer { | |
| self.events_dropped_handle.emit(Count(non_log_count)) | ||
| } | ||
|
|
||
| if json_values.is_empty() { | ||
| if logs.is_empty() { | ||
| return Ok(()); | ||
| } | ||
|
|
||
| match self.schema_mode { | ||
| let record_batch = match self.schema_mode { | ||
| // In strict mode, check for extra top-level fields not in the schema. | ||
| ParquetSchemaMode::Strict => { | ||
| for event in &events { | ||
| if let Some(log) = event.maybe_as_log() | ||
| && let Some(object_map) = log.as_map() | ||
| { | ||
| for log in &logs { | ||
| if let Some(object_map) = log.as_map() { | ||
| for top_level in object_map.keys() { | ||
| if !self.schema_field_names.contains(top_level.as_str()) { | ||
| return Err(Box::new(ArrowEncodingError::SchemaFetchError { | ||
|
|
@@ -360,18 +352,29 @@ impl tokio_util::codec::Encoder<Vec<Event>> for ParquetSerializer { | |
| } | ||
| } | ||
| } | ||
| build_record_batch(Arc::clone(&self.schema), &logs).map_err(Box::new)? | ||
| } | ||
| ParquetSchemaMode::AutoInfer => { | ||
| // Schema inference is driven by Arrow's JSON schema inference, which still | ||
| // requires `serde_json::Value`s. | ||
| let json_values = match vector_log_events_to_json_values(&events) { | ||
| Ok(values) => values, | ||
| Err(e) => { | ||
| emit(JsonSerializationError { error: &e }); | ||
| return Err(Box::new(e)); | ||
| } | ||
| }; | ||
| let schema = ParquetSchemaGenerator::infer_schema(&json_values)?; | ||
| self.schema = Arc::new(ParquetSchemaGenerator::try_normalize_schema( | ||
| &events, schema, | ||
| )); | ||
| decode_rows_to_record_batch(Arc::clone(&self.schema), &json_values) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For Useful? React with 👍 / 👎. |
||
| .map_err(Box::new)? | ||
| } | ||
| ParquetSchemaMode::Relaxed => {} | ||
| } | ||
|
|
||
| let record_batch = | ||
| build_record_batch(Arc::clone(&self.schema), &json_values).map_err(Box::new)?; | ||
| ParquetSchemaMode::Relaxed => { | ||
| build_record_batch(Arc::clone(&self.schema), &logs).map_err(Box::new)? | ||
| } | ||
| }; | ||
|
|
||
| Self::write_record_batch(&record_batch, buffer, &self.writer_props).map_err(Box::new)?; | ||
|
|
||
|
|
@@ -627,6 +630,16 @@ mod tests { | |
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn autoinfer_encodes_non_finite_floats() { | ||
| let events = vec![ | ||
| create_event(vec![("ratio", f64::INFINITY)]), | ||
| create_event(vec![("ratio", f64::NEG_INFINITY)]), | ||
| ]; | ||
| let (_schema, num_rows) = encode_autoinfer_and_read_schema(events); | ||
| assert_eq!(num_rows, 2); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_parquet_empty_events() { | ||
| let mut serializer = ParquetSerializer::new(ParquetSerializerConfig { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In
schema_mode = auto_infer, this still infers fromserde_json::Values while the batch is now encoded from the originalLogEvents.serde_json::to_valueconverts non-finite floats such asinf/-inftonull, so a batch where a field contains only those values is inferred as an ArrowNullcolumn; the subsequentbuild_record_batch(..., &logs)then tries to decode the original float into thatNullcolumn and the parquet encode errors instead of producing the new pass-through behavior.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handled in 4fc638e