Skip to content

feat: split INSERT .. SELECT <columns> FROM unnest(..) into sharded tables (not only SELECT *) - #2000

Open
jkaczman wants to merge 1 commit into
jk-unnest-insert-splitfrom
jk-unnest-select-targets
Open

jkaczman wants to merge 1 commit into
jk-unnest-insert-splitfrom
jk-unnest-select-targets

Conversation

@jkaczman

@jkaczman jkaczman commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

#1988, the previous PR implementing unnest split, only handled splitting when the SELECT for the INSERT was SELECT *, e.g., in INSERT INTO <sharded table> .. SELECT * FROM unnest($1, ..). As this is unimplemented functionality, since #1987, we (correctly) returned an error for this, as this is one of the cases not handled by that previous PR (anything other than SELECT *).

Now, as an extension of #1988, this PR also splits these unnest INSERTs when the SELECT explicitly lists the output columns of unnest(..) one at a time instead of *, as well as when the SELECT includes constants (e.g., NULL).

The motivation for this is fully completing unnest support (instead of VALUES) for re-writing subqueries.

E.g., if we have a statement such as

INSERT INTO subpackages (id, user_id, order_id) SELECT id, id + 1, NULL FROM suborders

The subquery support I'm working on for unnest will run SELECT id, id + 1, NULL FROM suborders on the shards. The shards return some rows, e.g., (1, 2, NULL), (2, 3, NULL) and (3, 4, NULL). Then, subqueries will send these rows back to the INSERT as unnest(..) arrays. However, the shards return the NULL column with the type text (default Postgres association for the NULL constant). If we sent the NULL column as an array too, the array would be a text[], and Postgres would return an error when we insert text into the bigint column order_id. Not to mention, this is inefficient. Therefore, I opted when implementing support for this in subqueries to keep the constants in the SELECT as a nice workaround:

INSERT INTO subpackages (id, user_id, order_id) SELECT u.c1, u.c2, NULL FROM unnest($1::pg_catalog._int8, $2::pg_catalog._int8) AS u(c1, c2)

SELECT * would only return the columns of unnest(..), so there'd be no way for SELECT to include the NULL with SELECT * without the array mentioned prior. Therefore, we need this PR before subquery support can use unnest(..) to properly split constants apart from the unnest array.

To route the rows, this follows the alias AS u(c1, c2) and the SELECT to find the unnest(..) argument that Postgres uses to insert into the sharding key. Then, the rows are routed as if the client sent INSERT INTO subpackages (id, user_id, order_id) VALUES ($1, $2, NULL).

@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Metal bench (arm64) passed. All 475 measures within bounds.

suite throughput cpu/txn user cycles/txn user instr/txn kernel cycles/txn kernel instr/txn ctx/txn
pooler 18.89 (-0.1% ± 1.1%) 18,215 (-0.6% ± 0.2%) 32,041 (+0.0% ± 0.01%) 30,180 (+0.4% ± 1.8%) 51,253 (-0.0% ± 0.3%)
lb 19.51 (-0.4% ± 0.6%) 18,897 (-0.4% ± 0.5%) 32,935 (+0.0% ± 0.04%) 30,928 (-0.2% ± 0.8%) 52,361 (+0.0% ± 0.2%)
sharding-w2 32.95 (-1.4% ± 3.3%) 44,723 (-0.1% ± 1.1%) 97,406 (+0.0% ± 0.01%) 41,041 (-2.8% ± 6.8%) 53,639 (-0.0% ± 0.2%) 1.543 (-0.0% ± 0.3%)
sharding-w8 244,519 (+1.0% ± 1.3%) 29.49 (-0.9% ± 1.2%) 45,228 (+0.6% ± 0.5%) 52,931 (-0.0% ± 0.03%) 36,331 (-2.5% ± 2.5%) 42,372 (-0.0% ± 0.08%) 0.1634 (+0.5% ± 0.8%)
Counters per txn
suite frontend stall l1i refill backend stall
pooler 7,581 (-1.4% ± 0.7%) 2,617 (-0.2% ± 0.3%)
lb 7,974 (-0.6% ± 1.2%) 2,682 (+0.0% ± 0.6%)
sharding-w2 18,753 (+0.5% ± 2.8%) 4,194 (+0.3% ± 2.4%)
sharding-w8 21,198 (+1.3% ± 0.9%) 4,202 (+0.5% ± 2.7%)

Microbench: all 462 measures within bounds.

Hot code per txn
path base head change
pooler 112.0 KB 112.2 KB +0.2 KB
sharding-w2 165.7 KB 165.5 KB -0.2 KB

pooler:

