-
Notifications
You must be signed in to change notification settings - Fork 212
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(swing-store): faster import of swing-store #8522
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -152,21 +152,9 @@ for (const name of exporter.getArtifactNames()) { | |
|
||
## Import | ||
|
||
On other end of the export process is an importer. This is a new host application, which wants to start from the contents of the export, rather than initializing a brand new (empty) kernel state. | ||
On the other end of the export process is an importer. This is used to restore kernel state, so that a new host application can simply continue mostly as if it had been previously executing. The expectation is that the import and the execution are 2 independent events, and the execution doesn't need to be aware it was imported. | ||
|
||
When starting a brand new instance, host applications would normally call `openSwingStore(dirPath)` to create a new (empty) SwingStore, then call SwingSet's `initializeSwingset(config, .., kernelStorage)` to let the kernel initialize the DB with a config-dependent starting state: | ||
|
||
```js | ||
// this is done only the first time an instance is created: | ||
|
||
import { openSwingStore } from '@agoric/swing-store'; | ||
import { initializeSwingset } from '@agoric/swingset-vat'; | ||
const dirPath = './swing-store'; | ||
const { hostStorage, kernelStorage } = openSwingStore(dirPath); | ||
await initializeSwingset(config, argv, kernelStorage); | ||
``` | ||
|
||
Once the initial state is created, each time the application is launched, it will build a controller around the existing state: | ||
For reference, after the initial state is created, each time the application is launched, it builds a controller around the existing state: | ||
|
||
```js | ||
import { openSwingStore } from '@agoric/swing-store'; | ||
|
@@ -177,7 +165,7 @@ const controller = await makeSwingsetController(kernelStorage); | |
// ... now do things like controller.run(), etc | ||
``` | ||
|
||
When cloning an existing kernel, the initialization step is replaced with `importSwingStore`. The host application should feed the importer with the export data and artifacts, by passing an object that has the same API as the SwingStore's exporter: | ||
When cloning an existing kernel, the host application first imports and commits the restored state using `importSwingStore`. The host application should feed the importer with the export data and artifacts, by passing an object that has the same API as the SwingStore's exporter: | ||
|
||
```js | ||
import { importSwingStore } from '@agoric/swing-store'; | ||
|
@@ -188,11 +176,13 @@ const exporter = { | |
getArtifact(name) { // return blob of artifact data }, | ||
}; | ||
const { hostStorage } = importSwingStore(exporter, dirPath); | ||
hostStorage.commit(); | ||
// now the swingstore is fully populated | ||
// Update any hostStorage as needed | ||
await hostStorage.commit(); | ||
await hostStorage.close(); | ||
// now the populated swingstore can be re-opened using `openSwingStore`` | ||
``` | ||
|
||
Once the new SwingStore is fully populated with the previously-exported data, the host application can use `makeSwingsetController()` to build a kernel that will start from the exported state. | ||
Once the new SwingStore is fully populated with the previously-exported data, the host application can update any host specific state before committing and closing the SwingStore. `importSwingStore` returns only the host facet of the SwingStore instance, as it is not suitable for immediate execution. | ||
|
||
## Optional / Historical Data | ||
|
||
|
@@ -223,14 +213,14 @@ Also note that when a vat is terminated, we delete all information about it, inc | |
|
||
When importing, the `importSwingStore()` function's options bag takes a property named `artifactMode`, with the same meanings as for export. Importing with the `operational` mode will ignore any artifacts other than those needed for current operations, and will fail unless all such artifacts were available. Importing with `replay` will ignore spans from old incarnations, but will fail unless all spans from current incarnations are present. Importing with `archival` will fail unless all spans from all incarnations are present. There is no `debug` option during import. | ||
|
||
`importSwingStore()` returns a swingstore, which means its options bag also contains the same options as `openSwingStore()`, including the `keepTranscripts` option. This defaults to `true`, but if it were overridden to `false`, then the new swingstore will delete transcript spans as soon as they are no longer needed for operational purposes (e.g. when `transcriptStore.rolloverSpan()` is called). | ||
While `importSwingStore()`'s options bag accepts the same options as `openSwingStore()`, since it returns only the host facet of a SwingStore, some of these options might not be meaningful, such as `keepTranscripts`. | ||
|
||
So, to avoid pruning current-incarnation historical transcript spans when exporting from one swingstore to another, you must set (or avoid overriding) the following options along the way: | ||
|
||
* the original swingstore must not be opened with `{ keepTranscripts: false }`, otherwise the old spans will be pruned immediately | ||
* the export must use `makeSwingStoreExporter(dirpath, { artifactMode: 'replay'})`, otherwise the export will omit the old spans | ||
* the import must use `importSwingStore(exporter, dirPath, { artifactMode: 'replay'})`, otherwise the import will ignore the old spans | ||
* the `importSwingStore` call (and all subsequent `openSwingStore` calls) must not use `keepTranscripts: false`, otherwise the new swingstore will prune historical spans as new ones are created (during `rolloverSpan`). | ||
* subsequent `openSwingStore` calls must not use `keepTranscripts: false`, otherwise the new swingstore will prune historical spans as new ones are created (during `rolloverSpan`). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. retrospective nit: it might be a good idea to retain the admonition against having |
||
|
||
## Implementation Details | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,14 +11,16 @@ import { assertComplete } from './assertComplete.js'; | |
*/ | ||
|
||
/** | ||
* Function used to create a new swingStore from an object implementing the | ||
* Function used to populate a swingStore from an object implementing the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: It really is about creating a new swingStore. Changing it to say "populate a swingStore" kind of makes it sound like this could be used to fill a previously-created (perhaps empty, perhaps not) database, which suggests some sort of weird merge operation between the previous contents and the import dataset. |
||
* exporter API. The exporter API may be provided by a swingStore instance, or | ||
* implemented by a host to restore data that was previously exported. | ||
* implemented by a host to restore data that was previously exported. The | ||
* returned swingStore is not suitable for execution, and thus only contains | ||
* the host facet for committing the populated swingStore. | ||
* | ||
* @param {import('./exporter').SwingStoreExporter} exporter | ||
* @param {string | null} [dirPath] | ||
* @param {ImportSwingStoreOptions} [options] | ||
* @returns {Promise<import('./swingStore').SwingStore>} | ||
* @returns {Promise<Pick<import('./swingStore').SwingStore, 'hostStorage' | 'debug'>>} | ||
*/ | ||
export async function importSwingStore(exporter, dirPath = null, options = {}) { | ||
if (dirPath && typeof dirPath !== 'string') { | ||
|
@@ -27,8 +29,14 @@ export async function importSwingStore(exporter, dirPath = null, options = {}) { | |
const { artifactMode = 'operational', ...makeSwingStoreOptions } = options; | ||
validateArtifactMode(artifactMode); | ||
|
||
const store = makeSwingStore(dirPath, true, makeSwingStoreOptions); | ||
const { kernelStorage, internal } = store; | ||
const { hostStorage, kernelStorage, internal, debug } = makeSwingStore( | ||
dirPath, | ||
true, | ||
{ | ||
unsafeFastMode: true, | ||
...makeSwingStoreOptions, | ||
}, | ||
); | ||
|
||
// For every exportData entry, we add a DB record. 'kv' entries are | ||
// the "kvStore shadow table", and are not associated with any | ||
|
@@ -121,5 +129,5 @@ export async function importSwingStore(exporter, dirPath = null, options = {}) { | |
assertComplete(internal, checkMode); | ||
|
||
await exporter.close(); | ||
return store; | ||
return { hostStorage, debug }; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -203,9 +203,29 @@ export function makeSwingStore(dirPath, forceReset, options = {}) { | |
// mode that defers merge work for a later attempt rather than block any | ||
// potential readers or writers. See https://sqlite.org/wal.html for details. | ||
|
||
// However we also allow opening the DB with journaling off, which is unsafe | ||
// and doesn't support rollback, but avoids any overhead for large | ||
// transactions like for during an import. | ||
|
||
function setUnsafeFastMode(enabled) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's probably a better name for this, but the functionality seems good to me. |
||
const journalMode = enabled ? 'off' : 'wal'; | ||
const synchronousMode = enabled ? 'normal' : 'full'; | ||
!db.inTransaction || Fail`must not be in a transaction`; | ||
|
||
db.unsafeMode(!!enabled); | ||
// The WAL mode is persistent so it's not possible to switch to a different | ||
// mode for an existing DB. | ||
const actualMode = db.pragma(`journal_mode=${journalMode}`, { | ||
mhofman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
simple: true, | ||
}); | ||
actualMode === journalMode || | ||
filePath === ':memory:' || | ||
Fail`Couldn't set swing-store DB to ${journalMode} mode (is ${actualMode})`; | ||
db.pragma(`synchronous=${synchronousMode}`); | ||
} | ||
|
||
// PRAGMAs have to happen outside a transaction | ||
db.exec(`PRAGMA journal_mode=WAL`); | ||
db.exec(`PRAGMA synchronous=FULL`); | ||
setUnsafeFastMode(options.unsafeFastMode); | ||
|
||
// We use IMMEDIATE because the kernel is supposed to be the sole writer of | ||
// the DB, and if some other process is holding a write lock, we want to find | ||
|
@@ -481,7 +501,11 @@ export function makeSwingStore(dirPath, forceReset, options = {}) { | |
} | ||
|
||
/** @type {import('./internal.js').SwingStoreInternal} */ | ||
const internal = harden({ snapStore, transcriptStore, bundleStore }); | ||
const internal = harden({ | ||
snapStore, | ||
transcriptStore, | ||
bundleStore, | ||
}); | ||
|
||
async function repairMetadata(exporter) { | ||
return doRepairMetadata(internal, exporter); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means the
SwingStore
you get back fromimportSwingStore
is no longer suitable for general use, because it's got all the commit-safety modes turned off, yeah?Please update the docs in
docs/data-export.md
to mention this fact, around line 170-ish where theimportSwingSstore
is shown.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, it is not. I have now removed the
kernelStorage
facet from the return value ofimportSwingStore
.