Skip to content

Commit a39c19b

Browse files
committed
feat: move snapstore into SQLite database with the rest of the swingstore
This is phase 1 of #6742. These changes cease storing snapshots in files but instead keep them in a new table in the swingstore SQLite database. However, in this commit, snapshot tracking metadata is still managed the old way using entries in the kvstore, rather than being integrated directly into the snapshots table.
1 parent c680b92 commit a39c19b

13 files changed

+143
-202
lines changed

packages/SwingSet/misc-tools/replay-transcript.js

-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import zlib from 'zlib';
77
import readline from 'readline';
88
import process from 'process';
99
import { spawn } from 'child_process';
10-
import path from 'path';
1110
import { promisify } from 'util';
1211
import { createHash } from 'crypto';
1312
import { pipeline } from 'stream';
@@ -63,11 +62,8 @@ function makeSnapStoreIO() {
6362
return {
6463
createReadStream: fs.createReadStream,
6564
createWriteStream: fs.createWriteStream,
66-
fsync: fs.fsync,
6765
measureSeconds: makeMeasureSeconds(performance.now),
6866
open: fs.promises.open,
69-
rename: fs.promises.rename,
70-
resolve: path.resolve,
7167
stat: fs.promises.stat,
7268
tmpFile,
7369
tmpName,

packages/SwingSet/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
},
2323
"devDependencies": {
2424
"@types/microtime": "^2.1.0",
25+
"better-sqlite3": "^7.5.0",
26+
"@types/better-sqlite3": "^7.5.0",
2527
"@types/tmp": "^0.2.0",
2628
"tmp": "^0.2.1"
2729
},

packages/SwingSet/src/kernel/state/vatKeeper.js

+2-7
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,6 @@ export function makeVatKeeper(
602602
const info = await manager.makeSnapshot(snapStore);
603603
const {
604604
hash: snapshotID,
605-
newFile,
606605
rawByteCount,
607606
rawSaveSeconds,
608607
compressedByteCount,
@@ -611,7 +610,7 @@ export function makeVatKeeper(
611610
const old = getLastSnapshot();
612611
if (old && old.snapshotID !== snapshotID) {
613612
if (removeFromSnapshot(old.snapshotID) === 0) {
614-
snapStore.prepareToDelete(old.snapshotID);
613+
snapStore.deleteSnapshot(old.snapshotID);
615614
}
616615
}
617616
const endPosition = getTranscriptEndPosition();
@@ -624,7 +623,6 @@ export function makeVatKeeper(
624623
type: 'heap-snapshot-save',
625624
vatID,
626625
snapshotID,
627-
newFile,
628626
rawByteCount,
629627
rawSaveSeconds,
630628
compressedByteCount,
@@ -641,14 +639,11 @@ export function makeVatKeeper(
641639
if (notation) {
642640
const { snapshotID } = JSON.parse(notation);
643641
if (removeFromSnapshot(snapshotID) === 0) {
644-
// TODO: if we roll back (because the upgrade failed), we must
645-
// not really delete the snapshot
646-
snapStore.prepareToDelete(snapshotID);
642+
snapStore.deleteSnapshot(snapshotID);
647643
}
648644
kvStore.delete(skey);
649645
}
650646
}
651-
// TODO: same rollback concern
652647

653648
const endPos = getRequired(`${vatID}.t.endPosition`);
654649
kvStore.set(`${vatID}.t.startPosition`, endPos);

packages/SwingSet/test/snapshots/test-xsnap-store.js.md

-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ Generated by [AVA](https://avajs.dev).
1111
{
1212
compressSeconds: 0,
1313
hash: '8a0e3873976c50462d1b1dac59c912152b0e5cad5eeb9deca0ca64a087b4a873',
14-
newFile: true,
1514
rawByteCount: 167887,
1615
rawSaveSeconds: 0,
1716
}
@@ -21,7 +20,6 @@ Generated by [AVA](https://avajs.dev).
2120
{
2221
compressSeconds: 0,
2322
hash: '253ffe0fb0b9e555119f046990d58fb85693e1170e681adb52211f49d94e37d0',
24-
newFile: true,
2523
rawByteCount: 775831,
2624
rawSaveSeconds: 0,
2725
}
@@ -31,7 +29,6 @@ Generated by [AVA](https://avajs.dev).
3129
{
3230
compressSeconds: 0,
3331
hash: '2bca522840c90b8a0519fe846642746f01f62205154877c2332ad8829b69aa3e',
34-
newFile: true,
3532
rawByteCount: 777983,
3633
rawSaveSeconds: 0,
3734
}
Binary file not shown.

packages/SwingSet/test/test-xsnap-metering.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { test } from '../tools/prepare-test-env-ava.js';
44
import { spawn } from 'child_process';
55
import fs from 'fs';
66
import tmp from 'tmp';
7+
import sqlite3 from 'better-sqlite3';
78
import { makePromiseKit } from '@endo/promise-kit';
89
import { makeSnapStore, makeSnapStoreIO } from '@agoric/swing-store';
910

@@ -48,7 +49,8 @@ async function doTest(t, metered) {
4849
const pool = tmp.dirSync({ unsafeCleanup: true });
4950
t.teardown(() => pool.removeCallback());
5051
await fs.promises.mkdir(pool.name, { recursive: true });
51-
const store = makeSnapStore(pool.name, makeSnapStoreIO());
52+
const db = sqlite3(':memory:');
53+
const store = makeSnapStore(db, pool.name, makeSnapStoreIO());
5254

5355
const { p: p1, startXSnap: start1 } = make(store);
5456
let snapshotHash;

packages/SwingSet/test/test-xsnap-store.js

+18-12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import fs from 'fs';
44
import { spawn } from 'child_process';
55
import { type as osType } from 'os';
66
import tmp from 'tmp';
7+
import sqlite3 from 'better-sqlite3';
78
import test from 'ava';
89
import { makeMeasureSeconds } from '@agoric/internal';
910
import { xsnap } from '@agoric/xsnap';
@@ -32,9 +33,9 @@ const ld = (() => {
3233
});
3334
})();
3435

35-
/** @type {(fn: string, fullSize: number) => number} */
36-
const relativeSize = (fn, fullSize) =>
37-
Math.round((fs.statSync(fn).size / 1024 / fullSize) * 10) / 10;
36+
/** @type {(compressedSize: number, fullSize: number) => number} */
37+
const relativeSize = (compressedSize, fullSize) =>
38+
Math.round((compressedSize / 1024 / fullSize) * 10) / 10;
3839

3940
const snapSize = {
4041
raw: 417,
@@ -80,14 +81,15 @@ test(`create XS Machine, snapshot (${snapSize.raw} Kb), compress to smaller`, as
8081
t.teardown(() => pool.removeCallback());
8182
await fs.promises.mkdir(pool.name, { recursive: true });
8283

83-
const store = makeSnapStore(pool.name, makeMockSnapStoreIO());
84+
const db = sqlite3(':memory:');
85+
const store = makeSnapStore(db, pool.name, makeMockSnapStoreIO());
8486

85-
const { filePath: zfile } = await store.save(async snapFile => {
87+
const { compressedByteCount } = await store.save(async snapFile => {
8688
await vat.snapshot(snapFile);
8789
});
8890

8991
t.true(
90-
relativeSize(zfile, snapSize.raw) < 0.5,
92+
relativeSize(compressedByteCount, snapSize.raw) < 0.5,
9193
'compressed snapshots are smaller',
9294
);
9395
});
@@ -99,16 +101,17 @@ test('SES bootstrap, save, compress', async t => {
99101
const pool = tmp.dirSync({ unsafeCleanup: true });
100102
t.teardown(() => pool.removeCallback());
101103

102-
const store = makeSnapStore(pool.name, makeMockSnapStoreIO());
104+
const db = sqlite3(':memory:');
105+
const store = makeSnapStore(db, pool.name, makeMockSnapStoreIO());
103106

104107
await vat.evaluate('globalThis.x = harden({a: 1})');
105108

106-
const { filePath: zfile } = await store.save(async snapFile => {
109+
const { compressedByteCount } = await store.save(async snapFile => {
107110
await vat.snapshot(snapFile);
108111
});
109112

110113
t.true(
111-
relativeSize(zfile, snapSize.SESboot) < 0.5,
114+
relativeSize(compressedByteCount, snapSize.SESboot) < 0.5,
112115
'compressed snapshots are smaller',
113116
);
114117
});
@@ -117,7 +120,8 @@ test('create SES worker, save, restore, resume', async t => {
117120
const pool = tmp.dirSync({ unsafeCleanup: true });
118121
t.teardown(() => pool.removeCallback());
119122

120-
const store = makeSnapStore(pool.name, makeMockSnapStoreIO());
123+
const db = sqlite3(':memory:');
124+
const store = makeSnapStore(db, pool.name, makeMockSnapStoreIO());
121125

122126
const vat0 = await bootSESWorker('ses-boot2', async m => m);
123127
t.teardown(() => vat0.close());
@@ -146,7 +150,8 @@ test('XS + SES snapshots are long-term deterministic', async t => {
146150
t.teardown(() => pool.removeCallback());
147151
t.log({ pool: pool.name });
148152
await fs.promises.mkdir(pool.name, { recursive: true });
149-
const store = makeSnapStore(pool.name, makeMockSnapStoreIO());
153+
const db = sqlite3(':memory:');
154+
const store = makeSnapStore(db, pool.name, makeMockSnapStoreIO());
150155

151156
const vat = await bootWorker('xs1', async m => m, '1 + 1');
152157
t.teardown(() => vat.close());
@@ -197,7 +202,8 @@ async function makeTestSnapshot(t) {
197202
t.teardown(() => pool.removeCallback());
198203
// t.log({ pool: pool.name });
199204
await fs.promises.mkdir(pool.name, { recursive: true });
200-
const store = makeSnapStore(pool.name, makeMockSnapStoreIO());
205+
const db = sqlite3(':memory:');
206+
const store = makeSnapStore(db, pool.name, makeMockSnapStoreIO());
201207
const vat = await bootWorker('xs1', async m => m, '1 + 1');
202208
const bootScript = await ld.asset(
203209
'@agoric/xsnap/dist/bundle-ses-boot.umd.js',

packages/SwingSet/test/vat-warehouse/test-preload.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { test } from '../../tools/prepare-test-env-ava.js';
33

44
// import * as proc from 'child_process';
55
import tmp from 'tmp';
6+
import sqlite3 from 'better-sqlite3';
67
import {
78
initSwingStore,
89
makeSnapStore,
@@ -34,7 +35,8 @@ test('only preload maxVatsOnline vats', async t => {
3435
const argv = [];
3536

3637
const snapstorePath = tmp.dirSync({ unsafeCleanup: true }).name;
37-
const snapStore = makeSnapStore(snapstorePath, makeSnapStoreIO());
38+
const db = sqlite3(':memory:');
39+
const snapStore = makeSnapStore(db, snapstorePath, makeSnapStoreIO());
3840
const kernelStorage = { ...initSwingStore().kernelStorage, snapStore };
3941

4042
await initializeSwingset(config, argv, kernelStorage, initOpts);

packages/SwingSet/test/vat-warehouse/test-reload-snapshot.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { test } from '../../tools/prepare-test-env-ava.js';
33

44
import tmp from 'tmp';
5+
import sqlite3 from 'better-sqlite3';
56
import {
67
initSwingStore,
78
makeSnapStore,
@@ -25,7 +26,8 @@ test('vat reload from snapshot', async t => {
2526

2627
const snapstorePath = tmp.dirSync({ unsafeCleanup: true }).name;
2728

28-
const snapStore = makeSnapStore(snapstorePath, makeSnapStoreIO());
29+
const db = sqlite3(':memory:');
30+
const snapStore = makeSnapStore(db, snapstorePath, makeSnapStoreIO());
2931
const kernelStorage = { ...initSwingStore().kernelStorage, snapStore };
3032

3133
const argv = [];

packages/SwingSet/test/xsnap-snapshots/test-xsnap-snapshots.js

+11-11
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ test.skip('snapshots', async t => {
6868

6969
// the delivery of startVat and bootstrap() results in snapshot A
7070
const sidA = getLatestSnapshot();
71-
t.true(await snapStore.has(sidA));
71+
t.true(snapStore.has(sidA));
7272
t.deepEqual(getSnapshotUsers(sidA), [vatID]);
7373

7474
// increment() results in snapshot B
@@ -78,8 +78,8 @@ test.skip('snapshots', async t => {
7878
t.not(sidA, sidB);
7979
// the DB remembers 'A' as unused, so commit() will delete it, but
8080
// until then we should have both
81-
t.true(await snapStore.has(sidA));
82-
t.true(await snapStore.has(sidB));
81+
t.true(snapStore.has(sidA));
82+
t.true(snapStore.has(sidB));
8383
t.deepEqual(getSnapshotUsers(sidA), []);
8484
t.deepEqual(getSnapshotUsers(sidB), [vatID]);
8585

@@ -89,8 +89,8 @@ test.skip('snapshots', async t => {
8989
// used-by entries around forever
9090
await commit();
9191

92-
t.false(await snapStore.has(sidA));
93-
t.true(await snapStore.has(sidB));
92+
t.false(snapStore.has(sidA));
93+
t.true(snapStore.has(sidB));
9494
// t.deepEqual(getSnapshotUsers(sidA), undefined);
9595
t.deepEqual(getSnapshotUsers(sidA), []); // not deleted
9696
t.deepEqual(getSnapshotUsers(sidB), [vatID]);
@@ -101,31 +101,31 @@ test.skip('snapshots', async t => {
101101
await run('read');
102102

103103
t.is(getLatestSnapshot(), sidB);
104-
t.true(await snapStore.has(sidB));
104+
t.true(snapStore.has(sidB));
105105
t.deepEqual(getSnapshotUsers(sidA), []); // not deleted
106106
t.deepEqual(getSnapshotUsers(sidB), [vatID]);
107107

108108
// in the buggy version, this commit() deleted B
109109
await commit();
110110
t.is(getLatestSnapshot(), sidB);
111-
t.true(await snapStore.has(sidB)); // .. so this failed
111+
t.true(snapStore.has(sidB)); // .. so this failed
112112
t.deepEqual(getSnapshotUsers(sidA), []); // not deleted
113113
t.deepEqual(getSnapshotUsers(sidB), [vatID]);
114114

115115
await run('increment'); // results in snapshot C
116116
const sidC = getLatestSnapshot();
117117
t.not(sidC, sidB);
118-
t.true(await snapStore.has(sidB));
119-
t.true(await snapStore.has(sidC));
118+
t.true(snapStore.has(sidB));
119+
t.true(snapStore.has(sidC));
120120
t.deepEqual(getSnapshotUsers(sidA), []); // not deleted
121121
t.deepEqual(getSnapshotUsers(sidB), []);
122122
t.deepEqual(getSnapshotUsers(sidC), [vatID]);
123123

124124
// the commit() will delete B now that it is unused
125125
await commit();
126126
// in the buggy version, commit() failed because B was already deleted
127-
t.false(await snapStore.has(sidB));
128-
t.true(await snapStore.has(sidC));
127+
t.false(snapStore.has(sidB));
128+
t.true(snapStore.has(sidC));
129129
t.deepEqual(getSnapshotUsers(sidA), []); // not deleted
130130
t.deepEqual(getSnapshotUsers(sidB), []); // not deleted
131131
t.deepEqual(getSnapshotUsers(sidC), [vatID]);

0 commit comments

Comments
 (0)