Skip to content

Avoid duplicate seek for single-row inserts#1036

Merged
timsehn merged 1 commit into
masterfrom
perf/composite-types-next
May 22, 2026
Merged

Avoid duplicate seek for single-row inserts#1036
timsehn merged 1 commit into
masterfrom
perf/composite-types-next

Conversation

@timsehn

@timsehn timsehn commented May 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • combine single-row insert and same-size replace fast-path dispatch into one seek
  • keep the existing no-rechunk safety checks for inserts and existing same-size replacement behavior
  • remove the separate insert fast path call from flush dispatch

Benchmark target

Latest merged PR #1035 sysbench comments highest multiplier:

  • suite/key type: compositepk, 2-column INTEGER PRIMARY KEY(a,b) WITHOUT ROWID
  • metric: types_delete_insert_ac
  • mode: file-backed autocommit
  • multiplier in generated comment: 4.98x

On current master the benchmark schema has been corrected, so the local targeted SQL used composite keys for sbtest_types.

Local timing

Targeted 100k-row corrected composite types_delete_insert_ac, Doltlite only, 5 samples each:

  • clean master samples: 49,195 27,048 41,305 36,694 42,577 us, median 41,305 us
  • this branch samples: 38,643 20,950 26,351 21,582 37,605 us, median 26,351 us

Tests

  • make -j8 sqlite3.o libdoltlite.a
  • make -j8 invariant_test corruption_test && ./invariant_test && ./corruption_test
  • BENCH_ROWS=200 BENCH_RUNS=1 BENCH_MAX_MULTIPLIER=1000 BENCH_AVG_MAX_MULTIPLIER=1000 ./test/sysbench_compare_compositepk.sh
  • make -j8 c-tests && test/run_c_tests.sh .

@github-actions

Copy link
Copy Markdown

Sysbench-Style Benchmark: Doltlite vs SQLite

