Skip to content
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

fix(swingset): update lmdb #5488

Merged
merged 14 commits into from
Jun 6, 2022
Merged

fix(swingset): update lmdb #5488

merged 14 commits into from
Jun 6, 2022

Conversation

mhofman
Copy link
Member

@mhofman mhofman commented Jun 1, 2022

closes: #5031
best reviewed commit-by-commit

Description

Switches from node-lmdb to lmbd-js which seem more actively maintained. Disable useWritemap option which seem to be causing some issues (but not the only cause of them), as it seems no longer necessary.

This PR does the minimal amount of code changes in the swingstore by synchronous creating a transaction. This works with the new API because we already ensured that we only ever had one transaction at a time. Once inside a transaction, read after writes are guaranteed to contain the data from the transaction.

However to adapt to the more asynchronous nature of lmdb-js, this PR updates the API of the swingstore to make commit and close asynchronous. While closing a synchronous transaction is not actually asynchronous, it would be too much of a hack to make it happen synchronously. Since the committers were already all asynchronous, this was a low impact change.

lmdb-js also seem to have a couple differences in behavior, including not passing through the mapSize option or encoding strings (both keys and values) as UTF-8. The former will result in a non preallocated DB file (previously sparse) growing in size with usage. The latter has no practical impact since the LMDB key size was also increased.

Drive by fix of getKeys iterator interruption which were previously not closing cursors. I've decided to pass errors through, as these really should not happen.

Security Considerations

The max key size logic and checks in collections is now no longer matching reality as it assumed a UTF-16 encoding of strings. However since lmdb-js bumps the max key size, it is impossible to generate keys that are too large, and a new test asserts this.

Documentation Considerations

The collections documentation was amended.

Testing Considerations

Fixed some swingstore and swingset tests to handle async commit, and handle parallel execution.
Added a test for lmdb max key size.
Ran a full daily loadgen test on my machine, which used to crash reliably.

@mhofman mhofman added the force:integration Force integration tests to run on PR label Jun 1, 2022
@@ -88,7 +88,7 @@ while(1) {
processInboundIO();
const policy = make100CrankPolicy();
await controller.run(policy);
commit();
await commit();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also update callers of commit() in packages/solo/src and packages/cosmic-swingset/src.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They were already awaiting!

@michaelfig
Copy link
Member

Here's what I see missing await:


@mhofman
Copy link
Member Author

mhofman commented Jun 1, 2022

Here's what I see missing await:

That one is a JSONStore, which is a different API. I'm open to make it apparent async as well for consistency.

This one has already been updated:

@mhofman mhofman force-pushed the mhofman/5031-update-lmdb branch 2 times, most recently from bcca078 to 8edac53 Compare June 2, 2022 03:21
@mhofman mhofman requested review from FUDCo and warner June 2, 2022 03:35
@mhofman mhofman marked this pull request as ready for review June 2, 2022 03:36
@mhofman
Copy link
Member Author

mhofman commented Jun 2, 2022

That one is a JSONStore, which is a different API. I'm open to make it apparent async as well for consistency.

@michaelfig I did make it consistent since it didn't really hurt. The only somewhat similar API is now the chain-store, but that one seem to rely on a synchronous commit right now, and I didn't feel like untangling that.

@mhofman mhofman force-pushed the mhofman/5031-update-lmdb branch from 8edac53 to 35e40e3 Compare June 2, 2022 03:42
Copy link
Member

@warner warner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems good to me, but please get @FUDCo 's approval too, he knows the DB layer much better than me.

packages/cosmic-swingset/src/entrypoint.js Show resolved Hide resolved
packages/solo/src/entrypoint.js Show resolved Hide resolved
packages/solo/src/pipe-entrypoint.js Show resolved Hide resolved
packages/swing-store/test/test-state.js Show resolved Hide resolved
} else {
const result = v2.value;
v2 = it2.next();
/** @type {IteratorResult<any> | null} */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please coordinate with @gibson042 since his #5294 moves this function around.

@@ -198,7 +198,7 @@ function makeJSONStore(dirPath, forceReset = false) {
/**
* Commit unsaved changes.
*/
function commit() {
async function commit() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be async? This little "json store" doesn't overlap much with the SwingStore, but I suppose if there's any chance of both appearing in the same program, I can see a consistency argument.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I wasn't sure. Can easily revert this specific commit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I'm ambivalent.

packages/swing-store/src/swingStore.js Show resolved Hide resolved
packages/swing-store/src/swingStore.js Show resolved Hide resolved
t.teardown(cleanup);
t.is(isSwingStore(dbDir), false);
const store = initSwingStore(dbDir);
store.kvStore.set('€'.repeat(254), 'Money!');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's assert both sides of the size limit (i.e. assert that 255 3-byte-UTF-8 chars fails), since the success/failure of writes needs to be part of consensus. This isn't an exact constraint (we'd need a mix of encoded sizes, plus a healthy variety of surrogate pairs, to exercise this thoroughly), but I think it's good to be in the habit of knowing what the precise rules are.

I think that means this DB change is consensus-breaking (there are non-trivial unicode strings which would pass the earlier encoding scheme's limit but not the new one, or vice versa), but I'm ok with making that now.. better than later.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

255 3-byte-UTF-8 chars fails

It won't fail. The upper limit is actually 1971 bytes I believe.

I think that means this DB change is consensus-breaking

I don't believe so. Nothing is writing large keys since the only user controlled key size is the collections keys which is independently checked.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, ok, so the previous limits were (pardon my js/python pseudocode) str.length < X && len(str.encode('utf-16')) < Y, and the new limits are str.length < X && len(str.encode('utf-8') < Z, but for all possible strings, the .length < X limit (imposed by liveslots) is tighter than the encoded limit (imposed lower down). So landing this can't make any failing userspace behavior start to succeed, or succeeding userspace behavior start to fail.

As long as that criteria is met, I guess we don't need more stringent testing at this layer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this test is specifically about checking this assumption. That the worst case str = threeByteUtfChar.repeat(X) then len(str.encode('utf-8')) < Z. Aka 3 * X < Z

Copy link
Contributor

@FUDCo FUDCo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One minor stylistic quibble but aside from that this looks very good. I'm surprised (and happy) that the changes in the underlying LMDB API could be accommodated with such a light touch.

cursor.close();
/** @type {import("lmdb").RangeOptions} */
const rangeOptions = {};
if (start) rangeOptions.start = start;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why our linter didn't pick this up, but please write this as

if (start) {
  rangeOptions.start = start;
}

-- always use braces
-- no putting the if consequence on same line as the conditional

@mhofman mhofman force-pushed the mhofman/5031-update-lmdb branch from fa931fe to b14cdab Compare June 6, 2022 21:22
@mhofman mhofman added the automerge:rebase Automatically rebase updates, then merge label Jun 6, 2022
@mhofman mhofman removed the force:integration Force integration tests to run on PR label Jun 6, 2022
@mergify mergify bot merged commit 168da21 into master Jun 6, 2022
@mergify mergify bot deleted the mhofman/5031-update-lmdb branch June 6, 2022 22:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge:rebase Automatically rebase updates, then merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

non-deterministic crash of single node under load
4 participants