You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Optimizes SQLite JSONB serialization (ToSql<sql_types::Jsonb, Sqlite> for serde_json::Value) by replacing the previous quadratic nested-buffer copying with a linear, two-pass iterative write plan. Also introduces a Criterion-based benchmark suite (benches/sqlite_jsonb.rs) to track scaling across depth and size.
Motivation & Background
Previously, encoding nested JSON arrays and objects allocated a new Vec<u8> for every container level. When a nested container completed, its entire serialized buffer was copied into its parent container. For structures of depth $d$, this caused quadratic $O(d^2)$ buffer allocation and copying ($1 + 2 + \dots + d$ bytes).
As discussed in #5192 (following #5172), we can avoid intermediate buffer allocations and repeated copying by computing container sizes iteratively in advance and writing every header and payload directly into the final destination buffer.
Solution
The new encoding routine works in two linear phases without recursion:
Iterative Planning (Leaves to Root flattening):
Traverse the serde_json::Value iteratively using an explicit visits stack to prevent stack overflow on deep inputs. Each scalar or container is recorded into a sequential write_items plan (JsonbWriteItem::Scalar or JsonbWriteItem::Composite). Scalar payloads borrow their byte slices when possible (or store owned/escaped representations) without allocating intermediate container byte buffers.
Bottom-Up Size Calculation & Direct Write:
Iterate backwards over the plan (from leaves up to root) using an encoded_sizes stack to compute exact container payload sizes and header lengths with checked arithmetic.
Reserve the total calculated capacity in the destination buffer once (buffer.try_reserve(encoded_size)).
Perform a final forward pass over write_items, writing each header and payload into the reserved buffer exactly once.
Benchmark Results (Criterion)
A Criterion benchmark suite (benches/sqlite_jsonb.rs) was added comparing main and this branch across depth and size scaling:
Stress & Nesting validation: Verified encoding on deeply nested 2,000-level arrays, objects, and mixed structures without stack overflow or performance degradation.
Style & Lints: Passed cargo clippy -p diesel --bench sqlite_jsonb with warnings denied (-D warnings) and cargo fmt --check.
Changelog: Included entry in CHANGELOG.md under ### Changed.
AI-Use Disclosure
In accordance with Diesel's CONTRIBUTING.md and NLnet foundation guidelines on generative AI:
An AI coding assistant (Gemini / Antigravity) was used to assist in brainstorming the two-pass write-plan design, drafting the bottom-up size calculation arithmetic, generating test fixtures, and setting up the Criterion benchmark harness.
All code, memory safety boundaries, error handling, benchmarks, and diffs were manually reviewed, inspected, and verified by me.
All test suites, clippy runs (with warnings denied), benchmarks, and formatting checks were executed and validated locally prior to submission.
I checked for similar changes and make sure to reference them
I included a changelog entry for relevant new features or changes
@LucaCappelletti94 Thanks for the suggestion! I've added a dedicated Criterion benchmark suite in diesel/benches/sqlite_jsonb.rs and benchmarked both main and this branch across multiple depths and sizes.
Depth Scaling — Nested Arrays ([[[[... 1 ...]]]])
Depth
main (baseline)
This Branch
Speedup
10
2.05 µs
0.83 µs
2.45x faster (-59%)
50
10.62 µs
2.32 µs
4.58x faster (-78%)
100
29.51 µs
4.08 µs
7.23x faster (-86%)
250
77.54 µs
10.58 µs
7.33x faster (-86%)
500
195.70 µs
19.94 µs
9.81x faster (-90%)
1000
409.44 µs
40.11 µs
10.21x faster (-90%)
2000
924.53 µs
75.43 µs
12.26x faster (-92%)
On main, the nested buffer copying shows quadratic growth ($14.4\times$ from 10 to 100, $13.9\times$ from 100 to 1000). On this branch, scaling is strictly linear $O(d)$ — doubling the depth from 1,000 to 2,000 takes $1.88\times$ the time (40 µs to 75 µs), yielding a 12.26x speedup at depth 2,000.
For flat scalar arrays where no container nesting is present, both approaches scale linearly $O(N)$ with virtually identical times, confirming zero regression on flat inputs while providing up to 12.26x speedup on nested inputs.
I have also merged the latest upstream/main to resolve the merge conflict in diesel/src/sqlite/types/json.rs and incorporated the negative integer fix.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #5192.
Optimizes SQLite JSONB serialization (
ToSql<sql_types::Jsonb, Sqlite> for serde_json::Value) by replacing the previous quadratic nested-buffer copying with a linear, two-pass iterative write plan. Also introduces a Criterion-based benchmark suite (benches/sqlite_jsonb.rs) to track scaling across depth and size.Motivation & Background
Previously, encoding nested JSON arrays and objects allocated a new$d$ , this caused quadratic $O(d^2)$ buffer allocation and copying ($1 + 2 + \dots + d$ bytes).
Vec<u8>for every container level. When a nested container completed, its entire serialized buffer was copied into its parent container. For structures of depthAs discussed in #5192 (following #5172), we can avoid intermediate buffer allocations and repeated copying by computing container sizes iteratively in advance and writing every header and payload directly into the final destination buffer.
Solution
The new encoding routine works in two linear phases without recursion:
Iterative Planning (Leaves to Root flattening):
Traverse the
serde_json::Valueiteratively using an explicitvisitsstack to prevent stack overflow on deep inputs. Each scalar or container is recorded into a sequentialwrite_itemsplan (JsonbWriteItem::ScalarorJsonbWriteItem::Composite). Scalar payloads borrow their byte slices when possible (or store owned/escaped representations) without allocating intermediate container byte buffers.Bottom-Up Size Calculation & Direct Write:
Iterate backwards over the plan (from leaves up to root) using an
encoded_sizesstack to compute exact container payload sizes and header lengths with checked arithmetic.Reserve the total calculated capacity in the destination buffer once (
buffer.try_reserve(encoded_size)).Perform a final forward pass over
write_items, writing each header and payload into the reserved buffer exactly once.Benchmark Results (Criterion)
A Criterion benchmark suite (
benches/sqlite_jsonb.rs) was added comparingmainand this branch across depth and size scaling:1. Depth Scaling — Nested Arrays (
[[[[... 1 ...]]]])main(baseline)2. Depth Scaling — Nested Objects (
{"k": {"k": ...}})main(baseline)3. Size Scaling — Flat Array of Objects (
[{"id": i, "name": ...}])main(baseline)4. Size Scaling — Flat Array of Scalars (
[0, 1, 2, ..., N])main(baseline)Testing Performed
check_signed_integer).cargo bench -p diesel --bench sqlite_jsonb --features "sqlite serde_json".cargo clippy -p diesel --bench sqlite_jsonbwith warnings denied (-D warnings) andcargo fmt --check.CHANGELOG.mdunder### Changed.AI-Use Disclosure
In accordance with Diesel's
CONTRIBUTING.mdand NLnet foundation guidelines on generative AI: