feat: v0.6.1 — AriaEngine 可选自研 KVStore 后端(storageBackend: 'kv')+ KVStore APPEND 日志类型 + 10 个 aria+kv 集成测试 + 文档全量同步
CI / test (20.x) (push) Successful in 10m55s
CI / test (22.x) (push) Successful in 10m49s
CI / e2e (push) Successful in 9m54s
CI / test (18.x) (push) Successful in 10m58s
CI / test (24.x) (push) Successful in 10m41s

This commit is contained in:
thzxx
2026-08-10 14:52:38 +08:00
parent a56a496148
commit 25bab0f9aa
21 changed files with 895 additions and 42 deletions
+39 -2
View File
@@ -223,6 +223,18 @@ export class KVStore {
});
}
/**
* v0.6.1: 追加写入(value 拼接语义)— aria WAL 分片等追加型数据用。
* 日志记录 APPEND 类型(O(chunk) 高效),恢复时按 seq 顺序拼接,
* checkpoint 后快照含最终值。崩溃时该次追加全有或全无(单记录原子)。
*/
async appendValue(key: string, chunk: ArrayBuffer): Promise<void> {
if (chunk.byteLength === 0) return;
await this.enqueue(async () => {
await this.appendRecord({}, [], { [key]: chunk });
});
}
// =======================================================================
// 维护
// =======================================================================
@@ -301,9 +313,13 @@ export class KVStore {
}
/** 追加一条日志记录并更新内存索引(队列内调用,无并发) */
private async appendRecord(puts: Record<string, ArrayBuffer>, deletes: string[]): Promise<void> {
private async appendRecord(
puts: Record<string, ArrayBuffer>,
deletes: string[],
appends: Record<string, ArrayBuffer> = {},
): Promise<void> {
this.seq++;
const record = encodeLogRecord(this.seq, puts, deletes);
const record = encodeLogRecord(this.seq, puts, deletes, appends);
try {
// 日志追加:介质 append(真追加)或回退读+拼+写
const data = record.buffer.slice(record.byteOffset, record.byteOffset + record.byteLength) as ArrayBuffer;
@@ -334,6 +350,17 @@ export class KVStore {
for (const key of deletes) {
this.index.delete(key);
}
for (const [key, chunk] of Object.entries(appends)) {
const existing = this.index.get(key);
if (existing) {
const combined = new Uint8Array(existing.byteLength + chunk.byteLength);
combined.set(new Uint8Array(existing), 0);
combined.set(new Uint8Array(chunk), existing.byteLength);
this.index.set(key, combined.buffer as ArrayBuffer);
} else {
this.index.set(key, chunk);
}
}
this.logBytes += record.byteLength;
// 自动 checkpoint(日志超阈值)
@@ -358,6 +385,16 @@ export class KVStore {
for (const e of entries) {
if (e.op === KVLogOp.PUT) {
this.index.set(e.key, e.value);
} else if (e.op === KVLogOp.APPEND) {
const existing = this.index.get(e.key);
if (existing) {
const combined = new Uint8Array(existing.byteLength + e.value.byteLength);
combined.set(new Uint8Array(existing), 0);
combined.set(new Uint8Array(e.value), existing.byteLength);
this.index.set(e.key, combined.buffer as ArrayBuffer);
} else {
this.index.set(e.key, e.value);
}
} else {
this.index.delete(e.key);
}
+7
View File
@@ -23,6 +23,8 @@ import { crc32 } from '../aria/crc32';
export const enum KVLogOp {
PUT = 1,
DELETE = 2,
/** v0.6.1: 追加写入(value 拼接语义,恢复时按序 concataria WAL 分片用) */
APPEND = 3,
}
/** 解析后的日志记录 */
@@ -40,11 +42,13 @@ export interface KVLogRecord {
* @param seq 日志序号
* @param puts key → value 写入条目
* @param deletes 删除 key 列表
* @param appends key → 追加块列表(APPEND 语义,恢复时拼接)
*/
export function encodeLogRecord(
seq: number,
puts: Record<string, ArrayBuffer>,
deletes: string[] = [],
appends: Record<string, ArrayBuffer> = {},
): Uint8Array {
const encoder = new TextEncoder();
const entries: { op: KVLogOp; key: string; value: ArrayBuffer }[] = [];
@@ -54,6 +58,9 @@ export function encodeLogRecord(
for (const key of deletes) {
entries.push({ op: KVLogOp.DELETE, key, value: new ArrayBuffer(0) });
}
for (const [key, value] of Object.entries(appends)) {
entries.push({ op: KVLogOp.APPEND, key, value });
}
// 预编码 key 字节,计算总长度
const entryBytes: { op: KVLogOp; key: Uint8Array; value: Uint8Array }[] = [];