feat(engine): add LoggingPageStore and fetchPage
This commit is contained in:
1 parent
f45e7f0db3
commit
17660d9087
1 file changed
+71
@@ -0,0 +1,71 @@
|
||||
package kestrel.store.engine
|
||||
|
||||
import kestrel.store.page.Page
|
||||
import kestrel.store.page.PageStore
|
||||
import kestrel.store.txn.TransactionManager
|
||||
|
||||
/**
|
||||
* A [PageStore] decorator that watches every page a single logical operation
|
||||
* touches and turns whatever ends up dirty into [TransactionManager.logUpdate]
|
||||
* calls, without [kestrel.store.heap.HeapFile] or [kestrel.store.btree.BPlusTree]
|
||||
* knowing they are being logged - they were written against [PageStore] with
|
||||
* no notion of a transaction.
|
||||
*
|
||||
* One instance is meant to wrap exactly one logical operation (an insert, a
|
||||
* delete, ...): the first [fetchPage] or [newPage] for a given page id
|
||||
* captures its "before" image (an all-zero page for a freshly allocated one,
|
||||
* since that is the state [kestrel.store.wal.RecoveryManager] would undo
|
||||
* back to), and [finish] compares that snapshot against whatever the
|
||||
* delegate's live [Page] holds once the operation is done, logging one WAL
|
||||
* record per page whose bytes actually changed. [onTouch] is called only for
|
||||
* those actually-logged pages, once [finish] has decided which ones they
|
||||
* are: a page a caller merely read must never be reported, because its
|
||||
* cached bytes can be dirty with an earlier, already-committed transaction's
|
||||
* write that just has not been flushed yet, and [kestrel.store.wal.RecoveryManager]
|
||||
* never touches such a page on disk during rollback, so invalidating it in
|
||||
* the buffer pool would silently discard real data instead of undoing
|
||||
* anything.
|
||||
*/
|
||||
class LoggingPageStore(
|
||||
private val delegate: PageStore,
|
||||
private val txnManager: TransactionManager,
|
||||
private val txnId: Long,
|
||||
private val onTouch: (Long) -> Unit = {}
|
||||
) : PageStore {
|
||||
private val liveByPageId = LinkedHashMap<Long, Page>()
|
||||
private val beforeImages = HashMap<Long, ByteArray>()
|
||||
|
||||
override fun fetchPage(pageId: Long): Page {
|
||||
val page = delegate.fetchPage(pageId)
|
||||
track(pageId, page)
|
||||
return page
|
||||
}
|
||||
|
||||
override fun newPage(): Page {
|
||||
val page = delegate.newPage()
|
||||
beforeImages[page.id] = ByteArray(Page.PAGE_SIZE)
|
||||
track(page.id, page)
|
||||
return page
|
||||
}
|
||||
|
||||
override fun unpinPage(pageId: Long, markDirty: Boolean) = delegate.unpinPage(pageId, markDirty)
|
||||
|
||||
override fun deletePage(pageId: Long) = delegate.deletePage(pageId)
|
||||
|
||||
/** Logs one update record per page whose bytes differ from when it was first touched. */
|
||||
fun finish() {
|
||||
for ((pageId, page) in liveByPageId) {
|
||||
val before = beforeImages.getValue(pageId)
|
||||
val after = page.data
|
||||
if (!before.contentEquals(after)) {
|
||||
txnManager.logUpdate(txnId, pageId, before, after.copyOf())
|
||||
onTouch(pageId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun track(pageId: Long, page: Page) {
|
||||
liveByPageId.putIfAbsent(pageId, page)
|
||||
beforeImages.putIfAbsent(pageId, page.data.copyOf())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user