Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 74 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
npm install hyperbee2
```

Next major version for [hyperbee](https://github.com/holepunchto/hyperbee).
Will be merged in there and released a new major when fully done.

An append-only B-tree on top of a [Hypercore][hypercore].

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'd probably reorg this section. As a reader trying to get oriented without much context about legacy Hyperbee, I want to grok: what is this thing literally? What's the general shape of the problem it solves? Then more context about features. In that order, probably.

I want to read not just that the batch API is "better," but how is it better from the POV of an engineer? An engineer trying to solve a technical problem would be asking: "what kind of workloads is this designed for? How big is it supposed to scale? What problem motivated its design, and is that motivation similar to mine?" In the first 5 minutes of reading, just trying to understand the shape of the problem that this doohickey was designed to solve.

So maybe something like:

A P2P append-only multiwriter + multifork Bε-tree built atop Hypercore.

Hyperbee2 is the next generation of Hyperbee. It scales to billions of keys and provides conflict-free, high throughput reads and writes even under high contention.

Key features:
- Multiwriter support: one tree, thousands of concurrent writers
- Multifork capability: any tree node can hold a pointer to another core, enabling arbitrary forks
- Improved batch API: (what benefit does this provide?)
- Delta compression: (what benefit does this provide?)
- All the great features from legacy Hyperbee: sparse replication, range and prefix queries, (what else to mention?)

A P2P append-only multifork Bε-tree build on top of [Hypercore][hypercore].
This is the next generation of Hyperbee surpassing the original [hyperbee](https://github.com/holepunchto/hyperbee) in performance and features. Hyperbee2 adds a better batch API (see [WriteBatch](#writebatch)), delta compression and multiwriter capabilities. One key difference is that values in the tree can be pointers to other cores allowing for forking another tree.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Another thought from the search team POV:

Every time I look at a P2P database project, I ask myself the same question: what value/mutation semantics does it support? Meaning - what value types can be stored under a key? Is it a schema, or an arbitrary buffer, or what? What are the mutation semantics? Can I create a key that maps to an append-only set? What about mapping a key to a set that allows arbitrarily deletion? I still don't understand this aspect of Hyperbee/Hyperbee2 very well, and I'd probably want a bullet in the features about it.

## Usage

Expand Down Expand Up @@ -36,10 +34,6 @@ console.log(name.value.toString()) // example
console.log(email.value.toString()) // example@example.com
```

## License

Apache-2.0

## API

### Hyperbee
Expand All @@ -54,17 +48,24 @@ Options include:
```js
{
key: null, // Buffer or String. Key of Hypercore to load via Corestore
maxCacheSize: 4096, // Max number of nodes to keep in NodeCache
maxCacheSize: 4096, // Integer. Max number of nodes to keep in NodeCache
core: Hypercore(...), // Hypercore within the Corestore to use (defaults to loading key, or using name='bee')
view: false, // Is this a view of an open Hyperbee? (i.e. do not close underlying store)
writable: true, // Is append / truncate allowed on the underlying Hypercore?
unbatch: 0, // Number of write batches to rollback during bootstrap
autoUpdate: false, // Reload root node when underlying Hypercore is appended to?
preload: null, // A function called by ready() after the Hypercore is ready. Can be async.
wait: true, // Wait for Hypercore to download blocks
view: false, // Boolean. Is this a view of an open Hyperbee? (i.e. do not close underlying store)
writable: true, // Boolean. Is append / truncate allowed on the underlying Hypercore?
unbatch: 0, // Integer. Number of write batches to rollback during bootstrap
autoUpdate: !writable && !view, // Boolean. Reload root node when underlying Hypercore is appended to?
preload: null, // Function. A function called by ready() after the Hypercore is ready. Can be async.
wait: true, // Boolean. Wait for Hypercore to download blocks
timeout: 0, // Integer. Wait at most this many milliseconds for reads (0 means no timeout)
trace: null, // Function(core, seq). This function is called whenever a block is read, for debugging/metrics
encryption: null, // { key } (or other Hypercore encryption options) used to load/create encrypted cores
getEncryptionProvider: null, // Function(key) -> encryption options, called per Hypercore key.
// Defaults to always returning the `encryption` option above.
}
```

