Files
qb/freelist/freelist.go

267 lines
6.3 KiB
Go
Raw Normal View History

2026-02-10 14:02:11 +00:00
package freelist
import (
"fmt"
2026-05-31 20:01:28 +00:00
"io"
2026-02-23 11:02:29 +00:00
"os"
2026-02-10 14:02:11 +00:00
2026-06-10 06:18:45 +03:00
bin "gordenko.dev/dima/bin/little"
2026-05-21 23:38:52 +03:00
"gordenko.dev/dima/qb/util"
2026-02-23 11:02:29 +00:00
)
2026-05-31 20:01:28 +00:00
/*
Ідея - я фіксую кількість сторінок записаних в снапшоті.
Додаю і видаляю сторінки із основного файла, поки їх кількість > кількості зафіксованих.
Якщо pageNumbers видалено і кількість сторінок треба зменшити - нові сторінки пишу в delta файл.
При створенні снапшота - сторінки із delta файла записую в основний файл і фіксую в снапшоті
їх кількість. PageNumbers із буфера пишу в снапшот.
*/
2026-02-23 11:02:29 +00:00
const (
2026-05-21 23:38:52 +03:00
crcSize = 4
ptrSize = 4
2026-02-10 14:02:11 +00:00
)
type FreeList struct {
2026-05-31 20:01:28 +00:00
baseFile *os.File
deltaFile *os.File
pageSize int
pointersOnPage int
flushTreshold int // кількість вільних сторінок коли вже треба писати на диск
frozenPageCount int
basePagesCount int
deltaPagesCount int
free []uint32
2026-02-10 14:02:11 +00:00
}
2026-05-21 23:38:52 +03:00
type Options struct {
2026-05-31 20:01:28 +00:00
PageSize int
BaseFilePath string
DeltaFilePath string
FrozenPageCount int
2026-02-10 14:02:11 +00:00
}
2026-05-21 23:38:52 +03:00
func New(opt Options) (*FreeList, error) {
s := &FreeList{
2026-05-31 20:01:28 +00:00
pageSize: opt.PageSize,
pointersOnPage: (opt.PageSize - crcSize) / ptrSize,
frozenPageCount: opt.FrozenPageCount,
basePagesCount: opt.FrozenPageCount,
2026-02-23 11:02:29 +00:00
}
2026-05-21 23:38:52 +03:00
s.flushTreshold = int(float64(s.pointersOnPage) * 1.3)
2026-05-31 20:01:28 +00:00
var err error
s.baseFile, err = os.OpenFile(opt.BaseFilePath, os.O_RDWR|os.O_CREATE, 0666)
2026-05-21 23:38:52 +03:00
if err != nil {
return nil, err
2026-02-10 14:02:11 +00:00
}
2026-06-14 07:57:01 +03:00
s.deltaFile, err = os.OpenFile(opt.DeltaFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
2026-05-31 20:01:28 +00:00
if err != nil {
return nil, err
2026-05-21 23:38:52 +03:00
}
return s, nil
2026-02-10 14:02:11 +00:00
}
2026-05-31 20:01:28 +00:00
func (s *FreeList) Pages() int {
return s.basePagesCount + s.deltaPagesCount
}
2026-05-21 23:38:52 +03:00
func (s *FreeList) AddPageNumbers(pageNumbers []uint32) (err error) {
2026-02-10 14:02:11 +00:00
if len(pageNumbers) == 0 {
return
}
2026-02-23 11:02:29 +00:00
s.free = append(s.free, pageNumbers...)
2026-05-21 23:38:52 +03:00
for len(s.free) > s.flushTreshold {
err = s.save()
2026-02-23 11:02:29 +00:00
if err != nil {
2026-05-21 23:38:52 +03:00
return
2026-02-23 11:02:29 +00:00
}
2026-05-21 23:38:52 +03:00
// викидаю номери сторінок, які записав на диск
copy(s.free, s.free[s.pointersOnPage:])
s.free = s.free[:len(s.free)-s.pointersOnPage]
2026-06-19 07:25:46 +03:00
//fmt.Println("free:", s.free)
2026-02-23 11:02:29 +00:00
}
2026-05-21 23:38:52 +03:00
return
2026-02-10 14:02:11 +00:00
}
2026-02-23 11:02:29 +00:00
func (s *FreeList) save() error {
2026-06-19 07:25:46 +03:00
//fmt.Println("save:", s.free[:s.pointersOnPage])
2026-05-21 23:38:52 +03:00
buf := make([]byte, s.pageSize)
2026-02-23 11:02:29 +00:00
i := crcSize
2026-05-21 23:38:52 +03:00
for _, pageNo := range s.free[:s.pointersOnPage] {
2026-02-23 11:02:29 +00:00
bin.PutUint32(buf[i:], pageNo)
i += ptrSize
}
2026-06-13 08:01:42 +03:00
bin.PutUint32(buf, util.CalculateCRC32(buf[crcSize:]))
2026-05-21 23:38:52 +03:00
//
2026-05-31 20:01:28 +00:00
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++
}
2026-06-19 07:25:46 +03:00
//fmt.Println("write at:", off)
2026-05-31 20:01:28 +00:00
n, err := file.WriteAt(buf, off)
2026-02-23 11:02:29 +00:00
if err != nil {
return err
}
2026-05-21 23:38:52 +03:00
if n != s.pageSize {
return fmt.Errorf("written size %d bytes not equal page size %d", n, s.pageSize)
}
2026-05-31 20:01:28 +00:00
err = file.Sync()
2026-05-21 23:38:52 +03:00
if err != nil {
return err
2026-02-23 11:02:29 +00:00
}
return nil
}
func (s *FreeList) loadLastPage() error {
var (
2026-05-31 20:01:28 +00:00
file *os.File
off int64
buf = make([]byte, s.pageSize)
2026-02-23 11:02:29 +00:00
)
2026-05-31 20:01:28 +00:00
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)
2026-02-23 11:02:29 +00:00
if err != nil {
return fmt.Errorf("file.Seek: %s", err)
}
2026-05-21 23:38:52 +03:00
if n != s.pageSize {
return fmt.Errorf("read size %d bytes not equal page size %d", n, s.pageSize)
2026-02-23 11:02:29 +00:00
}
// check crc
2026-06-10 06:18:45 +03:00
checksum, _ := bin.GetUint32(buf[0:])
2026-06-13 08:01:42 +03:00
if util.CalculateCRC32(buf[crcSize:]) != checksum {
2026-05-31 20:01:28 +00:00
return fmt.Errorf("page %d is corrupted", s.basePagesCount)
2026-05-21 23:38:52 +03:00
}
2026-02-23 11:02:29 +00:00
for i := crcSize; i < len(buf); i += ptrSize {
2026-06-10 06:18:45 +03:00
pageNo, _ := bin.GetUint32(buf[i:])
s.free = append(s.free, pageNo)
2026-02-23 11:02:29 +00:00
}
2026-05-31 20:01:28 +00:00
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()
}
2026-05-21 23:38:52 +03:00
}
2026-02-23 11:02:29 +00:00
return nil
}
2026-05-21 23:38:52 +03:00
func (s *FreeList) GetPageNumber() (pageNo uint32, err error) {
2026-02-23 11:02:29 +00:00
if len(s.free) == 0 {
2026-05-31 20:01:28 +00:00
if s.basePagesCount > 0 || s.deltaPagesCount > 0 {
// якщо немає вільних сторінок у буфері, але є у freelist файлах -
// завантажую сторінку із диску
err = s.loadLastPage()
if err != nil {
return
}
} else {
2026-02-23 11:02:29 +00:00
return
}
2026-02-10 14:02:11 +00:00
}
2026-05-31 20:01:28 +00:00
if len(s.free) > 0 {
lastIdx := len(s.free) - 1
pageNo = s.free[lastIdx]
s.free = s.free[:lastIdx]
}
2026-02-10 14:02:11 +00:00
return
}
2026-05-21 23:38:52 +03:00
// для відновлення недозаповненнох сторінки із снапшота
2026-06-10 06:18:45 +03:00
// 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
// }
2026-02-10 14:02:11 +00:00
2026-05-21 23:38:52 +03:00
// для запису в snapshot
2026-06-10 06:18:45 +03:00
// func (s *FreeList) Serialize() ([]byte, error) {
// w := bytes.NewBuffer(nil)
// for _, pageNo := range s.free {
// bin.WriteUint32(w, pageNo)
// }
// return w.Bytes(), nil
// }
2026-05-31 20:01:28 +00:00
2026-06-09 08:16:16 +03:00
func (s *FreeList) Cached() []uint32 {
return s.free
}
2026-05-31 20:01:28 +00:00
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
}