OXYGENHUB
# 75b6c8665219 oxygen / docs / backlog-overnight-3.md
16.3 KiB · Markdown · last changed The full Linux run in the benchmarks 1 month ago renderedsource blame history raw
33e77a55 Andy McCall ↶ 1
# Overnight backlog 3 (from 2026-09-29): not worse than Git
2
3
Andy's brief: there is no point making a tool that is worse than Git.
4
Until oxy matches Git on a real, large tree, this is the only work:
5
speed, memory and space at scale, with correctness never traded away.
6
7
Work top to bottom. An item is done when:
8
- its tests pass and the gate is clean;
9
- it is measured before and after on Earth, and the numbers are in
10
  `docs/benchmarks.md`;
11
- it is committed with `oxy` as Andy and pushed to Earth.
12
13
If an item is too large, split it here and do the first part. Log each
14
iteration at the bottom.
15
16
## The yardstick
17
18
The Linux tip on Earth: 95 943 files, 1.8 GB, on 4 cores with 31 GiB.
19
Git 2.53 does this (from `~/bench/linux/results.txt`, 2026-09-29):
20
21
| Operation | Git | Target for oxy |
22
|---|---:|---:|
23
| First commit of the tree | 33.7 s, 42 MB | at most 50 s, under 2 GB |
24
| Clean `status` (warm) | 0.26 to 0.31 s, 31 MB | at most 0.4 s, under 300 MB |
25
| `status`, one file edited | 0.25 s | at most 0.4 s |
26
| `diff`, one file edited | 0.34 s | at most 0.5 s |
27
| `commit`, one file edited | 0.43 s | at most 0.6 s |
28
| `status`, 1 000 files edited | 0.26 s | at most 0.5 s |
29
| `diff`, 1 000 files edited | 1.38 s | at most 2 s |
30
| `commit`, 1 000 files edited | 0.76 s | at most 1.2 s |
31
| `log -n 100` | 0.05 s | at most 0.1 s |
32
| `blame MAINTAINERS` | 0.07 s | at most 0.2 s |
33
| Checkout of another state and back | 0.49 s and 0.38 s | at most 1 s each |
34
| Store after gc | 311 MB (`.git`) | at most 350 MB |
35
| Everyday commands | about 31 MB | under 300 MB |
36
37
The targets allow oxy a small margin, because it does more (conflicts
38
as data, signatures, an operation log). A target that turns out to be
39
out of reach for a structural reason is written up here, with the best
40
number reached and why. It is never quietly dropped.
41
42
## Rules for this loop
43
44
- **Measure first.** Before changing code, measure where the time or
45
  memory goes on Earth: `/usr/bin/time -v`, `perf record` (installed on
46
  Earth), `OXY_TIMING` phases, or a counting allocator behind a feature.
47
  Record what the profile showed in the log.
48
- **Heavy work only on Earth**, always under a cap:
49
  `systemd-run --user --scope -p MemoryMax=14G …`. Andy's workstation has
50
  crashed from memory before. Locally, run the gate and small cases only.
51
- **Getting code to Earth.** `~/bench/oxygen` on Earth is a plain copy of
52
  the source. Sync it with `rsync -a --delete --exclude target --exclude
53
  .oxy -e "ssh -i ~/.ssh/earth_claude" ~/development/oxygen/
54
  claude@earth:bench/oxygen/`.
55
  - `~/bench/linux-bench.sh` builds oxy from that copy and runs the whole
56
    comparison. `~/bench/first-commit.sh` runs only the first commit,
57
    status and fsck.
58
  - Run long jobs with Bash `run_in_background`, and wait for the
59
    completion notice rather than polling.
60
- **Correctness is never traded.** Every change keeps these passing:
61
  `oxy fsck` on the Linux repository, the property tests, and
62
  `oxy git verify` on the Git imports.
63
- **Format changes** (change, checkpoint, pack or wire) are allowed
64
  before 1.0, with these steps:
65
  - bump the spec draft and keep old objects readable with unchanged
66
    hashes;
67
  - bump `deps/oxygen` in oxygenflow with `make pins`, gate, commit,
68
    push and `make deploy-runner`;
69
  - then `make pins` in oxygenhub, gate, commit, push, `make deploy`,
70
    and check `/health`;
71
  - only then install the new oxy locally. Commit the format change
72
    itself with the old binary.
73
- **Memory and pace.** Every workstation build and test runs under
74
  `systemd-run --user --scope -p MemoryMax=8G`, and
75
  `CARGO_INCREMENTAL=0` (the disk is nearly full).
76
b9c2d543 Andy McCall ↶ 77
## Correctness first (from metalgear, 2026-09-29)
78
79
A working copy that silently becomes the wrong tree is worse than any
80
slowness, so these come before everything else.
81
1681323d Andy McCall ↶ 82
- [x] C1 **Working-copy writes hold the workspace lock** (oxygen#36). Checkout, bookmark switch (with --carry), pull's checkout, undo, discard and commit hold the lock from reading the head until their operation is recorded. `merge_views` never lets a working-copy change based on a replaced one win. Test: a snapshot concurrent with a checkout never records the checkout's writes as edits.
83
- [x] C2 **`bookmark create NAME CHANGE` refuses a partial state** (oxygen#37). When CHANGE is one of the current state's heads but not all of them, it refuses and explains, unless given --only. `bookmark switch` summarises a wide tree difference. The guide explains that bookmarks name states, not ancestry.
84
- [x] C3 **A parent's switch never rolls a subrepo's bookmark back** (oxygen#38).
85
- [x] C4 **`pull` says "Already up to date" while one behind** (oxygen#29). Reproduce it with a test, then fix it.
b9c2d543 Andy McCall ↶ 86
33e77a55 Andy McCall ↶ 87
## Memory
88
7757367b Andy McCall ↶ 89
- [x] M0 **Tools to see memory.** Add a counting global allocator behind
33e77a55 Andy McCall ↶ 90
  a cargo feature (peak bytes and allocation count per `OXY_TIMING`
91
  phase), so a profile names the structure that holds the memory. Then
92
  profile a clean `status` of the Linux repository, killed at 14 GB
93
  (oxygen#35), and the first commit (9.9 GB). Write down which
94
  structures hold what before changing anything.
7757367b Andy McCall ↶ 95
- [x] M1 **Status without every graph** (oxygen#35). A clean `status`
33e77a55 Andy McCall ↶ 96
  must not build every file graph. Unchanged files are skipped by the
97
  manifest (spec §12.4), and the first status after a commit must not
98
  replay the whole change. Options: the commit writes a checkpoint;
99
  history-only states load graphs on demand (`State::load_graphs`).
100
  Target: under 300 MB and 0.4 s.
f59376ea Andy McCall ↶ 101
- [x] M2 **Lines without an allocation each.** An insert's lines become
33e77a55 Andy McCall ↶ 102
  one shared buffer with line offsets (for example `Arc<[u8]>` and
103
  ranges) in the change and in the file graph. The file graph shares
104
  that buffer and does not copy it. `AtomInfo` and the edge map shrink.
105
  Measure bytes per line before and after.
106
- [ ] M3 **A commit that streams.** The snapshot builder stores each
107
  file's text blob as it reads it and keeps no content it no longer
108
  needs, and the state is built without a second copy. Target for the
109
  first commit: under 2 GB.
110
- [ ] M4 **Everyday commands under 300 MB** on the Linux repository:
111
  status, diff, commit, log and blame, one file and 1 000 files edited.
112
113
## Speed
114
36695c5e Andy McCall ↶ 115
- [x] S1 **First commit at most 50 s** (55.3 s now): profile with
33e77a55 Andy McCall ↶ 116
  `perf`, and parallelise reading, hashing and diffing across the four
117
  cores where it is safe.
118
- [ ] S2 **Status, diff and commit** with one and with 1 000 files
119
  edited, to the targets above.
120
- [ ] S3 **log, blame and checkout** on the Linux repository, to the
121
  targets above.
122
- [ ] S4 **fsck and gc** measured against Git's. Re-run Git's `fsck`
123
  with gc turned off: 451 s is suspect, because Git's own background gc
124
  raced it.
125
126
## Space
127
36695c5e Andy McCall ↶ 128
- [x] P1 **Store at most 350 MB after gc** (442 MB now against Git's
33e77a55 Andy McCall ↶ 129
  311 MB): measure what takes the space (changes, text blobs, graphs,
130
  checkpoints, indexes), then fix the largest. Compression level and
131
  dictionary, and packing order, are all candidates.
132
133
## History at scale
134
a87c7b9d Andy McCall ↶ 135
- [x] H1 **Importer name conflict** (oxygen#30): the 5 000-commit
33e77a55 Andy McCall ↶ 136
  vscode import differs from Git in 51 files. Fix the cause and add a
137
  test. `oxy git verify` must be clean.
138
- [ ] H2 **Import memory**: drop graphs untouched by recent commits and
139
  reload them on demand, so memory is bounded by the working set and not
140
  by the history.
141
- [ ] H3 **Linux, stage 2**: import slices of recent history (2 000,
142
  10 000 and 50 000 commits), each verified, then time log, blame and
143
  status against Git on the same slice.
144
- [ ] H4 **vscode's full history** (about 130 000 commits) into a scratch
145
  repository on Earth. It is not published. Verify it and time it.
146
- [ ] H5 **Linux's full history** (about 1.3 million commits) if H3 shows
147
  it can finish overnight. Otherwise, write up what stands in the way.
148
149
## Finally
150
151
- [ ] Z1 Re-run the full `~/bench/linux-bench.sh` with the final build,
152
  and put the complete Git against oxy table in `docs/benchmarks.md`.
153
  Summarise, for Andy, where oxy now matches or beats Git and where it
154
  does not.
155
156
## Log
157
158
- 2026-09-29 Backlog written. Starting point: the Linux first commit
159
  succeeds (55.3 s, 9.9 GB, 442 MB store) with change format 2
160
  (oxygen#34). A clean `status` is killed at 14 GB (oxygen#35).
1681323d Andy McCall ↶ 161
- 2026-09-29 **C1 to C4 done** (the metalgear findings in ~/oxygen_feedback.md, items 22 and 25 to 26). The gate passes: 205 tests, clippy clean.
162
  - **C1** (e8e51993, closes #36): the working-copy lock now covers checkout, switch (and --carry), merge, commit, amend and rewrites, discard and pull. It is re-entrant per thread, waits up to a minute, and a live process's lock is never taken over. A workspace changed by two concurrent operations is now an error. A race test runs twenty switches against a snapshot thread; without the lock it failed one run in three.
163
  - **C2** (bf423e78, closes #37): `bookmark create NAME CHANGE` refuses when it would leave out earlier work, unless given --upto or --only. `bookmark switch` says how many changes the new state lacks. The guide explains states for people coming from Git.
164
  - **C3** (6b582c4c, closes #38): a parent's checkout moves a subrepo's bookmark only forward, and the pin is read from the nested working copy. `--carry` leaves a subrepo whose pin is carried work as it is.
165
  - **C4** (5dfea7b7, closes #29): not a sync bug. rstar-api's operation log shows each such pull ran on a feature bookmark, while main, which the fetch had just updated, was the one behind. pull now names the bookmark it pulled and the others left behind.
166
  - **For Andy:** metalgear's working copy is sound, on develop and clean. `logo-25` holds just the logo change and its dependencies; delete it if it is not wanted.
7757367b Andy McCall ↶ 167
- 2026-09-30 **M0 and M1 done; M4, S2 and S3 part way** (oxygen#35 and oxygen#39). The tests and clippy pass after each step. Each step was measured on the Linux tip and is recorded in docs/benchmarks.md.
168
  - **M0** (f11e81a1): a counting allocator behind the `alloc-stats` feature; with `OXY_TIMINGS` it reports memory per phase.
169
  - **M1**, with these steps:
170
    - history-only loads read no text (22b70898), then read only a change's skeleton (39c6adea);
171
    - graph keys are written at commit (22b70898);
172
    - a clean status is answered from the manifest (b4fefa1e) with a parallel walk (e19aa6c5).
173
    - Clean status: killed at 14 GB before, now 0.44 s and 123 MB (Git: 0.22 s).
174
  - **History-only loads where no graph is needed** (26f81555): bookmark, merge, pull, tags, bisect, doctor and most history rewrites.
175
  - **Checkout**: a batch at a time (921fa890), without a snapshot when clean (d1b480f0), and against the manifest without the base state (2b7ce2c6).
176
    - One-file switch: killed before, now 2.1 s and 455 MB (Git: 0.2 s).
177
    - Local clone: killed before, now 110 s and 8 GB (Git: 9.6 s).
178
  - **log, blame and fsck** (34ea4ace), with metadata-only change reads:
179
    - log -n 100: 28 s and 4.7 GB before, now 2.0 s;
180
    - blame: killed before, now 1.1 s;
181
    - fsck: killed before, now 123 s and 4.5 GB.
182
  - **Snapshot**: a parallel walk with stats carried into the manifest (a578fa19), and a cached tree view (de3f07f8). Status with one file changed: 4.1 s before, now 2.3 s (Git: 0.23 s).
183
  - **Still far from Git**: one-file and 1 000-file status, diff and commit (2 to 5 s); switch (2 s); log (2 s); clone (110 s, 8 GB); first commit (56 s, 10 GB); fsck (4.5 GB).
184
    - The common cost is loading the first change's history (0.5 s) and building the tree view (0.4 s).
185
    - Next: a persistent per-state cache (tree view, graph keys and change set), Git's index in effect, then compact graphs (M2) and a streaming commit (M3).
f59376ea Andy McCall ↶ 186
- 2026-09-30 **M2 done, M3 and S1 part way** (oxygen#35). The first commit of the Linux tip now takes 40.7 to 41.6 s on a quiet Earth (S1 target: 50 s; Git: 34 to 38 s) and 4.9 GB RSS (from 10 GB).
187
  - **Encoding** (b7103633): each change is encoded once, the working copy's edits are moved rather than copied, and text blobs and chunk compression run on every core.
188
  - **Storing** (b7a090c7): texts go to the store a batch at a time.
189
  - **M2, lines** (086641a8): an insert's lines are one shared buffer, into which graph atoms point.
190
  - **Clone** (5d62fa66, 11bf3b00, 397d9f98): a local clone takes 48 s and 3.9 GB (from 96 s and 5.2 GB, and killed before that; Git: 9.6 s). The changes behind it:
191
    - an unedited file's graph is read off as a chain;
192
    - a bundle carries its changes' hashes;
193
    - a large change is read once per checkout;
194
    - a local fetch moves changes as stored.
195
  - **Hub and runner**: re-pinned to oxygen 397d9f98 and deployed (oxygenhub#58, a6f006ba; oxygenflow#15, 630b7fd6). The hub reads metadata or skeletons where it lists changes. Checked: /health, and the log, file history, tree and file pages.
196
  - **Note**: my pushes start Flow jobs on Earth, so timings under load are marked in benchmarks.md.
a87c7b9d Andy McCall ↶ 197
- 2026-09-30 **H1 done** (oxygen#30). Three causes, each from Git having no identity for a directory:
198
  - a directory deleted and made again became a second node;
199
  - a placement under a removed directory was out of reach;
200
  - orphans could not be seen.
201
  - The fix: an import revives the directory, and a merge retires a kept node's other placements and every orphan. There is a Git-built test for each cause; each reproduces the vscode symptom without its fix. Spec §23.
202
  - The vscode slice now verifies: all 1 747 commits and 3.3 million files.
203
  - `oxy git verify` is cached by graph key and blob: 115 s instead of 1 400 s.
b493d993 Andy McCall ↶ 204
- 2026-09-30 **H2, first part**: an import builds graphs only for the files a commit may change, from history-only states. The first 5 000 vscode commits: 309 s and 2.3 GB (from 412 s and 6.3 GB), and all verified. Next: verify and import reuse each commit's history instead of loading it again, before H3 to H5.
36695c5e Andy McCall ↶ 205
- 2026-09-30 **P1 and S1 done** (oxygen#40).
206
  - **Repack**: `oxy gc` repacks everything into one pack at zstd level 9, compressing on every core (9f7585d4).
207
  - **Format 3**: text blobs hold the lines' bytes, with no CBOR framing (1122dcb3). The runner (oxygenflow#16) and hub (oxygenhub#59) were deployed with the new reader first.
208
  - **Linux tip**: the store is 324 MB after gc (objects 306 MB; Git 311 MB). The first commit takes 32.6 s (Git 33.7 to 37.9 s), measured while Earth ran at load average 11 during the vscode import.
209
  - **H2**: the vscode import (H4) runs on Earth as a service.
2517abe6 Andy McCall ↶ 210
- 2026-09-30 **H4 attempted**: the full vscode import (166 425 commits) under a 14 GB cap. With changes added mixed (e15074c7), it still grew about 0.4 MB per commit: 4.3 GB after 35 minutes and roughly 9 000 commits, far past the cap long before the end. It was stopped. The growth is the importer's kept states: each is a deep clone of a history-only state (change metadata, tree maps, the deferred edits of every file), up to eight at a time, with a new clone at every branch point.
211
  - **Next (H2b)**: make a state cheap to clone by sharing its parts (per-file deferred entries and graphs behind `Arc`, copied on write; the change map and tree likewise), so kept states share what they have in common.
212
  - **Also**: an import should report progress on long runs.
f6451fbb Andy McCall ↶ 213
- 2026-09-30 **H2b, first part**: a state's change metadata and per-file deferred entries are shared between clones and copied on write, and a clone starts with an empty closure cache.
214
  - Measured: 5 000 vscode commits 317 s and 2.07 GB (unchanged); 20 000 commits 4 530 s and 6.7 GB. So most of the growth is the base state itself, not its clones.
215
  - Phases from 2 000 to 5 000 commits (2.5 times as many):
216
217
    | Phase | 2 000 commits | 5 000 commits |
218
    |---|---:|---:|
219
    | bases | 5.3 s | 24.4 s |
220
    | ls-tree | 20.8 s | 63.8 s |
221
    | render base | 20.8 s | 89.0 s |
222
    | build change | 97.3 s | 139.7 s |
223
224
  - Listing, rendering and rebuilding bases cost what the whole tree is, on every commit: oxygen#25. H3 to H5 need it. The plan:
225
    - list a commit's changes with `git diff-tree` against its first parent rather than `ls-tree`;
226
    - keep the tree view up to date as changes apply, rather than recomputing it;
227
    - render only the paths a commit touches.
75b6c866 Andy McCall ↶ 228
- 2026-09-30 **Full Linux run** (06:24): table in `docs/benchmarks.md`. oxy matches Git on the first commit (38.5 s against 38.8 s) and beats it on gc (39.7 s against 102.8 s); the store is 324 MB against 295 MB. Everyday commands are still 5 to 15 times slower: most of each is loading the base's history and tree view. A one-file commit no longer reloads the new base for its manifest keys: 3.1 s (from 4.7 s). oxygen#41 (a base index) is proposed and waits for Andy.