Skip to content

Commit 0265aa4

Browse files
Merge pull request #95 from oliver-oloughlin/patch/delete-many-performance-improvement
feat: improved performance of deleteMany and deleteAll
2 parents 6caaee8 + 242a65c commit 0265aa4

File tree

6 files changed

+100
-42
lines changed

6 files changed

+100
-42
lines changed

src/collection.ts

+23
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import type {
2727
} from "./types.ts"
2828
import {
2929
allFulfilled,
30+
atomicDelete,
3031
createHandlerId,
3132
createListSelector,
3233
extendKey,
@@ -440,6 +441,28 @@ export class Collection<
440441
* @returns A promise that resovles to an object containing the iterator cursor
441442
*/
442443
async deleteMany(options?: ListOptions<T1>) {
444+
// Perform quick delete if all documents are to be deleted
445+
if (
446+
!options?.consistency &&
447+
!options?.cursor &&
448+
!options?.endId &&
449+
!options?.startId &&
450+
!options?.filter &&
451+
!options?.limit
452+
) {
453+
// Create list iterator and empty keys list
454+
const iter = this.kv.list({ prefix: this._keys.baseKey }, options)
455+
const keys: Deno.KvKey[] = []
456+
457+
// Collect all collection entry keys
458+
for await (const { key } of iter) {
459+
keys.push(key)
460+
}
461+
462+
// Delete all keys and return
463+
return await atomicDelete(this.kv, keys, options?.batchSize)
464+
}
465+
443466
// Execute delete operation for each document entry
444467
const { cursor } = await this.handleMany(
445468
this._keys.idKey,

src/constants.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ export const SEGMENT_KEY_PREFIX = "__segment__"
1212
export const UNDELIVERED_KEY_PREFIX = "__undelivered__"
1313

1414
// Fixed limits
15-
export const ATOMIC_OPERATION_MUTATION_LIMIT = 20
15+
export const ATOMIC_OPERATION_MUTATION_LIMIT = 1_000
16+
17+
export const ATOMIC_OPERATION_SAFE_MUTATION_LIMIT = 20
1618

1719
export const GET_MANY_KEY_LIMIT = 10
1820

src/kvdex.ts

+12-30
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { Collection } from "./collection.ts"
2020
import { Document } from "./document.ts"
2121
import {
2222
allFulfilled,
23+
atomicDelete,
2324
createHandlerId,
2425
extendKey,
2526
parseQueueMessage,
@@ -189,7 +190,17 @@ export class KvDex<const T extends Schema<SchemaDefinition>> {
189190
* @returns Promise resolving to void.
190191
*/
191192
async deleteAll(options?: DeleteAllOptions) {
192-
return await _deleteAll(this.kv, this.schema, options)
193+
// Create iterator
194+
const iter = this.kv.list({ prefix: [KVDEX_KEY_PREFIX] })
195+
196+
// Collect all kvdex keys
197+
const keys: Deno.KvKey[] = []
198+
for await (const { key } of iter) {
199+
keys.push(key)
200+
}
201+
202+
// Delete all entries
203+
await atomicDelete(this.kv, keys, options?.atomicBatchSize)
193204
}
194205

195206
/**
@@ -514,32 +525,3 @@ async function _countAll(
514525
// Return the sum of collection counts
515526
return counts.reduce((sum, c) => sum + c, 0)
516527
}
517-
518-
/**
519-
* Delete all documents in the KV store.
520-
*
521-
* @param kv - Deno KV instance.
522-
* @param schemaOrCollection - Schema or Collection object.
523-
* @param options - DeleteAll options.
524-
* @returns Promise resolving to void.
525-
*/
526-
async function _deleteAll(
527-
kv: Deno.Kv,
528-
schemaOrCollection:
529-
| Schema<SchemaDefinition>
530-
| Collection<KvValue, CollectionOptions<KvValue>>,
531-
options?: DeleteAllOptions,
532-
) {
533-
// If input is a collection, delete all documents in the collection
534-
if (schemaOrCollection instanceof Collection) {
535-
await schemaOrCollection.deleteMany(options)
536-
return
537-
}
538-
539-
// Recursively delete all documents from schema collections
540-
await allFulfilled(
541-
Object.values(schemaOrCollection).map((val) =>
542-
_deleteAll(kv, val, options)
543-
),
544-
)
545-
}

src/types.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -208,21 +208,27 @@ export type LargeDocumentEntry = {
208208

209209
// Method Option types
210210
export type SetOptions = NonNullable<Parameters<Deno.Kv["set"]>["2"]> & {
211+
/** Number of retry attempts before returning failed operation */
211212
retry?: number
212213
}
213214

214215
export type ListOptions<T extends KvValue> = Deno.KvListOptions & {
215216
/**
216217
* Filter documents based on predicate.
217218
*
218-
* @param doc - Document
219-
* @returns true or false
219+
* @param doc - Document.
220+
* @returns true or false.
220221
*/
221222
filter?: (doc: Document<T>) => boolean
222223

224+
/** Id of document to start from. */
223225
startId?: KvId
224226

227+
/** Id of document to end at. */
225228
endId?: KvId
229+
230+
/** Batch size of atomic operations where applicable */
231+
atomicBatchSize?: number
226232
}
227233

228234
export type CountOptions<T extends KvValue> =
@@ -237,7 +243,7 @@ export type UpdateManyOptions<T extends KvValue> = ListOptions<T> & SetOptions
237243

238244
export type CountAllOptions = Pick<Deno.KvListOptions, "consistency">
239245

240-
export type DeleteAllOptions = Pick<Deno.KvListOptions, "consistency">
246+
export type DeleteAllOptions = Pick<ListOptions<KvValue>, "atomicBatchSize">
241247

242248
export type EnqueueOptions =
243249
& Omit<

src/utils.ts

+46-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
ATOMIC_OPERATION_MUTATION_LIMIT,
3+
ATOMIC_OPERATION_SAFE_MUTATION_LIMIT,
34
GET_MANY_KEY_LIMIT,
45
UNDELIVERED_KEY_PREFIX,
56
} from "./constants.ts"
@@ -321,8 +322,14 @@ export async function useAtomics<const T>(
321322
const slicedElements: T[][] = []
322323

323324
// Slice elements based on atomic mutations limit
324-
for (let i = 0; i < elements.length; i += ATOMIC_OPERATION_MUTATION_LIMIT) {
325-
slicedElements.push(elements.slice(i, i + ATOMIC_OPERATION_MUTATION_LIMIT))
325+
for (
326+
let i = 0;
327+
i < elements.length;
328+
i += ATOMIC_OPERATION_SAFE_MUTATION_LIMIT
329+
) {
330+
slicedElements.push(
331+
elements.slice(i, i + ATOMIC_OPERATION_SAFE_MUTATION_LIMIT),
332+
)
326333
}
327334

328335
// Invoke callback function for each element and execute atomic operation
@@ -479,3 +486,40 @@ export function createListSelector<const T extends KvValue>(
479486
end,
480487
}
481488
}
489+
490+
/**
491+
* Perform multiple delete operations with optimal efficiency using atomic operations.
492+
*
493+
* @param kv - Deno KV instance.
494+
* @param keys - Keys of documents to be deleted.
495+
* @param batchSize - Batch size of deletes in a single atomic operation.
496+
*/
497+
export async function atomicDelete(
498+
kv: Deno.Kv,
499+
keys: Deno.KvKey[],
500+
batchSize = ATOMIC_OPERATION_MUTATION_LIMIT / 2,
501+
) {
502+
// Initiate atomic operation and check
503+
let atomic = kv.atomic()
504+
let check = 0
505+
506+
// Loop over and add delete operation for each key
507+
for (const key of keys) {
508+
atomic.delete(key)
509+
510+
// If check is at limit, commit atomic operation
511+
if (check >= batchSize - 1) {
512+
await atomic.commit()
513+
514+
// Reset atomic operation and check
515+
atomic = kv.atomic()
516+
check = 0
517+
}
518+
519+
// Increment check
520+
check++
521+
}
522+
523+
// Commit final atomic operation
524+
await atomic.commit()
525+
}

tests/db/deleteAll.test.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,22 @@ Deno.test("db - deleteAll", async (t) => {
66
"Should delete all documents from the database",
77
async () => {
88
await useDb(async (db) => {
9-
const users = generateUsers(10)
9+
const users = generateUsers(100)
10+
const u64s = [
11+
new Deno.KvU64(0n),
12+
new Deno.KvU64(0n),
13+
]
1014

1115
const crs1 = await db.i_users.addMany(users)
1216
const crs2 = await db.l_users.addMany(users)
13-
const crs3 = await db.u64s.addMany([
14-
new Deno.KvU64(0n),
15-
new Deno.KvU64(0n),
16-
])
17+
const crs3 = await db.u64s.addMany(u64s)
1718

1819
assert(crs1.every((cr) => cr.ok))
1920
assert(crs2.every((cr) => cr.ok))
2021
assert(crs3.every((cr) => cr.ok))
2122

2223
const count1 = await db.countAll()
23-
assert(count1 === 22)
24+
assert(count1 === users.length * 2 + u64s.length)
2425

2526
await db.deleteAll()
2627

0 commit comments

Comments
 (0)