In-Memory

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 24,051 35,747 1.49
oltp_range_select 10,525 14,270 1.36
oltp_sum_range 9,401 14,226 1.51
oltp_order_range 2,574 3,233 1.26
oltp_distinct_range 3,634 4,350 1.20
oltp_index_scan 3,893 6,815 1.75
select_random_points 10,256 17,657 1.72
select_random_ranges 2,925 5,246 1.79
covering_index_scan 4,095 4,428 1.08
groupby_scan 30,801 34,429 1.12
index_join 5,845 9,170 1.57
index_join_scan 3,338 5,352 1.60
types_table_scan 1,065,247 1,384,936 1.30
table_scan 1,223,967 1,548,277 1.26
oltp_read_only 103,639 135,753 1.31
Average 1.42

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 175,068 237,955 1.36
oltp_insert 15,059 27,265 1.81
oltp_update_index 51,058 103,477 2.03
oltp_update_non_index 33,803 67,975 2.01
oltp_delete_insert 45,206 78,453 1.74
oltp_write_only 22,526 49,076 2.18
types_delete_insert 24,038 44,614 1.86
oltp_read_write 70,178 125,601 1.79
Average 1.85

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 97,250 103,195 1.06
oltp_range_select 18,718 35,594 1.90
oltp_sum_range 18,519 35,247 1.90
oltp_order_range 3,474 6,226 1.79
oltp_distinct_range 4,568 7,291 1.60
oltp_index_scan 11,782 17,497 1.49
select_random_points 23,918 63,453 2.65
select_random_ranges 10,509 14,831 1.41
covering_index_scan 11,160 7,947 0.71
groupby_scan 32,999 50,499 1.53
index_join 10,441 17,476 1.67
index_join_scan 4,347 12,156 2.80
types_table_scan 1,206,826 2,704,626 2.24
table_scan 1,409,499 3,395,343 2.41
oltp_read_only 217,156 287,279 1.32
Average 1.77

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 184,022 262,503 1.43
oltp_insert 22,104 37,372 1.69
oltp_update_index 123,631 225,143 1.82
oltp_update_non_index 77,535 147,860 1.91
oltp_delete_insert 93,066 156,848 1.69
oltp_write_only 56,703 103,013 1.82
types_delete_insert 48,783 85,547 1.75
oltp_read_write 119,641 265,617 2.22
Average 1.79

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,392 103,450 1.94
oltp_range_select 14,316 35,284 2.46
oltp_sum_range 14,123 35,165 2.49
oltp_order_range 3,103 6,221 2.00
oltp_distinct_range 4,141 7,305 1.76
oltp_index_scan 7,428 17,490 2.35
select_random_points 19,127 62,928 3.29
select_random_ranges 6,120 14,728 2.41
covering_index_scan 6,704 7,875 1.17
groupby_scan 32,814 50,301 1.53
index_join 7,974 17,639 2.21
index_join_scan 4,040 12,156 3.01
types_table_scan 1,199,735 2,704,809 2.25
table_scan 1,411,218 3,377,019 2.39
oltp_read_only 153,966 289,538 1.88
Average 2.21

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 22,083 79,842 3.62
oltp_insert_ac 24,456 97,977 4.01
oltp_update_index_ac 26,832 111,079 4.14
oltp_update_non_index_ac 22,188 88,880 4.01
oltp_delete_insert_ac 26,946 108,686 4.03
oltp_write_only_ac 27,471 97,054 3.53
types_delete_insert_ac 21,899 97,608 4.46
oltp_read_write_ac 31,299 115,604 3.69
Average 3.94

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

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,001 44,556 1.31
oltp_range_select 20,535 25,420 1.24
oltp_sum_range 18,477 25,702 1.39
oltp_order_range 3,796 4,431 1.17
oltp_distinct_range 4,854 5,507 1.13
oltp_index_scan 4,574 7,095 1.55
select_random_points 26,878 36,773 1.37
select_random_ranges 7,338 9,176 1.25
covering_index_scan 4,325 4,754 1.10
groupby_scan 40,329 45,218 1.12
index_join 8,242 12,123 1.47
index_join_scan 4,077 6,977 1.71
types_table_scan 1,126,095 1,517,707 1.35
table_scan 1,296,677 1,732,511 1.34
oltp_read_only 151,972 190,393 1.25
Average 1.32

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 241,969 319,812 1.32
oltp_insert 18,923 33,638 1.78
oltp_update_index 69,023 130,340 1.89
oltp_update_non_index 52,534 87,483 1.67
oltp_delete_insert 50,152 97,555 1.95
oltp_write_only 27,343 57,917 2.12
types_delete_insert 32,657 57,882 1.77
oltp_read_write 99,229 165,542 1.67
Average 1.77

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 125,635 126,282 1.01
oltp_range_select 31,282 54,419 1.74
oltp_sum_range 29,191 53,715 1.84
oltp_order_range 4,870 8,227 1.69
oltp_distinct_range 5,899 9,321 1.58
oltp_index_scan 14,339 21,301 1.49
select_random_points 45,990 99,378 2.16
select_random_ranges 16,280 21,462 1.32
covering_index_scan 12,699 11,201 0.88
groupby_scan 43,259 68,802 1.59
index_join 13,566 27,991 2.06
index_join_scan 5,402 18,652 3.45
types_table_scan 1,335,666 3,412,771 2.56
table_scan 1,591,043 4,245,656 2.67
oltp_read_only 294,889 389,001 1.32
Average 1.82

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 251,113 346,245 1.38
oltp_insert 25,377 49,961 1.97
oltp_update_index 158,696 292,217 1.84
oltp_update_non_index 104,768 186,522 1.78
oltp_delete_insert 108,089 205,460 1.90
oltp_write_only 63,467 132,922 2.09
types_delete_insert 60,968 108,693 1.78
oltp_read_write 167,890 350,376 2.09
Average 1.85

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,124 130,394 1.86
oltp_range_select 26,237 54,727 2.09
oltp_sum_range 24,656 54,928 2.23
oltp_order_range 4,424 8,378 1.89
oltp_distinct_range 5,506 9,327 1.69
oltp_index_scan 8,825 21,758 2.47
select_random_points 40,499 100,246 2.48
select_random_ranges 11,005 21,648 1.97
covering_index_scan 7,142 11,093 1.55
groupby_scan 42,767 67,094 1.57
index_join 11,095 27,978 2.52
index_join_scan 4,909 18,249 3.72
types_table_scan 1,341,460 3,436,590 2.56
table_scan 1,598,182 4,271,081 2.67
oltp_read_only 218,837 383,586 1.75
Average 2.20

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 15,568 58,884 3.78
oltp_insert_ac 19,689 78,904 4.01
oltp_update_index_ac 19,351 91,505 4.73
oltp_update_non_index_ac 16,606 73,663 4.44
oltp_delete_insert_ac 17,453 82,513 4.73
oltp_write_only_ac 20,078 83,366 4.15
types_delete_insert_ac 15,644 69,953 4.47
oltp_read_write_ac 25,727 101,690 3.95
Average 4.28

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

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,232 45,609 1.51
oltp_range_select 12,270 19,957 1.63
oltp_sum_range 11,622 19,246 1.66
oltp_order_range 2,870 3,831 1.33
oltp_distinct_range 3,932 4,891 1.24
oltp_index_scan 4,315 7,651 1.77
select_random_points 17,742 28,281 1.59
select_random_ranges 4,151 6,373 1.54
covering_index_scan 4,257 5,238 1.23
groupby_scan 32,275 39,141 1.21
index_join 6,696 12,755 1.90
index_join_scan 4,132 7,908 1.91
types_table_scan 1,140,774 1,597,725 1.40
table_scan 1,343,509 1,753,043 1.30
oltp_read_only 116,551 168,920 1.45
Average 1.51

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 239,516 338,322 1.41
oltp_insert 19,606 38,121 1.94
oltp_update_index 66,147 140,982 2.13
oltp_update_non_index 48,004 90,463 1.88
oltp_delete_insert 47,531 107,811 2.27
oltp_write_only 28,115 62,795 2.23
types_delete_insert 32,342 59,269 1.83
oltp_read_write 83,602 162,435 1.94
Average 1.96

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 104,652 119,406 1.14
oltp_range_select 21,819 48,896 2.24
oltp_sum_range 21,128 48,400 2.29
oltp_order_range 3,844 7,840 2.04
oltp_distinct_range 4,924 8,952 1.82
oltp_index_scan 12,840 21,283 1.66
select_random_points 33,304 82,513 2.48
select_random_ranges 11,418 17,464 1.53
covering_index_scan 11,690 12,910 1.10
groupby_scan 35,305 61,869 1.75
index_join 13,016 33,102 2.54
index_join_scan 6,550 23,180 3.54
types_table_scan 1,255,188 3,571,135 2.85
table_scan 1,476,246 4,254,377 2.88
oltp_read_only 238,517 361,128 1.51
Average 2.09

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 249,646 363,026 1.45
oltp_insert 36,139 58,663 1.62
oltp_update_index 172,757 308,326 1.78
oltp_update_non_index 105,259 187,845 1.78
oltp_delete_insert 113,683 214,882 1.89
oltp_write_only 71,208 134,766 1.89
types_delete_insert 63,178 115,971 1.84
oltp_read_write 155,863 346,999 2.23
Average 1.81

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,165 120,386 1.97
oltp_range_select 17,249 49,136 2.85
oltp_sum_range 16,607 48,912 2.95
oltp_order_range 3,489 7,901 2.26
oltp_distinct_range 4,596 9,009 1.96
oltp_index_scan 8,484 21,266 2.51
select_random_points 28,787 84,530 2.94
select_random_ranges 7,260 17,452 2.40
covering_index_scan 7,680 13,019 1.70
groupby_scan 34,978 62,005 1.77
index_join 11,000 33,532 3.05
index_join_scan 6,173 23,451 3.80
types_table_scan 1,265,086 3,598,708 2.84
table_scan 1,499,077 4,290,122 2.86
oltp_read_only 173,300 365,031 2.11
Average 2.53

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 22,550 85,545 3.79
oltp_insert_ac 23,980 100,792 4.20
oltp_update_index_ac 28,453 114,617 4.03
oltp_update_non_index_ac 22,613 99,466 4.40
oltp_delete_insert_ac 25,517 106,718 4.18
oltp_write_only_ac 25,102 103,070 4.11
types_delete_insert_ac 23,158 93,426 4.03
oltp_read_write_ac 31,331 119,385 3.81
Average 4.07

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

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 31,015 46,239 1.49
oltp_range_select 13,751 20,469 1.49
oltp_sum_range 12,282 20,003 1.63
oltp_order_range 3,059 3,931 1.29
oltp_distinct_range 4,071 5,068 1.24
oltp_index_scan 4,759 8,043 1.69
select_random_points 18,402 29,959 1.63
select_random_ranges 4,177 6,721 1.61
covering_index_scan 4,869 5,447 1.12
groupby_scan 32,541 39,568 1.22
index_join 6,974 13,052 1.87
index_join_scan 4,545 8,037 1.77
types_table_scan 1,230,983 1,655,183 1.34
table_scan 1,512,557 1,807,207 1.19
oltp_read_only 128,252 178,896 1.39
Average 1.46

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 233,296 345,506 1.48
oltp_insert 22,368 39,894 1.78
oltp_update_index 76,789 156,852 2.04
oltp_update_non_index 48,868 94,332 1.93
oltp_delete_insert 55,105 111,169 2.02
oltp_write_only 30,558 64,827 2.12
types_delete_insert 33,641 63,298 1.88
oltp_read_write 85,211 168,077 1.97
Average 1.90

