Skip to content
This repository has been archived by the owner on Dec 2, 2024. It is now read-only.

Reduce scope to what is strictly needed in levelup #90

Closed
vweevers opened this issue Sep 26, 2021 · 12 comments · Fixed by #89
Closed

Reduce scope to what is strictly needed in levelup #90

vweevers opened this issue Sep 26, 2021 · 12 comments · Fixed by #89
Labels
cleanup Housekeeping semver-major Changes that break backward compatibility

Comments

@vweevers
Copy link
Member

vweevers commented Sep 26, 2021

TLDR: thumbs up if you agree that deferring operations is only useful while the db is opening.

I'm working on getMany(). Which is a new abstract-leveldown and levelup method that gives abstract-leveldown the responsibility of checking whether the db is open when getMany() is called. As an experiment for Level/community#58. But I'm running into an inconsistency that's particularly problematic in subleveldown (which has to deal with two instances of deferred-leveldown and levelup, and is a test bed for API parity).

deferred-leveldown and levelup are inconsistent in their deferredOpen behavior: the former allows operations while the db has any status, the latter only if 'opening' or 'open'. For example, if the status is 'closed', deferred-leveldown will put operations in its queue. But in practice we don't get there because levelup will yield a new ReadError('Database is not open').

@ralphtheninja @juliangruber Given that the order of initialization in levelup is:

  • Wrap input db with deferred-leveldown
  • Start opening it (changing its status from 'new' to 'opening')
  • Start accepting operations (after returning from constructor)

I propose the following changes:

  • deferred-leveldown will only defer operations if the status of its underlying db is 'opening'
  • Otherwise it will yield new Error('Database is not open').

This is a breaking change for deferred-leveldown, but semver-patch for levelup.

See also Level/levelup#710 (comment).

@vweevers vweevers added semver-major Changes that break backward compatibility cleanup Housekeeping labels Sep 26, 2021
@juliangruber
Copy link
Member

  • deferred-leveldown will only defer operations if the status of its underlying db is 'opening'

I think there are some more cases we could discuss. Say the db was closed, should another operation cause it to reopen? Or it could even be currently closing and a consumer could expect the next operation to reopen it.

If after closing a db the user wants to use the deferred-leveldown behavior again, they need to ensure they called db.open(), so that it's in the opening state, right? I think that makes sense to me. Just want to check it with you

@vweevers
Copy link
Member Author

If after closing a db the user wants to use the deferred-leveldown behavior again, they need to ensure they called db.open(), so that it's in the opening state, right? I think that makes sense to me. Just want to check it with you

Right. The way I see it, deferredOpen is a convenience to avoid having to open the db. It goes away if the user explicitly closes the db. It then becomes the user's responsibility to explicitly open the db again. After which the convenience is back.

I reckon in most if not all cases, if the user is closing the db, they're not expecting to do further work on the db, and scheduling operations should be an error.

@vweevers
Copy link
Member Author

Which is to say, yielding new Error('Database is not open') is also a convenience :)

@ralphtheninja
Copy link
Member

ralphtheninja commented Sep 26, 2021

deferred-leveldown feels a bit hacky to me. I just want to entertain the idea of removing it in the long run. My gut feeling tells me we're having to fight it all the time and it's difficult to see what's going on. Might be a lot of work. I'm not saying we should. Just to think about it 😄

@vweevers
Copy link
Member Author

Oh, I definitely want to remove it. Just gradually.

@juliangruber
Copy link
Member

I want to remove it as well. I think we mostly had it because of callback inconvenience, now that we can await db.open() there's no big advantage any more

@juliangruber
Copy link
Member

I reckon in most if not all cases, if the user is closing the db, they're not expecting to do further work on the db, and scheduling operations should be an error.

I think you're right. The easiest path for them is to dispose of the db completely after closing it, and then starting fresh. So only treating the opening state makes sense 👍

@vweevers
Copy link
Member Author

To clarify, I want to remove deferred-leveldown, but not necessarily deferredOpen (as a feature). It does have a major benefit on sublevels for example. Don't want to have to do:

await db.open()
const sub = require('subleveldown')(db)
await sub.open()

That, plus widespread usage of deferredOpen, and that we may not be seeing other benefits, is why I want to remove it gradually. We may find a new place for it, or that opening sublevels isn't necessary, or... We'll see.

@juliangruber
Copy link
Member

