This commit is contained in:
2026-05-31 20:01:28 +00:00
parent e7ec6083cb
commit db5ecd3dfd
28 changed files with 2090 additions and 2155 deletions

View File

@@ -3,52 +3,82 @@ 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 {
file *os.File
pageSize int
pointersOnPage int
flushTreshold int // кількість вільних сторінок коли вже треба писати на диск
pages int
free []uint32
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
File *os.File
PageSize int
BaseFilePath string
DeltaFilePath string
FrozenPageCount int
}
func New(opt Options) (*FreeList, error) {
s := &FreeList{
file: opt.File,
pageSize: opt.PageSize,
pointersOnPage: (opt.PageSize - crcSize) / ptrSize,
pageSize: opt.PageSize,
pointersOnPage: (opt.PageSize - crcSize) / ptrSize,
frozenPageCount: opt.FrozenPageCount,
basePagesCount: opt.FrozenPageCount,
}
s.flushTreshold = int(float64(s.pointersOnPage) * 1.3)
info, err := s.file.Stat()
var err error
s.baseFile, err = os.OpenFile(opt.BaseFilePath, os.O_RDWR|os.O_CREATE, 0666)
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.pages = int(fileSize) / s.pageSize
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
@@ -63,11 +93,13 @@ func (s *FreeList) AddPageNumbers(pageNumbers []uint32) (err error) {
// викидаю номери сторінок, які записав на диск
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] {
@@ -76,28 +108,49 @@ func (s *FreeList) save() error {
}
bin.PutUint32(buf, util.CalcChecksum(buf[crcSize:]))
//
off := int64(s.pages * s.pageSize)
n, err := s.file.WriteAt(buf, off)
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 = s.file.Sync()
err = file.Sync()
if err != nil {
return err
}
s.pages++
return nil
}
func (s *FreeList) loadLastPage() error {
var (
buf = make([]byte, s.pageSize)
off = int64((s.pages - 1) * s.pageSize)
file *os.File
off int64
buf = make([]byte, s.pageSize)
)
n, err := s.file.ReadAt(buf, off)
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)
}
@@ -107,32 +160,49 @@ func (s *FreeList) loadLastPage() error {
// check crc
checksum := bin.GetUint32(buf[0:])
if util.CalcChecksum(buf[crcSize:]) != checksum {
return fmt.Errorf("page %d is corrupted", s.pages)
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:]))
}
s.pages--
err = s.file.Truncate(int64(s.pages * s.pageSize))
if err != nil {
return err
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.pages == 0 {
return
}
err = s.loadLastPage()
if err != nil {
if s.basePagesCount > 0 || s.deltaPagesCount > 0 {
// якщо немає вільних сторінок у буфері, але є у freelist файлах -
// завантажую сторінку із диску
err = s.loadLastPage()
if err != nil {
return
}
} else {
return
}
}
lastIdx := len(s.free) - 1
pageNo = s.free[lastIdx]
s.free = s.free[:lastIdx]
if len(s.free) > 0 {
lastIdx := len(s.free) - 1
pageNo = s.free[lastIdx]
s.free = s.free[:lastIdx]
}
return
}
@@ -144,6 +214,7 @@ func (s *FreeList) Restore(serialized []byte) error {
for i := 0; i < len(serialized); i += ptrSize {
s.free = append(s.free, bin.GetUint32(serialized[i:]))
}
fmt.Println(s.free)
return nil
}
@@ -155,3 +226,49 @@ func (s *FreeList) Serialize() ([]byte, error) {
}
return w.Bytes(), nil
}
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
}

View File

@@ -2,22 +2,39 @@ package freelist
import (
"fmt"
"os"
"testing"
)
func createFreeList() (_ *FreeList, err error) {
file, err := os.OpenFile("test.free", os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
return
}
return New(Options{
PageSize: 20,
File: file,
PageSize: 20,
BaseFilePath: "test.basefree",
DeltaFilePath: "test.deltafree",
FrozenPageCount: 2,
})
}
func TestAddToFreeList(t *testing.T) {
func TestBuffer(t *testing.T) {
freeList, err := createFreeList()
if err != nil {
t.Fatal(err)
}
pageNumbers := []uint32{1, 2, 3, 4}
//pageNumbers := []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
err = freeList.AddPageNumbers(pageNumbers)
if err != nil {
t.Fatal(err)
}
for range len(pageNumbers) + 1 {
pageNo, err := freeList.GetPageNumber()
fmt.Println(pageNo, err)
}
}
func TestSave(t *testing.T) {
freeList, err := createFreeList()
if err != nil {
t.Fatal(err)
@@ -29,16 +46,67 @@ func TestAddToFreeList(t *testing.T) {
if err != nil {
t.Fatal(err)
}
err = freeList.Merge()
if err != nil {
t.Fatal(err)
}
// for range len(pageNumbers) + 1 {
// pageNo, err := freeList.GetPageNumber()
// fmt.Println(pageNo, err)
// }
}
func TestGetFromFreeList(t *testing.T) {
func TestDelta(t *testing.T) {
freeList, err := createFreeList()
if err != nil {
t.Fatal(err)
}
for range 4 {
serialized := []byte{9, 0, 0, 0, 10, 0, 0, 0}
err = freeList.Restore(serialized)
if err != nil {
t.Fatal(err)
}
for range 3 {
pageNo, err := freeList.GetPageNumber()
fmt.Println(pageNo, err)
}
err = freeList.AddPageNumbers([]uint32{20, 21, 22})
if err != nil {
t.Fatal(err)
}
err = freeList.AddPageNumbers([]uint32{23, 24, 25, 26})
if err != nil {
t.Fatal(err)
}
serialized, err = freeList.Serialize()
if err != nil {
t.Fatal(err)
}
fmt.Println("free serialized:", serialized)
err = freeList.Merge()
if err != nil {
t.Fatal(err)
}
}
// func TestGetFromFreeList(t *testing.T) {
// freeList, err := createFreeList()
// if err != nil {
// t.Fatal(err)
// }
// for range 4 {
// pageNo, err := freeList.GetPageNumber()
// fmt.Println(pageNo, err)
// }
// }

BIN
freelist/test.basefree Normal file

Binary file not shown.

0
freelist/test.deltafree Normal file
View File

Binary file not shown.