File-Backed

Reads

Test SQLite (us) Doltlite (us) Multiplier
oltp_point_select 108,110 128,238 1.19
oltp_range_select 22,980 53,700 2.34
oltp_sum_range 22,293 53,168 2.38
oltp_order_range 3,946 8,334 2.11
oltp_distinct_range 5,066 9,316 1.84
oltp_index_scan 13,364 22,211 1.66
select_random_points 34,963 88,953 2.54
select_random_ranges 11,724 18,514 1.58
covering_index_scan 12,965 13,643 1.05
groupby_scan 36,205 65,617 1.81
index_join 15,243 35,162 2.31
index_join_scan 8,061 25,252 3.13
types_table_scan 1,337,334 3,996,239 2.99
table_scan 1,572,982 4,794,824 3.05
oltp_read_only 249,230 391,596 1.57
Average 2.10

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert 248,660 383,986 1.54
oltp_insert 49,950 62,350 1.25
oltp_update_index 200,735 338,008 1.68
oltp_update_non_index 126,684 202,841 1.60
oltp_delete_insert 142,374 229,782 1.61
oltp_write_only 121,153 142,343 1.17
types_delete_insert 72,260 126,747 1.75
oltp_read_write 211,786 357,582 1.69
Average 1.54

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 62,687 130,677 2.08
oltp_range_select 18,786 54,561 2.90
oltp_sum_range 17,682 54,485 3.08
oltp_order_range 3,775 8,518 2.26
oltp_distinct_range 4,740 9,482 2.00
oltp_index_scan 9,254 22,397 2.42
select_random_points 30,966 89,089 2.88
select_random_ranges 7,444 18,800 2.53
covering_index_scan 8,510 13,668 1.61
groupby_scan 35,402 66,077 1.87
index_join 13,074 36,737 2.81
index_join_scan 7,949 26,644 3.35
types_table_scan 1,319,857 4,036,013 3.06
table_scan 1,587,038 4,754,230 3.00
oltp_read_only 185,062 389,062 2.10
Average 2.53

Writes

Test SQLite (us) Doltlite (us) Multiplier
oltp_bulk_insert_ac 22,189 93,314 4.21
oltp_insert_ac 26,877 105,911 3.94
oltp_update_index_ac 30,928 128,512 4.16
oltp_update_non_index_ac 25,996 107,368 4.13
oltp_delete_insert_ac 26,642 114,150 4.28
oltp_write_only_ac 26,887 107,146 3.99
types_delete_insert_ac 22,601 106,214 4.70
oltp_read_write_ac 34,551 127,313 3.68
Average 4.14

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 3348431 into master May 22, 2026
12 checks passed
@timsehn timsehn deleted the perf/composite-types-next 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