Why don't you want to have to do that? That code looks fine to me. Async operations are cheap now, I think we can remove deferredOpen altogether

@vweevers
Copy link
Member Author

Asynchronicity is viral. Let's say a module takes a db as input, for example function myIndexer(db) which internally creates sublevels to store indexed data - or just to have a fresh db instance that can safely be monkeypatched. And it can't write to those sublevels until a await sub.open(). Assume that this module wants to intercept writes on the input db, in order to update indexes. Then, because of the required async initialization, it must change its signature to async function myIndexer(db).

@juliangruber
Copy link
Member

Pseudo code:

const myIndexer = db => {
  db.put = async (key, value) => {
    await sub.open()
    // ...
  }
}

Wouldn't something like this work? By moving the async operation into the db hooks

@vweevers
Copy link
Member Author

That just moves the problem. I think in userland you shouldn't have to worry about open/close state; saves boilerplate and prevents race issues.

deferredOpen isn't necessarily a complex feature (that should therefore be removed). The problem I have with the current implementation is that the feature is incomplete without levelup. Because it's levelup that auto-opens the db (and emits the events that are needed for logic like db.once('open', cb) if we wanted to implement it elsewhere). So:

require('levelup')(db).supports.deferredOpen // true
require('deferred-leveldown')(db).supports.deferredOpen // false!

vweevers added a commit that referenced this issue Sep 28, 2021
In other states (besides 'open') a 'Database is not open' error is
now thrown. This reduces the scope of `deferred-leveldown` to what's
strictly needed in `levelup` and aligns behavior.

In addition, override public methods of `abstract-leveldown` instead
of private methods. This has one downside: they need to do the same
callback to promise conversion that `abstract-leveldown` does. The
upside is that operation callbacks are not called before the db has
finished opening, including in cases where `abstract-leveldown` has
a fast-path, like on `db.batch([])` which because the array is
empty bypasses `_batch()`.

Closes #91
Closes #90
vweevers added a commit that referenced this issue Sep 28, 2021
In other states (besides 'open') a 'Database is not open' error is
now thrown. This reduces the scope of `deferred-leveldown` to what's
strictly needed in `levelup` and aligns behavior.

In addition, override public methods of `abstract-leveldown` instead
of private methods. This has one downside: they need to do the same
callback to promise conversion that `abstract-leveldown` does. The
upside is that operation callbacks are not called before the db has
finished opening, including in cases where `abstract-leveldown` has
a fast-path, like on `db.batch([])` which because the array is
empty bypasses `_batch()`.

Closes #91
Closes #90
vweevers added a commit that referenced this issue Sep 30, 2021
In other states (besides 'open') a 'Database is not open' error is
now thrown. This reduces the scope of `deferred-leveldown` to what's
strictly needed in `levelup` and aligns behavior.

In addition, override public methods of `abstract-leveldown` instead
of private methods. This has one downside: they need to do the same
callback to promise conversion that `abstract-leveldown` does. The
upside is that operation callbacks are not called before the db has
finished opening, including in cases where `abstract-leveldown` has
a fast-path, like on `db.batch([])` which because the array is
empty bypasses `_batch()`.

Closes #91
Closes #90
vweevers added a commit that referenced this issue Sep 30, 2021
In other states (besides 'open') a 'Database is not open' error is
now thrown. This reduces the scope of `deferred-leveldown` to what's
strictly needed in `levelup` and aligns behavior.

In addition, override public methods of `abstract-leveldown` instead
of private methods. This has one downside: they need to do the same
callback to promise conversion that `abstract-leveldown` does. The
upside is that operation callbacks are not called before the db has
finished opening, including in cases where `abstract-leveldown` has
a fast-path, like on `db.batch([])` which because the array is
empty bypasses `_batch()`.

Closes #91
Closes #90
vweevers added a commit to Level/levelup that referenced this issue Sep 30, 2021
As well as the encoding-down and memdown devDependencies.

Adds `db.status` and `db.isOperational()` for API parity.

Ref Level/deferred-leveldown#90
vweevers added a commit to Level/levelup that referenced this issue Oct 1, 2021
As well as the encoding-down and memdown devDependencies.

Adds `db.status` and `db.isOperational()` for API parity.

Ref Level/deferred-leveldown#90
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
cleanup Housekeeping semver-major Changes that break backward compatibility
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants