feat: v0.2.4 异步Compaction + 槽位压缩 + EXPLAIN + 慢查询日志
This commit is contained in:
@@ -67,6 +67,7 @@ export class LSM {
|
||||
private sstableStore: SSTableStore;
|
||||
private operationCount = 0;
|
||||
private initialized = false;
|
||||
private compacting = false; // 防止重复触发 compaction
|
||||
|
||||
constructor(config: LSMConfig) {
|
||||
this.memtable = new MemTable(config.memtableSizeThreshold ?? DEFAULT_MEMTABLE_SIZE);
|
||||
@@ -182,11 +183,33 @@ export class LSM {
|
||||
this.levels[0].push(meta);
|
||||
this.immutableMemtable = null;
|
||||
|
||||
if (this.levels[0].length >= 4) {
|
||||
this.compactLevelSync(0);
|
||||
// 异步触发 compaction(不阻塞当前写入)
|
||||
if (this.levels[0].length >= 4 && !this.compacting) {
|
||||
this.scheduleCompact(0);
|
||||
}
|
||||
}
|
||||
|
||||
/** 异步调度 compaction,使用 setTimeout 分片执行 */
|
||||
private scheduleCompact(level: number): void {
|
||||
if (level >= MAX_LSM_LEVELS - 1 || this.compacting) return;
|
||||
this.compacting = true;
|
||||
setTimeout(() => {
|
||||
try {
|
||||
this.compactLevelSync(level);
|
||||
} finally {
|
||||
this.compacting = false;
|
||||
// 连续触发:如果 compaction 后仍然超标,继续调度
|
||||
if (this.levels[level].length >= 4) {
|
||||
this.scheduleCompact(level);
|
||||
}
|
||||
// 检查下一级是否需要 compaction
|
||||
if (level + 1 < MAX_LSM_LEVELS - 1 && this.levels[level + 1].length >= 4) {
|
||||
this.scheduleCompact(level + 1);
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// =======================================================================
|
||||
// 读取
|
||||
// =======================================================================
|
||||
|
||||
@@ -128,6 +128,52 @@ export function freeSlot(buf: ArrayBuffer, slotIndex: number): void {
|
||||
setSlotEntry(buf, slotIndex, { offset: 0, length: 0 });
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩页面槽位:移除已删除 slot,整理碎片空间。
|
||||
* 将有效数据紧凑排列,释放空洞。
|
||||
*/
|
||||
export function compactSlots(buf: ArrayBuffer): number {
|
||||
const view = new DataView(buf);
|
||||
const slotCount = view.getUint16(9, false);
|
||||
if (slotCount === 0) return 0;
|
||||
|
||||
// 收集有效 slot(offset>0 的)
|
||||
const validSlots: { index: number; offset: number; length: number; data: Uint8Array }[] = [];
|
||||
for (let i = 0; i < slotCount; i++) {
|
||||
const entry = getSlotEntry(buf, i);
|
||||
if (entry.offset > 0 && entry.length > 0) {
|
||||
const data = new Uint8Array(buf, entry.offset, entry.length);
|
||||
validSlots.push({ index: i, offset: entry.offset, length: entry.length, data: new Uint8Array(data) });
|
||||
}
|
||||
}
|
||||
|
||||
if (validSlots.length === slotCount) return 0; // 无碎片
|
||||
|
||||
// 从页面底部重新紧凑排列
|
||||
let dataEnd = PAGE_SIZE;
|
||||
const newSlots: { offset: number; length: number }[] = [];
|
||||
|
||||
for (let i = validSlots.length - 1; i >= 0; i--) {
|
||||
const s = validSlots[i];
|
||||
dataEnd -= s.length;
|
||||
new Uint8Array(buf).set(s.data, dataEnd);
|
||||
newSlots.unshift({ offset: dataEnd, length: s.length });
|
||||
}
|
||||
|
||||
// 重写 slot directory
|
||||
view.setUint16(9, validSlots.length, false); // slotCount
|
||||
view.setUint16(7, dataEnd, false); // freeEnd
|
||||
for (let i = 0; i < validSlots.length; i++) {
|
||||
setSlotEntry(buf, i, newSlots[i]);
|
||||
}
|
||||
// 清除剩余 slot 条目
|
||||
for (let i = validSlots.length; i < slotCount; i++) {
|
||||
setSlotEntry(buf, i, { offset: 0, length: 0 });
|
||||
}
|
||||
|
||||
return slotCount - validSlots.length; // 回收的 slot 数量
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取指定 slot 的行数据。
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user