Files
qb/freelist/freelist.go
2026-06-09 08:16:16 +03:00

279 lines
6.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package freelist
import (
"bytes"
"fmt"
"io"
"os"
"gordenko.dev/dima/qb/bin"
"gordenko.dev/dima/qb/util"
)
/*
Ідея - я фіксую кількість сторінок записаних в снапшоті.
Додаю і видаляю сторінки із основного файла, поки їх кількість > кількості зафіксованих.
Якщо pageNumbers видалено і кількість сторінок треба зменшити - нові сторінки пишу в delta файл.
При створенні снапшота - сторінки із delta файла записую в основний файл і фіксую в снапшоті
їх кількість. PageNumbers із буфера пишу в снапшот.
*/
const (
crcSize = 4
ptrSize = 4
)
type FreeList struct {
baseFile *os.File
deltaFile *os.File
pageSize int
pointersOnPage int
flushTreshold int // кількість вільних сторінок коли вже треба писати на диск
frozenPageCount int
basePagesCount int
deltaPagesCount int
free []uint32
}
type Options struct {
PageSize int
BaseFilePath string
DeltaFilePath string
FrozenPageCount int
}
func New(opt Options) (*FreeList, error) {
s := &FreeList{
pageSize: opt.PageSize,
pointersOnPage: (opt.PageSize - crcSize) / ptrSize,
frozenPageCount: opt.FrozenPageCount,
basePagesCount: opt.FrozenPageCount,
}
s.flushTreshold = int(float64(s.pointersOnPage) * 1.3)
var err error
s.baseFile, err = os.OpenFile(opt.BaseFilePath, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return nil, err
}
s.deltaFile, err = os.OpenFile(opt.DeltaFilePath, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return nil, err
}
// info, err := s.baseFile.Stat()
// if err != nil {
// return nil, err
// }
// fileSize := info.Size()
// if fileSize > 0 {
// if (fileSize % int64(s.pageSize)) > 0 {
// return nil, fmt.Errorf("file size %d is not a multiple of page size %d",
// fileSize, s.pageSize)
// }
// s.basePagesCount = int(fileSize) / s.pageSize
// }
return s, nil
}
func (s *FreeList) Pages() int {
return s.basePagesCount + s.deltaPagesCount
}
func (s *FreeList) AddPageNumbers(pageNumbers []uint32) (err error) {
if len(pageNumbers) == 0 {
return
}
s.free = append(s.free, pageNumbers...)
for len(s.free) > s.flushTreshold {
err = s.save()
if err != nil {
return
}
// викидаю номери сторінок, які записав на диск
copy(s.free, s.free[s.pointersOnPage:])
s.free = s.free[:len(s.free)-s.pointersOnPage]
fmt.Println("free:", s.free)
}
return
}
func (s *FreeList) save() error {
fmt.Println("save:", s.free[:s.pointersOnPage])
buf := make([]byte, s.pageSize)
i := crcSize
for _, pageNo := range s.free[:s.pointersOnPage] {
bin.PutUint32(buf[i:], pageNo)
i += ptrSize
}
bin.PutUint32(buf, util.CalcChecksum(buf[crcSize:]))
//
var (
file *os.File
off int64
)
if s.deltaPagesCount > 0 || s.basePagesCount < s.frozenPageCount {
file = s.deltaFile
off = int64(s.deltaPagesCount * s.pageSize)
s.deltaPagesCount++
} else {
file = s.baseFile
off = int64(s.basePagesCount * s.pageSize)
s.basePagesCount++
}
fmt.Println("write at:", off)
n, err := file.WriteAt(buf, off)
if err != nil {
return err
}
if n != s.pageSize {
return fmt.Errorf("written size %d bytes not equal page size %d", n, s.pageSize)
}
err = file.Sync()
if err != nil {
return err
}
return nil
}
func (s *FreeList) loadLastPage() error {
var (
file *os.File
off int64
buf = make([]byte, s.pageSize)
)
if s.deltaPagesCount > 0 {
file = s.deltaFile
off = int64((s.deltaPagesCount - 1) * s.pageSize)
} else {
file = s.baseFile
off = int64((s.basePagesCount - 1) * s.pageSize)
}
n, err := file.ReadAt(buf, off)
if err != nil {
return fmt.Errorf("file.Seek: %s", err)
}
if n != s.pageSize {
return fmt.Errorf("read size %d bytes not equal page size %d", n, s.pageSize)
}
// check crc
checksum := bin.GetUint32(buf[0:])
if util.CalcChecksum(buf[crcSize:]) != checksum {
return fmt.Errorf("page %d is corrupted", s.basePagesCount)
}
for i := crcSize; i < len(buf); i += ptrSize {
s.free = append(s.free, bin.GetUint32(buf[i:]))
}
if s.deltaPagesCount > 0 {
s.deltaPagesCount--
err = file.Truncate(off)
if err != nil {
return err
}
return file.Sync()
} else {
s.basePagesCount--
if s.basePagesCount >= s.frozenPageCount {
err = s.baseFile.Truncate(off)
if err != nil {
return err
}
return file.Sync()
}
}
return nil
}
func (s *FreeList) GetPageNumber() (pageNo uint32, err error) {
if len(s.free) == 0 {
if s.basePagesCount > 0 || s.deltaPagesCount > 0 {
// якщо немає вільних сторінок у буфері, але є у freelist файлах -
// завантажую сторінку із диску
err = s.loadLastPage()
if err != nil {
return
}
} else {
return
}
}
if len(s.free) > 0 {
lastIdx := len(s.free) - 1
pageNo = s.free[lastIdx]
s.free = s.free[:lastIdx]
}
return
}
// для відновлення недозаповненнох сторінки із снапшота
func (s *FreeList) Restore(serialized []byte) error {
if (len(serialized) % ptrSize) != 0 {
return fmt.Errorf("wrong size")
}
for i := 0; i < len(serialized); i += ptrSize {
s.free = append(s.free, bin.GetUint32(serialized[i:]))
}
fmt.Println(s.free)
return nil
}
// для запису в snapshot
func (s *FreeList) Serialize() ([]byte, error) {
w := bytes.NewBuffer(nil)
for _, pageNo := range s.free {
bin.WriteUint32(w, pageNo)
}
return w.Bytes(), nil
}
func (s *FreeList) Cached() []uint32 {
return s.free
}
func (s *FreeList) Merge() (err error) {
var (
off = int64(s.basePagesCount * s.pageSize)
deltaSize = int64(s.deltaPagesCount * s.pageSize)
)
if s.deltaPagesCount > 0 {
var (
ret int64
copied int64
)
ret, err = s.baseFile.Seek(off, 0)
if err != nil {
return
}
if ret != off {
return fmt.Errorf("Seek failed")
}
copied, err = io.Copy(s.baseFile, s.deltaFile)
if err != nil {
return
}
if copied != deltaSize {
return fmt.Errorf("copy failed")
}
err = s.baseFile.Sync()
if err != nil {
return
}
err = s.deltaFile.Truncate(0)
if err != nil {
return
}
s.basePagesCount += s.deltaPagesCount
s.deltaPagesCount = 0
}
if s.basePagesCount < s.frozenPageCount {
off = int64(s.basePagesCount * s.pageSize)
err = s.baseFile.Truncate(off)
if err != nil {
return
}
}
s.frozenPageCount = s.basePagesCount
return nil
}