silk-graph 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. silk_graph-0.2.4/.dockerignore +12 -0
  2. silk_graph-0.2.4/.github/workflows/bench.yml +28 -0
  3. silk_graph-0.2.4/.github/workflows/ci.yml +63 -0
  4. silk_graph-0.2.4/.github/workflows/release.yml +125 -0
  5. silk_graph-0.2.4/.gitignore +18 -0
  6. silk_graph-0.2.4/BENCHMARKS.md +354 -0
  7. silk_graph-0.2.4/CHANGELOG.md +171 -0
  8. silk_graph-0.2.4/CONTRIBUTING.md +61 -0
  9. silk_graph-0.2.4/Cargo.lock +1320 -0
  10. silk_graph-0.2.4/Cargo.toml +56 -0
  11. silk_graph-0.2.4/DESIGN.md +1412 -0
  12. silk_graph-0.2.4/Dockerfile.bench +51 -0
  13. silk_graph-0.2.4/EXPERIMENTS.md +365 -0
  14. silk_graph-0.2.4/FAQ.md +828 -0
  15. silk_graph-0.2.4/INVARIANTS.md +80 -0
  16. silk_graph-0.2.4/LICENSE.md +108 -0
  17. silk_graph-0.2.4/Makefile +26 -0
  18. silk_graph-0.2.4/PKG-INFO +912 -0
  19. silk_graph-0.2.4/PROOF.md +351 -0
  20. silk_graph-0.2.4/PROTOCOL.md +329 -0
  21. silk_graph-0.2.4/QUERY_EXTENSIONS.md +109 -0
  22. silk_graph-0.2.4/README.md +887 -0
  23. silk_graph-0.2.4/ROADMAP.md +388 -0
  24. silk_graph-0.2.4/SECURITY.md +43 -0
  25. silk_graph-0.2.4/WHY.md +165 -0
  26. silk_graph-0.2.4/benches/bench_entry.rs +80 -0
  27. silk_graph-0.2.4/benches/bench_graph.rs +333 -0
  28. silk_graph-0.2.4/benches/bench_oplog.rs +110 -0
  29. silk_graph-0.2.4/benches/bench_sync.rs +408 -0
  30. silk_graph-0.2.4/examples/compaction.py +61 -0
  31. silk_graph-0.2.4/examples/concurrent_writes.py +100 -0
  32. silk_graph-0.2.4/examples/offline_first.py +88 -0
  33. silk_graph-0.2.4/examples/partial_views.py +72 -0
  34. silk_graph-0.2.4/examples/partition_heal.py +94 -0
  35. silk_graph-0.2.4/examples/query_builder.py +72 -0
  36. silk_graph-0.2.4/examples/ring_topology.py +105 -0
  37. silk_graph-0.2.4/examples/signing.py +93 -0
  38. silk_graph-0.2.4/examples/tail_subscription.py +106 -0
  39. silk_graph-0.2.4/examples/time_travel.py +64 -0
  40. silk_graph-0.2.4/experiments/__init__.py +0 -0
  41. silk_graph-0.2.4/experiments/adapters.py +320 -0
  42. silk_graph-0.2.4/experiments/bench_comparative.py +466 -0
  43. silk_graph-0.2.4/experiments/bench_requirements.txt +5 -0
  44. silk_graph-0.2.4/experiments/harness.py +212 -0
  45. silk_graph-0.2.4/experiments/test_compaction_correctness.py +344 -0
  46. silk_graph-0.2.4/experiments/test_compression.py +194 -0
  47. silk_graph-0.2.4/experiments/test_fault_injection.py +356 -0
  48. silk_graph-0.2.4/experiments/test_graph_comparison.py +344 -0
  49. silk_graph-0.2.4/experiments/test_memory_footprint.py +213 -0
  50. silk_graph-0.2.4/experiments/test_persistence_perf.py +245 -0
  51. silk_graph-0.2.4/experiments/test_sync_overlap.py +238 -0
  52. silk_graph-0.2.4/experiments/test_tail_bench.py +254 -0
  53. silk_graph-0.2.4/experiments/test_tail_breakdown.py +168 -0
  54. silk_graph-0.2.4/experiments/test_tail_dig.py +220 -0
  55. silk_graph-0.2.4/experiments/test_tail_overhead.py +94 -0
  56. silk_graph-0.2.4/pyproject.toml +38 -0
  57. silk_graph-0.2.4/pytests/test_buffer.py +253 -0
  58. silk_graph-0.2.4/pytests/test_compaction.py +316 -0
  59. silk_graph-0.2.4/pytests/test_compaction_policy.py +173 -0
  60. silk_graph-0.2.4/pytests/test_constraints.py +493 -0
  61. silk_graph-0.2.4/pytests/test_extend_ontology.py +338 -0
  62. silk_graph-0.2.4/pytests/test_gossip.py +115 -0
  63. silk_graph-0.2.4/pytests/test_graph_engine.py +215 -0
  64. silk_graph-0.2.4/pytests/test_hierarchy.py +187 -0
  65. silk_graph-0.2.4/pytests/test_invariants.py +435 -0
  66. silk_graph-0.2.4/pytests/test_ontology_compat.py +300 -0
  67. silk_graph-0.2.4/pytests/test_open_properties.py +213 -0
  68. silk_graph-0.2.4/pytests/test_partial_sync.py +262 -0
  69. silk_graph-0.2.4/pytests/test_persistence.py +163 -0
  70. silk_graph-0.2.4/pytests/test_persistent_merge.py +135 -0
  71. silk_graph-0.2.4/pytests/test_quarantine.py +210 -0
  72. silk_graph-0.2.4/pytests/test_query_builder.py +238 -0
  73. silk_graph-0.2.4/pytests/test_security.py +202 -0
  74. silk_graph-0.2.4/pytests/test_signing.py +209 -0
  75. silk_graph-0.2.4/pytests/test_store_basic.py +293 -0
  76. silk_graph-0.2.4/pytests/test_subscription.py +252 -0
  77. silk_graph-0.2.4/pytests/test_subtypes.py +275 -0
  78. silk_graph-0.2.4/pytests/test_sync.py +1014 -0
  79. silk_graph-0.2.4/pytests/test_tail.py +336 -0
  80. silk_graph-0.2.4/pytests/test_time_travel.py +240 -0
  81. silk_graph-0.2.4/python/silk/__init__.py +19 -0
  82. silk_graph-0.2.4/python/silk/__init__.pyi +565 -0
  83. silk_graph-0.2.4/python/silk/compaction.py +104 -0
  84. silk_graph-0.2.4/python/silk/compression.py +74 -0
  85. silk_graph-0.2.4/python/silk/query.py +221 -0
  86. silk_graph-0.2.4/python/silk/views.py +95 -0
  87. silk_graph-0.2.4/src/bloom.rs +266 -0
  88. silk_graph-0.2.4/src/buffer.rs +249 -0
  89. silk_graph-0.2.4/src/clock.rs +209 -0
  90. silk_graph-0.2.4/src/engine.rs +620 -0
  91. silk_graph-0.2.4/src/entry.rs +798 -0
  92. silk_graph-0.2.4/src/gossip.rs +260 -0
  93. silk_graph-0.2.4/src/graph.rs +1282 -0
  94. silk_graph-0.2.4/src/lib.rs +38 -0
  95. silk_graph-0.2.4/src/obslog.rs +480 -0
  96. silk_graph-0.2.4/src/ontology.rs +1965 -0
  97. silk_graph-0.2.4/src/oplog.rs +784 -0
  98. silk_graph-0.2.4/src/python/conversions.rs +374 -0
  99. silk_graph-0.2.4/src/python/mod.rs +1902 -0
  100. silk_graph-0.2.4/src/python/obslog.rs +91 -0
  101. silk_graph-0.2.4/src/python/snapshot.rs +171 -0
  102. silk_graph-0.2.4/src/python/tail.rs +246 -0
  103. silk_graph-0.2.4/src/store.rs +600 -0
  104. silk_graph-0.2.4/src/sync.rs +774 -0
  105. silk_graph-0.2.4/tests/integration_sync.rs +475 -0
  106. silk_graph-0.2.4/tests/stress_sync.rs +287 -0