crate base head change
pgdog::frontend::client::query_engine 19.7 KB 19.8 KB +0.1 KB
tokio 18.5 KB 18.4 KB -0.1 KB
pgdog::backend::pool 17.6 KB 17.5 KB -0.1 KB
aws-lc (TLS crypto) 10.6 KB 10.6 KB -0.1 KB
rustls (TLS) 8.3 KB 8.4 KB +0.1 KB
pgdog::frontend (other) 7.8 KB 7.8 KB -0.1 KB
pgdog::backend::server 7.2 KB 7.2 KB +0.0 KB
other 6.4 KB 6.6 KB +0.2 KB
everything else 15.8 KB 16.0 KB +0.2 KB
function head change
pgdog::frontend::client::Client::run::{{closure}} 5.4 KB +0.1 KB
pgdog::frontend::client::query_engine::query::<impl pgdog::frontend::client::query_engine: 5.4 KB +0.1 KB
pgdog::frontend::client::query_engine::QueryEngine::handle::{{closure}} 3.9 KB -0.1 KB
tokio::runtime::task::raw::poll 3.1 KB -0.1 KB
pgdog::frontend::client::query_engine::query::<impl pgdog::frontend::client::query_engine: 3.0 KB +0.1 KB

sharding-w2:

crate base head change
pgdog::frontend::router 28.9 KB 28.9 KB +0.0 KB
pgdog::frontend::client::query_engine 21.0 KB 21.3 KB +0.3 KB
tokio 18.5 KB 18.6 KB +0.1 KB
pgdog::backend::pool 17.9 KB 18.2 KB +0.3 KB
aws-lc (TLS crypto) 13.4 KB 12.6 KB -0.8 KB
other 9.7 KB 9.8 KB +0.1 KB
pgdog::frontend (other) 8.6 KB 8.6 KB -0.1 KB
rustls (TLS) 8.3 KB 8.3 KB +0.0 KB
everything else 39.4 KB 39.1 KB -0.2 KB
function head change
pgdog::frontend::client::Client::run::{{closure}} 6.0 KB -0.1 KB
pgdog::frontend::client::query_engine::query::<impl pgdog::frontend::client::query_engine: 5.4 KB +0.1 KB
pgdog::frontend::client::query_engine::QueryEngine::handle::{{closure}} 4.1 KB +0.0 KB
pgdog::frontend::router::parser::query::select::<impl pgdog::frontend::router::parser::que 3.4 KB +0.0 KB
tokio::runtime::task::raw::poll 3.2 KB -0.1 KB
lb flamegraph vs fae0376

lb

Open interactive for hover, zoom and search.

lb removed

Open interactive.

microbench flamegraph vs fae0376

microbench

Open interactive for hover, zoom and search.

microbench removed

Open interactive.

pooler flamegraph vs fae0376

pooler

Open interactive for hover, zoom and search.

pooler removed

Open interactive.

sharding-w2 flamegraph vs fae0376

sharding-w2

Open interactive for hover, zoom and search.

sharding-w2 removed

Open interactive.

sharding-w8 flamegraph vs fae0376

sharding-w8

Open interactive for hover, zoom and search.

sharding-w8 removed

Open interactive.

Logs

lb, microbench, pooler, sharding-w2, sharding-w8, verdict.json

Compared df1af3b with fae0376 on c8g.metal-24xl (on-demand).

@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Metal bench (x86) passed. All 475 measures within bounds.

suite throughput cpu/txn user cycles/txn user instr/txn kernel cycles/txn kernel instr/txn ctx/txn
pooler 26.03 (+0.1% ± 0.3%) 18,850 (-0.1% ± 0.1%) 33,925 (+0.0% ± 0.03%) 33,789 (+0.4% ± 0.6%) 36,509 (+0.1% ± 0.6%)
lb 26.37 (-0.0% ± 0.3%) 19,358 (-0.1% ± 0.1%) 34,887 (-0.0% ± 0.06%) 33,809 (+0.1% ± 0.5%) 37,337 (+0.3% ± 0.3%)
sharding-w2 42.24 (-0.9% ± 2.9%) 47,514 (-1.8% ± 4.3%) 101,334 (+0.0% ± 0.02%) 38,603 (+0.0% ± 4.0%) 40,146 (+0.0% ± 0.1%) 1.525 (+0.2% ± 0.9%)
sharding-w8 208,593 (+0.0% ± 0.5%) 32.7 (-0.1% ± 0.4%) 45,719 (-0.1% ± 0.4%) 55,661 (-0.0% ± 0.03%) 31,021 (-0.0% ± 0.8%) 32,464 (-0.0% ± 0.09%) 0.1709 (-0.1% ± 1.3%)
Counters per txn
suite frontend stall l1i refill backend stall
pooler 3,528 (-2.4% ± 0.4%) 1,197 (-2.2% ± 0.4%) 36,994 (+1.8% ± 0.7%)
lb 3,610 (-2.1% ± 1.0%) 1,217 (-1.8% ± 0.9%) 38,091 (+1.3% ± 0.9%)
sharding-w2 6,144 (-16.0% ± 30.0%) 1,621 (-4.9% ± 4.4%) 115,249 (+4.5% ± 10.9%)
sharding-w8 3,424 (-2.6% ± 0.4%) 995 (-2.2% ± 0.7%) 172,421 (-0.0% ± 0.7%)

