Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
12 changes: 5 additions & 7 deletions packages/beacon-node/src/chain/opPools/opPool.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Id, Repository} from "@lodestar/db";
import {DbBatch, Id, Repository} from "@lodestar/db";
import {
BLS_WITHDRAWAL_PREFIX,
ForkName,
Expand Down Expand Up @@ -440,23 +440,21 @@ async function persistDiff<K extends Id, V>(
serializeKey: (key: K) => number | string
): Promise<void> {
const persistedKeys = await dbRepo.keys();
const itemsToPut: {key: K; value: V}[] = [];
const keysToDelete: K[] = [];
const batch: DbBatch<K, V> = [];

const persistedKeysSerialized = new Set(persistedKeys.map(serializeKey));
for (const item of items) {
if (!persistedKeysSerialized.has(serializeKey(item.key))) {
itemsToPut.push(item);
batch.push({type: "put", key: item.key, value: item.value});
}
}

const targetKeysSerialized = new Set(items.map((item) => serializeKey(item.key)));
for (const persistedKey of persistedKeys) {
if (!targetKeysSerialized.has(serializeKey(persistedKey))) {
keysToDelete.push(persistedKey);
batch.push({type: "del", key: persistedKey});
}
}

if (itemsToPut.length > 0) await dbRepo.batchPut(itemsToPut);
if (keysToDelete.length > 0) await dbRepo.batchDelete(keysToDelete);
if (batch.length > 0) await dbRepo.batch(batch);
}
26 changes: 25 additions & 1 deletion packages/db/src/abstractPrefixedRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {Type} from "@chainsafe/ssz";
import {ChainForkConfig} from "@lodestar/config";
import {BUCKET_LENGTH} from "./const.js";
import {KeyValue} from "./controller/index.js";
import {Db, DbReqOpts, FilterOptions} from "./controller/interface.js";
import {Db, DbBatch, DbReqOpts, FilterOptions} from "./controller/interface.js";
import {encodeKey} from "./util.js";

type Id = Uint8Array | string | number | bigint;
Expand Down Expand Up @@ -141,6 +141,30 @@ export abstract class PrefixedRepository<P, I extends Id, T> {
await this.db.batchDelete(keys.flat(), this.dbReqOpts);
}

async batch(prefix: P, batch: DbBatch<I, T>): Promise<void> {
const batchWithKeys = [];
for (const b of batch) {
if (b.type === "del") {
batchWithKeys.push({type: b.type, key: this.wrapKey(this.encodeKeyRaw(prefix, b.key))});
} else {
batchWithKeys.push({
type: b.type,
key: this.wrapKey(this.encodeKeyRaw(prefix, b.key)),
value: this.encodeValue(b.value),
});
}
}
await this.db.batch(batchWithKeys, this.dbReqOpts);
}
Comment on lines +144 to +158
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The for loop with if/else can be simplified by using the map function on the batch array. This would make the code more concise and functional. Also, it's good practice to type batchWithKeys.

  async batch(prefix: P, batch: DbBatch<I, T>): Promise<void> {
    const batchWithKeys: DbBatch<Uint8Array, Uint8Array> = batch.map((b) => {
      const key = this.wrapKey(this.encodeKeyRaw(prefix, b.key));
      if (b.type === "del") {
        return {type: "del", key};
      } else {
        return {type: "put", key, value: this.encodeValue(b.value)};
      }
    });
    await this.db.batch(batchWithKeys, this.dbReqOpts);
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We prefer using for off instead of map iterators because of performance.


async batchBinary(prefix: P, batch: DbBatch<I, Uint8Array>): Promise<void> {
const batchWithKeys = [];
for (const b of batch) {
batchWithKeys.push({...b, key: this.wrapKey(this.encodeKeyRaw(prefix, b.key))});
}
await this.db.batch(batchWithKeys, this.dbReqOpts);
}
Comment on lines +160 to +166
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This for loop can be simplified by using the map function on the batch array, which is more concise. It's also good practice to type batchWithKeys.

  async batchBinary(prefix: P, batch: DbBatch<I, Uint8Array>): Promise<void> {
    const batchWithKeys: DbBatch<Uint8Array, Uint8Array> = batch.map((b) => ({
      ...b,
      key: this.wrapKey(this.encodeKeyRaw(prefix, b.key)),
    }));
    await this.db.batch(batchWithKeys, this.dbReqOpts);
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We prefer using for off instead of map iterators because of performance.


async *valuesStream(prefix: P | P[]): AsyncIterable<T> {
for (const p of Array.isArray(prefix) ? prefix : [prefix]) {
for await (const vb of this.db.valuesStream({
Expand Down
22 changes: 21 additions & 1 deletion packages/db/src/abstractRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {Type} from "@chainsafe/ssz";
import {ChainForkConfig} from "@lodestar/config";
import {BUCKET_LENGTH} from "./const.js";
import {FilterOptions, KeyValue} from "./controller/index.js";
import {Db, DbReqOpts} from "./controller/interface.js";
import {Db, DbBatch, DbReqOpts} from "./controller/interface.js";
import {encodeKey as _encodeKey} from "./util.js";

export type Id = Uint8Array | string | number | bigint;
Expand Down Expand Up @@ -130,6 +130,26 @@ export abstract class Repository<I extends Id, T> {
);
}

async batch(batch: DbBatch<I, T>): Promise<void> {
const batchWithKeys: DbBatch<Uint8Array, Uint8Array> = [];
for (const b of batch) {
if (b.type === "del") {
batchWithKeys.push({...b, key: this.encodeKey(b.key)});
} else {
batchWithKeys.push({...b, key: this.encodeKey(b.key), value: this.encodeValue(b.value)});
}
}
await this.db.batch(batchWithKeys, this.dbReqOpts);
}
Comment on lines +133 to +143
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The for loop with if/else can be simplified by using the map function on the batch array. This would make the code more concise, functional, and arguably more readable by explicitly constructing the new objects rather than using spread syntax with property overrides.

  async batch(batch: DbBatch<I, T>): Promise<void> {
    const batchWithKeys: DbBatch<Uint8Array, Uint8Array> = batch.map((b) => {
      if (b.type === "del") {
        return {type: "del", key: this.encodeKey(b.key)};
      } else {
        return {type: "put", key: this.encodeKey(b.key), value: this.encodeValue(b.value)};
      }
    });
    await this.db.batch(batchWithKeys, this.dbReqOpts);
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We prefer using for off instead of map iterators because of performance.


async batchBinary(batch: DbBatch<I, Uint8Array>): Promise<void> {
const batchWithKeys: DbBatch<Uint8Array, Uint8Array> = [];
for (const b of batch) {
batchWithKeys.push({...b, key: this.encodeKey(b.key)});
}
await this.db.batch(batchWithKeys, this.dbReqOpts);
}
Comment on lines +145 to +151
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This for loop can be simplified by using the map function on the batch array, which is more concise and follows a functional programming style.

  async batchBinary(batch: DbBatch<I, Uint8Array>): Promise<void> {
    const batchWithKeys: DbBatch<Uint8Array, Uint8Array> = batch.map((b) => ({
      ...b,
      key: this.encodeKey(b.key),
    }));
    await this.db.batch(batchWithKeys, this.dbReqOpts);
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We prefer using for off instead of map iterators because of performance.


async batchAdd(values: T[]): Promise<void> {
// handle single value in batchPut
await this.batchPut(
Expand Down
10 changes: 9 additions & 1 deletion packages/db/src/controller/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
export type {Db, DbReqOpts, DatabaseController, FilterOptions, KeyValue} from "./interface.js";
export type {
Db,
DbReqOpts,
DatabaseController,
FilterOptions,
KeyValue,
DbBatch,
DbBatchOperation,
} from "./interface.js";
export {LevelDbController} from "./level.js";
export type {LevelDbControllerMetrics} from "./metrics.js";
4 changes: 4 additions & 0 deletions packages/db/src/controller/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export interface KeyValue<K, V> {
value: V;
}

export type DbBatchOperation<K, V> = {type: "del"; key: K} | {type: "put"; key: K; value: V};
export type DbBatch<K, V> = DbBatchOperation<K, V>[];

export interface DatabaseController<K, V> {
// service start / stop

Expand All @@ -48,6 +51,7 @@ export interface DatabaseController<K, V> {

batchPut(items: KeyValue<K, V>[], opts?: DbReqOpts): Promise<void>;
batchDelete(keys: K[], opts?: DbReqOpts): Promise<void>;
batch(batch: DbBatch<K, V>, opts?: DbReqOpts): Promise<void>;

// Iterate over entries

Expand Down
9 changes: 8 additions & 1 deletion packages/db/src/controller/level.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Logger} from "@lodestar/utils";
import {ClassicLevel} from "classic-level";
import {DatabaseController, DatabaseOptions, DbReqOpts, FilterOptions, KeyValue} from "./interface.js";
import {DatabaseController, DatabaseOptions, DbBatch, DbReqOpts, FilterOptions, KeyValue} from "./interface.js";
import {LevelDbControllerMetrics} from "./metrics.js";

enum Status {
Expand Down Expand Up @@ -143,6 +143,13 @@ export class LevelDbController implements DatabaseController<Uint8Array, Uint8Ar
return this.db.batch(keys.map((key) => ({type: "del", key: key})));
}

batch(batch: DbBatch<Uint8Array, Uint8Array>, opts?: DbReqOpts): Promise<void> {
this.metrics?.dbWriteReq.inc({bucket: opts?.bucketId ?? BUCKET_ID_UNKNOWN}, 1);
this.metrics?.dbWriteItems.inc({bucket: opts?.bucketId ?? BUCKET_ID_UNKNOWN}, batch.length);

return this.db.batch(batch);
}

keysStream(opts: FilterOptions<Uint8Array> = {}): AsyncIterable<Uint8Array> {
return this.metricsIterator(this.db.keys(opts), (key) => key, opts.bucketId ?? BUCKET_ID_UNKNOWN);
}
Expand Down
49 changes: 49 additions & 0 deletions packages/db/test/unit/controller/level.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,55 @@ describe("LevelDB controller", () => {
expect((await db.entries()).length).toBe(0);
});

it.only("test batch", async () => {
const [
{key: k1, value: v1},
{key: k2, value: v2},
{key: k3, value: v3},
{key: k4, value: v4},
{key: k5, value: v5},
] = Array.from({length: 5}, (_, i) => ({
key: Buffer.from(`test${i}`),
value: Buffer.from(`some value ${i}`),
}));
await db.put(k1, v1);
await db.put(k2, v2);
await db.put(k3, v3);

expect(await db.entries()).toEqual([
{key: k1, value: v1},
{key: k2, value: v2},
{key: k3, value: v3},
]);

await db.batch([
{
type: "del",
key: k1,
},
{
type: "put",
key: k4,
value: v4,
},
{
type: "del",
key: k3,
},
{
type: "put",
key: k5,
value: v5,
},
]);

expect(await db.entries()).toEqual([
{key: k2, value: v2},
{key: k4, value: v4},
{key: k5, value: v5},
]);
});

it("test entries", async () => {
const k1 = Buffer.from("test1");
const k2 = Buffer.from("test2");
Expand Down
Loading