Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
fdee8cc
refactor: extract the schema language into worktable_dsl
Sep 1, 2026
67c99f0
Drop the redundant glob from the codegen shim
Sep 1, 2026
43e4497
Read a schema as data, and write one back
Sep 1, 2026
b736579
Say what changed between two schemas, and what it costs
Sep 1, 2026
43a24ba
Bake each table's declaration into the code it generates
Sep 1, 2026
0d0a6f7
Find the declarations in a source tree
Sep 1, 2026
44bf36c
Run the `pinned` example, rather than ignoring it
Sep 2, 2026
8f775b0
Express requirements as carets, and take ps-reclaim 0.1.1
Sep 2, 2026
b4d94ba
Publish worktable_dsl, and hold the three crates in lockstep
Sep 2, 2026
0414c6b
Accept a trailing comma after every block, and name the token that is…
Sep 2, 2026
c77296b
Let a schema be checked without expanding the macro
Sep 2, 2026
0d2fe7f
Draw the line between the grammar and the model above it
Sep 2, 2026
ae005a5
Delete in batches, by key list or by span
Sep 2, 2026
edd0fbf
Make the batch deletes async, like every other write
Sep 2, 2026
fd6617a
Race writers across every backend, above the count the suite reached
Sep 2, 2026
709fcb3
Do not take a global mutex to learn the free list is empty
Sep 2, 2026
1f5c301
Keep the release-only guard out of clippy's constant-assertion lint
Sep 2, 2026
076dfe1
Race `insert` against `insert_many` on every backend
Sep 2, 2026
2eb4e7d
Verify every key width the macro advertises, and move the sweep to th…
Sep 2, 2026
45a7b71
Free a run of links with one coalesce, not one per link
Sep 2, 2026
689a3f8
Stop rescanning the whole retirement queue on every sweep
Sep 2, 2026
27ff552
Publish worktable and worktable_codegen to crates.vip
Sep 2, 2026
c5d6f1d
Point at the renamed backend repo
Sep 2, 2026
511cf88
Publish worktable_dsl to crates.vip too, and before codegen
Sep 2, 2026
5f075a2
Propose a way to observe when a write is actually durable
Sep 2, 2026
5b5ce8e
Let a delete be the bit flip, and leave the cleanup to whoever needs it
Sep 2, 2026
679802a
Bound the torn-shutdown writer's range
Sep 2, 2026
453ebcc
Race readers against storage reuse, not just against inserts
Sep 2, 2026
0025e95
Record the branches that existed only on one machine
Sep 2, 2026
fe9f95e
Report an index over a missing column, rather than panicking on it
Sep 2, 2026
c942c77
Close two review blockers: a reclamation race and a parser panic
Sep 2, 2026
e2b6294
Make persisted bulk deletes actually reach the disk
Sep 2, 2026
e7a7beb
Close the remaining review findings in deletion, the free list and th…
Sep 2, 2026
88a1d1b
Let upsert insert without letting go of the row it is deciding about
Sep 2, 2026
aab5f4c
Make insert async, like every other write on this table
Sep 2, 2026
3801bc2
Make the embedded schema readable by the crate that embeds it
Sep 3, 2026
6ee1671
Plan a primary key generator change as a change
Sep 3, 2026
e3bc3c0
Add wt-dsl, so the schema language has a command line
Sep 3, 2026
b79353f
Fold in the DSL emitter binaries, and take the correction with them
Sep 3, 2026
ff2df9c
Check the primary key's backend and generator, which check never did
Sep 3, 2026
71caaaa
Release worktable_dsl as 1.0.0-beta.18, and localise the vacuum failure
Sep 3, 2026
c4fc1bf
Do not reclaim a page a row was inserted onto while it was being drained
Sep 3, 2026
5547916
Write down what the vacuum bugs have in common
Sep 3, 2026
1cf8285
Say which of the three backends the vacuum note is talking about
Sep 3, 2026
9d571d3
Assert what vacuum must not break, on all three backends
Sep 3, 2026
ddd7ba7
Plan vacuum from free ranges, which the registry already maintains
Sep 3, 2026
a9eecc0
Give arctic and congee an `iter`, so the backends are interchangeable
Sep 3, 2026
707d29b
Make vacuum reactive and stop it holding the registry for a whole sweep
Sep 3, 2026
97d30a7
Arctic takes signed keys
Sep 3, 2026
1454448
Ignore the persisted-vacuum reproduction, and say what it reproduces
Sep 3, 2026
67fa8c5
Recover batch collection when event order and operation order disagree
Sep 3, 2026
860c758
Pin the publish workflow to a commit sha
Sep 3, 2026
2af637b
Two doc comments describing code that had already changed
Sep 3, 2026
c4de85f
Separate widening the batch search from giving up on it
Sep 3, 2026
2fc5be9
WT-6: a bulk delete no longer holds every stripe for its whole duration
Sep 3, 2026
ffca5b3
A mutation sweep and an adversarial corpus over the DSL check
Sep 3, 2026
0fa0522
Retract the tail claim: a clean re-run does not reproduce it
Sep 3, 2026
ac51393
WT-8: fail CI on a duplicated WorkTablesIndex or data_bucket
Sep 3, 2026
761e5d5
Vacuum stress on the stack that will actually ship
Sep 3, 2026
fbedd4b
Stand down exponentially, which is what the 60% case needed
Sep 3, 2026
20414b3
Let a delete burst settle before the woken sweep runs
Sep 3, 2026
9a92003
Revert "Stand down exponentially, which is what the 60% case needed"
Sep 3, 2026
760f829
Remove the polling interval from the vacuum manager's config
Sep 3, 2026
587b957
Say what the stall diagnostic now knows
Sep 3, 2026
82c686c
Reapply "Stand down exponentially, which is what the 60% case needed"
Sep 3, 2026
6144ed0
Revert "Reapply "Stand down exponentially, which is what the 60% case…
Sep 3, 2026
41b63c1
Record what the vacuum manager actually did
Sep 3, 2026
bb5beef
Expose the page counts that verify a vacuum
Sep 3, 2026
9ae13a6
Sweep until the table is clean, not once per wake
Sep 3, 2026
decdd9f
Ask the table if it is busy, instead of guessing from space demand
Sep 3, 2026
af9f26c
Finish the beta17 mutation and DSL validation fixes
Sep 3, 2026
2f3b01a
Record the beta17 validation evidence
Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/publish-to-crates-vip.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: publish-to-crates.vip

# Publishes this workspace's crates to the private registry at crates.vip.
#
# The heavy lifting lives in api.crates.vip's reusable workflow: it packages the
# crate, derives the sparse-index entry from `cargo metadata`, and writes both to
# Tigris under conditional requests. Nothing is installed — the runner image
# already ships `aws` and `jq`.
#
# Publishing is manual on purpose. A published version is immutable, and
# `cargo package` embeds the git sha, so a version republished from a different
# commit is refused rather than silently replaced.
#
# The reusable workflow is pinned to a commit sha rather than `@master`. This
# job hands it the registry's Tigris credentials, so whoever can move that
# branch can read the secrets; a sha is the only ref that cannot be moved under
# us. Updating it is deliberate: read the diff on the pinned range first, then
# bump the three `uses:` lines together, since they are one workflow and a
# split pin means two versions publishing one workspace.

on:
workflow_dispatch:
inputs:
dry-run:
description: Derive the index entry and print it, but write nothing
required: false
type: boolean
default: false

jobs:
# The workspace publishes bottom-up. `worktable_codegen` depends on
# `worktable_dsl` by path *and* version, so the DSL entry has to exist in the
# registry before codegen's is written, exactly as it does on crates.io.
dsl:
uses: pathscale/crates.vip-backend/.github/workflows/publish-crate.yml@017e29a705cb8b4780dd6079b3739b4b5a84fda5
with:
manifest-path: dsl/Cargo.toml
dry-run: ${{ inputs.dry-run }}
secrets:
tigris-key-id: ${{ secrets.CRATES_VIP_TIGRIS_KEY_ID }}
tigris-key-secret: ${{ secrets.CRATES_VIP_TIGRIS_KEY_SECRET }}

codegen:
needs: dsl
uses: pathscale/crates.vip-backend/.github/workflows/publish-crate.yml@017e29a705cb8b4780dd6079b3739b4b5a84fda5
with:
manifest-path: codegen/Cargo.toml
dry-run: ${{ inputs.dry-run }}
secrets:
tigris-key-id: ${{ secrets.CRATES_VIP_TIGRIS_KEY_ID }}
tigris-key-secret: ${{ secrets.CRATES_VIP_TIGRIS_KEY_SECRET }}