@@ -0,0 +1,12 @@
1
+ target/
2
+ .bench-venv/
3
+ .venv/
4
+ venv/
5
+ __pycache__/
6
+ *.pyc
7
+ .pytest_cache/
8
+ *.egg-info/
9
+ dist/
10
+ internal/
11
+ .git/
12
+ .cache/
@@ -0,0 +1,28 @@
1
+ name: Benchmarks
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ env:
8
+ CARGO_TERM_COLOR: always
9
+
10
+ jobs:
11
+ bench:
12
+ name: Run benchmarks
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: dtolnay/rust-toolchain@stable
17
+ - uses: Swatinem/rust-cache@v2
18
+
19
+ - name: Run Criterion benchmarks
20
+ run: cargo bench --no-default-features 2>&1 | tee bench-output.txt
21
+
22
+ - name: Upload benchmark results
23
+ uses: actions/upload-artifact@v4
24
+ with:
25
+ name: benchmark-results
26
+ path: |
27
+ bench-output.txt
28
+ target/criterion/
@@ -0,0 +1,63 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ env:
10
+ CARGO_TERM_COLOR: always
11
+
12
+ jobs:
13
+ rust:
14
+ name: Rust tests
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: dtolnay/rust-toolchain@stable
19
+ with:
20
+ components: clippy, rustfmt
21
+ - uses: Swatinem/rust-cache@v2
22
+
23
+ - name: Check formatting
24
+ run: cargo fmt --check
25
+
26
+ - name: Clippy (no Python)
27
+ run: cargo clippy --no-default-features -- -D warnings
28
+
29
+ - name: Run Rust tests
30
+ run: cargo test --no-default-features
31
+
32
+ python:
33
+ name: Python tests (${{ matrix.python-version }})
34
+ runs-on: ubuntu-latest
35
+ strategy:
36
+ matrix:
37
+ python-version: ["3.11", "3.12", "3.13"]
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+ - uses: dtolnay/rust-toolchain@stable
41
+ - uses: Swatinem/rust-cache@v2
42
+
43
+ - name: Set up Python ${{ matrix.python-version }}
44
+ uses: actions/setup-python@v5
45
+ with:
46
+ python-version: ${{ matrix.python-version }}
47
+
48
+ - name: Build and install
49
+ run: |
50
+ pip install maturin pytest
51
+ maturin build --release --features python --out dist
52
+ pip install dist/*.whl
53
+
54
+ - name: Run Python tests
55
+ run: pytest pytests/ -v
56
+
57
+ - name: Run examples
58
+ if: matrix.python-version == '3.12'
59
+ run: |
60
+ python examples/offline_first.py
61
+ python examples/partition_heal.py
62
+ python examples/concurrent_writes.py
63
+ python examples/ring_topology.py
@@ -0,0 +1,125 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ build-wheels:
13
+ name: Build wheel (${{ matrix.os }} / ${{ matrix.target }})
14
+ runs-on: ${{ matrix.os }}
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ include:
19
+ # Linux x86_64 (most servers, CI runners). Built in a manylinux
20
+ # container so PyPI accepts the wheel — host builds get tagged
21
+ # `linux_x86_64` which PyPI rejects. --interpreter points maturin
22
+ # at the container's Python (host's Python is not on PATH inside).
23
+ - os: ubuntu-latest
24
+ target: x86_64
25
+ manylinux: 'auto'
26
+ extra-args: '--interpreter 3.12'
27
+ # Linux aarch64 (ARM servers, Docker on Apple Silicon, RPi 64-bit).
28
+ # Same container + --interpreter pattern, cross-compiled.
29
+ - os: ubuntu-latest
30
+ target: aarch64
31
+ manylinux: 'auto'
32
+ extra-args: '--interpreter 3.12'
33
+ # macOS arm64 (Apple Silicon).
34
+ - os: macos-14
35
+ target: aarch64
36
+ manylinux: 'off'
37
+ extra-args: ''
38
+ # Windows x86_64.
39
+ - os: windows-latest
40
+ target: x64
41
+ manylinux: 'off'
42
+ extra-args: ''
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+ - uses: dtolnay/rust-toolchain@stable
46
+ - uses: actions/setup-python@v5
47
+ with:
48
+ python-version: '3.12'
49
+ - name: Build wheel
50
+ uses: PyO3/maturin-action@v1
51
+ with:
52
+ target: ${{ matrix.target }}
53
+ args: --release --out dist ${{ matrix.extra-args }}
54
+ manylinux: ${{ matrix.manylinux }}
55
+ - uses: actions/upload-artifact@v4
56
+ with:
57
+ name: wheels-${{ matrix.os }}-${{ matrix.target }}
58
+ path: dist
59
+
60
+ build-sdist:
61
+ name: Build source distribution
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - uses: actions/checkout@v4
65
+ - name: Build sdist
66
+ uses: PyO3/maturin-action@v1
67
+ with:
68
+ command: sdist
69
+ args: --out dist
70
+ - uses: actions/upload-artifact@v4
71
+ with:
72
+ name: sdist
73
+ path: dist
74
+
75
+ publish-pypi:
76
+ name: Publish to PyPI
77
+ needs: [build-wheels, build-sdist]
78
+ runs-on: ubuntu-latest
79
+ environment: pypi
80
+ permissions:
81
+ id-token: write
82
+ steps:
83
+ - uses: actions/download-artifact@v4
84
+ with:
85
+ pattern: wheels-*
86
+ merge-multiple: true
87
+ path: dist
88
+ - uses: actions/download-artifact@v4
89
+ with:
90
+ name: sdist
91
+ path: dist
92
+ - uses: pypa/gh-action-pypi-publish@release/v1
93
+
94
+ publish-crates:
95
+ name: Publish to crates.io
96
+ runs-on: ubuntu-latest
97
+ continue-on-error: true
98
+ steps:
99
+ - uses: actions/checkout@v4
100
+ - uses: dtolnay/rust-toolchain@stable
101
+ - name: Publish
102
+ run: cargo publish --allow-dirty
103
+ env:
104
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }}
105
+
106
+ github-release:
107
+ name: Create GitHub Release
108
+ needs: [publish-pypi]
109
+ runs-on: ubuntu-latest
110
+ steps:
111
+ - uses: actions/checkout@v4
112
+ - uses: actions/download-artifact@v4
113
+ with:
114
+ pattern: wheels-*
115
+ merge-multiple: true
116
+ path: dist
117
+ - uses: actions/download-artifact@v4
118
+ with:
119
+ name: sdist
120
+ path: dist
121
+ - name: Create release
122
+ uses: softprops/action-gh-release@v2
123
+ with:
124
+ files: dist/*
125
+ generate_release_notes: true
@@ -0,0 +1,18 @@
1
+ /target/
2
+ *.so
3
+ *.dylib
4
+ *.dll
5
+ *.dSYM/
6
+ __pycache__/
7
+ *.pyc
8
+ .pytest_cache/
9
+ *.egg-info/
10
+ dist/
11
+ /.venv/
12
+ /venv/
13
+
14
+ # Internal planning/review docs — not for public repo
15
+ /internal/
16
+
17
+ # Benchmark venv (separate from dev environment)
18
+ .bench-venv/
@@ -0,0 +1,354 @@
1
+ # Comparative CRDT Benchmarks
2
+
3
+ > Measured 2026-03-27. silk-graph v0.1.6, Loro 1.10.3, pycrdt 0.12.50.
4
+ > All measurements in-memory, single-threaded. Verified across 3 local + 3 Docker runs.
5
+ > Reproducible: `docker build -f Dockerfile.bench -t silk-bench . && docker run --rm silk-bench`
6
+
7
+ ---
8
+
9
+ ## Systems Under Test
10
+
11
+ | System | Version | Data Model | CRDT Type | Language | Install |
12
+ |--------|---------|-----------|-----------|----------|---------|
13
+ | silk-graph | 0.1.5 | Property graph (Merkle-DAG oplog) | State-based (delta sync) | Rust + PyO3 | `pip install silk-graph` |
14
+ | Loro | 1.10.3 | Document (Map, List, Text, Tree) | State-based (Fugue) | Rust + PyO3 | `pip install loro` |
15
+ | pycrdt | 0.12.50 | Document (Map, Array, Text) | Op-based (YATA/Yjs) | Rust + PyO3 (Yrs) | `pip install pycrdt` |
16
+
17
+ Silk is a property-graph CRDT. Loro and pycrdt are document CRDTs. This benchmark measures shared CRDT operations (write, update, sync, merge), not data model expressiveness or domain-specific features (graph traversal, text editing, etc.).
18
+
19
+ ---
20
+
21
+ ## Methodology
22
+
23
+ **Hardware:** Apple M4 Max (16 cores, 128 GB RAM), macOS 15.7, Python 3.12.9.
24
+
25
+ **Procedure:** Each scenario runs 5 rounds (fresh stores per round) unless noted. Median reported. Timing via `time.perf_counter()`. All stores are in-memory (no disk I/O).
26
+
27
+ **Fairness:** Each system uses its natural API idiom:
28
+ - Silk: `add_node()` / `add_edge()` / `update_property()` / 3-phase offer/receive/merge sync
29
+ - Loro: `LoroMap.insert()` / version-vector delta export/import
30
+ - pycrdt: `Map[key] = value` / state-vector delta get_update/apply_update
31
+
32
+ Relationships modeled naturally per system: Silk uses typed graph edges; Loro and pycrdt store references as map properties.
33
+
34
+ Adapters normalize the interface without altering each system's internal behavior. Source: [`experiments/adapters.py`](experiments/adapters.py).
35
+
36
+ ---
37
+
38
+ ## Results
39
+
40
+ ### S1: Write Throughput
41
+
42
+ Create N entities, each with 3 properties (`name: str`, `status: str`, `seq: int`).
43
+
44
+ | System | N=100 | N=1,000 | N=10,000 |
45
+ |--------|-------|---------|----------|
46
+ | silk | 0.43 ms (233K ops/s) | 4.3 ms (233K ops/s) | 50.2 ms (199K ops/s) |
47
+ | loro | 0.25 ms (400K ops/s) | 2.4 ms (417K ops/s) | 27.2 ms (368K ops/s) |
48
+ | pycrdt | 0.42 ms (238K ops/s) | 3.8 ms (263K ops/s) | 40.2 ms (249K ops/s) |
49
+
50
+ ### S2: Update Throughput
51
+
52
+ Update one field on a single entity N times.
53
+
54
+ | System | N=100 | N=1,000 | N=10,000 |
55
+ |--------|-------|---------|----------|
56
+ | silk | 0.17 ms (588K ops/s) | 1.75 ms (571K ops/s) | 18.1 ms (552K ops/s) |
57
+ | loro | 0.08 ms (1.25M ops/s) | 0.75 ms (1.33M ops/s) | 7.5 ms (1.33M ops/s) |
58
+ | pycrdt | 0.30 ms (333K ops/s) | 2.8 ms (357K ops/s) | 27.0 ms (370K ops/s) |
59
+
60
+ ### S3: Sync Latency
61
+
62
+ Two peers each write M unique entities, then sync bidirectionally. Includes store creation and write time.
63
+
64
+ | System | M=100 | M=500 |
65
+ |--------|-------|-------|
66
+ | silk | 2.0 ms | 10.9 ms |
67
+ | loro | 0.9 ms | 4.7 ms |
68
+ | pycrdt | 1.4 ms | 7.1 ms |
69
+
70
+ ### S4: Sync Bandwidth
71
+
72
+ Bytes transferred for bidirectional sync of M entities (3 properties each).
73
+
74
+ | System | M=100 (total) | M=500 (total) |
75
+ |--------|--------------|--------------|
76
+ | silk | 34,650 | 175,300 |
77
+ | loro | 4,692 | 25,494 |
78
+ | pycrdt | 6,631 | 36,232 |
79
+
80
+ ### S5: Merge Correctness
81
+
82
+ Fork from shared state, concurrent update to the same field, bidirectional sync, verify both peers converge to the same value. 10 rounds.
83
+
84
+ | System | Converged | Rate |
85
+ |--------|-----------|------|
86
+ | silk | 10/10 | 100% |
87
+ | loro | 10/10 | 100% |
88
+ | pycrdt | 10/10 | 100% |
89
+
90
+ ### S6: Structured Workload
91
+
92
+ Users + projects + assignments + status updates. Each user assigned to 1–3 projects, then all project statuses updated.
93
+
94
+ | System | 50 users / 10 projects | 200 / 40 | 1000 / 200 |
95
+ |--------|----------------------|----------|------------|
96
+ | silk | 0.57 ms (302K ops/s) | 2.2 ms (305K ops/s) | 12.5 ms (274K ops/s) |
97
+ | loro | 0.36 ms (478K ops/s) | 1.4 ms (479K ops/s) | 7.0 ms (489K ops/s) |
98
+ | pycrdt | 4.4 ms (39K ops/s) | 73.2 ms (9K ops/s) | 2,711 ms (1.3K ops/s) |
99
+
100
+ Snapshot sizes at 1000 users / 200 projects:
101
+
102
+ | System | Snapshot |
103
+ |--------|---------|
104
+ | silk | 642 KB |
105
+ | loro | 69 KB |
106
+ | pycrdt | 92 KB |
107
+
108
+ ### S7: Multi-Peer Ring Convergence
109
+
110
+ N peers each write unique entities, then ring-sync (0→1→2→...→N-1→0) until converged.
111
+
112
+ | System | 3 peers × 100 | 5 × 100 | 10 × 50 |
113
+ |--------|--------------|---------|---------|
114
+ | silk | 7.2 ms / 6 rounds / 119 KB | 32.0 ms / 10 rounds / 394 KB | 119 ms / 20 rounds / 898 KB |
115
+ | loro | 1.4 ms / 6 rounds / 19 KB | 4.5 ms / 10 rounds / 62 KB | 10.5 ms / 20 rounds / 140 KB |
116
+ | pycrdt | 10.1 ms / 6 rounds / 28 KB | 43.8 ms / 10 rounds / 95 KB | 175 ms / 20 rounds / 211 KB |
117
+
118
+ All systems converge in the same number of rounds (2 × N for ring topology).
119
+
120
+ ### S8: Diverge-Then-Heal
121
+
122
+ Two peers fork from shared state, each writes independently, then sync to heal the partition.
123
+
124
+ | System | 100 shared + 50 divergent | 500 + 200 | 1000 + 500 |
125
+ |--------|--------------------------|-----------|------------|
126
+ | silk | 1.6 ms / 55 KB | 8.6 ms / 261 KB | 18.6 ms / 559 KB |
127
+ | loro | 0.29 ms / 2 KB | 1.1 ms / 10 KB | 3.3 ms / 26 KB |
128
+ | pycrdt | 0.65 ms / 3 KB | 3.0 ms / 14 KB | 9.3 ms / 36 KB |
129
+
130
+ ---
131
+
132
+ ## Analysis
133
+
134
+ ### Per-operation cost
135
+
136
+ Silk creates a content-addressed Merkle-DAG entry for every mutation: BLAKE3 hash, HLC clock, author identity, parent links, MessagePack serialization. This is the fixed cost of immutable, auditable, causally ordered operations.
137
+
138
+ | System | Write ops/s (N=1K) | Update ops/s (N=1K) | Update/Write ratio |
139
+ |--------|-------------------|--------------------|--------------------|
140
+ | silk | 233K | 571K | 2.5x |
141
+ | loro | 417K | 1.33M | 3.2x |
142
+ | pycrdt | 263K | 357K | 1.4x |
143
+
144
+ Loro's updates are in-place map mutations with near-zero per-operation overhead until commit. Silk's updates still create DAG entries but skip graph node creation. pycrdt's ratio is low because its writes are already lightweight (no per-write hashing).
145
+
146
+ ### Write scaling (S1)
147
+
148
+ | System | N=100 ops/s | N=10K ops/s | Degradation |
149
+ |--------|-------------|-------------|-------------|
150
+ | silk | 233K | 199K | -15% |
151
+ | loro | 400K | 368K | -8% |
152
+ | pycrdt | 238K | 249K | flat |
153
+
154
+ Silk degrades slightly as the oplog grows — HashMap lookup and head tracking cost increases with entry count. Loro and pycrdt maintain throughput.
155
+
156
+ ### Structured workload scaling (S6)
157
+
158
+ pycrdt's throughput drops from 249K ops/s (S1 flat writes) to 1.3K ops/s (S6 at 1000 users). Creating 1,200+ top-level maps causes per-operation overhead to grow non-linearly. Silk and Loro maintain throughput — Silk at 274K ops/s, Loro at 489K ops/s. For workloads with many distinct entities and relationships, pycrdt's architecture is not suited.
159
+
160
+ ### Sync bandwidth
161
+
162
+ | System | Bytes per entity (M=500) | Relative to Loro |
163
+ |--------|-------------------------|------------------|
164
+ | loro | 25 | 1.0x |
165
+ | pycrdt | 36 | 1.4x |
166
+ | silk | 175 | 6.9x |
167
+
168
+ This ratio is consistent across all scenarios (S4, S7, S8). Each Silk sync entry carries:
169
+ - 32 bytes BLAKE3 content hash
170
+ - HLC clock (instance_id string + physical_ms + logical)
171
+ - Author identity string
172
+ - Parent hash links (Merkle-DAG causal chain)
173
+ - MessagePack envelope
174
+
175
+ Loro and pycrdt send compact CRDT deltas without per-operation identity, integrity hashes, or causal links.
176
+
177
+ ### Sync compute efficiency (S3 vs S4)
178
+
179
+ | System | Sync latency (M=500) | Bandwidth (M=500) | Throughput |
180
+ |--------|---------------------|-------------------|------------|
181
+ | silk | 10.9 ms | 175 KB | 16.1 KB/ms |
182
+ | loro | 4.7 ms | 25 KB | 5.3 KB/ms |
183
+ | pycrdt | 7.1 ms | 36 KB | 5.1 KB/ms |
184
+
185
+ Silk processes 3x more bytes per millisecond. The latency gap is dominated by serialization volume, not compute.
186
+
187
+ ### Multi-peer scaling (S7)
188
+
189
+ All systems need the same number of sync rounds for ring convergence (2 × peer count — information propagates one hop per round). The per-round cost is proportional to each system's sync latency. At 10 peers, pycrdt (175ms) is slower than Silk (119ms) due to per-sync overhead accumulation.
190
+
191
+ ### Partition heal cost (S8)
192
+
193
+ At 1000 shared + 500 divergent entries per peer, Silk heals in 18.6ms transferring 559 KB. Loro heals in 3.3ms transferring 26 KB. The bandwidth ratio (22x) is higher than the 7x in S4 because S8's payload includes ancestor closure metadata for the shared prefix.
194
+
195
+ For context: a 1,500-entity partition heal in 18.6ms is practical for any sync interval. The bandwidth (559 KB) is a single HTTP response on any modern connection.
196
+
197
+ ### What Silk's overhead buys
198
+
199
+ None of the comparison systems provide these capabilities:
200
+
201
+ | Capability | Silk | Loro | pycrdt |
202
+ |-----------|------|------|--------|
203
+ | Content-addressed entries (tamper detection, deduplication) | Yes | No | No |
204
+ | Causal ordering via Merkle-DAG (happened-before relation) | Yes | No | No |
205
+ | Immutable audit trail (every mutation is a permanent entry) | Yes | No | No |
206
+ | Schema enforcement at write time (typed nodes, edges, properties) | Yes | No | No |
207
+ | Graph structure (typed edges, BFS, shortest path, pattern match) | Yes | No | No |
208
+ | Author authentication (ed25519 signatures per entry) | Yes | No | No |
209
+
210
+ These are architectural properties, not benchmarkable as throughput. The bandwidth and latency overhead is the cost of carrying per-operation integrity, identity, and causal structure.
211
+
212
+ ### Practical context
213
+
214
+ At 10,000 entities with 3 properties each, Silk writes the full graph in **50ms**. For the use cases Silk targets — infrastructure graphs, configuration sync, knowledge graphs syncing between devices or services — these numbers are within practical bounds:
215
+
216
+ - A 500-server infrastructure graph (servers, services, edges, properties): well under 10K entities. Full write in under 50ms.
217
+ - Periodic sync between two peers with 500 divergent entities: **11ms**. At sync intervals of 1–10 seconds, this is <1% of the sync window.
218
+ - A 1,500-entity partition heal (1000 shared + 500 divergent): **18.6ms**. Imperceptible at any sync interval.
219
+ - A 10K-entity sync over WAN: Silk transfers ~3.5 MB vs Loro's ~500 KB. On a 10 Mbps link, that's 2.8 seconds vs 0.4 seconds. On LAN, the difference is imperceptible.
220
+
221
+ The bandwidth gap is the primary engineering trade-off. For metered or highly constrained connections, optional payload compression (`ZlibCompression(level=1)`) reduces payloads by 68% at 29% latency overhead. See [EXP-05](EXPERIMENTS.md).
222
+
223
+ ---
224
+
225
+ ## Graph System Comparison (EXP-07)
226
+
227
+ Silk compared against NetworkX (plain in-memory graph, no sync) and TerminusDB (server-based versioned graph database). These are not competing products — they solve different problems. The comparison shows where each fits.
228
+
229
+ ### Systems
230
+
231
+ | System | Type | Sync | Schema | Persistence | Install |
232
+ |--------|------|------|--------|-------------|---------|
233
+ | Silk | Embedded CRDT graph | Automatic (conflict-free) | Write-time validation | redb (embedded) | `pip install silk-graph` |
234
+ | NetworkX | In-memory graph library | None | None | None | `pip install networkx` |
235
+ | TerminusDB | Server graph database | Git-style (push/pull/clone) | OWL-based | Built-in | Docker + `pip install terminusdb` |
236
+
237
+ ### Write / Query Performance (1000 entities, 3 properties each)
238
+
239
+ | System | Write | Query all | Update 100 | Snapshot |
240
+ |--------|-------|-----------|------------|----------|
241
+ | NetworkX | 0.52 ms (1.9M ops/s) | 0.01 ms | 0.01 ms | 54 KB |
242
+ | Silk | 5.2 ms (193K ops/s) | 0.39 ms | 0.23 ms | 272 KB |
243
+ | TerminusDB | 199 ms (5K ops/s) | 18.0 ms | 18.3 ms | N/A (server) |
244
+
245
+ ### Traversal (1000-node chain)
246
+
247
+ | Algorithm | Silk (Rust) | NetworkX (Python) | Ratio |
248
+ |-----------|------------|-------------------|-------|
249
+ | BFS | 0.20 ms | 0.68 ms | Silk 3.4x faster |
250
+ | DFS | 0.20 ms | 0.71 ms | Silk 3.5x faster |
251
+
252
+ Silk's Rust-native traversal outperforms NetworkX's pure Python implementation despite carrying CRDT metadata per node.
253
+
254
+ ### Cost of each feature layer
255
+
256
+ | Transition | Overhead | What you gain |
257
+ |-----------|----------|---------------|
258
+ | NetworkX → Silk | ~8x write cost | CRDT sync, schema enforcement, persistence, content addressing, audit trail, author authentication |
259
+ | Silk → TerminusDB | ~40x write cost | WOQL/GraphQL query language, git-style branching, OWL reasoning, full ACID, server-side compute |
260
+
261
+ ### Which tool for which job
262
+
263
+ **Use NetworkX when:**
264
+ - You need graph algorithms (PageRank, centrality, community detection, Dijkstra) on data that's already in your process
265
+ - Single-process, single-user, no sync needed
266
+ - Exploration, analysis, visualization of graph data
267
+ - You have another system (Silk, a database, CSV files) providing the data, and NetworkX is the analytics layer on top
268
+
269
+ **Use Silk when:**
270
+ - Multiple devices or services need the same graph and must work offline
271
+ - You need schema enforcement at write time — invalid data rejected before it enters the system
272
+ - You need an audit trail — every mutation is an immutable, content-addressed entry
273
+ - You need sync without a server — any two Silk instances converge automatically
274
+ - The graph fits in memory (<50K nodes on typical hardware, <100K on servers)
275
+ - Examples: infrastructure knowledge graphs, configuration sync, field data collection, local-first collaborative apps
276
+
277
+ **Use TerminusDB when:**
278
+ - You need a server-side graph database with a query language (WOQL, GraphQL)
279
+ - You need git-style branching, merging, and history management for graph data
280
+ - You need OWL-level schema reasoning (inference, subsumption)
281
+ - You have a central server and clients that push/pull — the git model fits your workflow
282
+ - You can accept that conflicts require manual resolution (rebase), not automatic merge
283
+ - Examples: data product versioning, collaborative ontology management, regulated environments needing full ACID
284
+
285
+ **Use Silk + NetworkX together when:**
286
+ - Silk handles storage, sync, and schema enforcement across peers
287
+ - NetworkX runs analytics (PageRank, community detection, Dijkstra) on the synced graph
288
+ - This is the intended architecture: Silk is the sync/storage layer, NetworkX (or igraph, or any graph library) is the analytics layer
289
+
290
+ TerminusDB and Silk are not interchangeable. TerminusDB is a database server; Silk is an embedded library. Choosing between them is like choosing between PostgreSQL and SQLite — different deployment models for different constraints.
291
+
292
+ ---
293
+
294
+ ## Limitations
295
+
296
+ ### Persistence overhead (EXP-08)
297
+
298
+ All benchmarks above are in-memory. Persistent storage (redb) adds I/O cost:
299
+
300
+ | Operation | In-memory | Persistent | Overhead | Notes |
301
+ |-----------|-----------|-----------|----------|-------|
302
+ | Write 1000 entities (individual) | 4.6 ms | 4,456 ms | ~1000x | Each write = redb commit + fsync |
303
+ | Sync 1000 entities (batched) | 11.3 ms | 13.8 ms | 1.2x | Sync batches all entries in one transaction |
304
+ | Startup (open 1000-entity store) | N/A | 31 ms | — | Oplog reconstruction + graph rebuild |
305
+
306
+ The per-write overhead is extreme because each `append()` commits a redb transaction (atomic fsync). For burst writes, use in-memory stores and sync to persistent, or batch via `merge_sync_payload()`. Sync batches entries into a single transaction, so the persistent overhead drops to 1.2-3x.
307
+
308
+ **Recommendation:** Use in-memory stores for write-heavy paths. Use persistent stores for durability at sync boundaries. This matches the local-first pattern: write locally (fast), sync periodically (durable).
309
+
310
+ ### Evaluation scope and limitations
311
+
312
+ **What these benchmarks are:** Engineering evidence for the performance characteristics of silk-graph's CRDT operations, measured on a single machine with controlled workloads. They are honest about where Silk is slower and why.
313
+
314
+ **What these benchmarks are NOT:** A comprehensive academic evaluation. Specifically:
315
+
316
+ - **No like-for-like graph-replication baseline exists.** Silk is the only embedded CRDT property graph we are aware of. Loro, pycrdt, and Automerge are document CRDTs (different data model). TerminusDB is a server-based versioned graph (different deployment model). NetworkX is a plain graph library (no sync). We compare against what exists, but the comparison is inherently cross-category.
317
+ - **No multi-machine or WAN measurements.** All sync benchmarks measure serialization + merge cost, not network round-trip time. Real-world sync latency includes network I/O, which depends on deployment topology and is independent of the CRDT engine.
318
+ - **No fault-injection during benchmarks.** Fault injection is tested separately ([EXP-06](EXPERIMENTS.md)) for correctness, but not for performance under adversarial conditions (e.g., sync throughput during message loss).
319
+ - **Single hardware platform.** All measurements on Apple M4 Max. Results on x86, ARM servers, or resource-constrained devices may differ.
320
+ - **Automerge excluded.** Python bindings (v0.1.2) do not support mutation or merge operations needed for benchmarking.
321
+
322
+ ---
323
+
324
+ ## Reproduction
325
+
326
+ ### Docker (recommended — fully isolated, reproducible)
327
+
328
+ ```bash
329
+ docker build -f Dockerfile.bench -t silk-bench .
330
+
331
+ # Run all experiments (19 tests)
332
+ docker run --rm silk-bench
333
+
334
+ # Run comparative benchmarks with table output
335
+ docker run --rm silk-bench python experiments/bench_comparative.py
336
+
337
+ # JSON output
338
+ docker run --rm silk-bench python experiments/bench_comparative.py --json
339
+
340
+ # Single scenario
341
+ docker run --rm silk-bench python experiments/bench_comparative.py --scenario=S6
342
+ ```
343
+
344
+ ### Local (requires Rust toolchain + Python 3.12)
345
+
346
+ ```bash
347
+ python -m venv .bench-venv
348
+ source .bench-venv/bin/activate
349
+ pip install -r experiments/bench_requirements.txt
350
+ maturin develop --release
351
+ python experiments/bench_comparative.py
352
+ ```
353
+
354
+ Source: [`experiments/bench_comparative.py`](experiments/bench_comparative.py), [`experiments/adapters.py`](experiments/adapters.py), [`Dockerfile.bench`](Dockerfile.bench).