Skip to content

Commit 291205f

Browse files
authored
perf(cubejs-backend-native): Transfer ResultWrapper rawData as Buffer(JSON), ~10x faster (cube-js#10728)
Serialize JS result rows once on the JS side and pass them across the Neon bridge as a Buffer, so Rust can consume them via serde_json::from_slice instead of walking a JsArray through JsValueDeserializer.
1 parent 15626b1 commit 291205f

2 files changed

Lines changed: 28 additions & 1 deletion

File tree

packages/cubejs-backend-native/js/ResultWrapper.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export class ResultWrapper extends BaseWrapper implements DataResult {
126126
}
127127
this.cached = true;
128128
}
129+
129130
return this.cache;
130131
}
131132

@@ -139,7 +140,14 @@ export class ResultWrapper extends BaseWrapper implements DataResult {
139140
return [this.nativeReference];
140141
}
141142

142-
return [this.jsResult];
143+
// Serialize to a Buffer so the Rust side can decode via
144+
// serde_json::from_slice instead of walking a JsArray through the
145+
// Neon bridge with JsValueDeserializer. On 5 MB of AoO rows
146+
// (~21k rows × 8 fields) the JsArray walk costs ~80 ms locally;
147+
// Buffer + serde_json is ~7× faster (M3 MAX) and tracks V8's JSON.parse
148+
// (~11 ms on the same payload). On a real server it should be 3-6× slower,
149+
// so avoiding the JsArray walk matters even more there.
150+
return [Buffer.from(JSON.stringify(this.jsResult))];
143151
}
144152

145153
public setTransformData(td: any) {

packages/cubejs-backend-native/src/orchestrator.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,18 @@ impl ResultWrapper {
9090
let query_result =
9191
if let Ok(js_box) = raw_data_js.downcast::<JsBox<Arc<QueryResult>>, _>(cx) {
9292
Arc::clone(&js_box)
93+
} else if let Ok(js_buffer) = raw_data_js.downcast::<JsBuffer, _>(cx) {
94+
let bytes = js_buffer.as_slice(cx);
95+
let js_raw_data: JsRawData = serde_json::from_slice(bytes).map_err(|e| {
96+
CubeError::internal(format!(
97+
"Can't parse raw data JSON from JS ResultWrapper: {}",
98+
e
99+
))
100+
})?;
101+
102+
QueryResult::from_js_raw_data(js_raw_data)
103+
.map(Arc::new)
104+
.map_cube_err("Can't build results data from JS rawData")?
93105
} else if let Ok(js_array) = raw_data_js.downcast::<JsArray, _>(cx) {
94106
let deserializer = JsValueDeserializer::new(cx, js_array.upcast());
95107
let js_raw_data: JsRawData = match Deserialize::deserialize(deserializer) {
@@ -222,6 +234,13 @@ fn extract_query_result(
222234
) -> Result<Arc<QueryResult>, anyhow::Error> {
223235
if let Ok(js_box) = data_arg.downcast::<JsBox<Arc<QueryResult>>, _>(cx) {
224236
Ok(Arc::clone(&js_box))
237+
} else if let Ok(js_buffer) = data_arg.downcast::<JsBuffer, _>(cx) {
238+
let bytes = js_buffer.as_slice(cx);
239+
let js_raw_data: JsRawData = serde_json::from_slice(bytes)?;
240+
241+
QueryResult::from_js_raw_data(js_raw_data)
242+
.map(Arc::new)
243+
.map_err(anyhow::Error::from)
225244
} else if let Ok(js_array) = data_arg.downcast::<JsArray, _>(cx) {
226245
let deserializer = JsValueDeserializer::new(cx, js_array.upcast());
227246
let js_raw_data: JsRawData = Deserialize::deserialize(deserializer)?;

0 commit comments

Comments
 (0)