Microbench: all 462 measures within bounds.

Hot code per txn
path base head change
pooler 111.6 KB 110.6 KB -0.9 KB
sharding-w2 163.8 KB 165.9 KB +2.1 KB

pooler:

crate base head change
pgdog::frontend::client::query_engine 21.0 KB 21.0 KB +0.0 KB
tokio 18.1 KB 18.3 KB +0.2 KB
pgdog::backend::pool 18.4 KB 18.1 KB -0.3 KB
pgdog::frontend (other) 9.4 KB 9.4 KB -0.1 KB
aws-lc (TLS crypto) 9.3 KB 9.1 KB -0.2 KB
rustls (TLS) 7.7 KB 7.8 KB +0.1 KB
pgdog::backend::server 6.9 KB 6.9 KB -0.1 KB
other 5.9 KB 5.9 KB +0.0 KB
everything else 14.8 KB 14.3 KB -0.5 KB
function head change
pgdog::frontend::client::Client::run::{{closure}} 7.2 KB -0.1 KB
pgdog::frontend::client::query_engine::query::<impl pgdog::frontend::client::query_engine: 6.0 KB -0.1 KB
pgdog::frontend::client::query_engine::QueryEngine::handle::{{closure}} 4.6 KB +0.1 KB
tokio::runtime::task::raw::poll 3.6 KB +0.1 KB
pgdog::frontend::client::query_engine::query::<impl pgdog::frontend::client::query_engine: 3.2 KB -0.1 KB

sharding-w2:

crate base head change
pgdog::frontend::router 28.6 KB 28.7 KB +0.1 KB
pgdog::frontend::client::query_engine 23.1 KB 22.4 KB -0.6 KB
pgdog::backend::pool 18.7 KB 18.6 KB -0.1 KB
tokio 18.4 KB 18.6 KB +0.1 KB
aws-lc (TLS crypto) 10.1 KB 11.0 KB +0.9 KB
other 10.3 KB 10.8 KB +0.4 KB
pgdog::frontend (other) 10.6 KB 10.8 KB +0.1 KB
pgdog::net 7.5 KB 7.7 KB +0.2 KB
everything else 36.4 KB 37.4 KB +1.1 KB
function head change
pgdog::frontend::client::Client::run::{{closure}} 8.2 KB +0.2 KB
pgdog::frontend::client::query_engine::query::<impl pgdog::frontend::client::query_engine: 5.9 KB -0.2 KB
pgdog::frontend::client::query_engine::QueryEngine::handle::{{closure}} 4.7 KB -0.2 KB
pgdog::frontend::router::parser::query::select::<impl pgdog::frontend::router::parser::que 4.4 KB +0.1 KB
tokio::runtime::task::raw::poll 3.9 KB -0.1 KB
lb flamegraph vs fae0376

lb

Open interactive for hover, zoom and search.

lb removed

Open interactive.

microbench flamegraph vs fae0376

microbench

Open interactive for hover, zoom and search.

microbench removed

Open interactive.

pooler flamegraph vs fae0376

pooler

Open interactive for hover, zoom and search.

pooler removed

Open interactive.

sharding-w2 flamegraph vs fae0376

sharding-w2

Open interactive for hover, zoom and search.

sharding-w2 removed

Open interactive.

sharding-w8 flamegraph vs fae0376

sharding-w8

Open interactive for hover, zoom and search.

sharding-w8 removed

Open interactive.

Logs

lb, microbench, pooler, sharding-w2, sharding-w8, verdict.json

Compared df1af3b with fae0376 on m7i.metal-48xl (spot).

@jkaczman
jkaczman force-pushed the jk-unnest-select-targets branch 4 times, most recently from 9762111 to df1af3b Compare November 9, 2026 22:01
@codecov

codecov Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.47368% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...frontend/router/parser/rewrite/statement/insert.rs 99.32% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@jkaczman
jkaczman force-pushed the jk-unnest-select-targets branch from df1af3b to 456e183 Compare November 9, 2026 23:50
@jkaczman
jkaczman force-pushed the jk-unnest-select-targets branch from 456e183 to 86e48ab Compare November 9, 2026 23:52

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant