Skip to content

Optimize single-row prolly mutations#1035

Merged
timsehn merged 2 commits into
masterfrom
perf/composite-types-ac
May 22, 2026
Merged

Optimize single-row prolly mutations#1035
timsehn merged 2 commits into
masterfrom
perf/composite-types-ac

Conversation

@timsehn

@timsehn timsehn commented May 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add conservative fast paths for single-row prolly delete/insert mutations when leaf chunk boundaries and separator keys are preserved
  • rebuild only the edited leaf plus ancestor spine instead of falling through to streaming merge
  • add invariant coverage for delete/reinsert on a multi-level integer-key table
  • fix non-int sysbench type benchmarks so sbtest_types uses each suite's actual key shape: composite, TEXT, or BLOB

Benchmark target

Latest merged PR #1034 sysbench comment highest multiplier was in the compositepk suite, file-backed autocommit, types_delete_insert_ac at 5.05x. That exposed single-row delete/reinsert costs, and the benchmark now uses the composite PK shape it is supposed to measure.

Local targeted 100k-row timing before the benchmark schema correction, Doltlite only:

  • baseline median: 257,222 us
  • this branch median: 145,565 us

Tests

  • make -j8 sqlite3.o libdoltlite.a
  • make -j8 doltlite
  • make -j8 invariant_test corruption_test && ./invariant_test && ./corruption_test
  • make -j8 c-tests && test/run_c_tests.sh .
  • targeted 100k-row types_delete_insert_ac baseline/new timing with test/sysbench_timer.c helper
  • BENCH_ROWS=200 BENCH_RUNS=1 BENCH_MAX_MULTIPLIER=1000 BENCH_AVG_MAX_MULTIPLIER=1000 ./test/sysbench_compare_compositepk.sh
  • BENCH_ROWS=200 BENCH_RUNS=1 BENCH_MAX_MULTIPLIER=1000 BENCH_AVG_MAX_MULTIPLIER=1000 ./test/sysbench_compare_textpk.sh
  • BENCH_ROWS=200 BENCH_RUNS=1 BENCH_MAX_MULTIPLIER=1000 BENCH_AVG_MAX_MULTIPLIER=1000 ./test/sysbench_compare_blobpk.sh

@github-actions

github-actions Bot commented May 22, 2026

Copy link
Copy Markdown

Sysbench-Style Benchmark (BLOB PK): Doltlite vs SQLite

Companion to the classic Sysbench-Style Benchmark. Every workload here
runs against tables with a 16-byte big-endian BLOB PRIMARY KEY.

Individual ratios gated at 6×; section averages gated at 5×.

In-Memory

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 30,303 44,738 1.48
oltp_range_select 12,557 19,875 1.58
oltp_sum_range 11,838 19,124 1.62
oltp_order_range 2,904 3,877 1.34
oltp_distinct_range 3,902 4,968 1.27
oltp_index_scan 4,437 7,669 1.73
select_random_points 17,991 27,742 1.54
select_random_ranges 4,025 6,480 1.61
covering_index_scan 4,348 5,441 1.25
groupby_scan 32,415 39,401 1.22
index_join 6,601 12,560 1.90
index_join_scan 3,897 7,464 1.92
types_table_scan 1,061,709 1,564,274 1.47
table_scan 1,214,036 1,747,479 1.44
oltp_read_only 125,625 170,264 1.36
Average 1.51

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 240,874 339,113 1.41
oltp_insert 19,953 37,853 1.90
oltp_update_index 67,422 139,571 2.07
oltp_update_non_index 47,613 89,403 1.88
oltp_delete_insert 47,776 105,845 2.22
oltp_write_only 27,207 62,878 2.31
types_delete_insert 32,067 59,113 1.84
oltp_read_write 82,540 161,417 1.96
Average 1.95

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 106,407 120,009 1.13
oltp_range_select 21,660 48,761 2.25
oltp_sum_range 21,099 48,234 2.29
oltp_order_range 3,909 7,825 2.00
oltp_distinct_range 4,908 8,958 1.83
oltp_index_scan 12,860 20,979 1.63
select_random_points 33,368 82,214 2.46
select_random_ranges 11,482 17,523 1.53
covering_index_scan 11,665 13,115 1.12
groupby_scan 35,058 61,274 1.75
index_join 13,068 32,789 2.51
index_join_scan 6,406 23,001 3.59
types_table_scan 1,235,877 3,550,057 2.87
table_scan 1,460,501 4,241,584 2.90
oltp_read_only 239,142 358,762 1.50
Average 2.09

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 251,294 364,040 1.45
oltp_insert 36,415 57,491 1.58
oltp_update_index 169,795 303,632 1.79
oltp_update_non_index 105,461 184,892 1.75
oltp_delete_insert 116,431 211,033 1.81
oltp_write_only 72,211 133,377 1.85
types_delete_insert 63,547 115,512 1.82
oltp_read_write 156,128 339,214 2.17
Average 1.78

