mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
* implemented distinct * implemented distinct * uses first cut of a buffer pool, and extendible hashing with thresholded spill to disk * tests * cleaned up some stuff around query plan output to make developing tooling easier * added optimization to call PQL Distinct() * fixed test * fix for passing wrong index name in orchestrator * back out change to DistinctTimestamp * fix other instance of wrong table name being passed * use full index name instead of abbreviated one for translation. sigh. * removed some unused code Co-authored-by: Matthew Jaffee <jaffee@pilosa.com>
262 lines
6.4 KiB
Go
262 lines
6.4 KiB
Go
package bufferpool
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
)
|
|
|
|
// FrameID is the type for frame id
|
|
type FrameID int
|
|
|
|
// PageID is the type for page id
|
|
type PageID int
|
|
|
|
var pageSyncPool = sync.Pool{
|
|
New: func() any {
|
|
pg := new(Page)
|
|
pg.id = PageID(INVALID_PAGE)
|
|
pg.isDirty = false
|
|
pg.pinCount = 0
|
|
return pg
|
|
},
|
|
}
|
|
|
|
// BufferPool represents a buffer pool of pages
|
|
type BufferPool struct {
|
|
// the underlying storage
|
|
diskManager DiskManager
|
|
// the actual pages in the buffer pool
|
|
pages []*Page
|
|
// the replacer that will elect replacements when buffer pool is full
|
|
replacer *ClockReplacer
|
|
// the list of free frames
|
|
freeList []FrameID
|
|
// the map of frames to page ids to frame ids
|
|
// frame ids are the offset into pages
|
|
// if you ask the pool for page 673, this will know at
|
|
// what offset in pages page 673 will exist
|
|
pageTable map[PageID]FrameID
|
|
}
|
|
|
|
// TODO(pok) implement a lazy writer
|
|
// * if free list is 'low' then
|
|
// * increase size of cache if there is physical memory available
|
|
// * write out old pages and boot them from the cache to increase free list
|
|
|
|
// TODO(pok) implement a checkpoint that scans the pool and writes out dirty pages every
|
|
// minute or so
|
|
|
|
// NewBufferPool returns a buffer pool
|
|
func NewBufferPool(maxSize int, diskManager DiskManager) *BufferPool {
|
|
freeList := make([]FrameID, 0)
|
|
pages := make([]*Page, maxSize)
|
|
for i := 0; i < maxSize; i++ {
|
|
frameNumber := FrameID(i)
|
|
freeList = append(freeList, frameNumber)
|
|
}
|
|
clockReplacer := NewClockReplacer(maxSize)
|
|
return &BufferPool{
|
|
diskManager: diskManager,
|
|
pages: pages,
|
|
replacer: clockReplacer,
|
|
freeList: freeList,
|
|
pageTable: make(map[PageID]FrameID),
|
|
}
|
|
}
|
|
|
|
// Dumps all the pages in the buffer pool
|
|
func (b *BufferPool) Dump() {
|
|
fmt.Println()
|
|
fmt.Printf("------------------------------------------------------------------------------------------\n")
|
|
fmt.Printf("BUFFER POOL\n")
|
|
for _, p := range b.pages {
|
|
if p != nil {
|
|
p.Dump("")
|
|
}
|
|
}
|
|
fmt.Printf("------------------------------------------------------------------------------------------\n")
|
|
fmt.Println()
|
|
}
|
|
|
|
// FetchPage fetches the requested page from the buffer pool.
|
|
func (b *BufferPool) FetchPage(pageID PageID) (*Page, error) {
|
|
// if it is in buffer pool already then just return it
|
|
if frameID, ok := b.pageTable[pageID]; ok {
|
|
page := b.pages[frameID]
|
|
page.pinCount++
|
|
b.replacer.Pin(frameID)
|
|
return page, nil
|
|
}
|
|
|
|
// not in the buffer pool so try the free list or
|
|
// the replacer will vote a page off the island
|
|
frameID, isFromFreeList, err := b.getFrameID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !isFromFreeList {
|
|
// if it didn't come from the freelist then
|
|
// remove page from current frame, writing it out if dirty
|
|
currentPage := b.pages[frameID]
|
|
if currentPage != nil {
|
|
if currentPage.isDirty {
|
|
b.diskManager.WritePage(currentPage)
|
|
}
|
|
|
|
delete(b.pageTable, currentPage.id)
|
|
}
|
|
}
|
|
|
|
// if we got to here, sorry, have to do an I/O
|
|
page, err := b.diskManager.ReadPage(pageID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
page.pinCount = 1
|
|
b.pageTable[pageID] = frameID
|
|
pageSyncPool.Put(b.pages[frameID])
|
|
b.pages[frameID] = page
|
|
b.replacer.Pin(frameID)
|
|
|
|
return page, nil
|
|
}
|
|
|
|
// UnpinPage unpins the target page from the buffer pool
|
|
func (b *BufferPool) UnpinPage(pageID PageID) error {
|
|
if frameID, ok := b.pageTable[pageID]; ok {
|
|
page := b.pages[frameID]
|
|
page.DecPinCount()
|
|
|
|
if page.pinCount <= 0 {
|
|
b.replacer.Unpin(frameID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
return errors.New("could not find page")
|
|
}
|
|
|
|
// FlushPage Flushes the target page to disk
|
|
func (b *BufferPool) FlushPage(pageID PageID) bool {
|
|
if frameID, ok := b.pageTable[pageID]; ok {
|
|
page := b.pages[frameID]
|
|
page.DecPinCount()
|
|
|
|
b.diskManager.WritePage(page)
|
|
page.isDirty = false
|
|
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// NewPage allocates a new page in the buffer pool with the disk manager help
|
|
func (b *BufferPool) NewPage() (*Page, error) {
|
|
// get a free frame
|
|
frameID, isFromFreeList, err := b.getFrameID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !isFromFreeList {
|
|
// remove page from current frame
|
|
currentPage := b.pages[frameID]
|
|
if currentPage != nil {
|
|
if currentPage.isDirty {
|
|
b.diskManager.WritePage(currentPage)
|
|
}
|
|
|
|
delete(b.pageTable, currentPage.id)
|
|
}
|
|
}
|
|
|
|
// allocates new page
|
|
pageID, err := b.diskManager.AllocatePage()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
page := &Page{pageID, 1, false, [PAGE_SIZE]byte{}}
|
|
page.WritePageNumber(int32(pageID))
|
|
page.WriteFreeSpaceOffset(int16(PAGE_SIZE))
|
|
page.WriteNextPointer(int32(INVALID_PAGE))
|
|
page.WritePrevPointer(int32(INVALID_PAGE))
|
|
|
|
// update the frame table
|
|
b.pageTable[pageID] = frameID
|
|
pageSyncPool.Put(b.pages[frameID])
|
|
b.pages[frameID] = page
|
|
|
|
return page, nil
|
|
}
|
|
|
|
// ScratchPage returns a page outside the buffer pool - do not use if you intend the page
|
|
// to be in the buffer pool (use NewPage() for that)
|
|
// ScratchPage is intended to be used in cases where you need the Page primitives
|
|
// and will copy the scratch page back over a real page later
|
|
func (b *BufferPool) ScratchPage() *Page {
|
|
page := &Page{
|
|
id: PageID(INVALID_PAGE),
|
|
pinCount: 0,
|
|
isDirty: false,
|
|
data: [PAGE_SIZE]byte{},
|
|
}
|
|
page.WritePageNumber(int32(INVALID_PAGE))
|
|
page.WriteFreeSpaceOffset(int16(PAGE_SIZE))
|
|
page.WriteNextPointer(int32(INVALID_PAGE))
|
|
page.WritePrevPointer(int32(INVALID_PAGE))
|
|
return page
|
|
}
|
|
|
|
// DeletePage deletes a page from the buffer pool
|
|
func (b *BufferPool) DeletePage(pageID PageID) error {
|
|
var frameID FrameID
|
|
var ok bool
|
|
if frameID, ok = b.pageTable[pageID]; !ok {
|
|
return nil
|
|
}
|
|
|
|
page := b.pages[frameID]
|
|
|
|
if page.pinCount > 0 {
|
|
return errors.New("pin count greater than 0")
|
|
}
|
|
delete(b.pageTable, page.id)
|
|
b.replacer.Pin(frameID)
|
|
b.diskManager.DeallocatePage(pageID)
|
|
|
|
b.freeList = append(b.freeList, frameID)
|
|
|
|
return nil
|
|
}
|
|
|
|
// FlushAllpages flushes all the pages in the buffer pool to disk
|
|
// Yeah, never call this unless you know what you are doing
|
|
func (b *BufferPool) FlushAllpages() {
|
|
for pageID := range b.pageTable {
|
|
b.FlushPage(pageID)
|
|
}
|
|
}
|
|
|
|
func (b *BufferPool) getFrameID() (FrameID, bool, error) {
|
|
if len(b.freeList) > 0 {
|
|
frameID, newFreeList := b.freeList[0], b.freeList[1:]
|
|
b.freeList = newFreeList
|
|
return frameID, true, nil
|
|
}
|
|
|
|
victim, err := b.replacer.Victim()
|
|
return victim, false, err
|
|
}
|
|
|
|
// OnDiskSize exposes the on disk size of the backing store
|
|
// behind this buffer pool
|
|
func (b *BufferPool) OnDiskSize() int64 {
|
|
return b.diskManager.FileSize()
|
|
}
|
|
|
|
// Close closes the buffer pool
|
|
func (b *BufferPool) Close() {
|
|
b.diskManager.Close()
|
|
}
|