# `worktable` depends on `worktable_codegen`, so the codegen crate has to be
# resolvable in the registry before this entry is written.
worktable:
needs: codegen
uses: pathscale/crates.vip-backend/.github/workflows/publish-crate.yml@017e29a705cb8b4780dd6079b3739b4b5a84fda5
with:
manifest-path: Cargo.toml
dry-run: ${{ inputs.dry-run }}
secrets:
tigris-key-id: ${{ secrets.CRATES_VIP_TIGRIS_KEY_ID }}
tigris-key-secret: ${{ secrets.CRATES_VIP_TIGRIS_KEY_SECRET }}
75 changes: 61 additions & 14 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,43 @@ jobs:
- name: Clippy (deny warnings)
run: cargo clippy --workspace --all-targets ${{ matrix.args }} -- -D warnings

duplicate_index_crates:
name: One version of each shared index crate
runs-on: ubicloud-standard-2
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: "true"
add-job-id-key: "false"
# Two versions of WorkTablesIndex in one graph split the type identity of
# `Pair` and `ChangeEvent`, and the build then fails with "expected
# ChangeEvent<Pair<T, Link>>, found a different ChangeEvent<Pair<T,
# Link>>" across dozens of unrelated-looking lines. It happens whenever
# data_bucket and worktable disagree about which version they want, which
# is every time one is bumped and released without the other.
#
# The caret requirements are correct and stay. This turns the mismatch
# into one named failure instead of a wall of trait errors, which is the
# guard WT-8 asked for.
- name: Refuse a duplicated WorkTablesIndex or data_bucket
run: |
duplicates=$(cargo tree --duplicates --edges normal 2>/dev/null \
| grep -E '^(WorkTablesIndex|data_bucket) v' | sort -u)
if [ -n "$duplicates" ]; then
echo "More than one version of a shared index crate is in the graph:"
echo "$duplicates"
echo
echo "WorkTablesIndex, data_bucket and worktable move as one train."
echo "Publish them in lockstep, or pin them to agree."
exit 1
fi
echo "one version of each: ok"

publish:
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
needs: [build, clippy_check]
needs: [build, clippy_check, duplicate_index_crates]
runs-on: ubicloud-standard-2
timeout-minutes: 45
steps:
Expand All @@ -74,16 +108,29 @@ jobs:
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
if [ -z "$CARGO_REGISTRY_TOKEN" ]; then echo "CARGO_REGISTRY_TOKEN not set; skipping publish"; exit 0; fi
v=$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -1)
if curl -fsSL "https://index.crates.io/wo/rk/worktable" | sed -n 's/.*"vers":"\([^"]*\)".*/\1/p' | grep -qx "$v"; then
echo "worktable $v is already on crates.io; nothing to publish"
exit 0
fi
# The main crate pins its codegen twin exactly, so the twin must land
# on the registry first; cargo waits for availability between the two.
cv=$(sed -n 's/^version = "\(.*\)"$/\1/p' codegen/Cargo.toml | head -1)
if ! curl -fsSL "https://index.crates.io/wo/rk/worktable_codegen" | sed -n 's/.*"vers":"\([^"]*\)".*/\1/p' | grep -qx "$cv"; then
cargo publish -p worktable_codegen
fi
cargo publish -p worktable
set -euo pipefail
if [ -z "${CARGO_REGISTRY_TOKEN:-}" ]; then echo "CARGO_REGISTRY_TOKEN not set; skipping publish"; exit 0; fi

# Publish in dependency order. worktable_codegen depends on
# worktable_dsl and worktable depends on worktable_codegen, both by
# path with an exact version, so each must be on the registry before
# the next is packaged. Omitting worktable_dsl here is what made
# `cargo publish -p worktable_codegen` fail with "no matching package
# named `worktable_dsl` found" the moment the DSL extraction landed.
publish_if_new() {
crate="$1"
manifest="$2"
version=$(sed -n 's/^version = "\(.*\)"$/\1/p' "$manifest" | head -1)
# crates.io index paths: four or more characters is {first two}/{next two}/{name}.
if curl -fsSL "https://index.crates.io/wo/rk/$crate" \
| sed -n 's/.*"vers":"\([^"]*\)".*/\1/p' | grep -qx "$version"; then
echo "$crate $version is already on crates.io; skipping"
return 0
fi
echo "publishing $crate $version"
cargo publish -p "$crate"
}

publish_if_new worktable_dsl dsl/Cargo.toml
publish_if_new worktable_codegen codegen/Cargo.toml
publish_if_new worktable Cargo.toml
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ success.
error is not something you introduced, and saying so requires checking.
- A build that finishes suspiciously fast was cached, not rebuilt. Force a real rebuild when
the rebuild is the thing you're verifying.
- **`ci-local.sh` runs your default toolchain; CI runs the newest stable.** A clippy lint
added between the two is invisible here and fails there, on code nobody touched. The
script prints both so a local pass is not mistaken for a CI pass. If they differ and the
lint matters, get the toolchain updated rather than pushing to find out.

## PR discipline

Expand Down
65 changes: 36 additions & 29 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[workspace]
members = ["codegen", "examples", "performance_measurement", "performance_measurement/codegen"]
members = ["codegen", "dsl", "examples", "performance_measurement", "performance_measurement/codegen"]

[package]
name = "worktable"
version = "1.0.0-beta.16"
version = "1.0.0-beta.17"
edition = "2024"
authors = ["Handy-caT"]
license = "MIT"
Expand Down Expand Up @@ -33,49 +33,56 @@ versioned-row-publication = ["worktable_codegen/versioned-row-publication"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-trait = "0.1.89"
arctic = { package = "arctic-wt", version = "0.1.6" }
congee = { package = "congee-wt", version = "0.4.3" }
convert_case = "0.6.0"
crc32fast = "1.5.0"
async-trait = "0.1"
arctic = { package = "arctic-wt", version = "0.1" }
congee = { package = "congee-wt", version = "0.4" }
convert_case = "0.6"
crc32fast = "1"
# Already in the dependency graph transitively (indexset's concurrent
# structures); used directly for read-side grace periods.
data_bucket = "=0.5.5"
data_bucket = "0.5"
# data_bucket = { git = "https://github.com/pathscale/DataBucket", branch = "page_cdc_correction", version = "0.2.7" }
# data_bucket = { path = "../DataBucket", version = "0.3.14" }
derive_more = { version = "2.0.1", features = ["from", "error", "display", "debug", "into"] }
eyre = "0.6.12"
fastrand = "2.3.0"
futures = "0.3.30"
indexset = { package = "WorkTablesIndex", version = "=0.0.9", default-features = false, features = ["concurrent", "cdc", "multimap"] }
vanilla_indexset = { package = "indexset", version = "=0.15.0", features = ["concurrent", "cdc", "multimap"] }
derive_more = { version = "2", features = ["from", "error", "display", "debug", "into"] }
eyre = "0.6"
fastrand = "2"
futures = "0.3"
indexset = { package = "WorkTablesIndex", version = "0.0", default-features = false, features = ["concurrent", "cdc", "multimap"] }
vanilla_indexset = { package = "indexset", version = "0.15", features = ["concurrent", "cdc", "multimap"] }
# indexset = { path = "../indexset", version = "0.15.0", features = ["concurrent", "cdc", "multimap"] }
# indexset = { package = "wt-indexset", version = "=0.12.12", features = ["concurrent", "cdc", "multimap"] }
log = "0.4.29"
ordered-float = "5.0.0"
parking_lot = "0.12.3"
log = "0.4"
ordered-float = "5"
parking_lot = "0.12"
performance_measurement = { path = "performance_measurement", version = "0.1.0", optional = true }
performance_measurement_codegen = { path = "performance_measurement/codegen", version = "0.1.0", optional = true }
prettytable-rs = "^0.10"
psc-nanoid = { version = "3.1.1", features = ["rkyv", "packed"] }
rkyv = { version = "0.8.17", features = ["uuid-1"] }
prettytable-rs = "0.10"
psc-nanoid = { version = "3", features = ["rkyv", "packed"] }
rkyv = { version = "0.8", features = ["uuid-1"] }
reqwest = { version = "0.12", optional = true, default-features = false, features = ["rustls-tls-webpki-roots", "charset", "http2"] }
ps-reclaim = "0.1.0"
rustc-hash = "2.1.1"
rusty-s3 = { version = "0.10.2", optional = true }
smart-default = "0.7.1"
ps-reclaim = "0.1"
rustc-hash = "2"
rusty-s3 = { version = "0.10", optional = true }
smart-default = "0.7"
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
url = { version = "2", optional = true }
uuid = { version = "1.24.0", features = ["v4", "v7"] }
uuid = { version = "1", features = ["v4", "v7"] }
walkdir = { version = "2", optional = true }
worktable_codegen = { path = "codegen", version = "=1.0.0-beta.15" }
# Exact for the same reason as `worktable_dsl` in codegen/Cargo.toml: carets do
# not match pre-releases, and these move as one train.
worktable_codegen = { path = "codegen", version = "=1.0.0-beta.17" }
# Re-exported below. Each generated table carries its declaration as a const
# whose documentation says to read it with `worktable_dsl::Schema::parse`; that
# instruction is only true if a plain `worktable` dependency can reach the
# crate. Exact for the same pre-release reason as codegen.
worktable_dsl = { path = "dsl", version = "=1.0.0-beta.18" }

[dev-dependencies]
chrono = "0.4.43"
chrono = "0.4"
criterion = { version = "0.5", features = ["async_tokio"] }
rand = "0.9.1"
tracing-subscriber = "0.3.23"
rand = "0.9"
tracing-subscriber = "0.3"

# Only under `--cfg loom`, so a normal build and a normal `cargo test` never
# resolve it. See src/partition/loom_tests.rs for how to run the models.
Expand Down
24 changes: 12 additions & 12 deletions benches/cases/full_featured.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn insert(c: &mut Criterion) {
another: format!("another_{}", fastrand::u64(..)),
something: fastrand::u64(..),
};
table.insert(black_box(row))
futures::executor::block_on(table.insert(black_box(row)))
},
BatchSize::SmallInput,
)
Expand All @@ -37,7 +37,7 @@ fn select_by_pk(c: &mut Criterion) {
another: format!("another_{}", fastrand::u64(..)),
something: fastrand::u64(..),
};
table.insert(row).unwrap()
futures::executor::block_on(table.insert(row)).unwrap()
})
.collect();

Expand All @@ -60,7 +60,7 @@ fn select_by_unique_index(c: &mut Criterion) {
another: format!("another_{}", i),
something: i,
};
table.insert(row).unwrap();
futures::executor::block_on(table.insert(row)).unwrap();
}

c.bench_function("full_featured_select_by_val1", |b| {
Expand All @@ -82,7 +82,7 @@ fn select_by_non_unique_index(c: &mut Criterion) {
another: format!("cat_{}", i % 10),
something: i,
};
table.insert(row).unwrap();
futures::executor::block_on(table.insert(row)).unwrap();
}

c.bench_function("full_featured_select_by_another", |b| {
Expand All @@ -107,7 +107,7 @@ fn update(c: &mut Criterion) {
another: format!("another_{}", i),
something: i,
};
pks.push(table.insert(row).unwrap());
pks.push(table.insert(row).await.unwrap());
}
pks
});
Expand Down Expand Up @@ -141,7 +141,7 @@ fn update_by_pk_query(c: &mut Criterion) {
another: format!("another_{}", i),
something: i,
};
table.insert(row).unwrap();
table.insert(row).await.unwrap();
}
});

Expand Down Expand Up @@ -169,7 +169,7 @@ fn update_by_unique_index_query(c: &mut Criterion) {
another: format!("another_{}", i),
something: i,
};
table.insert(row).unwrap();
table.insert(row).await.unwrap();
}
});

Expand All @@ -196,7 +196,7 @@ fn in_place_update(c: &mut Criterion) {
another: "test".to_string(),
something: 0,
};
table.insert(row).unwrap().into()
futures::executor::block_on(table.insert(row)).unwrap().into()
};

c.bench_function("full_featured_in_place_update_val", |b| {
Expand All @@ -219,7 +219,7 @@ fn delete(c: &mut Criterion) {
another: format!("temp_{}", fastrand::u64(..)),
something: fastrand::u64(..),
};
table.insert(row).unwrap()
futures::executor::block_on(table.insert(row)).unwrap()
},
|pk: FullFeaturedPrimaryKey| rt.block_on(async { table.delete(black_box(pk)).await.unwrap() }),
BatchSize::SmallInput,
Expand All @@ -242,7 +242,7 @@ fn delete_by_index_query(c: &mut Criterion) {
another: another.clone(),
something: fastrand::u64(..),
};
table.insert(row).unwrap();
futures::executor::block_on(table.insert(row)).unwrap();
another
},
|another: String| rt.block_on(async { table.delete_by_another(another).await.unwrap() }),
Expand Down Expand Up @@ -319,7 +319,7 @@ fn batch_insert(c: &mut Criterion) {
another: format!("another_{}", i),
something: i as u64,
};
table.insert(black_box(row)).unwrap();
futures::executor::block_on(table.insert(black_box(row))).unwrap();
}
black_box(table)
},
Expand Down Expand Up @@ -347,7 +347,7 @@ fn batch_select_pk(c: &mut Criterion) {
another: format!("another_{}", fastrand::u64(..)),
something: fastrand::u64(..),
};
table.insert(row).unwrap()
futures::executor::block_on(table.insert(row)).unwrap()
})
.collect();

Expand Down
Loading
Loading