File-Backed (autocommit)

Each statement runs as its own transaction — exposes per-commit
fixed costs that the wrapped-in-BEGIN/COMMIT tests amortize away.
SQLite uses WAL mode with synchronous=FULL in this section so
the comparison uses SQLite's durable WAL autocommit path.

Reads

Reads have no commit cost; these are the same SQL files as the
File-Backed Reads section, included here for symmetry and to
catch any per-statement overhead doltlite pays on the read path.

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 60,951 119,200 1.96
oltp_range_select 17,284 48,622 2.81
oltp_sum_range 16,879 48,673 2.88
oltp_order_range 3,415 7,857 2.30
oltp_distinct_range 4,506 8,984 1.99
oltp_index_scan 8,502 21,104 2.48
select_random_points 28,537 82,518 2.89
select_random_ranges 7,202 17,677 2.45
covering_index_scan 7,495 13,078 1.74
groupby_scan 34,791 61,636 1.77
index_join 11,005 32,545 2.96
index_join_scan 6,065 23,123 3.81
types_table_scan 1,239,354 3,552,837 2.87
table_scan 1,475,800 4,244,559 2.88
oltp_read_only 172,894 356,859 2.06
Average 2.52

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 25,648 81,407 3.17
oltp_insert_ac 26,658 103,507 3.88
oltp_update_index_ac 28,778 115,560 4.02
oltp_update_non_index_ac 24,424 96,179 3.94
oltp_delete_insert_ac 25,329 107,446 4.24
oltp_write_only_ac 25,367 110,130 4.34
types_delete_insert_ac 23,494 101,801 4.33
oltp_read_write_ac 32,751 126,058 3.85
Average 3.97

100000 rows, median of 5 invocations per test, workload-only timing via host monotonic clock when available.

Performance Ceiling Check (6x individual, 5x average)

All tests within ceilings.

@github-actions

github-actions Bot commented May 22, 2026

Copy link
Copy Markdown

Sysbench-Style Benchmark (composite PK): Doltlite vs SQLite

Companion to the classic Sysbench-Style Benchmark. Every workload here
runs against tables with a 2-column INTEGER PRIMARY KEY(a, b) WITHOUT ROWID.

Individual ratios gated at 6×; section averages gated at 5×.

In-Memory

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 34,567 45,159 1.31
oltp_range_select 21,634 26,032 1.20
oltp_sum_range 19,493 24,586 1.26
oltp_order_range 3,868 4,445 1.15
oltp_distinct_range 4,973 5,600 1.13
oltp_index_scan 4,841 7,558 1.56
select_random_points 28,520 38,394 1.35
select_random_ranges 7,789 9,548 1.23
covering_index_scan 4,350 5,075 1.17
groupby_scan 40,631 45,976 1.13
index_join 8,133 12,575 1.55
index_join_scan 4,289 7,301 1.70
types_table_scan 1,231,850 1,523,209 1.24
table_scan 1,288,917 1,724,256 1.34
oltp_read_only 152,131 186,643 1.23
Average 1.30

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 243,447 318,062 1.31
oltp_insert 19,104 33,559 1.76
oltp_update_index 68,552 125,676 1.83
oltp_update_non_index 50,612 84,717 1.67
oltp_delete_insert 49,933 96,561 1.93
oltp_write_only 27,190 59,898 2.20
types_delete_insert 32,454 58,576 1.80
oltp_read_write 99,565 165,861 1.67
Average 1.77

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 125,465 126,519 1.01
oltp_range_select 31,007 53,883 1.74
oltp_sum_range 29,790 52,426 1.76
oltp_order_range 4,796 8,214 1.71
oltp_distinct_range 5,868 9,220 1.57
oltp_index_scan 14,322 20,943 1.46
select_random_points 45,057 96,534 2.14
select_random_ranges 16,607 21,333 1.28
covering_index_scan 12,468 11,180 0.90
groupby_scan 43,260 66,855 1.55
index_join 13,552 27,298 2.01
index_join_scan 5,421 17,976 3.32
types_table_scan 1,331,767 3,401,565 2.55
table_scan 1,622,575 4,241,182 2.61
oltp_read_only 289,897 377,485 1.30
Average 1.79

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 253,224 345,155 1.36
oltp_insert 25,080 50,359 2.01
oltp_update_index 170,297 283,933 1.67
oltp_update_non_index 105,153 185,849 1.77
oltp_delete_insert 106,449 200,695 1.89
oltp_write_only 64,347 129,461 2.01
types_delete_insert 61,477 109,515 1.78
oltp_read_write 165,846 348,309 2.10
Average 1.82

File-Backed (autocommit)

Each statement runs as its own transaction — exposes per-commit
fixed costs that the wrapped-in-BEGIN/COMMIT tests amortize away.
SQLite uses WAL mode with synchronous=FULL in this section so
the comparison uses SQLite's durable WAL autocommit path.

Reads

Reads have no commit cost; these are the same SQL files as the
File-Backed Reads section, included here for symmetry and to
catch any per-statement overhead doltlite pays on the read path.

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 70,792 130,086 1.84
oltp_range_select 25,998 55,887 2.15
oltp_sum_range 24,577 54,203 2.21
oltp_order_range 4,374 8,244 1.88
oltp_distinct_range 5,452 9,372 1.72
oltp_index_scan 9,122 21,777 2.39
select_random_points 40,810 97,606 2.39
select_random_ranges 11,287 21,193 1.88
covering_index_scan 7,504 11,030 1.47
groupby_scan 42,577 66,778 1.57
index_join 10,802 27,253 2.52
index_join_scan 4,959 17,885 3.61
types_table_scan 1,306,061 3,381,265 2.59
table_scan 1,558,204 4,231,301 2.72
oltp_read_only 213,055 380,100 1.78
Average 2.18

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 15,317 56,969 3.72
oltp_insert_ac 18,334 77,267 4.21
oltp_update_index_ac 21,487 89,051 4.14
oltp_update_non_index_ac 17,604 75,329 4.28
oltp_delete_insert_ac 18,543 83,884 4.52
oltp_write_only_ac 18,492 85,416 4.62
types_delete_insert_ac 15,574 77,487 4.98
oltp_read_write_ac 26,174 110,395 4.22
Average 4.34

100000 rows, median of 5 invocations per test, workload-only timing via host monotonic clock when available.

Performance Ceiling Check (6x individual, 5x average)

All tests within ceilings.

@github-actions

github-actions Bot commented May 22, 2026

Copy link
Copy Markdown

Sysbench-Style Benchmark (TEXT PK): Doltlite vs SQLite

Companion to the classic Sysbench-Style Benchmark. Every workload here
runs against tables with a 32-char hex TEXT PRIMARY KEY (UUID-shaped).

Individual ratios gated at 6×; section averages gated at 5×.

In-Memory

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 29,369 45,458 1.55
oltp_range_select 13,031 20,391 1.56
oltp_sum_range 11,810 19,638 1.66
oltp_order_range 3,015 3,918 1.30
oltp_distinct_range 4,094 4,999 1.22
oltp_index_scan 4,517 7,824 1.73
select_random_points 17,663 29,392 1.66
select_random_ranges 4,024 6,492 1.61
covering_index_scan 4,383 5,201 1.19
groupby_scan 32,604 39,127 1.20
index_join 6,709 12,437 1.85
index_join_scan 4,584 7,796 1.70
types_table_scan 1,081,628 1,608,248 1.49
table_scan 1,299,840 1,750,462 1.35
oltp_read_only 116,590 169,890 1.46
Average 1.50

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 228,627 335,867 1.47
oltp_insert 21,191 38,726 1.83
oltp_update_index 69,087 143,469 2.08
oltp_update_non_index 47,684 91,451 1.92
oltp_delete_insert 49,345 107,718 2.18
oltp_write_only 27,657 63,530 2.30
types_delete_insert 31,557 60,035 1.90
oltp_read_write 82,658 162,599 1.97
Average 1.96

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 104,667 122,185 1.17
oltp_range_select 22,273 51,801 2.33
oltp_sum_range 21,382 51,731 2.42
oltp_order_range 3,865 8,104 2.10
oltp_distinct_range 4,895 9,151 1.87
oltp_index_scan 12,886 21,225 1.65
select_random_points 33,016 83,932 2.54
select_random_ranges 11,453 17,664 1.54
covering_index_scan 12,287 13,172 1.07
groupby_scan 35,327 64,942 1.84
index_join 14,068 34,541 2.46
index_join_scan 7,625 24,992 3.28
types_table_scan 1,308,794 3,906,242 2.98
table_scan 1,557,068 4,617,665 2.97
oltp_read_only 243,273 381,725 1.57
Average 2.12

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 240,360 367,738 1.53
oltp_insert 47,694 59,472 1.25
oltp_update_index 187,332 319,416 1.71
oltp_update_non_index 137,497 199,190 1.45
oltp_delete_insert 130,240 228,012 1.75
oltp_write_only 128,539 141,116 1.10
types_delete_insert 68,390 124,040 1.81
oltp_read_write 184,135 366,256 1.99
Average 1.57

File-Backed (autocommit)

Each statement runs as its own transaction — exposes per-commit
fixed costs that the wrapped-in-BEGIN/COMMIT tests amortize away.
SQLite uses WAL mode with synchronous=FULL in this section so
the comparison uses SQLite's durable WAL autocommit path.

Reads

Reads have no commit cost; these are the same SQL files as the
File-Backed Reads section, included here for symmetry and to
catch any per-statement overhead doltlite pays on the read path.

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 61,216 126,235 2.06
oltp_range_select 18,203 53,527 2.94
oltp_sum_range 17,010 53,031 3.12
oltp_order_range 3,498 8,168 2.34
oltp_distinct_range 4,553 9,138 2.01
oltp_index_scan 8,744 21,170 2.42
select_random_points 28,214 84,513 3.00
select_random_ranges 7,079 17,734 2.51
covering_index_scan 7,979 13,275 1.66
groupby_scan 34,636 64,278 1.86
index_join 11,889 34,594 2.91
index_join_scan 7,277 24,536 3.37
types_table_scan 1,309,750 3,855,156 2.94
table_scan 1,502,430 4,525,660 3.01
oltp_read_only 174,773 374,230 2.14
Average 2.55

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 23,776 78,654 3.31
oltp_insert_ac 26,303 90,179 3.43
oltp_update_index_ac 26,832 110,646 4.12
oltp_update_non_index_ac 22,082 96,284 4.36
oltp_delete_insert_ac 25,206 103,540 4.11
oltp_write_only_ac 24,652 99,864 4.05
types_delete_insert_ac 21,354 87,943 4.12
oltp_read_write_ac 31,446 121,335 3.86
Average 3.92

100000 rows, median of 5 invocations per test, workload-only timing via host monotonic clock when available.

Performance Ceiling Check (6x individual, 5x average)

All tests within ceilings.

@github-actions

github-actions Bot commented May 22, 2026

Copy link
Copy Markdown

Sysbench-Style Benchmark: Doltlite vs SQLite

In-Memory

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 23,797 36,175 1.52
oltp_range_select 9,684 14,295 1.48
oltp_sum_range 9,117 14,268 1.56
oltp_order_range 2,516 3,204 1.27
oltp_distinct_range 3,556 4,272 1.20
oltp_index_scan 3,667 6,623 1.81
select_random_points 9,270 17,311 1.87
select_random_ranges 2,696 5,197 1.93
covering_index_scan 4,113 4,596 1.12
groupby_scan 30,740 34,432 1.12
index_join 5,966 9,334 1.56
index_join_scan 3,489 5,708 1.64
types_table_scan 1,135,043 1,434,103 1.26
table_scan 1,234,842 1,542,049 1.25
oltp_read_only 112,941 145,683 1.29
Average 1.46

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 178,869 246,451 1.38
oltp_insert 15,939 28,356 1.78
oltp_update_index 50,450 101,641 2.01
oltp_update_non_index 33,549 67,572 2.01
oltp_delete_insert 43,461 77,839 1.79
oltp_write_only 21,222 48,592 2.29
types_delete_insert 24,186 44,249 1.83
oltp_read_write 65,089 129,977 2.00
Average 1.89

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 98,074 103,406 1.05
oltp_range_select 18,935 35,225 1.86
oltp_sum_range 18,703 35,088 1.88
oltp_order_range 3,513 6,224 1.77
oltp_distinct_range 4,563 7,276 1.59
oltp_index_scan 11,925 17,369 1.46
select_random_points 23,576 62,635 2.66
select_random_ranges 10,606 14,961 1.41
covering_index_scan 11,326 8,113 0.72
groupby_scan 33,429 51,710 1.55
index_join 10,466 18,825 1.80
index_join_scan 4,727 13,510 2.86
types_table_scan 1,264,624 2,760,426 2.18
table_scan 1,427,462 3,333,273 2.34
oltp_read_only 219,534 282,985 1.29
Average 1.76

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 187,880 263,182 1.40
oltp_insert 22,004 37,838 1.72
oltp_update_index 125,051 227,782 1.82
oltp_update_non_index 76,904 145,567 1.89
oltp_delete_insert 95,070 164,779 1.73
oltp_write_only 58,274 107,012 1.84
types_delete_insert 52,162 89,362 1.71
oltp_read_write 122,323 262,594 2.15
Average 1.78

File-Backed (autocommit)

Each statement runs as its own transaction — exposes per-commit
fixed costs that the wrapped-in-BEGIN/COMMIT tests amortize away.
SQLite uses WAL mode with synchronous=FULL in this section so
the comparison uses SQLite's durable WAL autocommit path.

Reads

Reads have no commit cost; these are the same SQL files as the
File-Backed Reads section, included here for symmetry and to
catch any per-statement overhead doltlite pays on the read path.

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 53,763 103,791 1.93
oltp_range_select 14,392 36,107 2.51
oltp_sum_range 14,417 35,953 2.49
oltp_order_range 3,305 6,394 1.93
oltp_distinct_range 4,343 7,589 1.75
oltp_index_scan 7,749 17,554 2.27
select_random_points 19,243 62,627 3.25
select_random_ranges 6,202 14,885 2.40
covering_index_scan 7,020 7,930 1.13
groupby_scan 32,675 50,489 1.55
index_join 8,009 17,916 2.24
index_join_scan 3,951 12,623 3.19
types_table_scan 1,232,537 2,818,399 2.29
table_scan 1,437,551 3,467,889 2.41
oltp_read_only 159,797 286,671 1.79
Average 2.21

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 21,928 74,876 3.41
oltp_insert_ac 25,570 98,223 3.84
oltp_update_index_ac 25,720 111,430 4.33
oltp_update_non_index_ac 23,537 92,706 3.94
oltp_delete_insert_ac 24,217 105,058 4.34
oltp_write_only_ac 24,012 99,301 4.14
types_delete_insert_ac 22,492 85,821 3.82
oltp_read_write_ac 31,792 112,827 3.55
Average 3.92

100000 rows, median of 5 invocations per test, workload-only timing via host monotonic clock when available.

Performance Ceiling Check (6x individual, 5x average)

All tests within ceilings.

@timsehn timsehn merged commit 3f95ec5 into master May 22, 2026
12 checks passed
@timsehn timsehn deleted the perf/composite-types-ac branch May 28, 2026 23:23
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