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, cacheCapacity) {
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
- const dataCopy = new Uint8Array(this.pageSize);
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
- * WAL 체크포인트 시점에 호출되어야 합니다.
9593
+ * 페이지 삭제.
9594
+ * 실제 페이지 해제는 상위 레이어(FreeList)에서 관리합니다.
9595
+ * @param pageId 페이지 ID
9616
9596
  */
9617
- async flush() {
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
- * WAL 없이 트랜잭션 커밋 시, 해당 트랜잭션의 dirty pages만 선택적으로 flush합니다.
9633
- * @param pages 기록할 페이지 맵 (PageID -> PageData)
9600
+ * 페이지 존재 여부 확인
9601
+ * @param pageId 페이지 ID
9602
+ * @returns 존재하면 true
9634
9603
  */
9635
- async flushPages(pages) {
9636
- if (pages.size === 0) {
9637
- return;
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, pageCacheCapacity);
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. 메모리의 더티 페이지를 DB 파일에 기록 (Flush)
10083
- * 2. DB 파일 물리적 동기화 (Sync/fsync)
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 pageStrategy Page MVCC Strategy for disk I/O
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, pageStrategy, lockManager, pfs) {
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.pageStrategy = pageStrategy;
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
- /** Page MVCC Strategy for disk access */
10919
- pageStrategy;
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. Uses dirty buffer if available, otherwise disk.
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 dirty = this.dirtyPages.get(pageId);
10949
- if (dirty) {
10950
- return dirty;
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
- return await this.pageStrategy.read(pageId);
10891
+ const copy = new Uint8Array(data.length);
10892
+ copy.set(data);
10893
+ return copy;
10953
10894
  }
10954
10895
  /**
10955
- * Writes a page to the transaction buffer.
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
- if (!this.undoPages.has(pageId)) {
10961
- const existingData = await this.pageStrategy.read(pageId);
10962
- const snapshot = new Uint8Array(existingData.length);
10963
- snapshot.set(existingData);
10964
- this.undoPages.set(pageId, snapshot);
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
- if (this.pfs.wal && this.dirtyPages.size > 0) {
10996
- await this.pfs.wal.prepareCommit(this.dirtyPages);
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
- for (const [pageId, data] of this.dirtyPages) {
11000
- await this.pageStrategy.write(pageId, data);
10949
+ await tx.commit();
10950
+ if (hasDirtyPages) {
10951
+ await this.rootTx.commit();
11001
10952
  }
11002
- if (!this.pfs.wal) {
11003
- await this.pfs.strategy.flushPages(this.dirtyPages);
11004
- } else {
11005
- this.pfs.wal.incrementWrittenPages(this.dirtyPages.size);
11006
- if (this.pfs.wal.shouldCheckpoint(this.pfs.options.walCheckpointThreshold)) {
11007
- shouldTriggerCheckpoint = true;
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.dirtyPages.clear();
11030
- this.undoPages.clear();
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: 2 /* Info */
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.getPageStrategy(),
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. 메모리의 더티 페이지를 DB 파일에 기록 (Flush)
145
- * 2. DB 파일 물리적 동기화 (Sync/fsync)
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, cacheCapacity: 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
- getFileSize(): number;
43
+ sync(): Promise<void>;
62
44
  /**
63
- * 캐시 초기화
45
+ * 현재 파일 크기 반환
64
46
  */
65
- clearCache(): void;
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
- /** Page MVCC Strategy for disk access */
26
- private readonly pageStrategy;
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 pageStrategy Page MVCC Strategy for disk I/O
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, pageStrategy: PageMVCCStrategy, lockManager: LockManager, pfs: PageFileSystem);
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. Uses dirty buffer if available, otherwise disk.
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 to the transaction buffer.
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
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dataply",
3
- "version": "0.0.26-alpha.5",
3
+ "version": "0.0.26-alpha.6",
4
4
  "description": "A lightweight storage engine for Node.js with support for MVCC, WAL.",
5
5
  "license": "MIT",
6
6
  "author": "izure <admin@izure.org>",