This commit is contained in:
2026-05-10 00:59:47 +00:00
parent 271046231c
commit 9b38586ed8
30 changed files with 3560 additions and 1823 deletions

View File

@@ -8,7 +8,6 @@ import (
"path/filepath"
"sync"
diploma "gordenko.dev/dima/qb"
"gordenko.dev/dima/qb/bin"
)
@@ -19,8 +18,8 @@ const (
filePerm = 0770
// common
crc32Idx = PageSize - 4
pageType = PageSize - 5
crc32Idx = PageSize - 4
pageTypeIdx = PageSize - 5
// index page
indexRecordsQtyIdx = PageSize - 8
@@ -73,16 +72,16 @@ type _page struct {
}
type Atree struct {
freelist FreeList
file *os.File
mutex sync.Mutex
freelist FreeList
allocatedPagesQty uint32
pages map[uint32]*_page
pageWaits map[uint32][]chan readResult
pagesToRead []uint32
readSignalCh chan struct{}
writeSignalCh chan struct{}
writeTasksQueue []WriteTask
//writeTasksQueue []WriteTask
}
type Options struct {
@@ -131,7 +130,7 @@ func New(opt Options) (*Atree, error) {
}
tree := &Atree{
//freelist: opt.FreeList,
freelist: opt.FreeList,
file: file,
allocatedPagesQty: allocatedPagesQty,
pages: make(map[uint32]*_page),
@@ -144,7 +143,7 @@ func New(opt Options) (*Atree, error) {
}
func (s *Atree) Run() {
go s.pageWriter()
//go s.pageWriter()
go s.pageReader()
}
@@ -161,6 +160,7 @@ func (s *Atree) findDataPage(rootPageNo uint32, timestamp uint32) (uint32, []byt
foundPageNo := findPageNo(buf, timestamp)
s.releasePage(indexPageNo)
// fix
if buf[isDataPageNumbersIdx] == 1 {
buf, err := s.fetchDataPage(foundPageNo)
if err != nil {
@@ -173,20 +173,20 @@ func (s *Atree) findDataPage(rootPageNo uint32, timestamp uint32) (uint32, []byt
}
}
type pathLeg struct {
type PathLeg struct {
PageNo uint32
Data []byte
}
type pathToDataPage struct {
Legs []pathLeg
type PathToDataPage struct {
Legs []PathLeg
LastPageNo uint32
}
func (s *Atree) findPathToLastPage(rootPageNo uint32) (_ pathToDataPage, err error) {
func (s *Atree) FindPathToLastPage(rootPageNo uint32) (_ PathToDataPage, err error) {
var (
pageNo = rootPageNo
legs []pathLeg
legs []PathLeg
)
for {
@@ -197,7 +197,7 @@ func (s *Atree) findPathToLastPage(rootPageNo uint32) (_ pathToDataPage, err err
return
}
legs = append(legs, pathLeg{
legs = append(legs, PathLeg{
PageNo: pageNo,
Data: buf,
// childIdx не нужен
@@ -205,8 +205,9 @@ func (s *Atree) findPathToLastPage(rootPageNo uint32) (_ pathToDataPage, err err
foundPageNo := getLastPageNo(buf)
// fix
if buf[isDataPageNumbersIdx] == 1 {
return pathToDataPage{
return PathToDataPage{
Legs: legs,
LastPageNo: foundPageNo,
}, nil
@@ -218,26 +219,19 @@ func (s *Atree) findPathToLastPage(rootPageNo uint32) (_ pathToDataPage, err err
// APPEND DATA PAGE
//MetricID uint32
//Timestamp uint32
//Value float64
//RootPageNo uint32
//PrevPageNo uint32
type AppendDataPageReq struct {
MetricID uint32
Timestamp uint32
Value float64
Since uint32
RootPageNo uint32
PrevPageNo uint32
TimestampsChunks [][]byte
TimestampsSize uint16
ValuesChunks [][]byte
ValuesSize uint16
}
type Report struct {
//IsDataPageReused bool
//DataPageNo uint32
IsRootChanged bool
NewRootPageNo uint32
//ReusedIndexPages []uint32
Pages []PageToWrite
}
// type ChangedPage struct {
// PageNo uint32
@@ -253,186 +247,165 @@ type Report struct {
// Тому що є проблема із відновленням FreeList після збою, якщо з нього будуть паралельно
// забирати та добавляти номери сторінок інші потоки.
// Це буде працювати, якщо додавання в txlog і маніпуляції із freeList будуть відбуватись в одному потоці
func (s *Atree) AppendDataPage(req AppendDataPageReq) (_ Report, err error) {
// NotLinkedDataPage - це data сторінка із payload, але без встановленого prevPageNo та без розрахованого CRC32
type NotLinkedDataPage struct {
Since uint32
Data []byte
}
func (s NotLinkedDataPage) SetPrevPageNo(prevPageNo uint32) {
bin.PutUint32(s.Data[prevPageIdx:], prevPageNo)
bin.PutUint32(s.Data[crc32Idx:], calcChecksum(s.Data[:crc32Idx]))
}
type AppendDataPagesReq struct {
LastPageNo uint32
Legs []PathLeg
DataPages []NotLinkedDataPage
}
type Report struct {
NewRootPageNo uint32
LastPageNo uint32
Pages []PageToWrite
}
// Можливо atree не треба блокувати при читанні. Я можу створити копії index сторінок,
// а потім під 1 мутексом замінити в pages.
func (s *Atree) AppendDataPages(req AppendDataPagesReq) Report {
var (
pagesToRelease []uint32
report Report
//pagesToRelease []uint32
newRootPageNo uint32
lastPageNo = req.LastPageNo
pages []PageToWrite
legs = req.Legs
changed = make(map[uint32]PathLeg)
)
newDataPage := s.allocPage()
pagesToRelease = append(pagesToRelease, newDataPage.PageNo)
for _, p := range req.DataPages {
newDataPageNo, isReused := s.allocPageNumber() // alloc only number
// pagesToRelease = append(pagesToRelease, newDataPage.PageNo)
chunksToDataPage(newDataPage.Data, chunksToDataPageReq{
PrevPageNo: req.PrevPageNo,
TimestampsChunks: req.TimestampsChunks,
TimestampsSize: req.TimestampsSize,
ValuesChunks: req.ValuesChunks,
ValuesSize: req.ValuesSize,
})
// set prevPageNo
p.SetPrevPageNo(lastPageNo)
report.Pages = append(report.Pages, PageToWrite{
PageNo: newDataPage.PageNo,
Data: newDataPage.Data,
IsReused: newDataPage.IsReused,
})
// redoWriter, err := NewWriter(WriterOptions{
// MetricID: req.MetricID,
// Timestamp: req.Timestamp,
// Value: req.Value,
// IsDataPageReused: newDataPage.IsReused,
// DataPageNo: newDataPage.PageNo,
// Page: newDataPage.Data,
// })
// if err != nil {
// return
// }
if req.RootPageNo > 0 {
var path pathToDataPage
path, err = s.findPathToLastPage(req.RootPageNo)
if err != nil {
return
}
for _, leg := range path.Legs {
pagesToRelease = append(pagesToRelease, leg.PageNo)
}
if path.LastPageNo != req.PrevPageNo {
diploma.Abort(
diploma.WrongPrevPageNo,
fmt.Errorf("bug: last pageNo %d in tree != prev pageNo %d in _metric",
path.LastPageNo, req.PrevPageNo),
)
}
newPageNo := newDataPage.PageNo
lastIdx := len(path.Legs) - 1
for legIdx := lastIdx; legIdx >= 0; legIdx-- {
leg := path.Legs[legIdx]
ok := appendPair(leg.Data, req.Since, newPageNo)
if ok {
// index
report.Pages = append(report.Pages, PageToWrite{
PageNo: leg.PageNo,
Data: leg.Data,
})
// err = redoWriter.AppendIndexPage(leg.PageNo, leg.Data, 0)
// if err != nil {
// return
// }
break
}
newIndexPage := s.allocPage()
pagesToRelease = append(pagesToRelease, newIndexPage.PageNo)
appendPair(newIndexPage.Data, req.Since, newPageNo)
// ставлю мітку що всі pageNo на сторінці - це data pageNo
if legIdx == lastIdx {
newIndexPage.Data[isDataPageNumbersIdx] = 1
}
// flags = 0
// if newIndexPage.IsReused {
// flags |= FlagReused
// }
report.Pages = append(report.Pages, PageToWrite{
PageNo: newIndexPage.PageNo,
Data: newIndexPage.Data,
IsReused: newIndexPage.IsReused,
})
// err = redoWriter.AppendIndexPage(newIndexPage.PageNo, newIndexPage.Data, flags)
// if err != nil {
// return
// }
//
newPageNo = newIndexPage.PageNo
if legIdx == 0 {
newRoot := s.allocPage()
pagesToRelease = append(pagesToRelease, newRoot.PageNo)
appendPair(newRoot.Data, getSince(leg.Data), leg.PageNo) // old rootPageNo
appendPair(newRoot.Data, req.Since, newIndexPage.PageNo)
// Фиксирую новый root в REDO логе
report.Pages = append(report.Pages, PageToWrite{
PageNo: newRoot.PageNo,
Data: newRoot.Data,
IsReused: newRoot.IsReused,
})
report.NewRootPageNo = newRoot.PageNo
// flags = FlagNewRoot
// if newRoot.IsReused {
// flags |= FlagReused
// }
// err = redoWriter.AppendIndexPage(newRoot.PageNo, newRoot.Data, flags)
// if err != nil {
// return
// }
break
}
}
} else {
newRoot := s.allocPage()
pagesToRelease = append(pagesToRelease, newRoot.PageNo)
newRoot.Data[isDataPageNumbersIdx] = 1
appendPair(newRoot.Data, req.Since, newDataPage.PageNo)
report.Pages = append(report.Pages, PageToWrite{
PageNo: newRoot.PageNo,
Data: newRoot.Data,
IsReused: newRoot.IsReused,
pages = append(pages, PageToWrite{
PageNo: newDataPageNo,
Data: p.Data,
IsReused: isReused,
})
report.NewRootPageNo = newRoot.PageNo
// flags = FlagNewRoot
// if newRoot.IsReused {
// flags |= FlagReused
// }
// err = redoWriter.AppendIndexPage(newRoot.PageNo, newRoot.Data, flags)
// if err != nil {
// return
// FIX - після додавання index page потрібно модифікувати path,
// або будувати щось типу дерева знизу вверх
if len(legs) > 0 {
newPageNo := newDataPageNo
lastIdx := len(legs) - 1
for legIdx := lastIdx; legIdx >= 0; legIdx-- {
leg := req.Legs[legIdx]
ok := appendPair(leg.Data, p.Since, newPageNo)
if ok {
// index FIX
// потрібно запам'ятати змінені сторінки, але їх можуть змінювати
// кілька ітерацій, тому додавати в Pages не можна.
// на індексній сторінці достатньо місця. Запис вставлено.
changed[leg.PageNo] = leg
break
}
// на індексній сторінці НЕ достатньо місця. Створюю нову.
newIndexPage := s.allocPage()
//pagesToRelease = append(pagesToRelease, newIndexPage.PageNo)
appendPair(newIndexPage.Data, p.Since, newPageNo)
// ставлю мітку що всі pageNo на сторінці - це data pageNo
// fix - єдина оптимізація від існування isDataPageNumbersIdx - getAllPages не завантажує data pages
// if legIdx == lastIdx {
// newIndexPage.Data[isDataPageNumbersIdx] = 1
// }
pages = append(pages, PageToWrite{
PageNo: newIndexPage.PageNo,
Data: newIndexPage.Data,
IsReused: newIndexPage.IsReused,
})
// замінюю крок в path на новий
legs[legIdx] = PathLeg{
PageNo: newIndexPage.PageNo,
Data: newIndexPage.Data,
}
//
newPageNo = newIndexPage.PageNo
if legIdx == 0 {
newRoot := s.allocPage()
//pagesToRelease = append(pagesToRelease, newRoot.PageNo)
appendPair(newRoot.Data, getSince(leg.Data), leg.PageNo) // old rootPageNo
appendPair(newRoot.Data, p.Since, newIndexPage.PageNo)
// Фиксирую новый root в REDO логе
pages = append(pages, PageToWrite{
PageNo: newRoot.PageNo,
Data: newRoot.Data,
IsReused: newRoot.IsReused,
})
newRootPageNo = newRoot.PageNo
// додаю в початок списку кроків нову root сторінку
legs = append([]PathLeg{
{
PageNo: newRoot.PageNo,
Data: newRoot.Data,
},
}, legs...)
break
}
}
} else {
// індексну root сторінку створюю одразу для першої data сторінки,
// root data сторінки, як в B+Tree не буває.
newRoot := s.allocPage()
//pagesToRelease = append(pagesToRelease, newRoot.PageNo)
//newRoot.Data[isDataPageNumbersIdx] = 1
appendPair(newRoot.Data, p.Since, newDataPageNo)
pages = append(pages, PageToWrite{
PageNo: newRoot.PageNo,
Data: newRoot.Data,
IsReused: newRoot.IsReused,
})
newRootPageNo = newRoot.PageNo
// додаю в початок списку кроків нову root сторінку
legs = append([]PathLeg{
{
PageNo: newRoot.PageNo,
Data: newRoot.Data,
},
}, legs...)
}
// На данний момен схема - наступна. Всі сторінки - data та index - зафіксовані в кеші.
// Отже запис на диск пройде максимально швидко. Після цього ReferenceCount кожної
// сторінки зменшиться на 1. Оскільки на метрику утримується XLock, сторінки мають
// ReferenceCount = 1 (немає інших читачів).
// for _, pageNo := range indexPagesToRelease {
// s.releasePage(pageNo)
// }
lastPageNo = newDataPageNo
}
for _, leg := range changed {
// fix recalc checksum
pages = append(pages, PageToWrite{
PageNo: leg.PageNo,
Data: leg.Data,
})
}
return Report{
NewRootPageNo: newRootPageNo,
LastPageNo: lastPageNo,
Pages: pages,
}
// err = redoWriter.Close()
// if err != nil {
// return
// }
// На данний момен схема - наступна. Всі сторінки - data та index - зафіксовані в кеші.
// Отже запис на диск пройде максимально швидко. Після цього ReferenceCount кожної
// сторінки зменшиться на 1. Оскільки на метрику утримується XLock, сторінки мають
// ReferenceCount = 1 (немає інших читачів).
// waitCh := make(chan struct{})
// task := WriteTask{
// WaitCh: waitCh,
// Pages: report.Pages,
// }
// s.appendWriteTaskToQueue(task)
// <-waitCh
// for _, pageNo := range dataPagesToRelease {
// s.releasePage(pageNo)
// }
// for _, pageNo := range indexPagesToRelease {
// s.releasePage(pageNo)
// }
return report, nil
}
// DELETE
type PageLists struct {
DataPages []uint32
IndexPages []uint32
}
type Level struct {
PageNo uint32
PageData []byte
@@ -440,31 +413,29 @@ type Level struct {
ChildQty int
}
func (s *Atree) GetAllPages(rootPageNo uint32) (_ PageLists, err error) {
func (s *Atree) GetAllPages(rootPageNo uint32) (_ []uint32, err error) {
var (
dataPages []uint32
indexPages []uint32
levels []*Level
pageNumbers []uint32
levels []*Level
)
buf, err := s.fetchIndexPage(rootPageNo)
if err != nil {
err = fmt.Errorf("fetchIndexPage(%d): %s", rootPageNo, err)
return
return nil, fmt.Errorf("fetchIndexPage(%d): %s", rootPageNo, err)
}
indexPages = append(indexPages, rootPageNo)
pageNumbers = append(pageNumbers, rootPageNo)
if buf[isDataPageNumbersIdx] == 1 {
pageNumbers := listPageNumbers(buf)
dataPages = append(dataPages, pageNumbers...)
// if buf[isDataPageNumbersIdx] == 1 {
// pageNumbers := listPageNumbers(buf)
// dataPages = append(dataPages, pageNumbers...)
s.releasePage(rootPageNo)
// s.releasePage(rootPageNo)
return PageLists{
DataPages: dataPages,
IndexPages: indexPages,
}, nil
}
// return PageLists{
// DataPages: dataPages,
// IndexPages: indexPages,
// }, nil
// }
levels = append(levels, &Level{
PageNo: rootPageNo,
@@ -475,10 +446,7 @@ func (s *Atree) GetAllPages(rootPageNo uint32) (_ PageLists, err error) {
for {
if len(levels) == 0 {
return PageLists{
DataPages: dataPages,
IndexPages: indexPages,
}, nil
return pageNumbers, nil
}
lastIdx := len(levels) - 1
@@ -489,17 +457,15 @@ func (s *Atree) GetAllPages(rootPageNo uint32) (_ PageLists, err error) {
level.Idx++
var buf []byte
buf, err = s.fetchIndexPage(pageNo)
buf, err = s.fetchPage(pageNo)
if err != nil {
err = fmt.Errorf("fetchIndexPage(%d): %s", pageNo, err)
return
return nil, fmt.Errorf("fetchPage(%d): %s", pageNo, err)
}
indexPages = append(indexPages, pageNo)
if buf[isDataPageNumbersIdx] == 1 {
pageNumbers := listPageNumbers(buf)
dataPages = append(dataPages, pageNumbers...)
pageNumbers = append(pageNumbers, pageNo)
if buf[pageTypeIdx] == PageTypeData {
//pageNumbers := listPageNumbers(buf)
//dataPages = append(dataPages, pageNumbers...)
s.releasePage(pageNo)
} else {
levels = append(levels, &Level{

View File

@@ -34,20 +34,31 @@ func (s *Atree) DeletePages(pageNumbers []uint32) {
}
func (s *Atree) fetchIndexPage(pageNo uint32) ([]byte, error) {
return s.fetchPage(pageNo, PageTypeIndex)
buf, err := s.fetchPage(pageNo)
if err != nil {
return nil, err
}
if buf[pageTypeIdx] != PageTypeIndex {
return nil, fmt.Errorf("wrong pageType %d instead of %d", buf[pageTypeIdx], PageTypeIndex)
}
return buf, nil
}
func (s *Atree) fetchDataPage(pageNo uint32) ([]byte, error) {
return s.fetchPage(pageNo, PageTypeData)
buf, err := s.fetchPage(pageNo)
if err != nil {
return nil, err
}
if buf[pageTypeIdx] != PageTypeIndex {
return nil, fmt.Errorf("wrong pageType %d instead of %d", buf[pageTypeIdx], PageTypeIndex)
}
return buf, nil
}
func (s *Atree) fetchPage(pageNo uint32, pageTypeIdx byte) ([]byte, error) {
func (s *Atree) fetchPage(pageNo uint32) ([]byte, error) {
s.mutex.Lock()
p, ok := s.pages[pageNo]
if ok {
if p.Buf[pageTypeIdx] != pageTypeIdx {
return nil, fmt.Errorf("wrong pageType %d instead of %d", p.Buf[pageTypeIdx], pageTypeIdx)
}
p.ReferenceCount++
s.mutex.Unlock()
return p.Buf, nil
@@ -93,23 +104,28 @@ func (s *Atree) releasePage(pageNo uint32) {
}
}
func (s *Atree) allocPageNumber() (uint32, bool) {
// fix
return 0, true
}
func (s *Atree) allocPage() AllocatedPage {
var (
allocated = AllocatedPage{
Data: make([]byte, PageSize),
}
)
allocated.PageNo = s.freelist.ReservePage()
//allocated.PageNo = s.freelist.ReservePage()
s.mutex.Lock()
if allocated.PageNo > 0 {
allocated.IsReused = true
} else {
if s.allocatedPagesQty == math.MaxUint32 {
qb.Abort(qb.MaxAtreeSizeExceeded, errors.New("no space in Atree index"))
}
s.allocatedPagesQty++
allocated.PageNo = s.allocatedPagesQty
}
// if allocated.PageNo > 0 {
// allocated.IsReused = true
// } else {
// if s.allocatedPagesQty == math.MaxUint32 {
// qb.Abort(qb.MaxAtreeSizeExceeded, errors.New("no space in Atree index"))
// }
// s.allocatedPagesQty++
// allocated.PageNo = s.allocatedPagesQty
// }
// s.pages[allocated.PageNo] = &_page{
// // fix pageType
@@ -241,61 +257,61 @@ func (s *Atree) readPages() {
// WRITE
func (s *Atree) pageWriter() {
for {
select {
case <-s.writeSignalCh:
err := s.writeTasks()
if err != nil {
qb.Abort(qb.WriteToAtreeFailed, err)
}
}
}
}
// func (s *Atree) pageWriter() {
// for {
// select {
// case <-s.writeSignalCh:
// err := s.writeTasks()
// if err != nil {
// qb.Abort(qb.WriteToAtreeFailed, err)
// }
// }
// }
// }
type WriteTask struct {
WaitCh chan struct{}
Pages []PageToWrite
}
// type WriteTask struct {
// WaitCh chan struct{}
// Pages []PageToWrite
// }
func (s *Atree) appendWriteTaskToQueue(task WriteTask) {
s.mutex.Lock()
s.writeTasksQueue = append(s.writeTasksQueue, task)
s.mutex.Unlock()
// func (s *Atree) appendWriteTaskToQueue(task WriteTask) {
// s.mutex.Lock()
// s.writeTasksQueue = append(s.writeTasksQueue, task)
// s.mutex.Unlock()
select {
case s.writeSignalCh <- struct{}{}:
default:
}
}
// select {
// case s.writeSignalCh <- struct{}{}:
// default:
// }
// }
func (s *Atree) writeTasks() error {
s.mutex.Lock()
tasks := s.writeTasksQueue
s.writeTasksQueue = nil
s.mutex.Unlock()
// func (s *Atree) writeTasks() error {
// s.mutex.Lock()
// tasks := s.writeTasksQueue
// s.writeTasksQueue = nil
// s.mutex.Unlock()
for _, task := range tasks {
for _, p := range task.Pages {
if len(p.Data) != PageSize {
return fmt.Errorf("wrong page %d size: %d",
p.PageNo, len(p.Data))
}
bin.PutUint32(p.Data[crc32Idx:], calcChecksum(p.Data[:crc32Idx]))
// for _, task := range tasks {
// for _, p := range task.Pages {
// if len(p.Data) != PageSize {
// return fmt.Errorf("wrong page %d size: %d",
// p.PageNo, len(p.Data))
// }
// bin.PutUint32(p.Data[crc32Idx:], calcChecksum(p.Data[:crc32Idx]))
off := (p.PageNo - 1) * PageSize
n, err := s.file.WriteAt(p.Data, int64(off))
if err != nil {
return err
}
if n != len(p.Data) {
return fmt.Errorf("write %d instead of %d", n, len(p.Data))
}
}
close(task.WaitCh)
}
return nil
}
// off := (p.PageNo - 1) * PageSize
// n, err := s.file.WriteAt(p.Data, int64(off))
// if err != nil {
// return err
// }
// if n != len(p.Data) {
// return fmt.Errorf("write %d instead of %d", n, len(p.Data))
// }
// }
// close(task.WaitCh)
// }
// return nil
// }
// IO
@@ -339,9 +355,9 @@ func openFile(fileName string, pageSize int) (_ *os.File, _ uint32, err error) {
return file, uint32(allocatedPagesQty), nil
}
func (s *Atree) ApplyREDO(task WriteTask) {
s.appendWriteTaskToQueue(task)
}
// func (s *Atree) ApplyREDO(task WriteTask) {
// s.appendWriteTaskToQueue(task)
// }
func (s *Atree) verifyCRC(data []byte, pageSize int) error {
var (

View File

@@ -55,16 +55,15 @@ func BinarySearch(qty int, keyComparator bin.KeyComparator) (elemIdx int, isFoun
}
}
type chunksToDataPageReq struct {
PrevPageNo uint32
type ChunksToNotLinkedDataPageReq struct {
// PrevPageNo uint32
TimestampsChunks [][]byte
TimestampsSize uint16
ValuesChunks [][]byte
ValuesSize uint16
}
func chunksToDataPage(buf []byte, req chunksToDataPageReq) {
bin.PutUint32(buf[prevPageIdx:], req.PrevPageNo)
func ChunksToNotLinkedDataPage(buf []byte, req ChunksToNotLinkedDataPageReq) {
bin.PutUint16(buf[timestampsSizeIdx:], req.TimestampsSize)
bin.PutUint16(buf[valuesSizeIdx:], req.ValuesSize)
@@ -96,7 +95,6 @@ func chunksToDataPage(buf []byte, req chunksToDataPageReq) {
break
}
}
bin.PutUint32(buf[crc32Idx:], calcChecksum(buf[:crc32Idx]))
}
func setPrevPageNo(buf []byte, pageNo uint32) {