dataply 0.0.26-alpha.5 → 0.0.26-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js
CHANGED
|
@@ -9553,45 +9553,27 @@ var WALManager = class {
|
|
|
9553
9553
|
|
|
9554
9554
|
// src/core/PageMVCCStrategy.ts
|
|
9555
9555
|
var import_node_fs2 = __toESM(require("node:fs"));
|
|
9556
|
-
var PageMVCCStrategy = class {
|
|
9557
|
-
constructor(fileHandle, pageSize
|
|
9556
|
+
var PageMVCCStrategy = class extends AsyncMVCCStrategy2 {
|
|
9557
|
+
constructor(fileHandle, pageSize) {
|
|
9558
|
+
super();
|
|
9558
9559
|
this.fileHandle = fileHandle;
|
|
9559
9560
|
this.pageSize = pageSize;
|
|
9560
|
-
this.cache = new LRUMap2(cacheCapacity);
|
|
9561
9561
|
this.fileSize = import_node_fs2.default.fstatSync(fileHandle).size;
|
|
9562
9562
|
}
|
|
9563
|
-
/** LRU 캐시 (페이지 ID -> 페이지 데이터) */
|
|
9564
|
-
cache;
|
|
9565
|
-
/** 디스크에 기록되지 않은 변경된 페이지들 (페이지 ID -> 페이지 데이터) */
|
|
9566
|
-
dirtyPages = /* @__PURE__ */ new Map();
|
|
9567
9563
|
/** 파일 크기 (논리적) */
|
|
9568
9564
|
fileSize;
|
|
9569
9565
|
/**
|
|
9570
9566
|
* 디스크에서 페이지를 읽습니다.
|
|
9571
|
-
* 캐시에 있으면 캐시에서 반환합니다.
|
|
9572
9567
|
* @param pageId 페이지 ID
|
|
9573
9568
|
* @returns 페이지 데이터
|
|
9574
9569
|
*/
|
|
9575
9570
|
async read(pageId) {
|
|
9576
|
-
const dirty = this.dirtyPages.get(pageId);
|
|
9577
|
-
if (dirty) {
|
|
9578
|
-
const copy = new Uint8Array(this.pageSize);
|
|
9579
|
-
copy.set(dirty);
|
|
9580
|
-
return copy;
|
|
9581
|
-
}
|
|
9582
|
-
const cached = this.cache.get(pageId);
|
|
9583
|
-
if (cached) {
|
|
9584
|
-
return cached;
|
|
9585
|
-
}
|
|
9586
9571
|
const buffer = new Uint8Array(this.pageSize);
|
|
9587
9572
|
const pageStartPos = pageId * this.pageSize;
|
|
9588
9573
|
if (pageStartPos >= this.fileSize) {
|
|
9589
9574
|
return buffer;
|
|
9590
9575
|
}
|
|
9591
9576
|
await this._readFromDisk(buffer, pageStartPos);
|
|
9592
|
-
const cacheCopy = new Uint8Array(this.pageSize);
|
|
9593
|
-
cacheCopy.set(buffer);
|
|
9594
|
-
this.cache.set(pageId, cacheCopy);
|
|
9595
9577
|
return buffer;
|
|
9596
9578
|
}
|
|
9597
9579
|
/**
|
|
@@ -9601,47 +9583,27 @@ var PageMVCCStrategy = class {
|
|
|
9601
9583
|
*/
|
|
9602
9584
|
async write(pageId, data) {
|
|
9603
9585
|
const pageStartPos = pageId * this.pageSize;
|
|
9604
|
-
|
|
9605
|
-
dataCopy.set(data);
|
|
9606
|
-
this.dirtyPages.set(pageId, dataCopy);
|
|
9607
|
-
this.cache.set(pageId, dataCopy);
|
|
9586
|
+
await this._writeToDisk(data, pageStartPos);
|
|
9608
9587
|
const endPosition = pageStartPos + this.pageSize;
|
|
9609
9588
|
if (endPosition > this.fileSize) {
|
|
9610
9589
|
this.fileSize = endPosition;
|
|
9611
9590
|
}
|
|
9612
9591
|
}
|
|
9613
9592
|
/**
|
|
9614
|
-
*
|
|
9615
|
-
*
|
|
9593
|
+
* 페이지 삭제.
|
|
9594
|
+
* 실제 페이지 해제는 상위 레이어(FreeList)에서 관리합니다.
|
|
9595
|
+
* @param pageId 페이지 ID
|
|
9616
9596
|
*/
|
|
9617
|
-
async
|
|
9618
|
-
if (this.dirtyPages.size === 0) {
|
|
9619
|
-
return;
|
|
9620
|
-
}
|
|
9621
|
-
const snapshot = new Map(this.dirtyPages);
|
|
9622
|
-
const sortedPageIds = Array.from(snapshot.keys()).sort((a, b) => a - b);
|
|
9623
|
-
for (const pageId of sortedPageIds) {
|
|
9624
|
-
const data = snapshot.get(pageId);
|
|
9625
|
-
const position = pageId * this.pageSize;
|
|
9626
|
-
await this._writeToDisk(data, position);
|
|
9627
|
-
this.dirtyPages.delete(pageId);
|
|
9628
|
-
}
|
|
9597
|
+
async delete(pageId) {
|
|
9629
9598
|
}
|
|
9630
9599
|
/**
|
|
9631
|
-
*
|
|
9632
|
-
*
|
|
9633
|
-
* @
|
|
9600
|
+
* 페이지 존재 여부 확인
|
|
9601
|
+
* @param pageId 페이지 ID
|
|
9602
|
+
* @returns 존재하면 true
|
|
9634
9603
|
*/
|
|
9635
|
-
async
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
}
|
|
9639
|
-
const sortedPageIds = Array.from(pages.keys()).sort((a, b) => a - b);
|
|
9640
|
-
for (const pageId of sortedPageIds) {
|
|
9641
|
-
const data = pages.get(pageId);
|
|
9642
|
-
const position = pageId * this.pageSize;
|
|
9643
|
-
await this._writeToDisk(data, position);
|
|
9644
|
-
}
|
|
9604
|
+
async exists(pageId) {
|
|
9605
|
+
const pageStartPos = pageId * this.pageSize;
|
|
9606
|
+
return pageStartPos < this.fileSize;
|
|
9645
9607
|
}
|
|
9646
9608
|
/**
|
|
9647
9609
|
* 메인 DB 파일의 물리적 동기화를 수행합니다 (fsync).
|
|
@@ -9654,39 +9616,12 @@ var PageMVCCStrategy = class {
|
|
|
9654
9616
|
});
|
|
9655
9617
|
});
|
|
9656
9618
|
}
|
|
9657
|
-
/**
|
|
9658
|
-
* 페이지 삭제 (실제로는 캐시에서만 제거)
|
|
9659
|
-
* 실제 페이지 해제는 상위 레이어(FreeList)에서 관리합니다.
|
|
9660
|
-
* @param pageId 페이지 ID
|
|
9661
|
-
*/
|
|
9662
|
-
async delete(pageId) {
|
|
9663
|
-
this.dirtyPages.delete(pageId);
|
|
9664
|
-
this.cache.delete(pageId);
|
|
9665
|
-
}
|
|
9666
|
-
/**
|
|
9667
|
-
* 페이지 존재 여부 확인
|
|
9668
|
-
* @param pageId 페이지 ID
|
|
9669
|
-
* @returns 존재하면 true
|
|
9670
|
-
*/
|
|
9671
|
-
async exists(pageId) {
|
|
9672
|
-
if (this.dirtyPages.has(pageId)) {
|
|
9673
|
-
return true;
|
|
9674
|
-
}
|
|
9675
|
-
const pageStartPos = pageId * this.pageSize;
|
|
9676
|
-
return pageStartPos < this.fileSize;
|
|
9677
|
-
}
|
|
9678
9619
|
/**
|
|
9679
9620
|
* 현재 파일 크기 반환
|
|
9680
9621
|
*/
|
|
9681
9622
|
getFileSize() {
|
|
9682
9623
|
return this.fileSize;
|
|
9683
9624
|
}
|
|
9684
|
-
/**
|
|
9685
|
-
* 캐시 초기화
|
|
9686
|
-
*/
|
|
9687
|
-
clearCache() {
|
|
9688
|
-
this.cache.clear();
|
|
9689
|
-
}
|
|
9690
9625
|
// ─────────────────────────────────────────────────────────────
|
|
9691
9626
|
// Private helpers
|
|
9692
9627
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -9722,13 +9657,15 @@ var PageFileSystem = class {
|
|
|
9722
9657
|
const walPath = options.wal;
|
|
9723
9658
|
this.walManager = walPath && walLogger ? new WALManager(walPath, pageSize, walLogger) : null;
|
|
9724
9659
|
this.pageManagerFactory = new PageManagerFactory();
|
|
9725
|
-
this.pageStrategy = new PageMVCCStrategy(fileHandle, pageSize
|
|
9660
|
+
this.pageStrategy = new PageMVCCStrategy(fileHandle, pageSize);
|
|
9661
|
+
this.rootTransaction = new AsyncMVCCTransaction2(this.pageStrategy, { cacheCapacity: pageCacheCapacity });
|
|
9726
9662
|
this.logger = logger;
|
|
9727
9663
|
}
|
|
9728
9664
|
pageFactory = new PageManagerFactory();
|
|
9729
9665
|
walManager;
|
|
9730
9666
|
pageManagerFactory;
|
|
9731
9667
|
pageStrategy;
|
|
9668
|
+
rootTransaction;
|
|
9732
9669
|
logger;
|
|
9733
9670
|
/** 글로벌 동기화(체크포인트/커밋)를 위한 Mutex */
|
|
9734
9671
|
lockPromise = Promise.resolve();
|
|
@@ -9769,6 +9706,12 @@ var PageFileSystem = class {
|
|
|
9769
9706
|
getPageStrategy() {
|
|
9770
9707
|
return this.pageStrategy;
|
|
9771
9708
|
}
|
|
9709
|
+
/**
|
|
9710
|
+
* Returns the root MVCC transaction.
|
|
9711
|
+
*/
|
|
9712
|
+
getRootTransaction() {
|
|
9713
|
+
return this.rootTransaction;
|
|
9714
|
+
}
|
|
9772
9715
|
/**
|
|
9773
9716
|
* Updates the bitmap status for a specific page.
|
|
9774
9717
|
* @param pageId The ID of the page to update
|
|
@@ -10066,27 +10009,14 @@ var PageFileSystem = class {
|
|
|
10066
10009
|
metadataManager.setFreePageId(metadata, pageId);
|
|
10067
10010
|
await this.setPage(0, metadata, tx);
|
|
10068
10011
|
}
|
|
10069
|
-
/**
|
|
10070
|
-
* WAL에 커밋합니다.
|
|
10071
|
-
* @param dirtyPages 변경된 페이지들
|
|
10072
|
-
*/
|
|
10073
|
-
async commitToWAL(dirtyPages) {
|
|
10074
|
-
if (this.walManager) {
|
|
10075
|
-
this.logger.debug(`Committing ${dirtyPages.size} pages to WAL`);
|
|
10076
|
-
await this.walManager.prepareCommit(dirtyPages);
|
|
10077
|
-
await this.walManager.finalizeCommit(true);
|
|
10078
|
-
}
|
|
10079
|
-
}
|
|
10080
10012
|
/**
|
|
10081
10013
|
* 체크포인트를 수행합니다.
|
|
10082
|
-
* 1.
|
|
10083
|
-
* 2.
|
|
10084
|
-
* 3. WAL 로그 파일 비우기 (Clear/Truncate)
|
|
10014
|
+
* 1. DB 파일 물리적 동기화 (Sync/fsync)
|
|
10015
|
+
* 2. WAL 로그 파일 비우기 (Clear/Truncate)
|
|
10085
10016
|
*/
|
|
10086
10017
|
async checkpoint() {
|
|
10087
10018
|
this.logger.info("Starting checkpoint");
|
|
10088
10019
|
await this.runGlobalLock(async () => {
|
|
10089
|
-
await this.pageStrategy.flush();
|
|
10090
10020
|
await this.pageStrategy.sync();
|
|
10091
10021
|
if (this.walManager) {
|
|
10092
10022
|
await this.walManager.clear();
|
|
@@ -10891,17 +10821,16 @@ var Transaction = class {
|
|
|
10891
10821
|
/**
|
|
10892
10822
|
* @param id Transaction ID
|
|
10893
10823
|
* @param context Transaction context
|
|
10894
|
-
* @param
|
|
10824
|
+
* @param rootTx Root MVCC Transaction
|
|
10895
10825
|
* @param lockManager LockManager instance
|
|
10896
10826
|
* @param pfs Page File System
|
|
10897
|
-
* @param reloadBPTree Callback to reload BPTree cache on rollback
|
|
10898
10827
|
*/
|
|
10899
|
-
constructor(id, context,
|
|
10828
|
+
constructor(id, context, rootTx, lockManager, pfs) {
|
|
10900
10829
|
this.context = context;
|
|
10901
10830
|
this.lockManager = lockManager;
|
|
10902
10831
|
this.pfs = pfs;
|
|
10903
10832
|
this.id = id;
|
|
10904
|
-
this.
|
|
10833
|
+
this.rootTx = rootTx;
|
|
10905
10834
|
}
|
|
10906
10835
|
/** Transaction ID */
|
|
10907
10836
|
id;
|
|
@@ -10909,16 +10838,25 @@ var Transaction = class {
|
|
|
10909
10838
|
heldLocks = /* @__PURE__ */ new Set();
|
|
10910
10839
|
/** Held page locks (PageID -> LockID) */
|
|
10911
10840
|
pageLocks = /* @__PURE__ */ new Map();
|
|
10912
|
-
/** Dirty Pages modified by the transaction: PageID -> Modified Page Buffer */
|
|
10913
|
-
dirtyPages = /* @__PURE__ */ new Map();
|
|
10914
|
-
/** Undo pages: PageID -> Original Page Buffer (Snapshot) */
|
|
10915
|
-
undoPages = /* @__PURE__ */ new Map();
|
|
10916
10841
|
/** List of callbacks to execute on commit */
|
|
10917
10842
|
commitHooks = [];
|
|
10918
|
-
/**
|
|
10919
|
-
|
|
10843
|
+
/** Nested MVCC Transaction for snapshot isolation (lazy init) */
|
|
10844
|
+
mvccTx = null;
|
|
10845
|
+
/** Root MVCC Transaction reference */
|
|
10846
|
+
rootTx;
|
|
10920
10847
|
/** Release function for global write lock, set by DataplyAPI */
|
|
10921
10848
|
_writeLockRelease = null;
|
|
10849
|
+
/**
|
|
10850
|
+
* Lazily initializes the nested MVCC transaction.
|
|
10851
|
+
* This ensures the snapshot is taken at the time of first access,
|
|
10852
|
+
* picking up the latest committed root version.
|
|
10853
|
+
*/
|
|
10854
|
+
ensureMvccTx() {
|
|
10855
|
+
if (!this.mvccTx) {
|
|
10856
|
+
this.mvccTx = this.rootTx.createNested();
|
|
10857
|
+
}
|
|
10858
|
+
return this.mvccTx;
|
|
10859
|
+
}
|
|
10922
10860
|
/**
|
|
10923
10861
|
* Registers a commit hook.
|
|
10924
10862
|
* @param hook Function to execute
|
|
@@ -10940,30 +10878,35 @@ var Transaction = class {
|
|
|
10940
10878
|
return this._writeLockRelease !== null;
|
|
10941
10879
|
}
|
|
10942
10880
|
/**
|
|
10943
|
-
* Reads a page
|
|
10881
|
+
* Reads a page through the MVCC transaction.
|
|
10944
10882
|
* @param pageId Page ID
|
|
10945
10883
|
* @returns Page data
|
|
10946
10884
|
*/
|
|
10947
10885
|
async readPage(pageId) {
|
|
10948
|
-
const
|
|
10949
|
-
|
|
10950
|
-
|
|
10886
|
+
const tx = this.ensureMvccTx();
|
|
10887
|
+
const data = await tx.read(pageId);
|
|
10888
|
+
if (data === null) {
|
|
10889
|
+
return new Uint8Array(this.pfs.pageSize);
|
|
10951
10890
|
}
|
|
10952
|
-
|
|
10891
|
+
const copy = new Uint8Array(data.length);
|
|
10892
|
+
copy.set(data);
|
|
10893
|
+
return copy;
|
|
10953
10894
|
}
|
|
10954
10895
|
/**
|
|
10955
|
-
* Writes a page
|
|
10896
|
+
* Writes a page through the MVCC transaction.
|
|
10956
10897
|
* @param pageId Page ID
|
|
10957
10898
|
* @param data Page data
|
|
10958
10899
|
*/
|
|
10959
10900
|
async writePage(pageId, data) {
|
|
10960
|
-
|
|
10961
|
-
|
|
10962
|
-
|
|
10963
|
-
|
|
10964
|
-
|
|
10901
|
+
const tx = this.ensureMvccTx();
|
|
10902
|
+
const exists = await tx.exists(pageId);
|
|
10903
|
+
if (exists) {
|
|
10904
|
+
const copy = new Uint8Array(data.length);
|
|
10905
|
+
copy.set(data);
|
|
10906
|
+
await tx.write(pageId, copy);
|
|
10907
|
+
} else {
|
|
10908
|
+
await tx.create(pageId, data);
|
|
10965
10909
|
}
|
|
10966
|
-
this.dirtyPages.set(pageId, data);
|
|
10967
10910
|
}
|
|
10968
10911
|
/**
|
|
10969
10912
|
* Acquires a write lock.
|
|
@@ -10990,21 +10933,31 @@ var Transaction = class {
|
|
|
10990
10933
|
await hook();
|
|
10991
10934
|
}
|
|
10992
10935
|
});
|
|
10936
|
+
const tx = this.ensureMvccTx();
|
|
10993
10937
|
let shouldTriggerCheckpoint = false;
|
|
10994
10938
|
await this.pfs.runGlobalLock(async () => {
|
|
10995
|
-
|
|
10996
|
-
|
|
10939
|
+
const entries = tx.getResultEntries();
|
|
10940
|
+
const dirtyPages = /* @__PURE__ */ new Map();
|
|
10941
|
+
for (const entry of [...entries.created, ...entries.updated]) {
|
|
10942
|
+
dirtyPages.set(entry.key, entry.data);
|
|
10943
|
+
}
|
|
10944
|
+
const hasDirtyPages = dirtyPages.size > 0;
|
|
10945
|
+
if (this.pfs.wal && hasDirtyPages) {
|
|
10946
|
+
await this.pfs.wal.prepareCommit(dirtyPages);
|
|
10997
10947
|
await this.pfs.wal.writeCommitMarker();
|
|
10998
10948
|
}
|
|
10999
|
-
|
|
11000
|
-
|
|
10949
|
+
await tx.commit();
|
|
10950
|
+
if (hasDirtyPages) {
|
|
10951
|
+
await this.rootTx.commit();
|
|
11001
10952
|
}
|
|
11002
|
-
if (
|
|
11003
|
-
|
|
11004
|
-
|
|
11005
|
-
|
|
11006
|
-
|
|
11007
|
-
|
|
10953
|
+
if (hasDirtyPages) {
|
|
10954
|
+
if (!this.pfs.wal) {
|
|
10955
|
+
await this.pfs.strategy.sync();
|
|
10956
|
+
} else {
|
|
10957
|
+
this.pfs.wal.incrementWrittenPages(dirtyPages.size);
|
|
10958
|
+
if (this.pfs.wal.shouldCheckpoint(this.pfs.options.walCheckpointThreshold)) {
|
|
10959
|
+
shouldTriggerCheckpoint = true;
|
|
10960
|
+
}
|
|
11008
10961
|
}
|
|
11009
10962
|
}
|
|
11010
10963
|
});
|
|
@@ -11012,8 +10965,6 @@ var Transaction = class {
|
|
|
11012
10965
|
await this.pfs.checkpoint();
|
|
11013
10966
|
}
|
|
11014
10967
|
} finally {
|
|
11015
|
-
this.dirtyPages.clear();
|
|
11016
|
-
this.undoPages.clear();
|
|
11017
10968
|
this.releaseAllLocks();
|
|
11018
10969
|
if (this._writeLockRelease) {
|
|
11019
10970
|
this._writeLockRelease();
|
|
@@ -11026,8 +10977,9 @@ var Transaction = class {
|
|
|
11026
10977
|
*/
|
|
11027
10978
|
async rollback() {
|
|
11028
10979
|
try {
|
|
11029
|
-
this.
|
|
11030
|
-
|
|
10980
|
+
if (this.mvccTx) {
|
|
10981
|
+
this.mvccTx.rollback();
|
|
10982
|
+
}
|
|
11031
10983
|
this.releaseAllLocks();
|
|
11032
10984
|
} finally {
|
|
11033
10985
|
if (this._writeLockRelease) {
|
|
@@ -11036,12 +10988,6 @@ var Transaction = class {
|
|
|
11036
10988
|
}
|
|
11037
10989
|
}
|
|
11038
10990
|
}
|
|
11039
|
-
/**
|
|
11040
|
-
* Returns the dirty pages map.
|
|
11041
|
-
*/
|
|
11042
|
-
__getDirtyPages() {
|
|
11043
|
-
return this.dirtyPages;
|
|
11044
|
-
}
|
|
11045
10991
|
/**
|
|
11046
10992
|
* Releases all locks.
|
|
11047
10993
|
*/
|
|
@@ -11133,6 +11079,7 @@ var DataplyAPI = class {
|
|
|
11133
11079
|
constructor(file, options) {
|
|
11134
11080
|
this.file = file;
|
|
11135
11081
|
this.hook = useHookall(this);
|
|
11082
|
+
this.latcher = new Ryoiki3();
|
|
11136
11083
|
this.options = this.verboseOptions(options);
|
|
11137
11084
|
this.loggerManager = new LoggerManager(this.options.logLevel);
|
|
11138
11085
|
this.logger = this.loggerManager.create("DataplyAPI");
|
|
@@ -11185,6 +11132,38 @@ var DataplyAPI = class {
|
|
|
11185
11132
|
txIdCounter;
|
|
11186
11133
|
/** Promise-chain mutex for serializing write operations */
|
|
11187
11134
|
writeQueue = Promise.resolve();
|
|
11135
|
+
/** Lock manager. Used for managing transactions */
|
|
11136
|
+
latcher;
|
|
11137
|
+
/**
|
|
11138
|
+
* Acquire a read lock on the given page ID and execute the given function.
|
|
11139
|
+
* @param pageId Page ID to acquire a read lock on
|
|
11140
|
+
* @param fn Function to execute while holding the read lock
|
|
11141
|
+
* @returns The result of the given function
|
|
11142
|
+
*/
|
|
11143
|
+
async latchReadLock(pageId, fn) {
|
|
11144
|
+
let lockId;
|
|
11145
|
+
return this.latcher.readLock(this.latcher.range(pageId, 1), async (_lockId) => {
|
|
11146
|
+
lockId = _lockId;
|
|
11147
|
+
return fn();
|
|
11148
|
+
}).finally(() => {
|
|
11149
|
+
this.latcher.readUnlock(lockId);
|
|
11150
|
+
});
|
|
11151
|
+
}
|
|
11152
|
+
/**
|
|
11153
|
+
* Acquire a write lock on the given page ID and execute the given function.
|
|
11154
|
+
* @param pageId Page ID to acquire a write lock on
|
|
11155
|
+
* @param fn Function to execute while holding the write lock
|
|
11156
|
+
* @returns The result of the given function
|
|
11157
|
+
*/
|
|
11158
|
+
async latchWriteLock(pageId, fn) {
|
|
11159
|
+
let lockId;
|
|
11160
|
+
return this.latcher.writeLock(this.latcher.range(pageId, 1), async (_lockId) => {
|
|
11161
|
+
lockId = _lockId;
|
|
11162
|
+
return fn();
|
|
11163
|
+
}).finally(() => {
|
|
11164
|
+
this.latcher.writeUnlock(lockId);
|
|
11165
|
+
});
|
|
11166
|
+
}
|
|
11188
11167
|
/**
|
|
11189
11168
|
* Verifies if the page file is a valid Dataply file.
|
|
11190
11169
|
* The metadata page must be located at the beginning of the Dataply file.
|
|
@@ -11213,7 +11192,7 @@ var DataplyAPI = class {
|
|
|
11213
11192
|
pagePreallocationCount: 1e3,
|
|
11214
11193
|
wal: null,
|
|
11215
11194
|
walCheckpointThreshold: 1e3,
|
|
11216
|
-
logLevel:
|
|
11195
|
+
logLevel: 0 /* None */
|
|
11217
11196
|
}, options);
|
|
11218
11197
|
}
|
|
11219
11198
|
/**
|
|
@@ -11334,7 +11313,7 @@ var DataplyAPI = class {
|
|
|
11334
11313
|
return new Transaction(
|
|
11335
11314
|
++this.txIdCounter,
|
|
11336
11315
|
this.txContext,
|
|
11337
|
-
this.pfs.
|
|
11316
|
+
this.pfs.getRootTransaction(),
|
|
11338
11317
|
this.lockManager,
|
|
11339
11318
|
this.pfs
|
|
11340
11319
|
);
|
|
@@ -11354,7 +11333,7 @@ var DataplyAPI = class {
|
|
|
11354
11333
|
* Used internally by runWithDefaultWrite.
|
|
11355
11334
|
* @returns A release function
|
|
11356
11335
|
*/
|
|
11357
|
-
acquireWriteLock() {
|
|
11336
|
+
async acquireWriteLock() {
|
|
11358
11337
|
this.logger.debug("Acquiring write lock");
|
|
11359
11338
|
const previous = this.writeQueue;
|
|
11360
11339
|
let release;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type DataplyOptions, type DataplyMetadata } from '../types';
|
|
2
2
|
import { type IHookall } from 'hookall';
|
|
3
|
+
import { Ryoiki } from 'ryoiki';
|
|
3
4
|
import { PageFileSystem } from './PageFileSystem';
|
|
4
5
|
import { RowTableEngine } from './RowTableEngine';
|
|
5
6
|
import { TextCodec } from '../utils/TextCodec';
|
|
@@ -47,7 +48,23 @@ export declare class DataplyAPI {
|
|
|
47
48
|
private txIdCounter;
|
|
48
49
|
/** Promise-chain mutex for serializing write operations */
|
|
49
50
|
private writeQueue;
|
|
51
|
+
/** Lock manager. Used for managing transactions */
|
|
52
|
+
protected readonly latcher: Ryoiki;
|
|
50
53
|
constructor(file: string, options: DataplyOptions);
|
|
54
|
+
/**
|
|
55
|
+
* Acquire a read lock on the given page ID and execute the given function.
|
|
56
|
+
* @param pageId Page ID to acquire a read lock on
|
|
57
|
+
* @param fn Function to execute while holding the read lock
|
|
58
|
+
* @returns The result of the given function
|
|
59
|
+
*/
|
|
60
|
+
latchReadLock<T>(pageId: number, fn: () => Promise<T>): Promise<T>;
|
|
61
|
+
/**
|
|
62
|
+
* Acquire a write lock on the given page ID and execute the given function.
|
|
63
|
+
* @param pageId Page ID to acquire a write lock on
|
|
64
|
+
* @param fn Function to execute while holding the write lock
|
|
65
|
+
* @returns The result of the given function
|
|
66
|
+
*/
|
|
67
|
+
latchWriteLock<T>(pageId: number, fn: () => Promise<T>): Promise<T>;
|
|
51
68
|
/**
|
|
52
69
|
* Verifies if the page file is a valid Dataply file.
|
|
53
70
|
* The metadata page must be located at the beginning of the Dataply file.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IndexPage, MetadataPage, DataplyOptions } from '../types';
|
|
2
2
|
import type { Transaction } from './transaction/Transaction';
|
|
3
|
+
import { AsyncMVCCTransaction } from 'mvcc-api';
|
|
3
4
|
import { PageManagerFactory } from './Page';
|
|
4
5
|
import { WALManager } from './WALManager';
|
|
5
6
|
import { PageMVCCStrategy } from './PageMVCCStrategy';
|
|
@@ -17,6 +18,7 @@ export declare class PageFileSystem {
|
|
|
17
18
|
protected readonly walManager: WALManager | null;
|
|
18
19
|
protected readonly pageManagerFactory: PageManagerFactory;
|
|
19
20
|
protected readonly pageStrategy: PageMVCCStrategy;
|
|
21
|
+
protected readonly rootTransaction: AsyncMVCCTransaction<PageMVCCStrategy, number, Uint8Array>;
|
|
20
22
|
protected readonly logger: Logger;
|
|
21
23
|
/** 글로벌 동기화(체크포인트/커밋)를 위한 Mutex */
|
|
22
24
|
private lockPromise;
|
|
@@ -39,6 +41,10 @@ export declare class PageFileSystem {
|
|
|
39
41
|
* Returns the page strategy for transaction use.
|
|
40
42
|
*/
|
|
41
43
|
getPageStrategy(): PageMVCCStrategy;
|
|
44
|
+
/**
|
|
45
|
+
* Returns the root MVCC transaction.
|
|
46
|
+
*/
|
|
47
|
+
getRootTransaction(): AsyncMVCCTransaction<PageMVCCStrategy, number, Uint8Array>;
|
|
42
48
|
/**
|
|
43
49
|
* Updates the bitmap status for a specific page.
|
|
44
50
|
* @param pageId The ID of the page to update
|
|
@@ -134,16 +140,10 @@ export declare class PageFileSystem {
|
|
|
134
140
|
* @param tx Transaction
|
|
135
141
|
*/
|
|
136
142
|
setFreePage(pageId: number, tx: Transaction): Promise<void>;
|
|
137
|
-
/**
|
|
138
|
-
* WAL에 커밋합니다.
|
|
139
|
-
* @param dirtyPages 변경된 페이지들
|
|
140
|
-
*/
|
|
141
|
-
commitToWAL(dirtyPages: Map<number, Uint8Array>): Promise<void>;
|
|
142
143
|
/**
|
|
143
144
|
* 체크포인트를 수행합니다.
|
|
144
|
-
* 1.
|
|
145
|
-
* 2.
|
|
146
|
-
* 3. WAL 로그 파일 비우기 (Clear/Truncate)
|
|
145
|
+
* 1. DB 파일 물리적 동기화 (Sync/fsync)
|
|
146
|
+
* 2. WAL 로그 파일 비우기 (Clear/Truncate)
|
|
147
147
|
*/
|
|
148
148
|
checkpoint(): Promise<void>;
|
|
149
149
|
/**
|
|
@@ -1,23 +1,20 @@
|
|
|
1
|
+
import { AsyncMVCCStrategy } from 'mvcc-api';
|
|
1
2
|
/**
|
|
2
3
|
* 페이지 수준 MVCC Strategy.
|
|
3
4
|
* mvcc-api의 AsyncMVCCStrategy를 상속하여 디스크 I/O를 담당합니다.
|
|
5
|
+
* 캐시 및 버퍼 관리는 mvcc-api의 AsyncMVCCTransaction이 담당합니다.
|
|
4
6
|
*
|
|
5
7
|
* 키: 페이지 ID (number)
|
|
6
8
|
* 값: 페이지 데이터 (Uint8Array)
|
|
7
9
|
*/
|
|
8
|
-
export declare class PageMVCCStrategy {
|
|
10
|
+
export declare class PageMVCCStrategy extends AsyncMVCCStrategy<number, Uint8Array> {
|
|
9
11
|
private readonly fileHandle;
|
|
10
12
|
private readonly pageSize;
|
|
11
|
-
/** LRU 캐시 (페이지 ID -> 페이지 데이터) */
|
|
12
|
-
private readonly cache;
|
|
13
|
-
/** 디스크에 기록되지 않은 변경된 페이지들 (페이지 ID -> 페이지 데이터) */
|
|
14
|
-
private readonly dirtyPages;
|
|
15
13
|
/** 파일 크기 (논리적) */
|
|
16
14
|
private fileSize;
|
|
17
|
-
constructor(fileHandle: number, pageSize: number
|
|
15
|
+
constructor(fileHandle: number, pageSize: number);
|
|
18
16
|
/**
|
|
19
17
|
* 디스크에서 페이지를 읽습니다.
|
|
20
|
-
* 캐시에 있으면 캐시에서 반환합니다.
|
|
21
18
|
* @param pageId 페이지 ID
|
|
22
19
|
* @returns 페이지 데이터
|
|
23
20
|
*/
|
|
@@ -29,22 +26,7 @@ export declare class PageMVCCStrategy {
|
|
|
29
26
|
*/
|
|
30
27
|
write(pageId: number, data: Uint8Array): Promise<void>;
|
|
31
28
|
/**
|
|
32
|
-
*
|
|
33
|
-
* WAL 체크포인트 시점에 호출되어야 합니다.
|
|
34
|
-
*/
|
|
35
|
-
flush(): Promise<void>;
|
|
36
|
-
/**
|
|
37
|
-
* 지정된 페이지들만 디스크에 기록합니다.
|
|
38
|
-
* WAL 없이 트랜잭션 커밋 시, 해당 트랜잭션의 dirty pages만 선택적으로 flush합니다.
|
|
39
|
-
* @param pages 기록할 페이지 맵 (PageID -> PageData)
|
|
40
|
-
*/
|
|
41
|
-
flushPages(pages: Map<number, Uint8Array>): Promise<void>;
|
|
42
|
-
/**
|
|
43
|
-
* 메인 DB 파일의 물리적 동기화를 수행합니다 (fsync).
|
|
44
|
-
*/
|
|
45
|
-
sync(): Promise<void>;
|
|
46
|
-
/**
|
|
47
|
-
* 페이지 삭제 (실제로는 캐시에서만 제거)
|
|
29
|
+
* 페이지 삭제.
|
|
48
30
|
* 실제 페이지 해제는 상위 레이어(FreeList)에서 관리합니다.
|
|
49
31
|
* @param pageId 페이지 ID
|
|
50
32
|
*/
|
|
@@ -56,13 +38,13 @@ export declare class PageMVCCStrategy {
|
|
|
56
38
|
*/
|
|
57
39
|
exists(pageId: number): Promise<boolean>;
|
|
58
40
|
/**
|
|
59
|
-
*
|
|
41
|
+
* 메인 DB 파일의 물리적 동기화를 수행합니다 (fsync).
|
|
60
42
|
*/
|
|
61
|
-
|
|
43
|
+
sync(): Promise<void>;
|
|
62
44
|
/**
|
|
63
|
-
*
|
|
45
|
+
* 현재 파일 크기 반환
|
|
64
46
|
*/
|
|
65
|
-
|
|
47
|
+
getFileSize(): number;
|
|
66
48
|
private _readFromDisk;
|
|
67
49
|
private _writeToDisk;
|
|
68
50
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { PageFileSystem } from '../PageFileSystem';
|
|
2
|
+
import type { AsyncMVCCTransaction } from 'mvcc-api';
|
|
3
|
+
import type { PageMVCCStrategy } from '../PageMVCCStrategy';
|
|
2
4
|
import { LockManager } from './LockManager';
|
|
3
5
|
import { TransactionContext } from './TxContext';
|
|
4
|
-
import { PageMVCCStrategy } from '../PageMVCCStrategy';
|
|
5
6
|
/**
|
|
6
7
|
* Transaction class.
|
|
7
8
|
* Manages the lifecycle and resources of a database transaction.
|
|
9
|
+
* Internally wraps a nested AsyncMVCCTransaction for snapshot isolation.
|
|
8
10
|
*/
|
|
9
11
|
export declare class Transaction {
|
|
10
12
|
readonly context: TransactionContext;
|
|
@@ -16,25 +18,28 @@ export declare class Transaction {
|
|
|
16
18
|
private heldLocks;
|
|
17
19
|
/** Held page locks (PageID -> LockID) */
|
|
18
20
|
private pageLocks;
|
|
19
|
-
/** Dirty Pages modified by the transaction: PageID -> Modified Page Buffer */
|
|
20
|
-
private dirtyPages;
|
|
21
|
-
/** Undo pages: PageID -> Original Page Buffer (Snapshot) */
|
|
22
|
-
private undoPages;
|
|
23
21
|
/** List of callbacks to execute on commit */
|
|
24
22
|
private commitHooks;
|
|
25
|
-
/**
|
|
26
|
-
private
|
|
23
|
+
/** Nested MVCC Transaction for snapshot isolation (lazy init) */
|
|
24
|
+
private mvccTx;
|
|
25
|
+
/** Root MVCC Transaction reference */
|
|
26
|
+
private readonly rootTx;
|
|
27
27
|
/** Release function for global write lock, set by DataplyAPI */
|
|
28
28
|
private _writeLockRelease;
|
|
29
29
|
/**
|
|
30
30
|
* @param id Transaction ID
|
|
31
31
|
* @param context Transaction context
|
|
32
|
-
* @param
|
|
32
|
+
* @param rootTx Root MVCC Transaction
|
|
33
33
|
* @param lockManager LockManager instance
|
|
34
34
|
* @param pfs Page File System
|
|
35
|
-
* @param reloadBPTree Callback to reload BPTree cache on rollback
|
|
36
35
|
*/
|
|
37
|
-
constructor(id: number, context: TransactionContext,
|
|
36
|
+
constructor(id: number, context: TransactionContext, rootTx: AsyncMVCCTransaction<PageMVCCStrategy, number, Uint8Array>, lockManager: LockManager, pfs: PageFileSystem);
|
|
37
|
+
/**
|
|
38
|
+
* Lazily initializes the nested MVCC transaction.
|
|
39
|
+
* This ensures the snapshot is taken at the time of first access,
|
|
40
|
+
* picking up the latest committed root version.
|
|
41
|
+
*/
|
|
42
|
+
private ensureMvccTx;
|
|
38
43
|
/**
|
|
39
44
|
* Registers a commit hook.
|
|
40
45
|
* @param hook Function to execute
|
|
@@ -50,13 +55,13 @@ export declare class Transaction {
|
|
|
50
55
|
*/
|
|
51
56
|
__hasWriteLockRelease(): boolean;
|
|
52
57
|
/**
|
|
53
|
-
* Reads a page
|
|
58
|
+
* Reads a page through the MVCC transaction.
|
|
54
59
|
* @param pageId Page ID
|
|
55
60
|
* @returns Page data
|
|
56
61
|
*/
|
|
57
62
|
readPage(pageId: number): Promise<Uint8Array>;
|
|
58
63
|
/**
|
|
59
|
-
* Writes a page
|
|
64
|
+
* Writes a page through the MVCC transaction.
|
|
60
65
|
* @param pageId Page ID
|
|
61
66
|
* @param data Page data
|
|
62
67
|
*/
|
|
@@ -74,10 +79,6 @@ export declare class Transaction {
|
|
|
74
79
|
* Rolls back the transaction.
|
|
75
80
|
*/
|
|
76
81
|
rollback(): Promise<void>;
|
|
77
|
-
/**
|
|
78
|
-
* Returns the dirty pages map.
|
|
79
|
-
*/
|
|
80
|
-
__getDirtyPages(): Map<number, Uint8Array>;
|
|
81
82
|
/**
|
|
82
83
|
* Releases all locks.
|
|
83
84
|
*/
|