docs: consolidate cache guidance and keep generated reports local

This commit is contained in:
Thomas Durieux committed 2026-11-02 12:24:01 +00:00
1 parent dd668c1f4b
commit 0efa1590c9
16 files changed
+201 -18469

No files matched your search

+3
View File
@@ -124,3 +124,6 @@ temp/
# GitHub App signing key
/secrets/github-app.pem
# Generated local benchmark reports
/benchmark-results/
@@ -1,108 +0,0 @@
{
"measuredAt": "2026-11-02T11:05:58.163Z",
"scope": "Synthetic metadata-only scale probe. Dummy blob rows have no payload files; excludes directory scanning and disk reads for those rows. Real reads use one verified blob. Eviction probe uses real 64 KiB files.",
"node": "v24.14.1",
"cardinality": [
{
"seedBlobs": 0,
"seedAliases": 0,
"seedDecisions": 0,
"seedHistory": 0,
"adminStats": {
"p50Ms": 0.32291899999972884,
"p95Ms": 0.47828900000013164,
"maxMs": 0.49711900000011155
},
"warmSourceHit": {
"p50Ms": 2.9400040000000445,
"p95Ms": 4.2662209999998595,
"maxMs": 4.463114000000132
},
"decisionWrite": {
"p50Ms": 0.4946559999998499,
"p95Ms": 0.900854999999865,
"maxMs": 16.506318000000192
},
"smallFill": {
"p50Ms": 43.615020000000186,
"p95Ms": 83.75168699999995,
"maxMs": 103.38539700000001
}
},
{
"seedBlobs": 10000,
"seedAliases": 20000,
"seedDecisions": 10000,
"seedHistory": 10000,
"adminStats": {
"p50Ms": 0.3126299999994444,
"p95Ms": 0.4548949999998513,
"maxMs": 0.6803079999999682
},
"warmSourceHit": {
"p50Ms": 2.6403629999995246,
"p95Ms": 14.954797000000326,
"maxMs": 19.31438099999923
},
"decisionWrite": {
"p50Ms": 0.4195850000005521,
"p95Ms": 0.5379259999999704,
"maxMs": 0.6729980000000069
},
"smallFill": {
"p50Ms": 39.785445999999865,
"p95Ms": 53.938020999999935,
"maxMs": 58.02874900000006
}
},
{
"seedBlobs": 100000,
"seedAliases": 200000,
"seedDecisions": 100000,
"seedHistory": 100000,
"adminStats": {
"p50Ms": 0.3238130000027013,
"p95Ms": 0.4950620000017807,
"maxMs": 0.649769000003289
},
"warmSourceHit": {
"p50Ms": 2.7466110000023036,
"p95Ms": 6.374782999999297,
"maxMs": 15.351889999998093
},
"decisionWrite": {
"p50Ms": 0.7600549999988289,
"p95Ms": 1.1067369999946095,
"maxMs": 1.1147449999989476
},
"smallFill": {
"p50Ms": 44.32314000000042,
"p95Ms": 68.3110549999983,
"maxMs": 91.21001899999828
}
}
],
"eviction": {
"requests": 300,
"distinctObjects": 100,
"upstreamFills": 300,
"hitRate": 0,
"elapsedMs": 13436.223987999998,
"p50Ms": 41.134181999994325,
"p95Ms": 57.91661600000225,
"peakAllocated": 3997696,
"final": {
"blobs": 60,
"originalBytes": 3932160,
"storedBytes": 3932160,
"allocatedBytes": 3932160,
"activeReaders": 0,
"decisions": 0,
"reservedBytes": 0,
"budgetBytes": 4194304,
"maxObjectBytes": 65536,
"oldestAccess": 1793617555801
}
},
"assertions": "passed"
}
-108
View File
@@ -1,108 +0,0 @@
{
"measuredAt": "2026-11-02T10:42:36.314Z",
"scope": "Synthetic metadata-only scale probe. Dummy blob rows have no payload files; excludes directory scanning and disk reads for those rows. Real reads use one verified blob. Eviction probe uses real 64 KiB files.",
"node": "v24.14.1",
"cardinality": [
{
"seedBlobs": 0,
"seedAliases": 0,
"seedDecisions": 0,
"seedHistory": 0,
"adminStats": {
"p50Ms": 0.5495260000002418,
"p95Ms": 0.8194220000000314,
"maxMs": 0.8764419999997699
},
"warmSourceHit": {
"p50Ms": 22.106009000000086,
"p95Ms": 32.67359299999998,
"maxMs": 38.92065599999978
},
"decisionWrite": {
"p50Ms": 6.600801999999931,
"p95Ms": 12.472383000000264,
"maxMs": 15.8033989999999
},
"smallFill": {
"p50Ms": 71.12627699999939,
"p95Ms": 98.24754799999982,
"maxMs": 100.35789100000011
}
},
{
"seedBlobs": 10000,
"seedAliases": 20000,
"seedDecisions": 10000,
"seedHistory": 10000,
"adminStats": {
"p50Ms": 6.904306000000361,
"p95Ms": 9.677666999999929,
"maxMs": 12.638716999999815
},
"warmSourceHit": {
"p50Ms": 22.83701199999996,
"p95Ms": 47.79449499999828,
"maxMs": 102.36107600000014
},
"decisionWrite": {
"p50Ms": 8.062501999999768,
"p95Ms": 13.995523999999932,
"maxMs": 14.909832000001188
},
"smallFill": {
"p50Ms": 75.03789299999971,
"p95Ms": 117.16210499999943,
"maxMs": 143.80038499999955
}
},
{
"seedBlobs": 100000,
"seedAliases": 200000,
"seedDecisions": 100000,
"seedHistory": 100000,
"adminStats": {
"p50Ms": 66.83604900000137,
"p95Ms": 78.69937500000015,
"maxMs": 87.98320400000011
},
"warmSourceHit": {
"p50Ms": 26.310491000000184,
"p95Ms": 82.98941600000035,
"maxMs": 112.1549300000006
},
"decisionWrite": {
"p50Ms": 22.948387000000366,
"p95Ms": 45.47333599999911,
"maxMs": 56.50094800000079
},
"smallFill": {
"p50Ms": 136.3280279999999,
"p95Ms": 214.8330500000011,
"maxMs": 312.4176129999978
}
}
],
"eviction": {
"requests": 300,
"distinctObjects": 100,
"upstreamFills": 300,
"hitRate": 0,
"elapsedMs": 24658.882357999995,
"p50Ms": 74.17433299999539,
"p95Ms": 107.83662400000321,
"peakAllocated": 3997696,
"final": {
"blobs": 60,
"originalBytes": 3932160,
"storedBytes": 3932160,
"allocatedBytes": 3932160,
"oldestAccess": 1793616151486,
"budgetBytes": 4194304,
"reservedBytes": 0,
"maxObjectBytes": 65536,
"activeReaders": 0,
"decisions": 0
}
},
"assertions": "passed"
}
-497
View File
@@ -1,497 +0,0 @@
{
"measuredAt": "2026-11-02T10:48:59.787Z",
"environment": {
"execution": "compiled JavaScript",
"node": "v24.14.1",
"platform": "linux",
"arch": "x64",
"cpu": "Intel Xeon E312xx (Sandy Bridge, IBRS update)",
"logicalCpus": 12,
"totalMemoryGiB": 35.22977066040039,
"loadAverage": [
4.84,
4.64,
4.01
]
},
"methodology": {
"scope": "Local loopback HTTP, deterministic synthetic content, isolated process per scenario; no production traffic",
"upstreamDelayMs": 20,
"rawSourceBaseline": "Raw local file; anonymization and gzip repeated every request; no transformed-result cache. Not a complete legacy deployment comparison.",
"measurements": "Cold request then 3 warmups, 2 batches each at concurrency 1 (16 requests) and 8 (32 requests). CPU and RSS include server, loopback client, hashing and Node runtime; worker-thread CPU included. First cold request also includes worker startup when applicable.",
"limitations": "Warm OS page cache; no TLS, proxy, GitHub bandwidth, database auth or real-world RTT. Small sample p95 is descriptive, not a capacity SLA. Fresh metadata indexes; not a full-size index test.",
"legacyComparison": "Existing large-file transformedFile cache enabled, isolated TMPDIR, local source file, no Redis coordination or database authorization."
},
"scenarios": [
{
"size": 2097152,
"changed": false,
"mode": "legacy-transform",
"encoding": "gzip",
"binary": false,
"cold": {
"ms": 3637.6617370000004,
"ttfb": 3425.310189,
"bytes": 799931,
"wallMs": 3672.4500099999996,
"cpuMs": 4809.962
},
"batches": [
{
"concurrency": 1,
"repeat": 0,
"requests": 16,
"elapsedMs": 4325.215086,
"requestsPerSecond": 3.6992379990047968,
"p50Ms": 259.56264199999896,
"p95Ms": 326.0954209999991,
"maxMs": 326.0954209999991,
"ttfbP95Ms": 100.11303399999997,
"cpuMs": 7363.262,
"cpuMsPerRequest": 460.203875,
"peakRssMiB": 269.8828125,
"eventLoopP95Ms": 11.419647,
"wireBytes": 12798896,
"latenciesMs": [
271.195162,
326.0954209999991,
325.21412000000055,
268.0655480000005,
254.55466100000012,
262.5113799999999,
254.16493199999968,
256.40336600000046,
270.1401279999991,
254.43623800000023,
300.7399460000015,
260.95136600000114,
253.6488389999995,
259.56264199999896,
254.29303199999958,
246.70169999999962
]
},
{
"concurrency": 1,
"repeat": 1,
"requests": 16,
"elapsedMs": 4144.421229,
"requestsPerSecond": 3.8606114378630894,
"p50Ms": 257.6290540000009,
"p95Ms": 284.40178000000014,
"maxMs": 284.40178000000014,
"ttfbP95Ms": 99.43109800000093,
"cpuMs": 7050.23,
"cpuMsPerRequest": 440.639375,
"peakRssMiB": 270.28515625,
"eventLoopP95Ms": 11.386879,
"wireBytes": 12798896,
"latenciesMs": [
255.06874499999867,
245.9648269999998,
263.4925280000007,
240.60825599999953,
265.1590239999987,
264.77967999999964,
243.70354000000043,
269.05731899999955,
257.6290540000009,
255.93209899999965,
264.11589300000014,
276.2427490000009,
257.6929230000005,
284.40178000000014,
255.67801800000052,
239.9359570000015
]
},
{
"concurrency": 8,
"repeat": 0,
"requests": 32,
"elapsedMs": 2457.372474,
"requestsPerSecond": 13.022038921072411,
"p50Ms": 627.6892860000007,
"p95Ms": 675.1984000000011,
"maxMs": 676.5109240000002,
"ttfbP95Ms": 362.1158489999998,
"cpuMs": 9279.507,
"cpuMsPerRequest": 289.98459375,
"peakRssMiB": 257.109375,
"eventLoopP95Ms": 14.229503,
"wireBytes": 25597792,
"latenciesMs": [
638.7337129999996,
644.2589910000006,
653.9658299999992,
660.062434999998,
653.491895000001,
667.5545409999995,
675.1984000000011,
676.5109240000002,
518.4652159999969,
508.24069299999974,
529.0743550000007,
519.2867880000013,
517.0170130000006,
535.1560919999974,
534.1093989999972,
539.1497820000004,
619.183696,
632.2522540000027,
627.6892860000007,
645.0611839999983,
651.0646340000021,
634.4353510000001,
625.0313089999981,
635.0648269999983,
653.2990800000007,
641.2500100000034,
620.1606979999997,
619.6126290000029,
621.0784349999994,
635.4328630000018,
609.3666200000007,
604.615855
]
},
{
"concurrency": 8,
"repeat": 1,
"requests": 32,
"elapsedMs": 2332.6840220000013,
"requestsPerSecond": 13.718103137073737,
"p50Ms": 591.6440280000024,
"p95Ms": 635.7273109999987,
"maxMs": 641.7715180000014,
"ttfbP95Ms": 325.4839319999992,
"cpuMs": 8533.531,
"cpuMsPerRequest": 266.67284375,
"peakRssMiB": 274.7109375,
"eventLoopP95Ms": 14.196735,
"wireBytes": 25597792,
"latenciesMs": [
597.2558779999999,
606.2361659999988,
615.4332890000005,
619.6175079999994,
623.0158960000008,
626.0080349999989,
626.6588200000006,
626.664377000001,
502.19446700000117,
489.31906900000104,
500.0022510000017,
517.2302520000012,
523.0413550000012,
527.0690040000009,
527.3038940000006,
531.9332829999985,
578.1515760000002,
617.7991489999986,
576.213284999998,
635.7273109999987,
591.6440280000024,
641.7715180000014,
588.0470649999988,
617.4792789999992,
592.6901799999978,
596.9206689999992,
576.3831680000003,
576.4393070000006,
568.0410200000006,
592.3916649999992,
599.7627519999987,
584.0048639999986
]
}
],
"upstreamFills": 1,
"source": null,
"results": null,
"metrics": [
{
"name": "inspected_unchanged",
"traffic": "background",
"count": 100,
"bytes": 209715200,
"ms": 4554.967507000029,
"b0": 0,
"b1": 0,
"b2": 32,
"b3": 67,
"b4": 0,
"b5": 1,
"b6": 0
},
{
"name": "legacy_result_hit",
"traffic": "background",
"count": 99,
"bytes": 0,
"ms": 0,
"b0": 99,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "legacy_result_miss",
"traffic": "background",
"count": 1,
"bytes": 0,
"ms": 0,
"b0": 1,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
}
],
"wireBytesPerResponse": 799931,
"uncompressedBytes": 2097152,
"verifiedResponses": 100
},
{
"size": 2097152,
"changed": true,
"mode": "legacy-transform",
"encoding": "gzip",
"binary": false,
"cold": {
"ms": 2903.1252619999996,
"ttfb": 2710.632705,
"bytes": 799783,
"wallMs": 2938.953792,
"cpuMs": 3776.116
},
"batches": [
{
"concurrency": 1,
"repeat": 0,
"requests": 16,
"elapsedMs": 4094.466308000001,
"requestsPerSecond": 3.9077131905416564,
"p50Ms": 250.63896699999987,
"p95Ms": 328.3509729999996,
"maxMs": 328.3509729999996,
"ttfbP95Ms": 139.6392849999993,
"cpuMs": 5652.677,
"cpuMsPerRequest": 353.2923125,
"peakRssMiB": 335.65234375,
"eventLoopP95Ms": 11.599871,
"wireBytes": 12796528,
"latenciesMs": [
274.80920400000014,
250.63896699999987,
328.3509729999996,
261.9780500000006,
224.44531199999983,
288.4866490000004,
260.0317410000007,
265.3711569999996,
220.20456599999852,
236.22282199999972,
256.1077639999985,
283.01616699999977,
235.04550499999823,
246.0147109999998,
221.8804140000011,
237.00552799999969
]
},
{
"concurrency": 1,
"repeat": 1,
"requests": 16,
"elapsedMs": 3646.5368070000004,
"requestsPerSecond": 4.387724804884987,
"p50Ms": 218.23768400000154,
"p95Ms": 286.0494230000004,
"maxMs": 286.0494230000004,
"ttfbP95Ms": 137.9453310000008,
"cpuMs": 5961.413,
"cpuMsPerRequest": 372.5883125,
"peakRssMiB": 336.703125,
"eventLoopP95Ms": 13.033471,
"wireBytes": 12796528,
"latenciesMs": [
203.70693100000062,
211.7173640000001,
252.71899200000007,
286.0494230000004,
211.1080320000001,
274.02094000000034,
212.45707100000072,
181.00006699999904,
206.20519800000147,
234.44334700000036,
247.34129100000064,
221.80664199999956,
194.29920999999922,
232.47182499999872,
218.23768400000154,
254.94733899999846
]
},
{
"concurrency": 8,
"repeat": 0,
"requests": 32,
"elapsedMs": 2860.0330489999997,
"requestsPerSecond": 11.188681896941256,
"p50Ms": 674.8104320000002,
"p95Ms": 937.1299240000008,
"maxMs": 956.4731780000002,
"ttfbP95Ms": 349.0807139999997,
"cpuMs": 8294.185,
"cpuMsPerRequest": 259.19328125,
"peakRssMiB": 306.8125,
"eventLoopP95Ms": 17.088511,
"wireBytes": 25593056,
"latenciesMs": [
838.9425090000004,
836.6894339999999,
840.9874359999994,
914.8391210000009,
929.2231830000001,
932.435884999999,
937.1299240000008,
956.4731780000002,
609.6644930000002,
624.2318410000007,
635.5053589999989,
565.2249200000006,
555.5831100000014,
544.6693990000003,
574.0213219999987,
577.7646199999999,
662.6372410000004,
674.8104320000002,
673.4079709999987,
651.1047060000001,
651.0912209999988,
671.7599570000002,
700.2244200000005,
673.7866299999987,
716.5474200000026,
688.9938089999996,
682.0821550000001,
701.2659249999997,
688.1068889999988,
664.9055890000018,
677.3972719999983,
690.5366940000022
]
},
{
"concurrency": 8,
"repeat": 1,
"requests": 32,
"elapsedMs": 2979.963332000003,
"requestsPerSecond": 10.738387166167978,
"p50Ms": 715.5634050000008,
"p95Ms": 839.5880639999996,
"maxMs": 839.9379399999998,
"ttfbP95Ms": 469.71283799999946,
"cpuMs": 7910.049,
"cpuMsPerRequest": 247.18903125,
"peakRssMiB": 308.02734375,
"eventLoopP95Ms": 19.464191,
"wireBytes": 25593056,
"latenciesMs": [
675.5995089999997,
677.2758930000018,
682.5500949999987,
695.3682000000008,
695.637243000001,
707.3496059999998,
708.2368339999994,
715.5634050000008,
622.8828360000007,
622.234027999999,
640.7814050000015,
628.4150020000016,
638.2158200000013,
632.4952449999982,
640.3528240000014,
655.5803180000003,
819.2730220000012,
839.5432240000009,
815.6718389999987,
823.1129099999998,
807.2870309999998,
839.9379399999998,
839.5880639999996,
822.5235159999975,
800.3038809999998,
836.5671479999983,
794.8266949999997,
782.2958900000012,
780.0966379999991,
797.6877550000027,
833.3983110000008,
831.9884109999985
]
}
],
"upstreamFills": 1,
"source": null,
"results": null,
"metrics": [
{
"name": "inspected_changed",
"traffic": "background",
"count": 100,
"bytes": 209715200,
"ms": 4202.822877000019,
"b0": 0,
"b1": 0,
"b2": 34,
"b3": 65,
"b4": 0,
"b5": 1,
"b6": 0
},
{
"name": "legacy_result_hit",
"traffic": "background",
"count": 99,
"bytes": 0,
"ms": 0,
"b0": 99,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "legacy_result_miss",
"traffic": "background",
"count": 1,
"bytes": 0,
"ms": 0,
"b0": 1,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
}
],
"wireBytesPerResponse": 799783,
"uncompressedBytes": 2115777,
"verifiedResponses": 100
}
],
"completedAt": "2026-11-02T10:49:41.535Z",
"assertions": "All 200 response bodies verified against expected output or the validated wire digest"
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,51 +0,0 @@
{
"scope": "Compiled local loopback changed-text gzip responses; repeated optimized measurements ran after all other task benchmarks/tests ended. The full optimized run overlapped the metadata benchmark during its early scenarios. Shared host, synthetic workload; no production capacity claim.",
"verifiedOptimizedResponses": 2500,
"rows": [
{
"bytes": 16384,
"before": {
"serialP50Ms": 44.88915300000008,
"concurrency8P95Ms": 462.1094459999995
},
"optimized": {
"serialP50Ms": 8.908395999999811,
"concurrency8P95Ms": 111.85025099999984
},
"repeat": {
"serialP50Ms": 7.257277000000158,
"concurrency8P95Ms": 114.94514900000013
}
},
{
"bytes": 262144,
"before": {
"serialP50Ms": 45.89932399999998,
"concurrency8P95Ms": 444.0948499999995
},
"optimized": {
"serialP50Ms": 9.481284999999843,
"concurrency8P95Ms": 136.36852099999942
},
"repeat": {
"serialP50Ms": 9.021533999999974,
"concurrency8P95Ms": 107.61299899999995
}
},
{
"bytes": 2097152,
"before": {
"serialP50Ms": 60.49789199999941,
"concurrency8P95Ms": 528.2149260000006
},
"optimized": {
"serialP50Ms": 24.991613999999572,
"concurrency8P95Ms": 177.60703299999932
},
"repeat": {
"serialP50Ms": 19.012864000000263,
"concurrency8P95Ms": 216.19974999999977
}
}
]
}
-11
View File
@@ -1,11 +0,0 @@
<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Shared cache performance evaluation</title><style>
body{font-family:var(--font-sans,system-ui);color:var(--foreground,#17212b);line-height:1.5;margin:0}h2{font-size:20px;margin:0 0 8px}p{margin:8px 0 16px}.muted,small{color:var(--muted-foreground,#52606d)}label{font-weight:600}select{font:inherit;background:var(--card,#fff);color:inherit;border:1px solid var(--border,#aaa);border-radius:6px;padding:8px;margin:8px 0;width:100%;max-width:420px}table{border-collapse:collapse;width:100%;font-variant-numeric:tabular-nums}th,td{text-align:right;padding:12px 8px;border-bottom:1px solid var(--border,#ddd)}th:first-child,td:first-child{text-align:left;padding-left:0}thead th{font-size:12px;font-weight:600}tbody th{font-size:14px;font-weight:500}td{font-weight:650}small{display:block;font-size:11px;font-weight:400}.bad{color:var(--destructive,#ad2222)}.good{color:var(--success,#237044)}ul{padding-left:20px}li{margin:8px 0}.note{font-size:13px}a{color:var(--accent-foreground,#2563a5)}@media(max-width:420px){th,td{padding:10px 4px;font-size:13px}thead th{font-size:11px}h2{font-size:18px}}</style>
<h2>Keep shared-cache rollout disabled for now</h2>
<p>The current cache slows small and medium warm reads. Large files benefit. A benchmark-only SQLite change identifies a substantial part of the overhead.</p>
<label for="metric">Compare warm changed-text responses</label><br><select id="metric"><option value="serial:p50Ms">Median latency · 1 client · milliseconds</option><option value="concurrent8:p95Ms">p95 latency · 8 clients · milliseconds</option><option value="concurrent8:requestsPerSecond">Throughput · 8 clients · requests/second</option><option value="concurrent8:cpuMsPerRequest">CPU · 8 clients · milliseconds/request</option><option value="concurrent8:peakRssMiB">Peak sampled process memory · MiB</option></select>
<table aria-label="Warm cache benchmark comparison"><thead><tr><th>File / baseline</th><th>Baseline</th><th>Current<br>shared cache</th><th>Experimental<br>SQLite NORMAL</th></tr></thead><tbody id="rows"></tbody></table>
<p class="note muted">16 and 256 KiB: raw source cache with repeated transformation and gzip. 2 MiB: existing transformed-file cache. Two warm batches per setting: 32 serial requests or 64 requests at concurrency eight. CPU and memory include both server and benchmark client.</p>
<ul><li><strong>Correctness:</strong> 2,700 HTTP responses passed byte checks.</li><li><strong>Admin scaling:</strong> one pool statistics query reached 78.7 ms p95 at 100,000 synthetic blob records. It blocks the event loop.</li><li><strong>Eviction:</strong> the 4 MiB limit held, but cycling over a 6.25 MiB working set caused 300 misses in 300 reads.</li><li><strong>Next steps:</strong> reduce metadata commits, cache admin aggregates, and test a representative mixed workload across server processes.</li></ul>
<p class="note">NORMAL was tested only on temporary cache indexes. In WAL mode it can lose recent commits after power loss, so recovery needs validation before adoption. <a href="https://www.sqlite.org/pragma.html#pragma_synchronous">SQLite durability documentation</a>.</p>
<p class="note muted">Local compiled-code evaluation · Node 24.14.1 · 2 November 2026. Shared host, synthetic content, warm filesystem cache, no TLS or production traffic. These measurements are not production capacity guarantees. Application settings remain unchanged.</p>
<script>const data=[{"sizeBytes": 16384, "label": "16 KiB", "baseline": "raw source cache", "baselineMetrics": {"coldMs": 145.9149209999996, "serial": {"p50Ms": 8.318075000000135, "p95Ms": 10.736985999999888, "requestsPerSecond": 117.44442918645578, "cpuMsPerRequest": 11.06740625, "peakRssMiB": 146.50390625}, "concurrent8": {"p50Ms": 50.20099499999969, "p95Ms": 71.84271200000012, "requestsPerSecond": 142.73942902734086, "cpuMsPerRequest": 9.620406249999998, "peakRssMiB": 170.53125}}, "currentMetrics": {"coldMs": 709.8329450000001, "serial": {"p50Ms": 44.88915300000008, "p95Ms": 68.10151200000018, "requestsPerSecond": 20.710932531095363, "cpuMsPerRequest": 13.53253125, "peakRssMiB": 147.47265625}, "concurrent8": {"p50Ms": 362.3927130000002, "p95Ms": 462.1094459999995, "requestsPerSecond": 20.755357071571403, "cpuMsPerRequest": 11.042078125, "peakRssMiB": 154.1328125}}, "experimentalNormalMetrics": {"coldMs": 211.99763500000017, "serial": {"p50Ms": 6.900600000000395, "p95Ms": 9.86466399999972, "requestsPerSecond": 134.73985687410286, "cpuMsPerRequest": 8.7118125, "peakRssMiB": 141.24609375}, "concurrent8": {"p50Ms": 38.635037999999895, "p95Ms": 46.82847199999969, "requestsPerSecond": 197.41218048206238, "cpuMsPerRequest": 6.15025, "peakRssMiB": 147.6875}}, "gzipSavingPercent": 61.97907215871288}, {"sizeBytes": 262144, "label": "256 KiB", "baseline": "raw source cache", "baselineMetrics": {"coldMs": 100.41967600000021, "serial": {"p50Ms": 36.49021599999992, "p95Ms": 53.95963900000015, "requestsPerSecond": 26.236596142201567, "cpuMsPerRequest": 42.746187500000005, "peakRssMiB": 215.1875}, "concurrent8": {"p50Ms": 120.57758600000034, "p95Ms": 250.78685799999994, "requestsPerSecond": 53.7503475211922, "cpuMsPerRequest": 37.485390625, "peakRssMiB": 268.109375}}, "currentMetrics": {"coldMs": 726.5002400000001, "serial": {"p50Ms": 45.89932399999998, "p95Ms": 73.79595599999993, "requestsPerSecond": 20.372770202529612, "cpuMsPerRequest": 16.253875, "peakRssMiB": 160.78515625}, "concurrent8": {"p50Ms": 326.38565800000015, "p95Ms": 444.0948499999995, "requestsPerSecond": 23.057422718927143, "cpuMsPerRequest": 12.072328124999999, "peakRssMiB": 185.7578125}}, "experimentalNormalMetrics": {"coldMs": 293.5069330000001, "serial": {"p50Ms": 11.702889000000141, "p95Ms": 16.41937599999983, "requestsPerSecond": 80.13277379588973, "cpuMsPerRequest": 14.18359375, "peakRssMiB": 160.62890625}, "concurrent8": {"p50Ms": 72.28398599999946, "p95Ms": 89.62643300000036, "requestsPerSecond": 105.43423551997391, "cpuMsPerRequest": 11.027765625, "peakRssMiB": 185.6328125}}, "gzipSavingPercent": 61.890196641851716}, {"sizeBytes": 2097152, "label": "2 MiB", "baseline": "existing transformed cache", "baselineMetrics": {"coldMs": 2903.1252619999996, "serial": {"p50Ms": 236.22282199999972, "p95Ms": 288.4866490000004, "requestsPerSecond": 4.133831174669408, "cpuMsPerRequest": 362.9403125, "peakRssMiB": 336.703125}, "concurrent8": {"p50Ms": 690.5366940000022, "p95Ms": 929.2231830000001, "requestsPerSecond": 10.958910900736049, "cpuMsPerRequest": 253.19115625, "peakRssMiB": 308.02734375}}, "currentMetrics": {"coldMs": 4133.51807, "serial": {"p50Ms": 60.49789199999941, "p95Ms": 100.03485599999931, "requestsPerSecond": 15.358397676800456, "cpuMsPerRequest": 50.210843749999995, "peakRssMiB": 298.671875}, "concurrent8": {"p50Ms": 478.30868799999917, "p95Ms": 528.2149260000006, "requestsPerSecond": 16.483621344424616, "cpuMsPerRequest": 55.64621875, "peakRssMiB": 296.6875}}, "experimentalNormalMetrics": {"coldMs": 3895.830503, "serial": {"p50Ms": 25.95565600000009, "p95Ms": 32.11927700000069, "requestsPerSecond": 40.429950182644916, "cpuMsPerRequest": 43.713375, "peakRssMiB": 307.65625}, "concurrent8": {"p50Ms": 161.56209900000067, "p95Ms": 324.75534399999924, "requestsPerSecond": 43.95237969807028, "cpuMsPerRequest": 39.839515625000004, "peakRssMiB": 307.93359375}}, "gzipSavingPercent": 62.199088089151175}];const select=document.getElementById('metric');function render(){const [group,metric]=select.value.split(':');document.getElementById('rows').innerHTML=data.map(r=>{const values=['baseline','current','experimentalNormal'].map(k=>r[k+'Metrics'][group][metric]);return '<tr><th scope="row">'+r.label+'<small>'+r.baseline+'</small></th>'+values.map((v,i)=>{const ratio=v/values[0];const faster=metric==='requestsPerSecond'?ratio>1:ratio<1;return '<td class="'+(i?(faster?'good':'bad'):'')+'">'+v.toFixed(1)+'</td>'}).join('')+'</tr>'}).join('')}select.addEventListener('change',render);render();</script></html>
File diff suppressed because it is too large. Load diff
-301
View File
@@ -1,301 +0,0 @@
{
"measuredAt": "2026-11-02T10:48:24.664Z",
"runtime": {
"execution": "compiled JavaScript",
"node": "v24.14.1",
"platform": "linux",
"arch": "x64",
"cpu": "Intel Xeon E312xx (Sandy Bridge, IBRS update)",
"logicalCpus": 12,
"totalMemoryGiB": 35.22977066040039,
"loadAverage": [
2.19,
3.52,
3.52
]
},
"scope": "Compiled local loopback HTTP; changed text; 32 serial and 64 concurrency-8 warm samples per scenario. Percentiles pooled across two batches. CPU/RSS include client and server. Experimental NORMAL affects disposable benchmark databases only.",
"verifiedHttpResponses": 2700,
"rows": [
{
"sizeBytes": 16384,
"label": "16 KiB",
"baseline": "raw source cache",
"baselineMetrics": {
"coldMs": 145.9149209999996,
"serial": {
"p50Ms": 8.318075000000135,
"p95Ms": 10.736985999999888,
"requestsPerSecond": 117.44442918645578,
"cpuMsPerRequest": 11.06740625,
"peakRssMiB": 146.50390625
},
"concurrent8": {
"p50Ms": 50.20099499999969,
"p95Ms": 71.84271200000012,
"requestsPerSecond": 142.73942902734086,
"cpuMsPerRequest": 9.620406249999998,
"peakRssMiB": 170.53125
}
},
"currentMetrics": {
"coldMs": 709.8329450000001,
"serial": {
"p50Ms": 44.88915300000008,
"p95Ms": 68.10151200000018,
"requestsPerSecond": 20.710932531095363,
"cpuMsPerRequest": 13.53253125,
"peakRssMiB": 147.47265625
},
"concurrent8": {
"p50Ms": 362.3927130000002,
"p95Ms": 462.1094459999995,
"requestsPerSecond": 20.755357071571403,
"cpuMsPerRequest": 11.042078125,
"peakRssMiB": 154.1328125
}
},
"experimentalNormalMetrics": {
"coldMs": 211.99763500000017,
"serial": {
"p50Ms": 6.900600000000395,
"p95Ms": 9.86466399999972,
"requestsPerSecond": 134.73985687410286,
"cpuMsPerRequest": 8.7118125,
"peakRssMiB": 141.24609375
},
"concurrent8": {
"p50Ms": 38.635037999999895,
"p95Ms": 46.82847199999969,
"requestsPerSecond": 197.41218048206238,
"cpuMsPerRequest": 6.15025,
"peakRssMiB": 147.6875
}
},
"gzipSavingPercent": 61.97907215871288
},
{
"sizeBytes": 262144,
"label": "256 KiB",
"baseline": "raw source cache",
"baselineMetrics": {
"coldMs": 100.41967600000021,
"serial": {
"p50Ms": 36.49021599999992,
"p95Ms": 53.95963900000015,
"requestsPerSecond": 26.236596142201567,
"cpuMsPerRequest": 42.746187500000005,
"peakRssMiB": 215.1875
},
"concurrent8": {
"p50Ms": 120.57758600000034,
"p95Ms": 250.78685799999994,
"requestsPerSecond": 53.7503475211922,
"cpuMsPerRequest": 37.485390625,
"peakRssMiB": 268.109375
}
},
"currentMetrics": {
"coldMs": 726.5002400000001,
"serial": {
"p50Ms": 45.89932399999998,
"p95Ms": 73.79595599999993,
"requestsPerSecond": 20.372770202529612,
"cpuMsPerRequest": 16.253875,
"peakRssMiB": 160.78515625
},
"concurrent8": {
"p50Ms": 326.38565800000015,
"p95Ms": 444.0948499999995,
"requestsPerSecond": 23.057422718927143,
"cpuMsPerRequest": 12.072328124999999,
"peakRssMiB": 185.7578125
}
},
"experimentalNormalMetrics": {
"coldMs": 293.5069330000001,
"serial": {
"p50Ms": 11.702889000000141,
"p95Ms": 16.41937599999983,
"requestsPerSecond": 80.13277379588973,
"cpuMsPerRequest": 14.18359375,
"peakRssMiB": 160.62890625
},
"concurrent8": {
"p50Ms": 72.28398599999946,
"p95Ms": 89.62643300000036,
"requestsPerSecond": 105.43423551997391,
"cpuMsPerRequest": 11.027765625,
"peakRssMiB": 185.6328125
}
},
"gzipSavingPercent": 61.890196641851716
},
{
"sizeBytes": 2097152,
"label": "2 MiB",
"baseline": "existing transformed cache",
"baselineMetrics": {
"coldMs": 2903.1252619999996,
"serial": {
"p50Ms": 236.22282199999972,
"p95Ms": 288.4866490000004,
"requestsPerSecond": 4.133831174669408,
"cpuMsPerRequest": 362.9403125,
"peakRssMiB": 336.703125
},
"concurrent8": {
"p50Ms": 690.5366940000022,
"p95Ms": 929.2231830000001,
"requestsPerSecond": 10.958910900736049,
"cpuMsPerRequest": 253.19115625,
"peakRssMiB": 308.02734375
}
},
"currentMetrics": {
"coldMs": 4133.51807,
"serial": {
"p50Ms": 60.49789199999941,
"p95Ms": 100.03485599999931,
"requestsPerSecond": 15.358397676800456,
"cpuMsPerRequest": 50.210843749999995,
"peakRssMiB": 298.671875
},
"concurrent8": {
"p50Ms": 478.30868799999917,
"p95Ms": 528.2149260000006,
"requestsPerSecond": 16.483621344424616,
"cpuMsPerRequest": 55.64621875,
"peakRssMiB": 296.6875
}
},
"experimentalNormalMetrics": {
"coldMs": 3895.830503,
"serial": {
"p50Ms": 25.95565600000009,
"p95Ms": 32.11927700000069,
"requestsPerSecond": 40.429950182644916,
"cpuMsPerRequest": 43.713375,
"peakRssMiB": 307.65625
},
"concurrent8": {
"p50Ms": 161.56209900000067,
"p95Ms": 324.75534399999924,
"requestsPerSecond": 43.95237969807028,
"cpuMsPerRequest": 39.839515625000004,
"peakRssMiB": 307.93359375
}
},
"gzipSavingPercent": 62.199088089151175
}
],
"indexProbe": {
"measuredAt": "2026-11-02T10:42:36.314Z",
"scope": "Synthetic metadata-only scale probe. Dummy blob rows have no payload files; excludes directory scanning and disk reads for those rows. Real reads use one verified blob. Eviction probe uses real 64 KiB files.",
"node": "v24.14.1",
"cardinality": [
{
"seedBlobs": 0,
"seedAliases": 0,
"seedDecisions": 0,
"seedHistory": 0,
"adminStats": {
"p50Ms": 0.5495260000002418,
"p95Ms": 0.8194220000000314,
"maxMs": 0.8764419999997699
},
"warmSourceHit": {
"p50Ms": 22.106009000000086,
"p95Ms": 32.67359299999998,
"maxMs": 38.92065599999978
},
"decisionWrite": {
"p50Ms": 6.600801999999931,
"p95Ms": 12.472383000000264,
"maxMs": 15.8033989999999
},
"smallFill": {
"p50Ms": 71.12627699999939,
"p95Ms": 98.24754799999982,
"maxMs": 100.35789100000011
}
},
{
"seedBlobs": 10000,
"seedAliases": 20000,
"seedDecisions": 10000,
"seedHistory": 10000,
"adminStats": {
"p50Ms": 6.904306000000361,
"p95Ms": 9.677666999999929,
"maxMs": 12.638716999999815
},
"warmSourceHit": {
"p50Ms": 22.83701199999996,
"p95Ms": 47.79449499999828,
"maxMs": 102.36107600000014
},
"decisionWrite": {
"p50Ms": 8.062501999999768,
"p95Ms": 13.995523999999932,
"maxMs": 14.909832000001188
},
"smallFill": {
"p50Ms": 75.03789299999971,
"p95Ms": 117.16210499999943,
"maxMs": 143.80038499999955
}
},
{
"seedBlobs": 100000,
"seedAliases": 200000,
"seedDecisions": 100000,
"seedHistory": 100000,
"adminStats": {
"p50Ms": 66.83604900000137,
"p95Ms": 78.69937500000015,
"maxMs": 87.98320400000011
},
"warmSourceHit": {
"p50Ms": 26.310491000000184,
"p95Ms": 82.98941600000035,
"maxMs": 112.1549300000006
},
"decisionWrite": {
"p50Ms": 22.948387000000366,
"p95Ms": 45.47333599999911,
"maxMs": 56.50094800000079
},
"smallFill": {
"p50Ms": 136.3280279999999,
"p95Ms": 214.8330500000011,
"maxMs": 312.4176129999978
}
}
],
"eviction": {
"requests": 300,
"distinctObjects": 100,
"upstreamFills": 300,
"hitRate": 0,
"elapsedMs": 24658.882357999995,
"p50Ms": 74.17433299999539,
"p95Ms": 107.83662400000321,
"peakAllocated": 3997696,
"final": {
"blobs": 60,
"originalBytes": 3932160,
"storedBytes": 3932160,
"allocatedBytes": 3932160,
"oldestAccess": 1793616151486,
"budgetBytes": 4194304,
"reservedBytes": 0,
"maxObjectBytes": 65536,
"activeReaders": 0,
"decisions": 0
}
},
"assertions": "passed"
},
"recommendation": "Keep production disabled pending reduction of synchronous metadata commits and retesting. No application settings changed."
}
-455
View File
@@ -1,455 +0,0 @@
{
"node": "v24.14.1",
"measuredAt": "2026-11-02T10:19:09.724Z",
"scope": "Synthetic local acceptance workload; not a production projection",
"sourceRequests": 5,
"upstreamFills": 1,
"source": {
"blobs": 1,
"originalBytes": 19200,
"storedBytes": 139,
"allocatedBytes": 4096,
"oldestAccess": 1793614746536,
"budgetBytes": 1048576,
"reservedBytes": 0,
"maxObjectBytes": 32768,
"activeReaders": 0,
"decisions": 0
},
"results": {
"blobs": 1,
"originalBytes": 19600,
"storedBytes": 140,
"allocatedBytes": 4096,
"oldestAccess": 1793614747125,
"budgetBytes": 1048576,
"reservedBytes": 0,
"maxObjectBytes": 32768,
"activeReaders": 0,
"decisions": 3
},
"eviction": {
"blobs": 13,
"originalBytes": 52000,
"storedBytes": 52000,
"allocatedBytes": 53248,
"oldestAccess": 1793614748735,
"budgetBytes": 65536,
"reservedBytes": 0,
"maxObjectBytes": 4096,
"activeReaders": 0,
"decisions": 0
},
"decisionsReused": 2,
"unchangedDecisionsComputed": 2,
"changedOutputsComputed": 1,
"compressionSavingPercent": 99.27604166666667,
"responses": [
{
"status": 200,
"encoding": "gzip",
"bytes": 139
},
{
"status": 200,
"encoding": "gzip",
"bytes": 139
},
{
"status": 200,
"encoding": "gzip",
"bytes": 139
},
{
"status": 200,
"encoding": "gzip",
"bytes": 140
},
{
"status": 200,
"encoding": "gzip",
"bytes": 140
}
],
"metrics": [
{
"name": "compress",
"traffic": "file",
"count": 2,
"bytes": 38800,
"ms": 11.220815999999104,
"b0": 0,
"b1": 0,
"b2": 2,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "decision_hit",
"traffic": "file",
"count": 2,
"bytes": 38400,
"ms": 0,
"b0": 2,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "decision_miss",
"traffic": "file",
"count": 3,
"bytes": 57600,
"ms": 0,
"b0": 3,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "decompress",
"traffic": "file",
"count": 3,
"bytes": 57600,
"ms": 11.292176999999356,
"b0": 0,
"b1": 0,
"b2": 3,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "inspected_changed",
"traffic": "file",
"count": 1,
"bytes": 19200,
"ms": 7.701614000000518,
"b0": 0,
"b1": 0,
"b2": 1,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "inspected_unchanged",
"traffic": "file",
"count": 2,
"bytes": 38400,
"ms": 8.860568000000058,
"b0": 0,
"b1": 0,
"b2": 2,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "result_fill",
"traffic": "file",
"count": 1,
"bytes": 19600,
"ms": 0,
"b0": 1,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "result_first_seen_miss",
"traffic": "file",
"count": 3,
"bytes": 0,
"ms": 0,
"b0": 3,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "result_miss",
"traffic": "file",
"count": 3,
"bytes": 57600,
"ms": 0,
"b0": 3,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "result_stored",
"traffic": "file",
"count": 1,
"bytes": 140,
"ms": 0,
"b0": 1,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "rewrite_changed",
"traffic": "file",
"count": 1,
"bytes": 19200,
"ms": 49.649725999999646,
"b0": 0,
"b1": 0,
"b2": 0,
"b3": 1,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "rewrite_changed_cached",
"traffic": "file",
"count": 1,
"bytes": 19200,
"ms": 0,
"b0": 1,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "rewrite_unchanged",
"traffic": "file",
"count": 2,
"bytes": 38400,
"ms": 139.08289400000012,
"b0": 0,
"b1": 0,
"b2": 0,
"b3": 2,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "rewrite_unchanged_cached",
"traffic": "file",
"count": 1,
"bytes": 19200,
"ms": 0,
"b0": 1,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "source_fill",
"traffic": "file",
"count": 1,
"bytes": 19200,
"ms": 0,
"b0": 1,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "source_first_seen_miss",
"traffic": "file",
"count": 1,
"bytes": 0,
"ms": 0,
"b0": 1,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "source_hit",
"traffic": "file",
"count": 4,
"bytes": 76800,
"ms": 0,
"b0": 4,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "source_miss",
"traffic": "file",
"count": 1,
"bytes": 19200,
"ms": 0,
"b0": 1,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "source_reuse_1d",
"traffic": "file",
"count": 4,
"bytes": 76800,
"ms": 0,
"b0": 4,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "source_reuse_1h",
"traffic": "file",
"count": 4,
"bytes": 76800,
"ms": 0,
"b0": 4,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "source_reuse_7d",
"traffic": "file",
"count": 4,
"bytes": 76800,
"ms": 0,
"b0": 4,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "source_stored",
"traffic": "file",
"count": 1,
"bytes": 139,
"ms": 0,
"b0": 1,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "workload_eviction",
"traffic": "background",
"count": 17,
"bytes": 68000,
"ms": 16365,
"b0": 0,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 17,
"b5": 0,
"b6": 0
},
{
"name": "workload_fill",
"traffic": "background",
"count": 30,
"bytes": 120000,
"ms": 0,
"b0": 30,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "workload_first_seen_miss",
"traffic": "background",
"count": 30,
"bytes": 0,
"ms": 0,
"b0": 30,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "workload_miss",
"traffic": "background",
"count": 30,
"bytes": 0,
"ms": 0,
"b0": 30,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
},
{
"name": "workload_stored",
"traffic": "background",
"count": 30,
"bytes": 120000,
"ms": 0,
"b0": 30,
"b1": 0,
"b2": 0,
"b3": 0,
"b4": 0,
"b5": 0,
"b6": 0
}
],
"assertions": "passed"
}
+2 -278
View File
@@ -128,281 +128,5 @@ clients can wait for the actual store reset.
## Shared blob cache
The optional shared filesystem cache stores immutable source content across projects.
It uses Node's built-in SQLite module and requires Node 22.15 or newer. All API and
streamer processes on one host must mount the same local cache directory. SQLite
WAL is not a distributed lock service: do not put this directory on NFS or share
it across hosts. S3 storage continues to use the existing storage implementation.
Enable the integrated implementation with `SHARED_CACHE_ENABLED=true`. The default
is false so a code deployment does not silently migrate production data. The other
settings are independent rollback controls:
- `SHARED_CACHE_PATH`: defaults to `.shared-cache-v1` inside `FOLDER`.
- `SHARED_CACHE_BYTES`: source pool budget, default 10 GiB.
- `SHARED_CACHE_RESULT_BYTES`: transformed output budget, default 512 MiB.
- `SHARED_CACHE_MAX_FILE_BYTES`: maximum source admission size, default 32 MiB.
- `SHARED_CACHE_FREE_BYTES`: free-device-space reserve, default 1 GiB.
- `SHARED_CACHE_LOW_WATER`: eviction target fraction, default 0.9.
- `SHARED_CACHE_GZIP`: store gzip when beneficial, default true.
- `SHARED_CACHE_GZIP_MIN_SAVING`: minimum fractional saving, default 0.10.
- `SHARED_CACHE_DECISIONS`: enable unchanged decisions and changed-output reuse,
default true.
- `CACHE_MONITORING_ENABLED`: collect cache observations, default true. Monitoring
is independent of shared storage and also records legacy cache/inspection events.
These are initial limits, not capacity recommendations from the production sample.
Restart processes together when changing pool limits. Reserve headroom for SQLite,
filesystem directories, existing caches and other application temporary files.
The budget accounts for blob allocation with a conservative 4 KiB minimum plus
in-flight reservations of three times the maximum object size and a small overhead.
Keep each pool budget larger than this reservation. Admission can bypass the cache
while other fills/readers occupy the budget. Oversized source objects are streamed
without admission, still subject to the application's file-size limit.
Git blobs are verified against the requested Git SHA before publication. Resolved
LFS content is admitted only after verifying both the Git pointer and its SHA-256
payload digest and size. Gzip is a representation of the same uncompressed content.
Files are written privately, synced and atomically renamed before index publication.
Fills and readers have renewable leases. Eviction skips readers, and stale fills
are recovered before their reservations are released. LRU access timestamps are
coarsened to 30 seconds. Orphan reconciliation runs at most hourly across processes.
Alias and reuse-history indexes are bounded; decisions expire after seven days and
are capped at 100,000 entries.
Existing generation caches are read as a migration fallback and verified before
promotion. They are not deleted during promotion, so rollback remains possible.
The new pool budget does not include legacy copies: monitor total filesystem usage
and retire legacy data only after validating rollout. Uploaded ZIP originals and
other durable source material are untouched. Repository removal invalidates access
and project mappings, while shared blobs remain subject to global eviction.
The HTTP fast path applies to individual file responses backed by the shared source
pool, including small text files. Decisions include content identity, path, project
context, all transformation options, generation/revision, mask, hostname and engine
version. Unchanged decisions serve the original representation. Changed output has
its own bounded pool. Gzip negotiation honors quality values, uses `Vary`, and
preserves MIME type. File validators are weak when shared delivery is enabled so
identity and gzip representations can revalidate the same logical content. HEAD
continues to use metadata only. Range requests receive a complete response; this
implementation does not add random access into gzip files. Website rendering keeps
its renderer cache; ZIP scans do not admit bulk entries into the source blob pool.
The admin overview's Cache section reads `/api/admin/cache?minutes=60` behind the
existing admin authorization. Windows are 15 minutes, one hour, 24 hours and seven
days. SQLite telemetry batches at most 512 groups every five seconds and retains
minute aggregates for 48 hours and hourly aggregates for 30 days. No paths, terms,
user IDs or blob hashes appear in metric labels. The UI distinguishes unavailable
telemetry, dropped events, disabled storage and empty windows. Rows separate file,
website, ZIP, background and known-monitor traffic. Monitor classification is an
operational heuristic, not a complete bot detector.
Hit counts and byte hit rates measure source lookups; misses classify first observed
keys, known misses and eviction refetches within bounded seven-day history. Reuse
intervals are consecutive blob accesses, not distinct people or browser visits.
Eviction time measures idle age; codec and inspection timings are elapsed work time,
not CPU samples. Cached decisions are separate from actual inspections. Minute and
hour boundaries make the displayed windows approximate.
Run the reproducible acceptance workload with:
```sh
node scripts/cache-workload.js docs/cache-workload-results.json
npx mocha test/shared-blob-cache.test.js test/shared-cache-http.test.js
```
The JSON report asserts cross-project source reuse, unchanged-decision reuse,
changed-output reuse, gzip delivery and budget enforcement. Its compression ratio
comes from synthetic text and must not be presented as a production projection.
Production rollout should compare request/byte hit rates, eviction refetches, p95
latency, upstream errors and free disk over a representative week, with large files,
ZIP work and known monitors visible separately.
### Benefits and costs
| Choice | Benefit | Cost or limit |
| --- | --- | --- |
| Global content identity | Reuses identical files across paths and projects; renames do not duplicate content | Requires verified hashes and authorization on every request; uses digest-prefix directories to avoid one huge directory |
| Byte budget with approximate LRU | Predictable blob footprint and automatic reclamation | Evicted files incur upstream fetches; active readers can temporarily prevent admission |
| Selective gzip | Reduces payload storage and can serve compressed bytes directly | First-fill compression costs work; identity consumers and uncached anonymization require decoding |
| Rule-aware decisions and results | Repeated unchanged reads skip regex inspection; changed output is reusable | More metadata and a separate output budget; settings changes produce new decisions |
| SQLite coordination and telemetry | Cross-process coordination without another service; admin can measure actual reuse and rewrite work | Local-filesystem scope; short synchronous transactions; metadata/headroom are outside blob budgets |
### Rollout sequence
1. Deploy with shared storage disabled and collect legacy hits, inspections and codec
timings. Keep known-monitor traffic separate from file readers.
2. Enable the shared cache on the single-host filesystem deployment with both byte
budgets configured. Keep legacy copies for rollback.
3. Compare a representative week of byte/request hits, rewrite decisions, eviction
refetches, request latency, upstream errors and whole-device free space. Adjust
admission size and budgets from those observations.
4. Retire legacy cache copies only after the rollback window. Uploaded originals
remain durable and are never candidates for shared-pool eviction.
The implementation and acceptance workload are local changes; this work does not
enable or migrate production storage.
### Production observations used for this design
The read-only audit on 2026-11-02 found 425,312 source files across 31,522 projects,
containing 144.32 GB of payload. Deduplication within projects could remove 1.87 GB;
global deduplication could remove 2.93 GB total (about 2.03%), including another
1.06 GB across projects. One file could not be read. These are decimal GB and
payload savings, not a compression estimate or the expected new working-set size.
The preceding 24-hour access sample contained 29,980 successful file-body responses,
including 9,249 repeat responses (30.9%). URL repetitions are not the same as blob
cache hits. A separate approximately two-hour metric window recorded 2,302 source
hits out of 3,600 lookups (63.9%). This supports retaining a bounded source cache;
it does not establish the optimal byte budget.
In the uniform anonymization replay, 937 resident files were measured: 764 text,
173 binary, and 21 with changed output. Thirteen resolved files exceeded the 4 MiB
sample limit, and missing files/mappings were excluded. The sample's mean gzip-6
work was 1.46 ms per file and mean decompression was 0.24 ms; these replay timings
are not live-request latency or a forecast for large files. Continuous telemetry is
needed to replace these samples with representative workload measurements.
### Local acceptance evidence
The checked-in `cache-workload-results.json` is generated by assertions, not hand
written. Five file requests across two projects needed one upstream source fill;
unchanged and changed responses reused decisions, and changed bytes occupied one
result blob. The 64 KiB eviction workload ended at 53,248 allocated bytes with no
remaining fill reservations. Compression figures describe this synthetic fixture.
Validation also covers separate operating-system producers, failed/invalid fills,
crash publication recovery, active-reader eviction protection, LFS pointer/payload
verification, gzip negotiation, rule changes, admin authorization and HEAD/304.
The disposable MongoDB/Redis performance integration run passed 130 tests. The
admin Cache section was checked at 1280 px and 390 px, including filter layout,
scrollable tables, empty filters and unavailable telemetry.
The final complete default test run recorded 1,062 passing, 159 pending and one
failure: `test/error-codes.test.js` reports nine missing frontend translations for
the pre-existing maintenance API changes. Cache tests passed in that full run.
TypeScript compilation, the scoped lint checks (existing warnings only), the UI
build and `git diff --check` passed. Production rollout was not performed.
## Initial cache performance evaluation, 2026-11-02
The initial implementation passed functional checks, but its metadata write path regressed small-file performance. The following measurements are preserved as the before-optimization baseline; the implementation and follow-up results below supersede the recommendations in this section. These are local measurements, not production capacity estimates.
### Warm changed-text responses
Each percentile pools two batches. Serial measurements contain 32 requests; concurrency-eight measurements contain 64. The small/medium baseline reads a local raw source and repeats anonymization/gzip. The 2 MiB baseline additionally uses the existing transformed-file cache, with 99 measured cache hits out of 100 requests. Authentication and Redis coordination are outside this comparison.
| File | Baseline median, ms | Current median, ms | Experimental NORMAL median, ms | Baseline p95 at 8 clients, ms | Current p95 at 8 clients, ms | NORMAL p95 at 8 clients, ms |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| 16 KiB | 8.3 | 44.9 | 6.9 | 71.8 | 462.1 | 46.8 |
| 256 KiB | 36.5 | 45.9 | 11.7 | 250.8 | 444.1 | 89.6 |
| 2 MiB | 236.2 | 60.5 | 26.0 | 929.2 | 528.2 | 324.8 |
### Findings
- The cache hit path performs synchronous SQLite writes for reader leases, reuse history and release. The temporary-database A/B changes only pool connections to `PRAGMA synchronous=NORMAL`; the measured reduction supports commit latency as a major bottleneck. This is a diagnostic experiment, not an implemented fix.
- NORMAL in WAL mode can lose recent committed transactions following power loss or a system crash. Validate recovery/accounting before changing the durability of disposable cache indexes. See [SQLite synchronous documentation](https://www.sqlite.org/pragma.html#pragma_synchronous).
- For changed 2 MiB content at eight clients, current shared caching reduced combined client/server CPU from 253.2 to 55.6 ms per request. Peak sampled RSS was 308.0 versus 296.7 MiB. These are whole-process values, including runtime, HTTP client and worker threads.
- Synthetic text transferred about 62.2% fewer bytes with gzip. Incompressible 256 KiB binary data was stored and served as identity, avoiding ineffective gzip storage. These compression ratios are not production projections.
- Cold responses include first-use initialization. For 16 KiB changed text they took 145.9 ms in the baseline, 709.8 ms currently, and 212.0 ms in the NORMAL experiment. Do not use this single cold sample as steady-state miss latency.
- At 100,000 synthetic blob rows, a single pool stats query took 78.7 ms p95 synchronously; saving a decision took 45.5 ms p95. Cache/materialize admin aggregates and reduce retention scans on request paths. Dummy blob rows have no files, so this measures metadata scaling, not full directory reconciliation.
- A 4 MiB pool cycling sequentially over 100 distinct 64 KiB objects stayed below budget, but all 300 reads fetched again. Its working set was larger than the pool. Reuse, request order and admission policy must inform the budget; LRU alone cannot guarantee hits.
### Reproduce and limitations
Node v24.14.1, Linux, Intel Xeon E312xx (Sandy Bridge, IBRS update), 12 logical CPUs. This is a shared host with background load, warm OS page cache, loopback HTTP and a synthetic 20 ms upstream wait. It excludes real upstream bandwidth, TLS, reverse proxies, database authorization, multi-process throughput and production request distributions. Small samples and fixed scenario order mean p95 and speedup ratios are descriptive. An initial ts-node exploration was superseded by the compiled results below. The metadata probe does no worker execution, and excludes TypeScript loading from its timed samples.
```sh
npx tsc --incremental false
CACHE_BENCH_COMPILED=1 node scripts/cache-performance.js
CACHE_BENCH_COMPILED=1 node scripts/cache-performance.js docs/cache-performance-normal-results.json --normal
CACHE_BENCH_COMPILED=1 node scripts/cache-performance.js docs/cache-performance-legacy-results.json --legacy
node scripts/cache-index-performance.js
```
All 2,700 HTTP responses were checked against expected bytes or a validated wire digest. Eviction assertions passed. Raw latency samples, CPU, RSS, event-loop delay, storage and metric counters are in `cache-performance-results.json`, `cache-performance-normal-results.json`, `cache-performance-legacy-results.json` and `cache-index-performance-results.json`. `cache-performance-summary.json` contains pooled comparisons.
Before rollout: reduce per-read durable commits without weakening active-reader eviction protection; consider NORMAL only for rebuildable indexes with crash/recovery tests; replace full admin stats scans with cached aggregates; then replay a representative mixed workload with several server processes. Application defaults and production were not changed by this evaluation.
## Optimized implementation, 2026-11-02
The pool now uses NORMAL WAL commits for reader leases, coarsened access history,
anonymization decisions and telemetry. Admission reservations, publication intent
before rename, and final blob/alias publication still use FULL commits. Payload
fsync remains in place. Reader leases still become visible immediately to other
processes and protect active streams from eviction. This saves hit-path disk
flushes without making payload accounting depend on disposable history.
Recent disposable metadata and telemetry can be lost after system failure.
Reservation and publication recovery conservatively retain accounting until files
are indexed or removed; missing payload files are invalidated and fetched again.
The SIGKILL test stops an actual producer after rename and before index publication,
then verifies reservation retention, orphan cleanup and successful refill. This
tests process failure, not a physical power cut.
Admin pool statistics now read transactional counters and an indexed oldest-access
lookup. Counters are initialized once for an existing index and maintained by
SQLite triggers in the same transactions as row changes. Decision/history/alias
retention deletes only excess rows through indexes. Expired decision/history
cleanup is limited to 256 rows each per admission or maintenance pass. Access
history persists at most once per key per 30 seconds, so reuse-window boundaries
have that resolution. Metadata remains capped at 100,000 decisions/history rows
and 200,000 aliases per pool.
Stop all cache-using processes together before upgrading an existing index, then
restart them on the same version. Do not mix old and new writers during migration.
The admin API response shape is unchanged. Shared caching remains opt-in with
`SHARED_CACHE_ENABLED=true`; production configuration was not changed.
The tradeoff is intentional: cache hits avoid durable flushes, while cold fills
still pay for durable publication and compression. Trigger updates add a small
amount of write work in exchange for constant-time aggregate reads. Approximate
LRU and fixed budgets can still thrash when the working set exceeds the pool.
### Follow-up measurements and validation
All 2,200 responses in the complete optimized HTTP run and all 300 in the focused
repeat matched expected output. The repeat ran after this task's other tests and
benchmarks ended. The early scenarios in the complete run overlapped the metadata
probe, so both results are retained rather than treating the faster run as a
universal estimate. These remain small synthetic samples on a shared host.
- 16 KiB changed text: warm serial median 44.9 → 7.3 ms; eight-client p95 462.1 → 114.9 ms.
- 256 KiB changed text: warm serial median 45.9 → 9.0 ms; eight-client p95 444.1 → 107.6 ms.
- 2048 KiB changed text: warm serial median 60.5 → 19.0 ms; eight-client p95 528.2 → 216.2 ms.
At 100,000 synthetic blob rows, admin stats p95 fell from 78.70 to 0.50 ms,
decision-write p95 from 45.47 to 1.11 ms, and warm-source-hit p95 from 82.99 to
6.37 ms. The 4 MiB eviction probe peaked at 3,997,696 allocated bytes; all budget
assertions passed. Its oversized sequential working set still produced no hits.
The final full suite recorded 1,065 passing, 159 pending and the existing single
error-code translation failure (nine maintenance API codes). New counter,
durability-mode and SIGKILL recovery tests passed, as did HTTP and admin UI
regressions. TypeScript, scoped lint and diff whitespace checks passed.
Production rollout and representative multi-process throughput testing remain
outside this local evaluation.
Reproduce the follow-up without overwriting the original baselines:
```sh
npx tsc
CACHE_BENCH_COMPILED=1 node scripts/cache-performance.js docs/cache-performance-optimized-results.json
node scripts/cache-index-performance.js docs/cache-index-performance-optimized-results.json
CACHE_BENCH_COMPILED=1 node scripts/cache-performance.js docs/cache-performance-optimized-repeat-results.json --shared
```
Raw results and the pooled comparison are in the corresponding
`cache-*-optimized-*.json` files.
### PR integration validation
Reapplied the cache changes onto main at `d0b217a` in an isolated checkout.
The production build and scoped ESLint passed, and 199 cache, HTTP, admin access,
HEAD/revalidation and Vue UI tests passed. Integration also covers public
submodule origin selection and withholding parent credentials. The historical
full-workspace and benchmark measurements above were not rerun as part of this
PR preparation.
See [Shared blob cache](shared-cache.md) for configuration, storage and quota
accounting, admin metrics, production observations, rollout and benchmark commands.
+188
View File
@@ -0,0 +1,188 @@
# Shared blob cache
The optional shared filesystem cache stores immutable source content across projects.
It uses Node's built-in SQLite module and requires Node 22.15 or newer. All API and
streamer processes on one host must mount the same local cache directory. SQLite
WAL is not a distributed lock service: do not put this directory on NFS or share
it across hosts. S3 storage continues to use the existing storage implementation.
Enable the integrated implementation with `SHARED_CACHE_ENABLED=true`. The default
is false so a code deployment does not silently migrate production data. The other
settings are independent rollback controls:
- `SHARED_CACHE_PATH`: defaults to `.shared-cache-v1` inside `FOLDER`.
- `SHARED_CACHE_BYTES`: source pool budget, default 10 GiB.
- `SHARED_CACHE_RESULT_BYTES`: transformed output budget, default 512 MiB.
- `SHARED_CACHE_MAX_FILE_BYTES`: maximum source admission size, default 32 MiB.
- `SHARED_CACHE_FREE_BYTES`: free-device-space reserve, default 1 GiB.
- `SHARED_CACHE_LOW_WATER`: eviction target fraction, default 0.9.
- `SHARED_CACHE_GZIP`: store gzip when beneficial, default true.
- `SHARED_CACHE_GZIP_MIN_SAVING`: minimum fractional saving, default 0.10.
- `SHARED_CACHE_DECISIONS`: enable unchanged decisions and changed-output reuse,
default true.
- `CACHE_MONITORING_ENABLED`: collect cache observations, default true. Monitoring
is independent of shared storage and also records legacy cache/inspection events.
Logical user quotas remain based on repository file sizes. Shared-pool deduplication,
compression and eviction do not change user quota usage.
These are initial limits, not capacity recommendations from the production sample.
Restart processes together when changing pool limits. Reserve headroom for SQLite,
filesystem directories, existing caches and other application temporary files.
The budget accounts for blob allocation with a conservative 4 KiB minimum plus
in-flight reservations of three times the maximum object size and a small overhead.
Keep each pool budget larger than this reservation. Admission can bypass the cache
while other fills/readers occupy the budget. Oversized source objects are streamed
without admission, still subject to the application's file-size limit.
Git blobs are verified against the requested Git SHA before publication. Resolved
LFS content is admitted only after verifying both the Git pointer and its SHA-256
payload digest and size. Gzip is a representation of the same uncompressed content.
Files are written privately, synced and atomically renamed before index publication.
Fills and readers have renewable leases. Eviction skips readers, and stale fills
are recovered before their reservations are released. LRU access timestamps are
coarsened to 30 seconds. Orphan reconciliation runs at most hourly across processes.
Alias and reuse-history indexes are bounded; decisions expire after seven days and
are capped at 100,000 entries.
Existing generation caches are read as a migration fallback and verified before
promotion. They are not deleted during promotion, so rollback remains possible.
The new pool budget does not include legacy copies: monitor total filesystem usage
and retire legacy data only after validating rollout. Uploaded ZIP originals and
other durable source material are untouched. Repository removal invalidates access
and project mappings, while shared blobs remain subject to global eviction.
The HTTP fast path applies to individual file responses backed by the shared source
pool, including small text files. Decisions include content identity, path, project
context, all transformation options, generation/revision, mask, hostname and engine
version. Unchanged decisions serve the original representation. Changed output has
its own bounded pool. Gzip negotiation honors quality values, uses `Vary`, and
preserves MIME type. File validators are weak when shared delivery is enabled so
identity and gzip representations can revalidate the same logical content. HEAD
continues to use metadata only. Range requests receive a complete response; this
implementation does not add random access into gzip files. Website rendering keeps
its renderer cache; ZIP scans do not admit bulk entries into the source blob pool.
The admin overview's Cache section reads `/api/admin/cache?minutes=60` behind the
existing admin authorization. Windows are 15 minutes, one hour, 24 hours and seven
days. SQLite telemetry batches at most 512 groups every five seconds and retains
minute aggregates for 48 hours and hourly aggregates for 30 days. No paths, terms,
user IDs or blob hashes appear in metric labels. The UI distinguishes unavailable
telemetry, dropped events, disabled storage and empty windows. Rows separate file,
website, ZIP, background and known-monitor traffic. Monitor classification is an
operational heuristic, not a complete bot detector.
Hit counts and byte hit rates measure source lookups; misses classify first observed
keys, known misses and eviction refetches within bounded seven-day history. Reuse
intervals are consecutive blob accesses, not distinct people or browser visits.
Eviction time measures idle age; codec and inspection timings are elapsed work time,
not CPU samples. Cached decisions are separate from actual inspections. Minute and
hour boundaries make the displayed windows approximate.
Run the reproducible acceptance workload with:
```sh
mkdir -p benchmark-results/cache
node scripts/cache-workload.js benchmark-results/cache/workload.json
npx mocha test/shared-blob-cache.test.js test/shared-cache-http.test.js
```
The JSON report asserts cross-project source reuse, unchanged-decision reuse,
changed-output reuse, gzip delivery and budget enforcement. Its compression ratio
comes from synthetic text and must not be presented as a production projection.
Production rollout should compare request/byte hit rates, eviction refetches, p95
latency, upstream errors and free disk over a representative week, with large files,
ZIP work and known monitors visible separately.
### Benefits and costs
| Choice | Benefit | Cost or limit |
| --- | --- | --- |
| Global content identity | Reuses identical files across paths and projects; renames do not duplicate content | Requires verified hashes and authorization on every request; uses digest-prefix directories to avoid one huge directory |
| Byte budget with approximate LRU | Predictable blob footprint and automatic reclamation | Evicted files incur upstream fetches; active readers can temporarily prevent admission |
| Selective gzip | Reduces payload storage and can serve compressed bytes directly | First-fill compression costs work; identity consumers and uncached anonymization require decoding |
| Rule-aware decisions and results | Repeated unchanged reads skip regex inspection; changed output is reusable | More metadata and a separate output budget; settings changes produce new decisions |
| SQLite coordination and telemetry | Cross-process coordination without another service; admin can measure actual reuse and rewrite work | Local-filesystem scope; short synchronous transactions; metadata/headroom are outside blob budgets |
### Rollout sequence
1. Deploy with shared storage disabled and collect legacy hits, inspections and codec
timings. Keep known-monitor traffic separate from file readers.
2. Enable the shared cache on the single-host filesystem deployment with both byte
budgets configured. Keep legacy copies for rollback.
3. Compare a representative week of byte/request hits, rewrite decisions, eviction
refetches, request latency, upstream errors and whole-device free space. Adjust
admission size and budgets from those observations.
4. Retire legacy cache copies only after the rollback window. Uploaded originals
remain durable and are never candidates for shared-pool eviction.
The implementation and acceptance workload are local changes; this work does not
enable or migrate production storage.
### Production observations used for this design
The read-only audit on 2026-11-02 found 425,312 source files across 31,522 projects,
containing 144.32 GB of payload. Deduplication within projects could remove 1.87 GB;
global deduplication could remove 2.93 GB total (about 2.03%), including another
1.06 GB across projects. One file could not be read. These are decimal GB and
payload savings, not a compression estimate or the expected new working-set size.
The preceding 24-hour access sample contained 29,980 successful file-body responses,
including 9,249 repeat responses (30.9%). URL repetitions are not the same as blob
cache hits. A separate approximately two-hour metric window recorded 2,302 source
hits out of 3,600 lookups (63.9%). This supports retaining a bounded source cache;
it does not establish the optimal byte budget.
In the uniform anonymization replay, 937 resident files were measured: 764 text,
173 binary, and 21 with changed output. Thirteen resolved files exceeded the 4 MiB
sample limit, and missing files/mappings were excluded. The sample's mean gzip-6
work was 1.46 ms per file and mean decompression was 0.24 ms; these replay timings
are not live-request latency or a forecast for large files. Continuous telemetry is
needed to replace these samples with representative workload measurements.
### Metadata durability and accounting
Reader leases, access history, decisions and telemetry use NORMAL WAL commits.
Reservations, publication intent and final blob publication use FULL commits;
payloads are synced before rename. Recent disposable metadata may be lost after
system failure. Recovery retains reservations until orphan files are removed or
accounted for. SIGKILL recovery is tested; physical power failure is not simulated.
Admin statistics use transactional counters and an indexed oldest-access lookup.
History and decisions are capped at 100,000 rows each, aliases at 200,000. Expired
history/decision cleanup removes at most 256 rows per pass. Access history has
30-second resolution. Stop all cache processes before upgrading an existing index
and restart them on the same version; do not mix old and new writers.
### Performance and validation
Local synthetic measurements on Node 24.14.1, dated 2026-11-02, compared the initial
FULL-commit implementation with optimized bookkeeping. Warm serial changed-text
medians fell from 44.9 to 7.3 ms at 16 KiB, 45.9 to 9.0 ms at 256 KiB, and 60.5 to
19.0 ms at 2 MiB. At 100,000 synthetic blob rows, admin stats p95 fell from 78.7 to
0.50 ms and decision-write p95 from 45.5 to 1.11 ms.
The optimized runs verified 2,500 HTTP responses. The 4 MiB eviction probe peaked
at 3,997,696 allocated bytes. Its oversized sequential working set had no hits:
a bounded LRU cache does not guarantee useful reuse. Measurements used loopback
HTTP, warm OS caches and a shared host; they exclude production traffic, TLS,
authorization overhead and multi-process throughput. They are not capacity estimates.
On the PR checkout based on main at d0b217a, the production build, scoped lint and
199 cache, HTTP, admin access, HEAD/revalidation and Vue UI tests passed. Coverage
includes process crash recovery, concurrent fills, active readers, LFS verification,
and public submodule origins without parent credentials. Admin layout was also
checked at 1280 px and 390 px during implementation.
Generate fresh reports locally; generated output is ignored by Git:
```sh
npx tsc
node scripts/cache-index-performance.js
CACHE_BENCH_COMPILED=1 node scripts/cache-performance.js
CACHE_BENCH_COMPILED=1 node scripts/cache-performance.js benchmark-results/cache/repeat.json --shared
```
Default reports go to `benchmark-results/cache/`. Scripts also accept an explicit
output path. Keep raw measurements with the evaluation that produced them rather
than committing generated reports to the documentation directory.
+4 -2
View File
@@ -2,7 +2,7 @@
const { strict: assert } = require('node:assert');
const { promises: fs } = require('node:fs');
const { tmpdir } = require('node:os');
const { join } = require('node:path');
const { join, dirname } = require('node:path');
const { Readable } = require('node:stream');
const { performance } = require('node:perf_hooks');
require('ts-node/register/transpile-only');
@@ -51,7 +51,9 @@ async function main(){
const report={measuredAt:new Date().toISOString(),scope:'Synthetic metadata-only scale probe. Dummy blob rows have no payload files; excludes directory scanning and disk reads for those rows. Real reads use one verified blob. Eviction probe uses real 64 KiB files.',
node:process.version,cardinality:results,eviction:{requests:300,distinctObjects:100,upstreamFills:upstream,hitRate:1-upstream/300,
elapsedMs:performance.now()-started,p50Ms:percentile(times,.5),p95Ms:percentile(times,.95),peakAllocated,final:cache.stats()},assertions:'passed'};
await fs.writeFile(process.argv[2]||'docs/cache-index-performance-results.json',JSON.stringify(report,null,2)+'\n');
const output = process.argv[2] || 'benchmark-results/cache/index.json';
await fs.mkdir(dirname(output), { recursive: true });
await fs.writeFile(output,JSON.stringify(report,null,2)+'\n');
process.stdout.write(JSON.stringify(report,null,2)+'\n');
}finally{cache?.close();await fs.rm(root,{recursive:true,force:true});}
}
+4 -2
View File
@@ -2,7 +2,7 @@
const { strict: assert } = require('node:assert');
const { promises: fs, createReadStream } = require('node:fs');
const { tmpdir, cpus, totalmem, loadavg } = require('node:os');
const { join } = require('node:path');
const { join, dirname } = require('node:path');
const { createHash } = require('node:crypto');
const { Readable } = require('node:stream');
const { pipeline } = require('node:stream/promises');
@@ -133,7 +133,9 @@ async function main() {
report.completedAt=new Date().toISOString();report.assertions=`All ${scenarios.length*100} response bodies verified against expected output or the validated wire digest`;
if (process.argv.includes('--normal')) report.methodology.experimentalOverride='PRAGMA synchronous=NORMAL in both temporary pool connections only. Application defaults are unchanged. WAL commits may be lost after power failure.';
if (process.argv.includes('--legacy')) report.methodology.legacyComparison='Existing large-file transformedFile cache enabled, isolated TMPDIR, local source file, no Redis coordination or database authorization.';
await fs.writeFile(process.argv[2]||'docs/cache-performance-results.json',JSON.stringify(report,null,2)+'\n');
const output = process.argv[2] || 'benchmark-results/cache/http.json';
await fs.mkdir(dirname(output), { recursive: true });
await fs.writeFile(output,JSON.stringify(report,null,2)+'\n');
process.stdout.write(JSON.stringify({scenarios:report.scenarios.length,assertions:report.assertions})+'\n');
}
if(process.argv[2]==='--worker')worker(JSON.parse(process.argv[3])).then(()=>process.exit(0),e=>{console.error(e);process.exit(1);});