tmp
This commit is contained in:
403
atree/atree.go
403
atree/atree.go
@@ -8,7 +8,6 @@ import (
|
||||
"sync"
|
||||
|
||||
"gordenko.dev/dima/qb/bin"
|
||||
"gordenko.dev/dima/qb/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -47,17 +46,6 @@ const (
|
||||
DataPagePayloadSize int = dataFooterIdx
|
||||
)
|
||||
|
||||
const (
|
||||
FlagReused byte = 1 // сторінка із FreeList
|
||||
FlagNewRoot byte = 2 // новая страница
|
||||
)
|
||||
|
||||
type PageToWrite struct {
|
||||
PageNo uint32
|
||||
Data []byte
|
||||
IsReused bool
|
||||
}
|
||||
|
||||
type _page struct {
|
||||
PageNo uint32
|
||||
Buf []byte
|
||||
@@ -85,13 +73,9 @@ func New(opt Options) (*Atree, error) {
|
||||
if opt.Dir == "" {
|
||||
return nil, errors.New("Dir option is required")
|
||||
}
|
||||
// if opt.RedoDir == "" {
|
||||
// return nil, errors.New("RedoDir option is required")
|
||||
// }
|
||||
if opt.DatabaseName == "" {
|
||||
return nil, errors.New("DatabaseName option is required")
|
||||
}
|
||||
|
||||
// открываю или создаю dbName.data и dbName.index файлы
|
||||
var (
|
||||
fileName = filepath.Join(opt.Dir, opt.DatabaseName+".db")
|
||||
@@ -205,193 +189,6 @@ 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 {
|
||||
Since uint32
|
||||
TimestampsChunks [][]byte
|
||||
TimestampsSize uint16
|
||||
ValuesChunks [][]byte
|
||||
ValuesSize uint16
|
||||
}
|
||||
|
||||
// type ChangedPage struct {
|
||||
// PageNo uint32
|
||||
// Data []byte
|
||||
// IsReused bool
|
||||
// }
|
||||
|
||||
// AppendDataPage - метод не записує дані в data-файл, а лише змінює дані
|
||||
// в page cache та freeList і повертає звіт що змінено.
|
||||
// Цей звіт txlog має записати в transaction log і лише потім можна змінювати data файл.
|
||||
// Є ідея - записати у index файли заглушки 255,255,255,255 замість номерів сторінок і зберегти зміщення.
|
||||
// А потім одним викликом отримати із FreeList список вільних сторінок.
|
||||
// Тому що є проблема із відновленням FreeList після збою, якщо з нього будуть паралельно
|
||||
// забирати та добавляти номери сторінок інші потоки.
|
||||
// Це буде працювати, якщо додавання в txlog і маніпуляції із freeList будуть відбуватись в одному потоці
|
||||
// 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:], util.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
|
||||
newRootPageNo uint32
|
||||
lastPageNo = req.LastPageNo
|
||||
pages []PageToWrite
|
||||
legs = req.Legs
|
||||
changed = make(map[uint32]PathLeg)
|
||||
)
|
||||
|
||||
for _, p := range req.DataPages {
|
||||
newDataPageNo, isReused := s.allocPageNumber() // alloc only number
|
||||
// pagesToRelease = append(pagesToRelease, newDataPage.PageNo)
|
||||
|
||||
// set prevPageNo
|
||||
p.SetPrevPageNo(lastPageNo)
|
||||
|
||||
pages = append(pages, PageToWrite{
|
||||
PageNo: newDataPageNo,
|
||||
Data: p.Data,
|
||||
IsReused: isReused,
|
||||
})
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE
|
||||
|
||||
type Level struct {
|
||||
@@ -469,3 +266,203 @@ func (s *Atree) GetAllPages(rootPageNo uint32) (_ []uint32, err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// const (
|
||||
// FlagReused byte = 1 // сторінка із FreeList
|
||||
// FlagNewRoot byte = 2 // новая страница
|
||||
// )
|
||||
|
||||
// type PageToWrite struct {
|
||||
// PageNo uint32
|
||||
// Data []byte
|
||||
// IsReused bool
|
||||
// }
|
||||
|
||||
//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
|
||||
// newRootPageNo uint32
|
||||
// lastPageNo = req.LastPageNo
|
||||
// pages []PageToWrite
|
||||
// legs = req.Legs
|
||||
// changed = make(map[uint32]PathLeg)
|
||||
// )
|
||||
|
||||
// for _, p := range req.DataPages {
|
||||
// newDataPageNo, isReused := s.allocPageNumber() // alloc only number
|
||||
// // pagesToRelease = append(pagesToRelease, newDataPage.PageNo)
|
||||
|
||||
// // set prevPageNo
|
||||
// p.SetPrevPageNo(lastPageNo)
|
||||
|
||||
// pages = append(pages, PageToWrite{
|
||||
// PageNo: newDataPageNo,
|
||||
// Data: p.Data,
|
||||
// IsReused: isReused,
|
||||
// })
|
||||
|
||||
// // 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,
|
||||
// }
|
||||
// }
|
||||
|
||||
// APPEND DATA PAGE
|
||||
|
||||
//MetricID uint32
|
||||
//Timestamp uint32
|
||||
//Value float64
|
||||
//RootPageNo uint32
|
||||
//PrevPageNo uint32
|
||||
|
||||
// type AppendDataPageReq struct {
|
||||
// Since uint32
|
||||
// TimestampsChunks [][]byte
|
||||
// TimestampsSize uint16
|
||||
// ValuesChunks [][]byte
|
||||
// ValuesSize uint16
|
||||
// }
|
||||
|
||||
// type ChangedPage struct {
|
||||
// PageNo uint32
|
||||
// Data []byte
|
||||
// IsReused bool
|
||||
// }
|
||||
|
||||
// AppendDataPage - метод не записує дані в data-файл, а лише змінює дані
|
||||
// в page cache та freeList і повертає звіт що змінено.
|
||||
// Цей звіт txlog має записати в transaction log і лише потім можна змінювати data файл.
|
||||
// Є ідея - записати у index файли заглушки 255,255,255,255 замість номерів сторінок і зберегти зміщення.
|
||||
// А потім одним викликом отримати із FreeList список вільних сторінок.
|
||||
// Тому що є проблема із відновленням FreeList після збою, якщо з нього будуть паралельно
|
||||
// забирати та добавляти номери сторінок інші потоки.
|
||||
// Це буде працювати, якщо додавання в txlog і маніпуляції із freeList будуть відбуватись в одному потоці
|
||||
// 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:], util.CalcChecksum(s.Data[:crc32Idx]))
|
||||
// }
|
||||
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user