The user may provide a custom encryption module as `opts.encryption`, which should satisfy the [HypercoreEncryption](https://github.com/holepunchto/hypercore-encryption) interface.

If you pass your own `core`, consider increasing the inflight range on it (`256` minimum recommended, e.g. `{ inflightRange: [256, 512] }` when creating the core via Corestore) to match `hyperbee2` constructed cores.

#### `await db.ready()`
Expand All @@ -86,18 +87,34 @@ Returns an object with the following properties:

```
{
length, // Number of blocks from the start of Hypercore that apply to this tree.
length, // Integer. Number of blocks from the start of Hypercore that apply to this tree.
key, // Buffer or null. The key of the underlying Hypercore.
}
```

If the Hyperbee is not ready, this will return null.
If the Hyperbee is not ready, this will return `null`.

#### `db.isGenesis()`

Returns true if the tree is empty, i.e. no blocks apply to it yet and `db.head().length` is 0.
Returns false if the Hyperbee is not ready.

#### `await db.compat()`

Returns the block format (`type`) that the tree is currently written in, e.g. `encoding.TYPE_LATEST` or `encoding.TYPE_COMPAT`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Just slightly bumped by the grammar here - could it be:

"Returns the block format of the tree"

or

"Returns the block format associated with the tree"


#### `await db.cores([options])`

Returns an array of the [Hypercore][hypercore] keys the Hyperbee references, i.e. its own core plus any other cores linked in via cross-tree writes (see [`db.write([options])`](#dbwriteoptions)).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Just another grammar bump, "keys the Hyperbee references" - could it instead be:

Returns an array of the Hypercore keys referenced by the Hyperbee

?


Options:

```js
{
local: true, // Boolean. Include a Hyperbee's own core key in the result.
}
```

#### `db.cache`

Read only. The NodeCache used by this Hyperbee.
Expand All @@ -113,7 +130,7 @@ is ready.

#### `db.closing`

Read only. Initially null. When `db.close()` is called, this is set to
Read only. Initially `null`. When `db.close()` is called, this is set to
a Promise that resolves to `undefined` when the close completes.

#### `db.opened`
Expand All @@ -137,7 +154,7 @@ Options:
```js
{
writable: false, // Boolean. Will the new tree be writable?
length: this.core.length, // Number. Length of blocks used from the Hypercore
length: this.core.length, // Integer. Length of blocks used from the Hypercore
key: null, // Buffer or null. Key of the Hypercore
timeout: this.timeout, // Number. Wait at most this many milliseconds per Hypercore read (0 means no timeout)
wait: this.wait, // Boolean. Wait for Hypercore to download blocks
Expand All @@ -154,7 +171,7 @@ Options:
```js
{
writable: this.writable, // Boolean. Is this tree writable after the move?
length: this.core.length, // Number. Length of blocks used from the Hypercore
length: this.core.length, // Integer. Length of blocks used from the Hypercore
key: null, // Buffer or null. Key of the Hypercore
}
```
Expand Down Expand Up @@ -194,20 +211,26 @@ Options:

```js
{
length: -1, // Length of blocks used from the Hypercore
length: -1, // Integer. Length of blocks used from the Hypercore
// (i.e. what point in the hypercore is the write
// going to extend when flush() is called?).
// If -1, the length will be calculated on flush()
// using root.seq + 1.
key: null, // Buffer or null. Key of the Hypercore.
autoUpdate: true, // Boolean .Should Hyperbee automatically reflect updates
autoUpdate: true, // Boolean. Should Hyperbee automatically reflect updates
// after each flush()?
compat: false, // Boolean. Write blocks compatible with Hyperbee 1?
type: encoding.TYPE_LATEST, // Number. Block format to use.
inlineValueSize: 1024, // Values smaller than this byte length are
type: encoding.TYPE_LATEST, // Integer. Block format to use.
deltaMax: 16, // Integer. Max number of chained node deltas before they
// are compacted into a cohort. See "Compression" below.
// 0 for `compat`/`type: 0` blocks, which never compress.
deltaMin: 1, // Integer. Min number of trailing deltas worth compacting
// into a cohort together, rather than rewriting the node
// in full. Infinity for `compat`/`type: 0` blocks.
inlineValueSize: 1024, // Integer. Values smaller than this byte length are
// written inline in the node. Larger values
// are referenced via a pointer into the block.
preferredBlockSize: 4096, // Try to write blocks of approximately this size
preferredBlockSize: 4096, // Integer. Try to write blocks of approximately this size
// when flushing updates.
}
```
Expand All @@ -231,8 +254,14 @@ Options:
gt: undefined, // Buffer. Key lower bound (exclusive)
lte: undefined, // Buffer. Key upper bound (inclusive)
lt: undefined, // Buffer. Key upper bound (exclusive)
highWaterMark: 16384, // Size of read ahead buffer calculated
highWaterMark: 16384, // Integer. Size of read ahead buffer calculated
// as: number of entries * 1024
timeout: 0, // Integer. Wait at most this many milliseconds (0 means no timeout).
// Defaults to the value of the Hyperbee's timeout option.
wait: true, // Boolean. Wait for Hypercore to download blocks
// Defaults to the value of the Hyperbee's wait option.
trace: null, // Function(core, seq). This function is called whenever a block is read.
// Defaults to the value of the Hyperbee's trace option.
}
```

Expand Down Expand Up @@ -322,12 +351,14 @@ Options:
```js
{
head: null, // null means use this.tree.head()
highWaterMark: 16384, // Size of read ahead buffer calculated
highWaterMark: 16384, // Integer. Size of read ahead buffer calculated
// as: number of entries * 1024
timeout: 0, // Wait at most this many milliseconds (0 means no timeout).
timeout: 0, // Integer. Wait at most this many milliseconds (0 means no timeout).
// Defaults to the value of the Hyperbee's timeout option.
wait: true, // Wait for Hypercore to download blocks
wait: true, // Boolean. Wait for Hypercore to download blocks
// Defaults to the value of the Hyperbee's wait option.
trace: null, // Function(core, seq). This function is called whenever a block is read.
// Defaults to the value of the Hyperbee's trace option.
}
```

Expand All @@ -336,12 +367,12 @@ Iterating over the stream will yield:
```js
{
head: {
length, // Number: number of blocks from the start of Hypercore
length, // Integer: number of blocks from the start of Hypercore
// that apply to this version of the tree
key, // Buffer or null: the key of the Hypercore for this version
},
tail: {
length, // Number: number of blocks from the start of Hypercore
length, // Integer: number of blocks from the start of Hypercore
// that apply to the previous version of the tree
key, // Buffer or null: the key of the Hypercore for the previous
// version
Expand All @@ -363,8 +394,8 @@ the following properties on success:
```js
{
core, // Hypercore: the hypercore the entry is stored in
offset, // Number: the index of the entry in the block
seq, // Number: the sequence number of the block in the hypercore
offset, // Integer: the index of the entry in the block
seq, // Integer: the sequence number of the block in the hypercore
key, // Buffer: the key of the entry
value, // Buffer: the value of the entry
}
Expand All @@ -391,8 +422,8 @@ the following properties on success:
```js
{
core, // Hypercore: the hypercore the entry is stored in
offset, // Number: the index of the entry in the block
seq, // Number: the sequence number of the block in the hypercore
offset, // Integer: the index of the entry in the block
seq, // Integer: the sequence number of the block in the hypercore
key, // Buffer: the key of the entry
value, // Buffer: the value of the entry
}
Expand All @@ -402,8 +433,9 @@ Options:

```js
{
timeout, // Number: wait at most this many milliseconds (0 means no timeout)
timeout, // Integer: wait at most this many milliseconds (0 means no timeout)
wait, // Boolean: wait for Hypercore to download blocks
trace, // Function(core, seq): called whenever a block is read
}
```

Expand Down Expand Up @@ -490,7 +522,7 @@ for await (const data of b.createReadStream(b)) {

### `await batch.lock()`

Aquires an exclusive write lock now instead of waiting for `flush()`
Acquires an exclusive write lock now instead of waiting for `flush()`
to be called. No writes will occur until this batch is closed allowing
you to keep a consistent view of the database while building the batch.

Expand All @@ -511,7 +543,7 @@ Queues an operation to clear all entries from the tree.

#### `await batch.flush()`

Aquires an exclusive write lock and applies the operations queued in this
Acquires an exclusive write lock and applies the operations queued in this
batch to the tree, clearing the queue.

**Warning:** continuing to use the batch after flushing can cause unpredictable
Expand Down Expand Up @@ -554,6 +586,10 @@ for await (const data of b.createReadStream(b)) {
Closes the batch without flushing operations. Subsequent attempts
to flush the batch will result in an error.

## License

Apache-2.0

[hypercore]: https://github.com/holepunchto/hypercore
[corestore]: https://github.com/holepunchto/corestore
[streamx]: https://github.com/mafintosh/streamx
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "hyperbee2",
"version": "2.16.0",
"description": "Scalable P2P BTree",
"description": "P2P Append-Only Multi-fork Bε-Tree",
"main": "index.js",
"files": [
"index.js",
Expand Down