tmp
This commit is contained in:
403
atree/atree.go
403
atree/atree.go
@@ -8,7 +8,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"gordenko.dev/dima/qb/bin"
|
"gordenko.dev/dima/qb/bin"
|
||||||
"gordenko.dev/dima/qb/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -47,17 +46,6 @@ const (
|
|||||||
DataPagePayloadSize int = dataFooterIdx
|
DataPagePayloadSize int = dataFooterIdx
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
FlagReused byte = 1 // сторінка із FreeList
|
|
||||||
FlagNewRoot byte = 2 // новая страница
|
|
||||||
)
|
|
||||||
|
|
||||||
type PageToWrite struct {
|
|
||||||
PageNo uint32
|
|
||||||
Data []byte
|
|
||||||
IsReused bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type _page struct {
|
type _page struct {
|
||||||
PageNo uint32
|
PageNo uint32
|
||||||
Buf []byte
|
Buf []byte
|
||||||
@@ -85,13 +73,9 @@ func New(opt Options) (*Atree, error) {
|
|||||||
if opt.Dir == "" {
|
if opt.Dir == "" {
|
||||||
return nil, errors.New("Dir option is required")
|
return nil, errors.New("Dir option is required")
|
||||||
}
|
}
|
||||||
// if opt.RedoDir == "" {
|
|
||||||
// return nil, errors.New("RedoDir option is required")
|
|
||||||
// }
|
|
||||||
if opt.DatabaseName == "" {
|
if opt.DatabaseName == "" {
|
||||||
return nil, errors.New("DatabaseName option is required")
|
return nil, errors.New("DatabaseName option is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
// открываю или создаю dbName.data и dbName.index файлы
|
// открываю или создаю dbName.data и dbName.index файлы
|
||||||
var (
|
var (
|
||||||
fileName = filepath.Join(opt.Dir, opt.DatabaseName+".db")
|
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
|
// DELETE
|
||||||
|
|
||||||
type Level struct {
|
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]))
|
||||||
|
// }
|
||||||
|
|
||||||
|
//
|
||||||
|
|||||||
@@ -4,23 +4,23 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
octopus "gordenko.dev/dima/qb"
|
"gordenko.dev/dima/qb"
|
||||||
"gordenko.dev/dima/qb/bin"
|
"gordenko.dev/dima/qb/bin"
|
||||||
"gordenko.dev/dima/qb/enc"
|
"gordenko.dev/dima/qb/enc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BackwardCursor struct {
|
type BackwardCursor struct {
|
||||||
metricType octopus.MetricType
|
metricType qb.MetricType
|
||||||
fracDigits byte
|
fracDigits byte
|
||||||
atree *Atree
|
atree *Atree
|
||||||
pageNo uint32
|
pageNo uint32
|
||||||
pageData []byte
|
pageData []byte
|
||||||
timestampDecompressor octopus.TimestampDecompressor
|
timestampDecompressor qb.TimestampDecompressor
|
||||||
valueDecompressor octopus.ValueDecompressor
|
valueDecompressor qb.ValueDecompressor
|
||||||
}
|
}
|
||||||
|
|
||||||
type BackwardCursorOptions struct {
|
type BackwardCursorOptions struct {
|
||||||
MetricType octopus.MetricType
|
MetricType qb.MetricType
|
||||||
FracDigits byte
|
FracDigits byte
|
||||||
PageNo uint32
|
PageNo uint32
|
||||||
PageData []byte
|
PageData []byte
|
||||||
@@ -29,12 +29,12 @@ type BackwardCursorOptions struct {
|
|||||||
|
|
||||||
func NewBackwardCursor(opt BackwardCursorOptions) (*BackwardCursor, error) {
|
func NewBackwardCursor(opt BackwardCursorOptions) (*BackwardCursor, error) {
|
||||||
switch opt.MetricType {
|
switch opt.MetricType {
|
||||||
case octopus.Instant, octopus.Cumulative:
|
case qb.Instant, qb.Cumulative:
|
||||||
// ok
|
// ok
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("MetricType option has wrong value: %d", opt.MetricType)
|
return nil, fmt.Errorf("MetricType option has wrong value: %d", opt.MetricType)
|
||||||
}
|
}
|
||||||
if opt.FracDigits > octopus.MaxFracDigits {
|
if opt.FracDigits > qb.MaxFracDigits {
|
||||||
return nil, errors.New("FracDigits option is required")
|
return nil, errors.New("FracDigits option is required")
|
||||||
}
|
}
|
||||||
if opt.Atree == nil {
|
if opt.Atree == nil {
|
||||||
@@ -137,11 +137,11 @@ func (s *BackwardCursor) makeDecompressors() error {
|
|||||||
vbuf := s.pageData[timestampsPayloadSize : timestampsPayloadSize+valuesPayloadSize]
|
vbuf := s.pageData[timestampsPayloadSize : timestampsPayloadSize+valuesPayloadSize]
|
||||||
|
|
||||||
switch s.metricType {
|
switch s.metricType {
|
||||||
case octopus.Instant:
|
case qb.Instant:
|
||||||
s.valueDecompressor = enc.NewReverseInstantDeltaDecompressor(
|
s.valueDecompressor = enc.NewReverseInstantDeltaDecompressor(
|
||||||
vbuf, s.fracDigits)
|
vbuf, s.fracDigits)
|
||||||
|
|
||||||
case octopus.Cumulative:
|
case qb.Cumulative:
|
||||||
s.valueDecompressor = enc.NewReverseCumulativeDeltaDecompressor(
|
s.valueDecompressor = enc.NewReverseCumulativeDeltaDecompressor(
|
||||||
vbuf, s.fracDigits)
|
vbuf, s.fracDigits)
|
||||||
|
|
||||||
@@ -151,8 +151,8 @@ func (s *BackwardCursor) makeDecompressors() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeDecompressors(pageData []byte, metricType octopus.MetricType, fracDigits byte) (
|
func makeDecompressors(pageData []byte, metricType qb.MetricType, fracDigits byte) (
|
||||||
octopus.TimestampDecompressor, octopus.ValueDecompressor, error,
|
qb.TimestampDecompressor, qb.ValueDecompressor, error,
|
||||||
) {
|
) {
|
||||||
timestampsPayloadSize := bin.GetUint16(pageData[timestampsSizeIdx:])
|
timestampsPayloadSize := bin.GetUint16(pageData[timestampsSizeIdx:])
|
||||||
valuesPayloadSize := bin.GetUint16(pageData[valuesSizeIdx:])
|
valuesPayloadSize := bin.GetUint16(pageData[valuesSizeIdx:])
|
||||||
@@ -170,13 +170,13 @@ func makeDecompressors(pageData []byte, metricType octopus.MetricType, fracDigit
|
|||||||
|
|
||||||
vbuf := pageData[timestampsPayloadSize : timestampsPayloadSize+valuesPayloadSize]
|
vbuf := pageData[timestampsPayloadSize : timestampsPayloadSize+valuesPayloadSize]
|
||||||
|
|
||||||
var valueDecompressor octopus.ValueDecompressor
|
var valueDecompressor qb.ValueDecompressor
|
||||||
switch metricType {
|
switch metricType {
|
||||||
case octopus.Instant:
|
case qb.Instant:
|
||||||
valueDecompressor = enc.NewReverseInstantDeltaDecompressor(
|
valueDecompressor = enc.NewReverseInstantDeltaDecompressor(
|
||||||
vbuf, fracDigits)
|
vbuf, fracDigits)
|
||||||
|
|
||||||
case octopus.Cumulative:
|
case qb.Cumulative:
|
||||||
valueDecompressor = enc.NewReverseCumulativeDeltaDecompressor(
|
valueDecompressor = enc.NewReverseCumulativeDeltaDecompressor(
|
||||||
vbuf, fracDigits)
|
vbuf, fracDigits)
|
||||||
|
|
||||||
|
|||||||
58
atree/io.go
58
atree/io.go
@@ -13,11 +13,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// fix - додати ID, щоб потім перемістити із тимчасового буфера в pages, або звільнити
|
// fix - додати ID, щоб потім перемістити із тимчасового буфера в pages, або звільнити
|
||||||
type AllocatedPage struct {
|
// type AllocatedPage struct {
|
||||||
PageNo uint32
|
// PageNo uint32
|
||||||
Data []byte
|
// Data []byte
|
||||||
IsReused bool
|
// IsReused bool
|
||||||
}
|
// }
|
||||||
|
|
||||||
type readResult struct {
|
type readResult struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
@@ -110,33 +110,33 @@ func (s *Atree) allocPageNumber() (uint32, bool) {
|
|||||||
return 0, true
|
return 0, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Atree) allocPage() AllocatedPage {
|
// func (s *Atree) allocPage() AllocatedPage {
|
||||||
var (
|
// var (
|
||||||
allocated = AllocatedPage{
|
// allocated = AllocatedPage{
|
||||||
Data: make([]byte, PageSize),
|
// Data: make([]byte, PageSize),
|
||||||
}
|
|
||||||
)
|
|
||||||
//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
|
|
||||||
// }
|
// }
|
||||||
|
// )
|
||||||
|
// //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
|
||||||
|
// // }
|
||||||
|
|
||||||
// s.pages[allocated.PageNo] = &_page{
|
// // s.pages[allocated.PageNo] = &_page{
|
||||||
// // fix pageType
|
// // // fix pageType
|
||||||
// PageNo: allocated.PageNo,
|
// // PageNo: allocated.PageNo,
|
||||||
// Buf: allocated.Data,
|
// // Buf: allocated.Data,
|
||||||
// ReferenceCount: 1,
|
// // ReferenceCount: 1,
|
||||||
|
// // }
|
||||||
|
// s.mutex.Unlock()
|
||||||
|
// return allocated
|
||||||
// }
|
// }
|
||||||
s.mutex.Unlock()
|
|
||||||
return allocated
|
|
||||||
}
|
|
||||||
|
|
||||||
// // fix - без freelist
|
// // fix - без freelist
|
||||||
// func (s *Atree) allocIndexPage() AllocatedPage {
|
// func (s *Atree) allocIndexPage() AllocatedPage {
|
||||||
|
|||||||
112
atree/misc.go
112
atree/misc.go
@@ -55,51 +55,51 @@ func BinarySearch(qty int, keyComparator bin.KeyComparator) (elemIdx int, isFoun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChunksToNotLinkedDataPageReq struct {
|
// type ChunksToNotLinkedDataPageReq struct {
|
||||||
// PrevPageNo uint32
|
// // PrevPageNo uint32
|
||||||
TimestampsChunks [][]byte
|
// TimestampsChunks [][]byte
|
||||||
TimestampsSize uint16
|
// TimestampsSize uint16
|
||||||
ValuesChunks [][]byte
|
// ValuesChunks [][]byte
|
||||||
ValuesSize uint16
|
// ValuesSize uint16
|
||||||
}
|
// }
|
||||||
|
|
||||||
func ChunksToNotLinkedDataPage(buf []byte, req ChunksToNotLinkedDataPageReq) {
|
// func ChunksToNotLinkedDataPage(buf []byte, req ChunksToNotLinkedDataPageReq) {
|
||||||
bin.PutUint16(buf[timestampsSizeIdx:], req.TimestampsSize)
|
// bin.PutUint16(buf[timestampsSizeIdx:], req.TimestampsSize)
|
||||||
bin.PutUint16(buf[valuesSizeIdx:], req.ValuesSize)
|
// bin.PutUint16(buf[valuesSizeIdx:], req.ValuesSize)
|
||||||
|
|
||||||
var (
|
// var (
|
||||||
remainingSize = int(req.TimestampsSize)
|
// remainingSize = int(req.TimestampsSize)
|
||||||
pos = 0
|
// pos = 0
|
||||||
)
|
// )
|
||||||
for _, chunk := range req.TimestampsChunks {
|
// for _, chunk := range req.TimestampsChunks {
|
||||||
if remainingSize >= len(chunk) {
|
// if remainingSize >= len(chunk) {
|
||||||
copy(buf[pos:], chunk)
|
// copy(buf[pos:], chunk)
|
||||||
remainingSize -= len(chunk)
|
// remainingSize -= len(chunk)
|
||||||
pos += len(chunk)
|
// pos += len(chunk)
|
||||||
} else {
|
// } else {
|
||||||
copy(buf[pos:], chunk[:remainingSize])
|
// copy(buf[pos:], chunk[:remainingSize])
|
||||||
break
|
// break
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
remainingSize = int(req.ValuesSize)
|
// remainingSize = int(req.ValuesSize)
|
||||||
pos = int(req.TimestampsSize)
|
// pos = int(req.TimestampsSize)
|
||||||
|
|
||||||
for _, chunk := range req.ValuesChunks {
|
// for _, chunk := range req.ValuesChunks {
|
||||||
if remainingSize >= len(chunk) {
|
// if remainingSize >= len(chunk) {
|
||||||
copy(buf[pos:], chunk)
|
// copy(buf[pos:], chunk)
|
||||||
remainingSize -= len(chunk)
|
// remainingSize -= len(chunk)
|
||||||
pos += len(chunk)
|
// pos += len(chunk)
|
||||||
} else {
|
// } else {
|
||||||
copy(buf[pos:], chunk[:remainingSize])
|
// copy(buf[pos:], chunk[:remainingSize])
|
||||||
break
|
// break
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func setPrevPageNo(buf []byte, pageNo uint32) {
|
// func setPrevPageNo(buf []byte, pageNo uint32) {
|
||||||
bin.PutUint32(buf[prevPageIdx:], pageNo)
|
// bin.PutUint32(buf[prevPageIdx:], pageNo)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func getPrevPageNo(buf []byte) uint32 {
|
func getPrevPageNo(buf []byte) uint32 {
|
||||||
return bin.GetUint32(buf[prevPageIdx:])
|
return bin.GetUint32(buf[prevPageIdx:])
|
||||||
@@ -191,20 +191,20 @@ func listPageNumbersSince(buf []byte, timestamp uint32) (pageNumbers []uint32) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSince(buf []byte) uint32 {
|
// func getSince(buf []byte) uint32 {
|
||||||
return bin.GetUint32(buf[0:])
|
// return bin.GetUint32(buf[0:])
|
||||||
}
|
// }
|
||||||
|
|
||||||
func appendPair(buf []byte, timestamp uint32, pageNo uint32) bool {
|
// func appendPair(buf []byte, timestamp uint32, pageNo uint32) bool {
|
||||||
qty := bin.GetUint16AsInt(buf[indexRecordsQtyIdx:])
|
// qty := bin.GetUint16AsInt(buf[indexRecordsQtyIdx:])
|
||||||
free := indexFooterIdx - qty*pairSize
|
// free := indexFooterIdx - qty*pairSize
|
||||||
if free < pairSize {
|
// if free < pairSize {
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
pos := qty * timestampSize
|
// pos := qty * timestampSize
|
||||||
bin.PutUint32(buf[pos:], timestamp)
|
// bin.PutUint32(buf[pos:], timestamp)
|
||||||
pos = indexFooterIdx - (qty+1)*PageNoSize
|
// pos = indexFooterIdx - (qty+1)*PageNoSize
|
||||||
bin.PutUint32(buf[pos:], pageNo)
|
// bin.PutUint32(buf[pos:], pageNo)
|
||||||
bin.PutIntAsUint16(buf[indexRecordsQtyIdx:], qty+1)
|
// bin.PutIntAsUint16(buf[indexRecordsQtyIdx:], qty+1)
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
package redox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"hash/crc32"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"gordenko.dev/dima/qb/bin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type REDOFile struct {
|
|
||||||
MetricID uint32
|
|
||||||
Timestamp uint32
|
|
||||||
Value float64
|
|
||||||
IsDataPageReused bool
|
|
||||||
DataPage PageToWrite
|
|
||||||
IsRootChanged bool
|
|
||||||
RootPageNo uint32
|
|
||||||
ReusedIndexPages []uint32
|
|
||||||
IndexPages []PageToWrite
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReadREDOFileReq struct {
|
|
||||||
FileName string
|
|
||||||
DataPageSize int
|
|
||||||
IndexPageSize int
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadREDOFile(req ReadREDOFileReq) (*REDOFile, error) {
|
|
||||||
buf, err := os.ReadFile(req.FileName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(buf) < 25 {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
end = len(buf) - 4
|
|
||||||
payload = buf[:end]
|
|
||||||
checksum = bin.GetUint32(buf[end:])
|
|
||||||
calculatedChecksum = crc32.ChecksumIEEE(payload)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Помилка чексуми означає що файл або недописаний, або пошкодженний
|
|
||||||
if checksum != calculatedChecksum {
|
|
||||||
return nil, fmt.Errorf("written checksum %d not equal calculated checksum %d",
|
|
||||||
checksum, calculatedChecksum)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
redoLog = REDOFile{
|
|
||||||
MetricID: bin.GetUint32(buf[0:]),
|
|
||||||
Timestamp: bin.GetUint32(buf[4:]),
|
|
||||||
Value: bin.GetFloat64(buf[8:]),
|
|
||||||
IsDataPageReused: buf[16] == 1,
|
|
||||||
DataPage: PageToWrite{
|
|
||||||
PageNo: bin.GetUint32(buf[17:]),
|
|
||||||
Data: buf[21 : 21+req.DataPageSize],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pos = 21 + req.DataPageSize
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
if pos == len(payload) {
|
|
||||||
return &redoLog, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if pos > len(payload) {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := buf[pos]
|
|
||||||
|
|
||||||
item := PageToWrite{
|
|
||||||
PageNo: bin.GetUint32(buf[pos+1:]),
|
|
||||||
}
|
|
||||||
pos += 5 // flags + pageNo
|
|
||||||
item.Data = buf[pos : pos+req.IndexPageSize]
|
|
||||||
pos += req.IndexPageSize
|
|
||||||
|
|
||||||
redoLog.IndexPages = append(redoLog.IndexPages, item)
|
|
||||||
|
|
||||||
if (flags & FlagReused) == FlagReused {
|
|
||||||
redoLog.ReusedIndexPages = append(redoLog.ReusedIndexPages, item.PageNo)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags & FlagNewRoot) == FlagNewRoot {
|
|
||||||
redoLog.IsRootChanged = true
|
|
||||||
redoLog.RootPageNo = item.PageNo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
package redox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"hash/crc32"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"gordenko.dev/dima/qb/bin"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
FlagReused byte = 1 // сторінка із FreeList
|
|
||||||
FlagNewRoot byte = 2 // новая страница
|
|
||||||
)
|
|
||||||
|
|
||||||
type PageToWrite struct {
|
|
||||||
PageNo uint32
|
|
||||||
Data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type Writer struct {
|
|
||||||
metricID uint32
|
|
||||||
timestamp uint32
|
|
||||||
value float64
|
|
||||||
tmp []byte
|
|
||||||
fileName string
|
|
||||||
file *os.File
|
|
||||||
hasher hash.Hash32
|
|
||||||
isDataPageReused bool
|
|
||||||
dataPageNo uint32
|
|
||||||
isRootChanged bool
|
|
||||||
newRootPageNo uint32
|
|
||||||
indexPages []uint32
|
|
||||||
reusedIndexPages []uint32
|
|
||||||
indexPagesToWrite []PageToWrite
|
|
||||||
}
|
|
||||||
|
|
||||||
type WriterOptions struct {
|
|
||||||
Dir string
|
|
||||||
MetricID uint32
|
|
||||||
Value float64
|
|
||||||
Timestamp uint32
|
|
||||||
IsDataPageReused bool
|
|
||||||
DataPageNo uint32
|
|
||||||
Page []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// dataPage можно записати 1 раз. Щоб не заплутувати інтерфейс - передаю data сторінку
|
|
||||||
// через Options. Index сторінок може бути від 1 до N, тому виділяю окремий метод
|
|
||||||
func NewWriter(opt WriterOptions) (*Writer, error) {
|
|
||||||
if opt.Dir == "" {
|
|
||||||
return nil, errors.New("Dir option is required")
|
|
||||||
}
|
|
||||||
if opt.MetricID == 0 {
|
|
||||||
return nil, errors.New("MetricID option is required")
|
|
||||||
}
|
|
||||||
if opt.DataPageNo == 0 {
|
|
||||||
return nil, errors.New("DataPageNo option is required")
|
|
||||||
}
|
|
||||||
// if len(opt.Page) != octopus.DataPageSize {
|
|
||||||
// return nil, fmt.Errorf("bug: wrong data page size %d", len(opt.Page))
|
|
||||||
// }
|
|
||||||
|
|
||||||
s := &Writer{
|
|
||||||
fileName: JoinREDOFileName(opt.Dir, opt.MetricID),
|
|
||||||
metricID: opt.MetricID,
|
|
||||||
timestamp: opt.Timestamp,
|
|
||||||
value: opt.Value,
|
|
||||||
tmp: make([]byte, 21),
|
|
||||||
isDataPageReused: opt.IsDataPageReused,
|
|
||||||
dataPageNo: opt.DataPageNo,
|
|
||||||
hasher: crc32.NewIEEE(),
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
s.file, err = os.OpenFile(s.fileName, os.O_CREATE|os.O_WRONLY, 0770)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.init(opt.Page)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Формат:
|
|
||||||
4b metricID
|
|
||||||
8b value
|
|
||||||
4b timestamp
|
|
||||||
1b flags (reused)
|
|
||||||
4b dataPageNo
|
|
||||||
8KB dataPage
|
|
||||||
*/
|
|
||||||
func (s *Writer) init(dataPage []byte) error {
|
|
||||||
bin.PutUint32(s.tmp[0:], s.metricID)
|
|
||||||
bin.PutUint32(s.tmp[4:], s.timestamp)
|
|
||||||
bin.PutFloat64(s.tmp[8:], s.value)
|
|
||||||
if s.isDataPageReused {
|
|
||||||
s.tmp[16] = 1
|
|
||||||
}
|
|
||||||
bin.PutUint32(s.tmp[17:], s.dataPageNo)
|
|
||||||
|
|
||||||
_, err := s.file.Write(s.tmp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.file.Write(dataPage)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.hasher.Write(s.tmp)
|
|
||||||
s.hasher.Write(dataPage)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Формат
|
|
||||||
1b index page flags
|
|
||||||
4b indexPageNo
|
|
||||||
Nb indexPage
|
|
||||||
*/
|
|
||||||
func (s *Writer) AppendIndexPage(indexPageNo uint32, indexPage []byte, flags byte) error {
|
|
||||||
s.tmp[0] = flags
|
|
||||||
bin.PutUint32(s.tmp[1:], indexPageNo)
|
|
||||||
_, err := s.file.Write(s.tmp[:5])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = s.file.Write(indexPage)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.hasher.Write(s.tmp[:5])
|
|
||||||
s.hasher.Write(indexPage)
|
|
||||||
|
|
||||||
s.indexPages = append(s.indexPages, indexPageNo)
|
|
||||||
|
|
||||||
if (flags & FlagReused) == FlagReused {
|
|
||||||
s.reusedIndexPages = append(s.reusedIndexPages, indexPageNo)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags & FlagNewRoot) == FlagNewRoot {
|
|
||||||
s.newRootPageNo = indexPageNo
|
|
||||||
s.isRootChanged = true
|
|
||||||
}
|
|
||||||
|
|
||||||
s.indexPagesToWrite = append(s.indexPagesToWrite,
|
|
||||||
PageToWrite{
|
|
||||||
PageNo: indexPageNo,
|
|
||||||
Data: indexPage,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Writer) IndexPagesToWrite() []PageToWrite {
|
|
||||||
return s.indexPagesToWrite
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Writer) Close() (err error) {
|
|
||||||
// финализирую запись
|
|
||||||
bin.PutUint32(s.tmp, s.hasher.Sum32())
|
|
||||||
_, err = s.file.Write(s.tmp[:4])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = s.file.Sync()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return s.file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Report struct {
|
|
||||||
FileName string
|
|
||||||
IsDataPageReused bool
|
|
||||||
DataPageNo uint32
|
|
||||||
IsRootChanged bool
|
|
||||||
NewRootPageNo uint32
|
|
||||||
ReusedIndexPages []uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Writer) GetReport() Report {
|
|
||||||
return Report{
|
|
||||||
FileName: s.fileName,
|
|
||||||
IsDataPageReused: s.isDataPageReused,
|
|
||||||
DataPageNo: s.dataPageNo,
|
|
||||||
//IndexPages: s.indexPages,
|
|
||||||
IsRootChanged: s.isRootChanged,
|
|
||||||
NewRootPageNo: s.newRootPageNo,
|
|
||||||
ReusedIndexPages: s.reusedIndexPages,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HELPERS
|
|
||||||
|
|
||||||
func JoinREDOFileName(dir string, metricID uint32) string {
|
|
||||||
return filepath.Join(dir, fmt.Sprintf("m%d.redo", metricID))
|
|
||||||
}
|
|
||||||
282
atree/writer.go
282
atree/writer.go
@@ -1,164 +1,158 @@
|
|||||||
package atree
|
package atree
|
||||||
|
|
||||||
import (
|
// type Writer struct {
|
||||||
"errors"
|
// metricID uint32
|
||||||
"hash"
|
// timestamp uint32
|
||||||
"hash/crc32"
|
// value float64
|
||||||
)
|
// tmp []byte
|
||||||
|
// hasher hash.Hash32
|
||||||
type Writer struct {
|
// isDataPageReused bool
|
||||||
metricID uint32
|
// dataPageNo uint32
|
||||||
timestamp uint32
|
// isRootChanged bool
|
||||||
value float64
|
// newRootPageNo uint32
|
||||||
tmp []byte
|
// indexPages []uint32
|
||||||
hasher hash.Hash32
|
// reusedIndexPages []uint32
|
||||||
isDataPageReused bool
|
// indexPagesToWrite []PageToWrite
|
||||||
dataPageNo uint32
|
|
||||||
isRootChanged bool
|
|
||||||
newRootPageNo uint32
|
|
||||||
indexPages []uint32
|
|
||||||
reusedIndexPages []uint32
|
|
||||||
indexPagesToWrite []PageToWrite
|
|
||||||
}
|
|
||||||
|
|
||||||
type WriterOptions struct {
|
|
||||||
MetricID uint32
|
|
||||||
Value float64
|
|
||||||
Timestamp uint32
|
|
||||||
IsDataPageReused bool
|
|
||||||
DataPageNo uint32
|
|
||||||
Page []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// dataPage можно записати 1 раз. Щоб не заплутувати інтерфейс - передаю data сторінку
|
|
||||||
// через Options. Index сторінок може бути від 1 до N, тому виділяю окремий метод
|
|
||||||
func NewWriter(opt WriterOptions) (*Writer, error) {
|
|
||||||
if opt.MetricID == 0 {
|
|
||||||
return nil, errors.New("MetricID option is required")
|
|
||||||
}
|
|
||||||
if opt.DataPageNo == 0 {
|
|
||||||
return nil, errors.New("DataPageNo option is required")
|
|
||||||
}
|
|
||||||
// if len(opt.Page) != octopus.DataPageSize {
|
|
||||||
// return nil, fmt.Errorf("bug: wrong data page size %d", len(opt.Page))
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
s := &Writer{
|
// type WriterOptions struct {
|
||||||
metricID: opt.MetricID,
|
// MetricID uint32
|
||||||
timestamp: opt.Timestamp,
|
// Value float64
|
||||||
value: opt.Value,
|
// Timestamp uint32
|
||||||
tmp: make([]byte, 21),
|
// IsDataPageReused bool
|
||||||
isDataPageReused: opt.IsDataPageReused,
|
// DataPageNo uint32
|
||||||
dataPageNo: opt.DataPageNo,
|
// Page []byte
|
||||||
hasher: crc32.NewIEEE(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// var err error
|
|
||||||
// err = s.init(opt.Page)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Формат:
|
|
||||||
4b metricID
|
|
||||||
8b value
|
|
||||||
4b timestamp
|
|
||||||
1b flags (reused)
|
|
||||||
4b dataPageNo
|
|
||||||
8KB dataPage
|
|
||||||
*/
|
|
||||||
// func (s *Writer) init(dataPage []byte) error {
|
|
||||||
// bin.PutUint32(s.tmp[0:], s.metricID)
|
|
||||||
// bin.PutUint32(s.tmp[4:], s.timestamp)
|
|
||||||
// bin.PutFloat64(s.tmp[8:], s.value)
|
|
||||||
// if s.isDataPageReused {
|
|
||||||
// s.tmp[16] = 1
|
|
||||||
// }
|
|
||||||
// bin.PutUint32(s.tmp[17:], s.dataPageNo)
|
|
||||||
|
|
||||||
// _, err := s.file.Write(s.tmp)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// _, err = s.file.Write(dataPage)
|
// // dataPage можно записати 1 раз. Щоб не заплутувати інтерфейс - передаю data сторінку
|
||||||
// if err != nil {
|
// // через Options. Index сторінок може бути від 1 до N, тому виділяю окремий метод
|
||||||
// return err
|
// func NewWriter(opt WriterOptions) (*Writer, error) {
|
||||||
|
// if opt.MetricID == 0 {
|
||||||
|
// return nil, errors.New("MetricID option is required")
|
||||||
// }
|
// }
|
||||||
|
// if opt.DataPageNo == 0 {
|
||||||
// s.hasher.Write(s.tmp)
|
// return nil, errors.New("DataPageNo option is required")
|
||||||
// s.hasher.Write(dataPage)
|
|
||||||
// return nil
|
|
||||||
// }
|
// }
|
||||||
|
// // if len(opt.Page) != octopus.DataPageSize {
|
||||||
/*
|
// // return nil, fmt.Errorf("bug: wrong data page size %d", len(opt.Page))
|
||||||
Формат
|
|
||||||
1b index page flags
|
|
||||||
4b indexPageNo
|
|
||||||
Nb indexPage
|
|
||||||
*/
|
|
||||||
// func (s *Writer) AppendIndexPage(indexPageNo uint32, indexPage []byte, flags byte) error {
|
|
||||||
// s.tmp[0] = flags
|
|
||||||
// bin.PutUint32(s.tmp[1:], indexPageNo)
|
|
||||||
// // _, err := s.file.Write(s.tmp[:5])
|
|
||||||
// // if err != nil {
|
|
||||||
// // return err
|
|
||||||
// // }
|
// // }
|
||||||
// // _, err = s.file.Write(indexPage)
|
|
||||||
|
// s := &Writer{
|
||||||
|
// metricID: opt.MetricID,
|
||||||
|
// timestamp: opt.Timestamp,
|
||||||
|
// value: opt.Value,
|
||||||
|
// tmp: make([]byte, 21),
|
||||||
|
// isDataPageReused: opt.IsDataPageReused,
|
||||||
|
// dataPageNo: opt.DataPageNo,
|
||||||
|
// hasher: crc32.NewIEEE(),
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // var err error
|
||||||
|
// // err = s.init(opt.Page)
|
||||||
|
// // if err != nil {
|
||||||
|
// // return nil, err
|
||||||
|
// // }
|
||||||
|
// return s, nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /*
|
||||||
|
// Формат:
|
||||||
|
// 4b metricID
|
||||||
|
// 8b value
|
||||||
|
// 4b timestamp
|
||||||
|
// 1b flags (reused)
|
||||||
|
// 4b dataPageNo
|
||||||
|
// 8KB dataPage
|
||||||
|
// */
|
||||||
|
// // func (s *Writer) init(dataPage []byte) error {
|
||||||
|
// // bin.PutUint32(s.tmp[0:], s.metricID)
|
||||||
|
// // bin.PutUint32(s.tmp[4:], s.timestamp)
|
||||||
|
// // bin.PutFloat64(s.tmp[8:], s.value)
|
||||||
|
// // if s.isDataPageReused {
|
||||||
|
// // s.tmp[16] = 1
|
||||||
|
// // }
|
||||||
|
// // bin.PutUint32(s.tmp[17:], s.dataPageNo)
|
||||||
|
|
||||||
|
// // _, err := s.file.Write(s.tmp)
|
||||||
// // if err != nil {
|
// // if err != nil {
|
||||||
// // return err
|
// // return err
|
||||||
// // }
|
// // }
|
||||||
|
|
||||||
// s.hasher.Write(s.tmp[:5])
|
// // _, err = s.file.Write(dataPage)
|
||||||
// s.hasher.Write(indexPage)
|
// // if err != nil {
|
||||||
|
// // return err
|
||||||
|
// // }
|
||||||
|
|
||||||
// s.indexPages = append(s.indexPages, indexPageNo)
|
// // s.hasher.Write(s.tmp)
|
||||||
|
// // s.hasher.Write(dataPage)
|
||||||
|
// // return nil
|
||||||
|
// // }
|
||||||
|
|
||||||
// if (flags & FlagReused) == FlagReused {
|
// /*
|
||||||
// s.reusedIndexPages = append(s.reusedIndexPages, indexPageNo)
|
// Формат
|
||||||
// }
|
// 1b index page flags
|
||||||
|
// 4b indexPageNo
|
||||||
|
// Nb indexPage
|
||||||
|
// */
|
||||||
|
// // func (s *Writer) AppendIndexPage(indexPageNo uint32, indexPage []byte, flags byte) error {
|
||||||
|
// // s.tmp[0] = flags
|
||||||
|
// // bin.PutUint32(s.tmp[1:], indexPageNo)
|
||||||
|
// // // _, err := s.file.Write(s.tmp[:5])
|
||||||
|
// // // if err != nil {
|
||||||
|
// // // return err
|
||||||
|
// // // }
|
||||||
|
// // // _, err = s.file.Write(indexPage)
|
||||||
|
// // // if err != nil {
|
||||||
|
// // // return err
|
||||||
|
// // // }
|
||||||
|
|
||||||
// if (flags & FlagNewRoot) == FlagNewRoot {
|
// // s.hasher.Write(s.tmp[:5])
|
||||||
// s.newRootPageNo = indexPageNo
|
// // s.hasher.Write(indexPage)
|
||||||
// s.isRootChanged = true
|
|
||||||
// }
|
|
||||||
|
|
||||||
// s.indexPagesToWrite = append(s.indexPagesToWrite,
|
// // s.indexPages = append(s.indexPages, indexPageNo)
|
||||||
// PageToWrite{
|
|
||||||
// PageNo: indexPageNo,
|
|
||||||
// Data: indexPage,
|
|
||||||
// })
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (s *Writer) IndexPagesToWrite() []PageToWrite {
|
// // if (flags & FlagReused) == FlagReused {
|
||||||
// return s.indexPagesToWrite
|
// // s.reusedIndexPages = append(s.reusedIndexPages, indexPageNo)
|
||||||
// }
|
// // }
|
||||||
|
|
||||||
// func (s *Writer) Close() (err error) {
|
// // if (flags & FlagNewRoot) == FlagNewRoot {
|
||||||
// // финализирую запись
|
// // s.newRootPageNo = indexPageNo
|
||||||
// bin.PutUint32(s.tmp, s.hasher.Sum32())
|
// // s.isRootChanged = true
|
||||||
// _, err = s.file.Write(s.tmp[:4])
|
// // }
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// err = s.file.Sync()
|
|
||||||
// if err != nil {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// return s.file.Close()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (s *Writer) GetReport() Report {
|
// // s.indexPagesToWrite = append(s.indexPagesToWrite,
|
||||||
// return Report{
|
// // PageToWrite{
|
||||||
// IsDataPageReused: s.isDataPageReused,
|
// // PageNo: indexPageNo,
|
||||||
// DataPageNo: s.dataPageNo,
|
// // Data: indexPage,
|
||||||
// //IndexPages: s.indexPages,
|
// // })
|
||||||
// IsRootChanged: s.isRootChanged,
|
// // return nil
|
||||||
// NewRootPageNo: s.newRootPageNo,
|
// // }
|
||||||
// ReusedIndexPages: s.reusedIndexPages,
|
|
||||||
// }
|
// // func (s *Writer) IndexPagesToWrite() []PageToWrite {
|
||||||
// }
|
// // return s.indexPagesToWrite
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // func (s *Writer) Close() (err error) {
|
||||||
|
// // // финализирую запись
|
||||||
|
// // bin.PutUint32(s.tmp, s.hasher.Sum32())
|
||||||
|
// // _, err = s.file.Write(s.tmp[:4])
|
||||||
|
// // if err != nil {
|
||||||
|
// // return err
|
||||||
|
// // }
|
||||||
|
// // err = s.file.Sync()
|
||||||
|
// // if err != nil {
|
||||||
|
// // return
|
||||||
|
// // }
|
||||||
|
// // return s.file.Close()
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // func (s *Writer) GetReport() Report {
|
||||||
|
// // return Report{
|
||||||
|
// // IsDataPageReused: s.isDataPageReused,
|
||||||
|
// // DataPageNo: s.dataPageNo,
|
||||||
|
// // //IndexPages: s.indexPages,
|
||||||
|
// // IsRootChanged: s.isRootChanged,
|
||||||
|
// // NewRootPageNo: s.newRootPageNo,
|
||||||
|
// // ReusedIndexPages: s.reusedIndexPages,
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
|||||||
34
atree/x.go
34
atree/x.go
@@ -1,6 +1,7 @@
|
|||||||
package atree
|
package atree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"gordenko.dev/dima/qb"
|
||||||
"gordenko.dev/dima/qb/bin"
|
"gordenko.dev/dima/qb/bin"
|
||||||
"gordenko.dev/dima/qb/util"
|
"gordenko.dev/dima/qb/util"
|
||||||
)
|
)
|
||||||
@@ -19,7 +20,7 @@ type DataPage struct {
|
|||||||
ValuesSize int
|
ValuesSize int
|
||||||
PageNo uint32
|
PageNo uint32
|
||||||
IsReused bool
|
IsReused bool
|
||||||
Checksum uint32
|
//Checksum uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
type IndexPage struct {
|
type IndexPage struct {
|
||||||
@@ -27,7 +28,6 @@ type IndexPage struct {
|
|||||||
Data []byte
|
Data []byte
|
||||||
PageNo uint32
|
PageNo uint32
|
||||||
IsReused bool
|
IsReused bool
|
||||||
Checksum uint32
|
|
||||||
}
|
}
|
||||||
type IndexLevel struct {
|
type IndexLevel struct {
|
||||||
// вже записані дані (лише для першої в списку індексної сторінки, беремо із _metric)
|
// вже записані дані (лише для першої в списку індексної сторінки, беремо із _metric)
|
||||||
@@ -57,7 +57,7 @@ type ChunksToDataPageReq struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return checksum
|
// return checksum
|
||||||
func ChunksToDataPage(buf []byte, req ChunksToDataPageReq) (checksum uint32) {
|
func ChunksToDataPage(buf []byte, req ChunksToDataPageReq) {
|
||||||
var (
|
var (
|
||||||
remainingSize = int(req.TimestampsSize)
|
remainingSize = int(req.TimestampsSize)
|
||||||
pos = 0
|
pos = 0
|
||||||
@@ -90,30 +90,29 @@ func ChunksToDataPage(buf []byte, req ChunksToDataPageReq) (checksum uint32) {
|
|||||||
bin.PutUint16(buf[valuesSizeIdx:], uint16(req.ValuesSize))
|
bin.PutUint16(buf[valuesSizeIdx:], uint16(req.ValuesSize))
|
||||||
bin.PutUint32(buf[prevPageIdx:], req.PrevPageNo)
|
bin.PutUint32(buf[prevPageIdx:], req.PrevPageNo)
|
||||||
|
|
||||||
checksum = util.CalcChecksum(buf[:dataCRC32Idx])
|
checksum := util.CalcChecksum(buf[:dataCRC32Idx])
|
||||||
bin.PutUint32(buf[dataCRC32Idx:], checksum)
|
bin.PutUint32(buf[dataCRC32Idx:], checksum)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DataToIndexPage(buf []byte, data []byte, lastLevel bool) (checksum uint32) {
|
func DataToIndexPage(buf []byte, data []byte, lastLevel bool) {
|
||||||
copy(buf, data)
|
copy(buf, data)
|
||||||
bin.PutUint16(buf[indexRecordsQtyIdx:], uint16(len(data)/indexRecordSize))
|
bin.PutUint16(buf[indexRecordsQtyIdx:], uint16(len(data)/indexRecordSize))
|
||||||
if lastLevel {
|
if lastLevel {
|
||||||
buf[isLastLevelIdx] = 1
|
buf[isLastLevelIdx] = 1
|
||||||
}
|
}
|
||||||
checksum = util.CalcChecksum(buf[:indexCRC32Idx])
|
checksum := util.CalcChecksum(buf[:indexCRC32Idx])
|
||||||
bin.PutUint32(buf[indexCRC32Idx:], checksum)
|
bin.PutUint32(buf[indexCRC32Idx:], checksum)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type Pager interface {
|
|
||||||
GetPageNumber() (uint32, bool)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fix - reduce leves
|
// fix - reduce leves
|
||||||
func AppendDataPagesToTree(pager Pager, levels []*IndexLevel, dataPages []*DataPage) {
|
func AppendDataPagesToTree(getDataPageNumber func() (uint32, bool, error), getIndexPageNumber func() (uint32, bool, error), levels []*IndexLevel, dataPages []*DataPage) {
|
||||||
for i, d := range dataPages[1:] {
|
for i, d := range dataPages[1:] {
|
||||||
d.PageNo, d.IsReused = pager.GetPageNumber()
|
pageNo, isReused, err := getDataPageNumber()
|
||||||
|
if err != nil {
|
||||||
|
qb.Abort(qb.FailedGetPageNumber, err)
|
||||||
|
}
|
||||||
|
d.PageNo = pageNo
|
||||||
|
d.IsReused = isReused
|
||||||
d.PrevPageNo = dataPages[i-1].PageNo
|
d.PrevPageNo = dataPages[i-1].PageNo
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +142,12 @@ func AppendDataPagesToTree(pager Pager, levels []*IndexLevel, dataPages []*DataP
|
|||||||
LowerTimestamp: level.LowerTimestamp,
|
LowerTimestamp: level.LowerTimestamp,
|
||||||
Data: level.Data,
|
Data: level.Data,
|
||||||
}
|
}
|
||||||
filled.PageNo, filled.IsReused = pager.GetPageNumber()
|
pageNo, isReused, err := getIndexPageNumber()
|
||||||
|
if err != nil {
|
||||||
|
qb.Abort(qb.FailedGetPageNumber, err)
|
||||||
|
}
|
||||||
|
filled.PageNo = pageNo
|
||||||
|
filled.IsReused = isReused
|
||||||
|
|
||||||
level.Filled = append(level.Filled, filled)
|
level.Filled = append(level.Filled, filled)
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -98,17 +98,17 @@ func TestTimeDelta(t *testing.T) {
|
|||||||
|
|
||||||
//c.Sync()
|
//c.Sync()
|
||||||
|
|
||||||
bound := c.GetState()
|
// bound := c.GetState()
|
||||||
|
|
||||||
fmt.Println("AFTER SYNC")
|
// fmt.Println("AFTER SYNC")
|
||||||
fmt.Printf("pos: %d\n", c.pos)
|
// fmt.Printf("pos: %d\n", c.pos)
|
||||||
chunks := buf.Chunks()
|
// chunks := buf.Chunks()
|
||||||
if len(chunks) > 0 {
|
// if len(chunks) > 0 {
|
||||||
fmt.Printf("%d\n", chunks[0])
|
// fmt.Printf("%d\n", chunks[0])
|
||||||
}
|
// }
|
||||||
|
|
||||||
d := NewReverseTimeDeltaDecompressor(buf, c.Size())
|
d := NewReverseTimeDeltaDecompressor(buf, c.Size())
|
||||||
d.RestoreFromBound(bound)
|
// d.RestoreFromBound(bound)
|
||||||
//d.RestoreFromEnd()
|
//d.RestoreFromEnd()
|
||||||
|
|
||||||
for range 12 {
|
for range 12 {
|
||||||
@@ -210,9 +210,9 @@ func TestInsdeltaBound(t *testing.T) {
|
|||||||
fmt.Printf("%d\n", buf.Chunks()[0])
|
fmt.Printf("%d\n", buf.Chunks()[0])
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
|
|
||||||
bound := c.GetState()
|
//bound := c.GetState()
|
||||||
|
|
||||||
fmt.Printf("bound: pos=%d, h=%d\n", bound.Pos, bound.H)
|
//fmt.Printf("bound: pos=%d, h=%d\n", bound.Pos, bound.H)
|
||||||
|
|
||||||
c.Append(248)
|
c.Append(248)
|
||||||
c.Append(305)
|
c.Append(305)
|
||||||
@@ -229,7 +229,7 @@ func TestInsdeltaBound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boundDecompressor := NewReverseInstantDeltaDecompressor(buf, c.Size(), fracDigits)
|
boundDecompressor := NewReverseInstantDeltaDecompressor(buf, c.Size(), fracDigits)
|
||||||
boundDecompressor.RestoreFromBound(bound)
|
//boundDecompressor.RestoreFromBound(bound)
|
||||||
|
|
||||||
fmt.Println("from bound:")
|
fmt.Println("from bound:")
|
||||||
for range 8 {
|
for range 8 {
|
||||||
@@ -270,9 +270,9 @@ func TestInsdeltaBound2(t *testing.T) {
|
|||||||
fmt.Printf("%d\n", buf.Chunks()[0])
|
fmt.Printf("%d\n", buf.Chunks()[0])
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
|
|
||||||
bound := c.GetState()
|
//bound := c.GetState()
|
||||||
|
|
||||||
fmt.Printf("bound: pos=%d, h=%d\n", bound.Pos, bound.H)
|
//fmt.Printf("bound: pos=%d, h=%d\n", bound.Pos, bound.H)
|
||||||
|
|
||||||
c.Append(70)
|
c.Append(70)
|
||||||
c.Append(10)
|
c.Append(10)
|
||||||
@@ -289,7 +289,7 @@ func TestInsdeltaBound2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
boundDecompressor := NewReverseInstantDeltaDecompressor(buf, c.Size(), fracDigits)
|
boundDecompressor := NewReverseInstantDeltaDecompressor(buf, c.Size(), fracDigits)
|
||||||
boundDecompressor.RestoreFromBound(bound)
|
//sboundDecompressor.RestoreFromBound(bound)
|
||||||
|
|
||||||
fmt.Println("from bound:")
|
fmt.Println("from bound:")
|
||||||
for range 8 {
|
for range 8 {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"gordenko.dev/dima/qb"
|
||||||
"gordenko.dev/dima/qb/conbuf"
|
"gordenko.dev/dima/qb/conbuf"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -180,15 +181,58 @@ type CumulativeDeltaBound struct {
|
|||||||
|
|
||||||
// delta h
|
// delta h
|
||||||
func (s *ReverseCumulativeDeltaCompressor) Lock() {
|
func (s *ReverseCumulativeDeltaCompressor) Lock() {
|
||||||
|
if s.state != nil {
|
||||||
|
qb.Abort(qb.RepeatableLock, nil)
|
||||||
|
}
|
||||||
|
// позиція посувається вліво, отже може перескочити на попередній chunk
|
||||||
|
pos := s.pos - 1 - s.lastDeltaSize
|
||||||
|
chunksQty := pos / conbuf.ChunkSize
|
||||||
|
if (pos % conbuf.ChunkSize) > 0 {
|
||||||
|
chunksQty++
|
||||||
|
}
|
||||||
s.state = &CumulativeDeltaBound{
|
s.state = &CumulativeDeltaBound{
|
||||||
Pos: s.pos - 1 - s.lastDeltaSize,
|
Pos: pos,
|
||||||
H: s.h,
|
H: s.h,
|
||||||
LastDelta: s.lastDelta,
|
LastDelta: s.lastDelta,
|
||||||
Chunks: s.buf.Chunks(),
|
Chunks: s.buf.Chunks()[:chunksQty],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ReverseCumulativeDeltaCompressor) CreateDecompressor(fracDigits byte) *ReverseCumulativeDeltaDecompressor {
|
// fix - повернути в Pool буфери
|
||||||
|
func (s *ReverseCumulativeDeltaCompressor) Unlock() {
|
||||||
|
s.state = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReverseCumulativeDeltaCompressor) Offset() int {
|
||||||
|
if s.state != nil {
|
||||||
|
return s.state.Pos
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReverseCumulativeDeltaCompressor) Snapshot() ([][]byte, int) {
|
||||||
|
if s.state == nil {
|
||||||
|
return s.buf.Chunks(), s.Size()
|
||||||
|
}
|
||||||
|
// ВАЖЛИВО!
|
||||||
|
// Треба відтворити стан останнього чанка
|
||||||
|
var (
|
||||||
|
pos = s.state.Pos
|
||||||
|
chunk = make([]byte, conbuf.ChunkSize)
|
||||||
|
lastChunkIdx = len(s.state.Chunks) - 1
|
||||||
|
qtyToCopy = pos % conbuf.ChunkSize
|
||||||
|
)
|
||||||
|
copy(chunk, s.state.Chunks[lastChunkIdx][:qtyToCopy])
|
||||||
|
chunks := append(s.state.Chunks[:lastChunkIdx], chunk)
|
||||||
|
|
||||||
|
buf := conbuf.New(chunks)
|
||||||
|
pos += buf.ReversePutVarUint64(pos, s.state.LastDelta)
|
||||||
|
buf.SetByte(pos, s.state.H)
|
||||||
|
pos++
|
||||||
|
return chunks, pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReverseCumulativeDeltaCompressor) CreateDecompressor(fracDigits byte) qb.ValueDecompressor {
|
||||||
if s.state == nil {
|
if s.state == nil {
|
||||||
d := NewReverseCumulativeDeltaDecompressor(s.buf, s.Size(), fracDigits)
|
d := NewReverseCumulativeDeltaDecompressor(s.buf, s.Size(), fracDigits)
|
||||||
d.RestoreFromEnd()
|
d.RestoreFromEnd()
|
||||||
@@ -199,11 +243,9 @@ func (s *ReverseCumulativeDeltaCompressor) CreateDecompressor(fracDigits byte) *
|
|||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ReverseCumulativeDeltaCompressor) Unlock() {
|
|
||||||
s.state = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ReverseCumulativeDeltaCompressor) Renew() {
|
func (s *ReverseCumulativeDeltaCompressor) Renew() {
|
||||||
|
// УВАГА!
|
||||||
|
// state не чіпаємо
|
||||||
s.buf = conbuf.New(nil)
|
s.buf = conbuf.New(nil)
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"gordenko.dev/dima/qb"
|
||||||
"gordenko.dev/dima/qb/conbuf"
|
"gordenko.dev/dima/qb/conbuf"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ type ReverseInstantDeltaCompressor struct {
|
|||||||
lastDelta int64
|
lastDelta int64
|
||||||
lastDeltaSize int
|
lastDeltaSize int
|
||||||
h byte
|
h byte
|
||||||
|
state *InstantDeltaBound
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReverseInstantDeltaCompressor(buf *conbuf.ContinuousBuffer, size int, fracDigits byte) *ReverseInstantDeltaCompressor {
|
func NewReverseInstantDeltaCompressor(buf *conbuf.ContinuousBuffer, size int, fracDigits byte) *ReverseInstantDeltaCompressor {
|
||||||
@@ -147,30 +149,76 @@ type InstantDeltaBound struct {
|
|||||||
Pos int
|
Pos int
|
||||||
H byte
|
H byte
|
||||||
LastDelta int64
|
LastDelta int64
|
||||||
|
Chunks [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ReverseInstantDeltaCompressor) GetState() InstantDeltaBound {
|
// delta h
|
||||||
return InstantDeltaBound{
|
func (s *ReverseInstantDeltaCompressor) Lock() {
|
||||||
Pos: s.pos - 1 - s.lastDeltaSize,
|
if s.state != nil {
|
||||||
|
qb.Abort(qb.RepeatableLock, nil)
|
||||||
|
}
|
||||||
|
// позиція посувається вліво, отже може перескочити на попередній chunk
|
||||||
|
pos := s.pos - 1 - s.lastDeltaSize
|
||||||
|
chunksQty := pos / conbuf.ChunkSize
|
||||||
|
if (pos % conbuf.ChunkSize) > 0 {
|
||||||
|
chunksQty++
|
||||||
|
}
|
||||||
|
s.state = &InstantDeltaBound{
|
||||||
|
Pos: pos,
|
||||||
H: s.h,
|
H: s.h,
|
||||||
LastDelta: s.lastDelta,
|
LastDelta: s.lastDelta,
|
||||||
|
Chunks: s.buf.Chunks()[:chunksQty],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ReverseInstantDeltaCompressor) Lock() {
|
// fix - повернути в Pool буфери
|
||||||
// s.state = &CumulativeDeltaBound{
|
|
||||||
// Pos: s.pos - 1 - s.lastDeltaSize,
|
|
||||||
// H: s.h,
|
|
||||||
// LastDelta: s.lastDelta,
|
|
||||||
// Chunks: s.buf.Chunks(),
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ReverseInstantDeltaCompressor) Unlock() {
|
func (s *ReverseInstantDeltaCompressor) Unlock() {
|
||||||
//s.state = nil
|
s.state = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReverseInstantDeltaCompressor) Offset() int {
|
||||||
|
if s.state != nil {
|
||||||
|
return s.state.Pos
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReverseInstantDeltaCompressor) Snapshot() ([][]byte, int) {
|
||||||
|
if s.state == nil {
|
||||||
|
return s.buf.Chunks(), s.Size()
|
||||||
|
}
|
||||||
|
// ВАЖЛИВО!
|
||||||
|
// Треба відтворити стан останнього чанка
|
||||||
|
var (
|
||||||
|
pos = s.state.Pos
|
||||||
|
chunk = make([]byte, conbuf.ChunkSize)
|
||||||
|
lastChunkIdx = len(s.state.Chunks) - 1
|
||||||
|
qtyToCopy = pos % conbuf.ChunkSize
|
||||||
|
)
|
||||||
|
copy(chunk, s.state.Chunks[lastChunkIdx][:qtyToCopy])
|
||||||
|
chunks := append(s.state.Chunks[:lastChunkIdx], chunk)
|
||||||
|
|
||||||
|
buf := conbuf.New(chunks)
|
||||||
|
pos += buf.ReversePutVarInt64(pos, s.state.LastDelta)
|
||||||
|
buf.SetByte(pos, s.state.H)
|
||||||
|
pos++
|
||||||
|
return chunks, pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReverseInstantDeltaCompressor) CreateDecompressor(fracDigits byte) qb.ValueDecompressor {
|
||||||
|
if s.state == nil {
|
||||||
|
d := NewReverseInstantDeltaDecompressor(s.buf, s.Size(), fracDigits)
|
||||||
|
d.RestoreFromEnd()
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
d := NewReverseInstantDeltaDecompressor(s.buf, s.Size(), fracDigits)
|
||||||
|
d.RestoreFromBound(*s.state)
|
||||||
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ReverseInstantDeltaCompressor) Renew() {
|
func (s *ReverseInstantDeltaCompressor) Renew() {
|
||||||
|
// УВАГА!
|
||||||
|
// state не чіпаємо
|
||||||
s.buf = conbuf.New(nil)
|
s.buf = conbuf.New(nil)
|
||||||
s.pos = 0
|
s.pos = 0
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
"gordenko.dev/dima/pretty"
|
"gordenko.dev/dima/pretty"
|
||||||
|
"gordenko.dev/dima/qb"
|
||||||
"gordenko.dev/dima/qb/conbuf"
|
"gordenko.dev/dima/qb/conbuf"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ type ReverseTimeDeltaCompressor struct {
|
|||||||
lastDelta uint32
|
lastDelta uint32
|
||||||
lastDeltaSize int
|
lastDeltaSize int
|
||||||
h byte
|
h byte
|
||||||
|
state *TimeDeltaBound
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReverseTimeDeltaCompressor(buf *conbuf.ContinuousBuffer, size int) *ReverseTimeDeltaCompressor {
|
func NewReverseTimeDeltaCompressor(buf *conbuf.ContinuousBuffer, size int) *ReverseTimeDeltaCompressor {
|
||||||
@@ -45,10 +47,6 @@ func (s *ReverseTimeDeltaCompressor) Size() int {
|
|||||||
return s.pos
|
return s.pos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ReverseTimeDeltaCompressor) Chunks() [][]byte {
|
|
||||||
return s.buf.Chunks()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ReverseTimeDeltaCompressor) CalcRequiredSpace(value uint32) int {
|
func (s *ReverseTimeDeltaCompressor) CalcRequiredSpace(value uint32) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -151,42 +149,109 @@ func (s *ReverseTimeDeltaCompressor) Sync() {
|
|||||||
s.pos += s.buf.ReversePutVarUint64(s.pos, uint64(s.lastUnixtime))
|
s.pos += s.buf.ReversePutVarUint64(s.pos, uint64(s.lastUnixtime))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIX - check methods
|
||||||
|
|
||||||
type TimeDeltaBound struct {
|
type TimeDeltaBound struct {
|
||||||
Pos int
|
Pos int
|
||||||
H byte
|
H byte
|
||||||
LastUnixtime uint32
|
LastUnixtime uint32
|
||||||
LastDelta uint32
|
LastDelta uint32
|
||||||
|
Chunks [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// delta h
|
// delta h
|
||||||
func (s *ReverseTimeDeltaCompressor) GetState() TimeDeltaBound { // fix replace by Lock
|
// func (s *ReverseTimeDeltaCompressor) GetState() TimeDeltaBound { // fix replace by Lock
|
||||||
bound := TimeDeltaBound{
|
// bound := TimeDeltaBound{
|
||||||
LastUnixtime: s.lastUnixtime,
|
// LastUnixtime: s.lastUnixtime,
|
||||||
}
|
// }
|
||||||
if s.pos > 0 {
|
// if s.pos > 0 {
|
||||||
bound.Pos = s.pos - 1 - s.lastDeltaSize
|
// bound.Pos = s.pos - 1 - s.lastDeltaSize
|
||||||
bound.H = s.h
|
// bound.H = s.h
|
||||||
bound.LastDelta = s.lastDelta
|
// bound.LastDelta = s.lastDelta
|
||||||
}
|
// }
|
||||||
return bound
|
// return bound
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
// delta h
|
||||||
func (s *ReverseTimeDeltaCompressor) Lock() {
|
func (s *ReverseTimeDeltaCompressor) Lock() {
|
||||||
// fix
|
if s.state != nil {
|
||||||
|
qb.Abort(qb.RepeatableLock, nil)
|
||||||
|
}
|
||||||
|
// позиція посувається вліво, отже може перескочити на попередній chunk
|
||||||
|
pos := s.pos - 1 - s.lastDeltaSize
|
||||||
|
chunksQty := pos / conbuf.ChunkSize
|
||||||
|
if (pos % conbuf.ChunkSize) > 0 {
|
||||||
|
chunksQty++
|
||||||
|
}
|
||||||
|
s.state = &TimeDeltaBound{
|
||||||
|
Pos: pos,
|
||||||
|
H: s.h,
|
||||||
|
LastUnixtime: s.lastUnixtime, // fix
|
||||||
|
LastDelta: s.lastDelta,
|
||||||
|
Chunks: s.buf.Chunks()[:chunksQty],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fix - повернути в Pool буфери
|
||||||
func (s *ReverseTimeDeltaCompressor) Unlock() {
|
func (s *ReverseTimeDeltaCompressor) Unlock() {
|
||||||
//s.state = nil // fix
|
s.state = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReverseTimeDeltaCompressor) Offset() int {
|
||||||
|
if s.state != nil {
|
||||||
|
return s.state.Pos
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// fix -
|
||||||
|
func (s *ReverseTimeDeltaCompressor) Snapshot() ([][]byte, int) {
|
||||||
|
if s.state == nil {
|
||||||
|
return s.buf.Chunks(), s.Size()
|
||||||
|
}
|
||||||
|
// ВАЖЛИВО!
|
||||||
|
// Треба відтворити стан останнього чанка
|
||||||
|
var (
|
||||||
|
pos = s.state.Pos
|
||||||
|
chunk = make([]byte, conbuf.ChunkSize)
|
||||||
|
lastChunkIdx = len(s.state.Chunks) - 1
|
||||||
|
qtyToCopy = pos % conbuf.ChunkSize
|
||||||
|
)
|
||||||
|
copy(chunk, s.state.Chunks[lastChunkIdx][:qtyToCopy])
|
||||||
|
chunks := append(s.state.Chunks[:lastChunkIdx], chunk)
|
||||||
|
|
||||||
|
buf := conbuf.New(chunks)
|
||||||
|
pos += buf.ReversePutVarUint64(pos, uint64(s.state.LastDelta))
|
||||||
|
buf.SetByte(pos, s.state.H)
|
||||||
|
pos++
|
||||||
|
return chunks, pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReverseTimeDeltaCompressor) CreateDecompressor() qb.TimestampDecompressor {
|
||||||
|
if s.state == nil {
|
||||||
|
d := NewReverseTimeDeltaDecompressor(s.buf, s.Size())
|
||||||
|
d.RestoreFromEnd()
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
d := NewReverseTimeDeltaDecompressor(s.buf, s.Size())
|
||||||
|
d.RestoreFromBound(*s.state)
|
||||||
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ReverseTimeDeltaCompressor) Renew() {
|
func (s *ReverseTimeDeltaCompressor) Renew() {
|
||||||
// s.buf = conbuf.New(nil)
|
// УВАГА!
|
||||||
// s.pos = 0
|
// state не чіпаємо
|
||||||
// //
|
s.buf = conbuf.New(nil)
|
||||||
|
s.pos = 0
|
||||||
|
//
|
||||||
//s.baseValue = 0
|
//s.baseValue = 0
|
||||||
// s.lastDelta = 0
|
s.lastDelta = 0
|
||||||
// s.lastDeltaSize = 0
|
s.lastDeltaSize = 0
|
||||||
// s.h = 0
|
s.h = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ReverseTimeDeltaCompressor) Chunks() [][]byte {
|
||||||
|
return s.buf.Chunks()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DECOMPRESSOR
|
// DECOMPRESSOR
|
||||||
|
|||||||
@@ -12,11 +12,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gordenko.dev/dima/qb"
|
|
||||||
"gordenko.dev/dima/qb/atree"
|
"gordenko.dev/dima/qb/atree"
|
||||||
"gordenko.dev/dima/qb/bin"
|
"gordenko.dev/dima/qb/bin"
|
||||||
"gordenko.dev/dima/qb/freelist"
|
"gordenko.dev/dima/qb/freelist"
|
||||||
"gordenko.dev/dima/qb/recovery"
|
|
||||||
"gordenko.dev/dima/qb/txlog"
|
"gordenko.dev/dima/qb/txlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,11 +22,11 @@ func JoinSnapshotFileName(dir string, logNumber int) string {
|
|||||||
return filepath.Join(dir, fmt.Sprintf("%d.snapshot", logNumber))
|
return filepath.Join(dir, fmt.Sprintf("%d.snapshot", logNumber))
|
||||||
}
|
}
|
||||||
|
|
||||||
type metricLockEntry struct {
|
// type metricLockEntry struct {
|
||||||
XLock bool
|
// XLock bool
|
||||||
RLocks int
|
// RLocks int
|
||||||
WaitQueue []any
|
// WaitQueue []any
|
||||||
}
|
// }
|
||||||
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
@@ -37,7 +35,8 @@ type Database struct {
|
|||||||
rLocksToRelease []uint32
|
rLocksToRelease []uint32
|
||||||
metrics map[uint32]*_metric
|
metrics map[uint32]*_metric
|
||||||
//metricLockEntries map[uint32]*metricLockEntry
|
//metricLockEntries map[uint32]*metricLockEntry
|
||||||
freeList *freelist.FreeList
|
dataFreeList *freelist.FreeList
|
||||||
|
indexFreeList *freelist.FreeList
|
||||||
dir string
|
dir string
|
||||||
databaseName string
|
databaseName string
|
||||||
txlog *txlog.Writer
|
txlog *txlog.Writer
|
||||||
@@ -82,16 +81,25 @@ func New(opt Options) (_ *Database, err error) {
|
|||||||
return nil, errors.New("WaitGroup option is required")
|
return nil, errors.New("WaitGroup option is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath := filepath.Join(opt.Dir, opt.DatabaseName+".free")
|
// deltaFreeListFile, err := os.OpenFile(
|
||||||
|
// filepath.Join(opt.Dir, opt.DatabaseName+".deltafree"),
|
||||||
|
// os.O_RDWR|os.O_CREATE,
|
||||||
|
// 0666,
|
||||||
|
// )
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
|
||||||
freeListFile, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0666)
|
dataFreeList, err := freelist.New(freelist.Options{
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
freeList, err := freelist.New(freelist.Options{
|
|
||||||
PageSize: 2048,
|
PageSize: 2048,
|
||||||
File: freeListFile,
|
BaseFilePath: filepath.Join(opt.Dir, opt.DatabaseName+".free_data_base"),
|
||||||
|
DeltaFilePath: filepath.Join(opt.Dir, opt.DatabaseName+".free_data_delta"),
|
||||||
|
})
|
||||||
|
|
||||||
|
indexFreeList, err := freelist.New(freelist.Options{
|
||||||
|
PageSize: 2048,
|
||||||
|
BaseFilePath: filepath.Join(opt.Dir, opt.DatabaseName+".free_index_base"),
|
||||||
|
DeltaFilePath: filepath.Join(opt.Dir, opt.DatabaseName+".free_index_delta"),
|
||||||
})
|
})
|
||||||
|
|
||||||
s := &Database{
|
s := &Database{
|
||||||
@@ -100,7 +108,8 @@ func New(opt Options) (_ *Database, err error) {
|
|||||||
databaseName: opt.DatabaseName,
|
databaseName: opt.DatabaseName,
|
||||||
metrics: make(map[uint32]*_metric),
|
metrics: make(map[uint32]*_metric),
|
||||||
//metricLockEntries: make(map[uint32]*metricLockEntry),
|
//metricLockEntries: make(map[uint32]*metricLockEntry),
|
||||||
freeList: freeList,
|
dataFreeList: dataFreeList,
|
||||||
|
indexFreeList: indexFreeList,
|
||||||
tcpPort: opt.TCPPort,
|
tcpPort: opt.TCPPort,
|
||||||
logfile: opt.Logfile,
|
logfile: opt.Logfile,
|
||||||
logger: log.New(opt.Logfile, "", log.LstdFlags),
|
logger: log.New(opt.Logfile, "", log.LstdFlags),
|
||||||
@@ -143,88 +152,89 @@ func (s *Database) ListenAndServe() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Database) recovery() {
|
func (s *Database) recovery() {
|
||||||
advisor, err := recovery.NewRecoveryAdvisor(recovery.RecoveryAdvisorOptions{
|
// FIX
|
||||||
Dir: s.dir,
|
// advisor, err := recovery.NewRecoveryAdvisor(recovery.RecoveryAdvisorOptions{
|
||||||
VerifySnapshot: s.verifySnapshot,
|
// Dir: s.dir,
|
||||||
})
|
// VerifySnapshot: s.verifySnapshot,
|
||||||
if err != nil {
|
// })
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
recipe, err := advisor.GetRecipe()
|
|
||||||
if err != nil {
|
|
||||||
qb.Abort(qb.GetRecoveryRecipeFailed, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var logNumber int
|
|
||||||
|
|
||||||
if recipe != nil {
|
|
||||||
if recipe.Snapshot != "" {
|
|
||||||
err = s.loadSnapshot(recipe.Snapshot)
|
|
||||||
if err != nil {
|
|
||||||
qb.Abort(qb.LoadSnapshotFailed, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, changesFileName := range recipe.Changes {
|
|
||||||
err = s.replayChanges(changesFileName)
|
|
||||||
if err != nil {
|
|
||||||
qb.Abort(qb.ReplayChangesFailed, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logNumber = recipe.LogNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
s.txlog, err = txlog.NewWriter(txlog.WriterOptions{
|
|
||||||
Dir: s.dir,
|
|
||||||
LogNumber: logNumber,
|
|
||||||
AppendToWorkerQueue: s.appendJobToWorkerQueue,
|
|
||||||
FreeList: s.freeList,
|
|
||||||
Atree: s.atree,
|
|
||||||
ExitCh: s.exitCh,
|
|
||||||
WaitGroup: s.waitGroup,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
qb.Abort(qb.CreateChangesWriterFailed, err)
|
|
||||||
|
|
||||||
}
|
|
||||||
go s.txlog.Run()
|
|
||||||
|
|
||||||
// fileNames, err := s.searchREDOFiles()
|
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// qb.Abort(qb.SearchREDOFilesFailed, err)
|
// panic(err)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// if len(fileNames) > 0 {
|
// recipe, err := advisor.GetRecipe()
|
||||||
// for _, fileName := range fileNames {
|
|
||||||
// err = s.replayREDOFile(fileName)
|
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// qb.Abort(qb.ReplayREDOFileFailed, err)
|
// qb.Abort(qb.GetRecoveryRecipeFailed, err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var logNumber int
|
||||||
|
|
||||||
|
// if recipe != nil {
|
||||||
|
// if recipe.Snapshot != "" {
|
||||||
|
// err = s.loadSnapshot(recipe.Snapshot)
|
||||||
|
// if err != nil {
|
||||||
|
// qb.Abort(qb.LoadSnapshotFailed, err)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// for _, changesFileName := range recipe.Changes {
|
||||||
|
// err = s.replayChanges(changesFileName)
|
||||||
|
// if err != nil {
|
||||||
|
// qb.Abort(qb.ReplayChangesFailed, err)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// logNumber = recipe.LogNumber
|
||||||
|
// }
|
||||||
|
|
||||||
|
// s.txlog, err = txlog.NewWriter(txlog.WriterOptions{
|
||||||
|
// Dir: s.dir,
|
||||||
|
// LogNumber: logNumber,
|
||||||
|
// AppendToWorkerQueue: s.appendJobToWorkerQueue,
|
||||||
|
// FreeList: s.freeList,
|
||||||
|
// Atree: s.atree,
|
||||||
|
// ExitCh: s.exitCh,
|
||||||
|
// WaitGroup: s.waitGroup,
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// qb.Abort(qb.CreateChangesWriterFailed, err)
|
||||||
|
|
||||||
|
// }
|
||||||
|
// go s.txlog.Run()
|
||||||
|
|
||||||
|
// // fileNames, err := s.searchREDOFiles()
|
||||||
|
// // if err != nil {
|
||||||
|
// // qb.Abort(qb.SearchREDOFilesFailed, err)
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // if len(fileNames) > 0 {
|
||||||
|
// // for _, fileName := range fileNames {
|
||||||
|
// // err = s.replayREDOFile(fileName)
|
||||||
|
// // if err != nil {
|
||||||
|
// // qb.Abort(qb.ReplayREDOFileFailed, err)
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // for _, fileName := range fileNames {
|
||||||
|
// // err = os.Remove(fileName)
|
||||||
|
// // if err != nil {
|
||||||
|
// // qb.Abort(qb.RemoveREDOFileFailed, err)
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// if recipe != nil {
|
||||||
|
// if recipe.CompleteSnapshot {
|
||||||
|
// err = s.dumpSnapshot(logNumber)
|
||||||
|
// if err != nil {
|
||||||
|
// qb.Abort(qb.DumpSnapshotFailed, err)
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// for _, fileName := range fileNames {
|
// for _, fileName := range recipe.ToDelete {
|
||||||
// err = os.Remove(fileName)
|
// err = os.Remove(fileName)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// qb.Abort(qb.RemoveREDOFileFailed, err)
|
// qb.Abort(qb.RemoveRecipeFileFailed, err)
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if recipe != nil {
|
|
||||||
if recipe.CompleteSnapshot {
|
|
||||||
err = s.dumpSnapshot(logNumber)
|
|
||||||
if err != nil {
|
|
||||||
qb.Abort(qb.DumpSnapshotFailed, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fileName := range recipe.ToDelete {
|
|
||||||
err = os.Remove(fileName)
|
|
||||||
if err != nil {
|
|
||||||
qb.Abort(qb.RemoveRecipeFileFailed, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (s *Database) searchREDOFiles() ([]string, error) {
|
// func (s *Database) searchREDOFiles() ([]string, error) {
|
||||||
|
|||||||
@@ -2,10 +2,18 @@ package database
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"gordenko.dev/dima/qb"
|
"gordenko.dev/dima/qb"
|
||||||
|
"gordenko.dev/dima/qb/atree"
|
||||||
|
"gordenko.dev/dima/qb/bin"
|
||||||
|
"gordenko.dev/dima/qb/txlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// METRIC
|
// METRIC
|
||||||
|
|
||||||
|
type IndexRec struct {
|
||||||
|
Since uint32
|
||||||
|
PageNo uint32
|
||||||
|
}
|
||||||
|
|
||||||
type _metric struct {
|
type _metric struct {
|
||||||
MetricType qb.MetricType
|
MetricType qb.MetricType
|
||||||
FracDigits byte
|
FracDigits byte
|
||||||
@@ -16,6 +24,12 @@ type _metric struct {
|
|||||||
Until uint32
|
Until uint32
|
||||||
Timestamps qb.TimestampCompressor
|
Timestamps qb.TimestampCompressor
|
||||||
Values qb.ValueCompressor
|
Values qb.ValueCompressor
|
||||||
|
// fix - add index levels
|
||||||
|
XLock bool
|
||||||
|
RLocks int
|
||||||
|
WaitQueue []any
|
||||||
|
//IndexLevels [][]IndexRec // root - last element
|
||||||
|
IndexLevels [][]byte // root - last element
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *_metric) ReinitBy(timestamp uint32, value float64) {
|
func (s *_metric) ReinitBy(timestamp uint32, value float64) {
|
||||||
@@ -41,3 +55,261 @@ func (s *_metric) DeleteMeasures() {
|
|||||||
s.Until = 0
|
s.Until = 0
|
||||||
s.UntilValue = 0
|
s.UntilValue = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func (s *_metric) startAppendMeasures(req tryAppendMeasuresReq, sendToStorage func(txlog.AppendedMeasures)) {
|
||||||
|
func (s *_metric) StartAppendMeasures(req tryAppendMeasuresReq, sendToStorage func(any)) {
|
||||||
|
var (
|
||||||
|
// AppendedMeasures struct {
|
||||||
|
// MetricID uint32
|
||||||
|
// TimestampsOffset int // (заповнені одразу)
|
||||||
|
// ValuesOffset int // (заповнені одразу)
|
||||||
|
// Timestamps [][]byte
|
||||||
|
// TimestampsSize int
|
||||||
|
// Values [][]byte
|
||||||
|
// ValuesSize int
|
||||||
|
// IndexLevels []*atree.IndexLevel
|
||||||
|
// DataPages []*atree.DataPage // fix - prevPageNo for the 1st data page
|
||||||
|
// }
|
||||||
|
|
||||||
|
timestamps = s.Timestamps
|
||||||
|
values = s.Values
|
||||||
|
|
||||||
|
indexLevels []*atree.IndexLevel
|
||||||
|
dataPages []*atree.DataPage
|
||||||
|
|
||||||
|
//written int
|
||||||
|
//resultCode byte
|
||||||
|
)
|
||||||
|
|
||||||
|
s.Values.Lock()
|
||||||
|
s.Timestamps.Lock()
|
||||||
|
|
||||||
|
for idx, measure := range req.Measures {
|
||||||
|
if s.Since == 0 {
|
||||||
|
s.Since = measure.Timestamp
|
||||||
|
} else {
|
||||||
|
// FIX - у випадку помилки треба в транзакції зберегти що помилка, але також зафіксувати скільки елементів збережено.
|
||||||
|
// якщо idx == 0 - одразу знімаю блокування і нічого не відправляю в txlog
|
||||||
|
if measure.Timestamp <= s.Until {
|
||||||
|
if idx == 0 {
|
||||||
|
s.Values.Unlock()
|
||||||
|
s.Timestamps.Unlock()
|
||||||
|
|
||||||
|
req.ResultCh <- tryAppendMeasuresResult{
|
||||||
|
ResultCode: ExpiredMeasure,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//resultCode = ExpiredMeasure
|
||||||
|
//written = idx
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.MetricType == qb.Cumulative && measure.Value < s.UntilValue {
|
||||||
|
if idx == 0 {
|
||||||
|
s.Values.Unlock()
|
||||||
|
s.Timestamps.Unlock()
|
||||||
|
|
||||||
|
req.ResultCh <- tryAppendMeasuresResult{
|
||||||
|
ResultCode: NonMonotonicValue,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//resultCode = NonMonotonicValue
|
||||||
|
//written = idx
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fix - 1 + 8 bytes
|
||||||
|
extraSpace := timestamps.CalcRequiredSpace(measure.Timestamp) +
|
||||||
|
values.CalcRequiredSpace(measure.Value)
|
||||||
|
|
||||||
|
totalSpace := timestamps.Size() + values.Size() + extraSpace
|
||||||
|
|
||||||
|
if totalSpace <= atree.DataPagePayloadSize {
|
||||||
|
// накопичую
|
||||||
|
timestamps.Append(measure.Timestamp)
|
||||||
|
values.Append(measure.Value)
|
||||||
|
} else {
|
||||||
|
// сторінка заповнена
|
||||||
|
// prevPageNo - виставляю в txlog, коли забираю номер сторінки із freeList або генерую новий
|
||||||
|
dataPages = append(dataPages, &atree.DataPage{
|
||||||
|
PrevPageNo: 0, // ?
|
||||||
|
LowerTimestamp: s.Since,
|
||||||
|
Timestamps: timestamps.Chunks(),
|
||||||
|
TimestampsSize: timestamps.Size(),
|
||||||
|
Values: values.Chunks(),
|
||||||
|
ValuesSize: values.Size(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// обнуляються буфери, але state незмінний
|
||||||
|
timestamps.Renew()
|
||||||
|
values.Renew()
|
||||||
|
|
||||||
|
timestamps.Append(measure.Timestamp)
|
||||||
|
values.Append(measure.Value)
|
||||||
|
|
||||||
|
s.Since = measure.Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Until = measure.Timestamp
|
||||||
|
s.UntilValue = measure.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// виділити змінені байти.
|
||||||
|
// скопіювати. Причому можна скопіювати зрізи chunks
|
||||||
|
|
||||||
|
if len(dataPages) > 0 {
|
||||||
|
// пишу в txlog довгим шляхом через redo файл і запис в data файл
|
||||||
|
for _, unfilled := range s.IndexLevels {
|
||||||
|
indexLevels = append(indexLevels, &atree.IndexLevel{
|
||||||
|
Offset: len(unfilled),
|
||||||
|
LowerTimestamp: bin.GetUint32(unfilled),
|
||||||
|
Data: unfilled,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// короткий шлях - запис лише в txlog
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToStorage(txlog.AppendedMeasures{
|
||||||
|
MetricID: req.MetricID,
|
||||||
|
TimestampsOffset: timestamps.Offset(), // state.Pos() з якої позиції дописувати дані на сторінку 0 (при відновленні)
|
||||||
|
ValuesOffset: values.Offset(), // state.Pos()
|
||||||
|
Timestamps: timestamps.Chunks(),
|
||||||
|
TimestampsSize: timestamps.Size(),
|
||||||
|
Values: values.Chunks(),
|
||||||
|
ValuesSize: values.Size(),
|
||||||
|
IndexLevels: indexLevels,
|
||||||
|
DataPages: dataPages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *_metric) FinAppendMeasures(rec txlog.AppendMeasuresSummary) {
|
||||||
|
// Видаляю state. Оригінальні Timestamps і Values вже мають останню версію
|
||||||
|
s.Values.Unlock()
|
||||||
|
s.Timestamps.Unlock()
|
||||||
|
// fix write index levels
|
||||||
|
// update prev pageNo
|
||||||
|
// if len(rec.DataPages) > 0 {
|
||||||
|
// s.LastPageNo = rec.DataPages[len(rec.DataPages)-1].PageNo
|
||||||
|
// }
|
||||||
|
if rec.LastPageNo > 0 {
|
||||||
|
s.LastPageNo = rec.LastPageNo
|
||||||
|
}
|
||||||
|
// В txlog я передав повний індекс. У нього додали елементи (можливо нові рівні).
|
||||||
|
// Тому проста заміна
|
||||||
|
s.IndexLevels = rec.Index
|
||||||
|
}
|
||||||
|
|
||||||
|
// READ
|
||||||
|
|
||||||
|
func (s *_metric) StartRangeScan(req tryRangeScanReq) {
|
||||||
|
if s.Since == 0 {
|
||||||
|
req.ResultCh <- rangeScanResult{
|
||||||
|
ResultCode: QueryDone,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Since > s.Until {
|
||||||
|
req.ResultCh <- rangeScanResult{
|
||||||
|
ResultCode: QueryDone,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Until < s.Since {
|
||||||
|
if s.RootPageNo > 0 {
|
||||||
|
req.ResultCh <- rangeScanResult{
|
||||||
|
ResultCode: UntilNotFound,
|
||||||
|
RootPageNo: s.RootPageNo,
|
||||||
|
FracDigits: s.FracDigits,
|
||||||
|
}
|
||||||
|
s.RLocks++
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
req.ResultCh <- rangeScanResult{
|
||||||
|
ResultCode: QueryDone,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timestampDecompressor := s.Timestamps.CreateDecompressor()
|
||||||
|
valueDecompressor := s.Values.CreateDecompressor(s.FracDigits)
|
||||||
|
|
||||||
|
for {
|
||||||
|
timestamp, done := timestampDecompressor.NextValue()
|
||||||
|
if done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
value, done := valueDecompressor.NextValue()
|
||||||
|
if done {
|
||||||
|
qb.Abort(qb.HasTimestampNoValueBug, ErrNoValueBug)
|
||||||
|
}
|
||||||
|
|
||||||
|
if timestamp <= req.Until {
|
||||||
|
req.ResponseWriter.FeedNoSend(timestamp, value)
|
||||||
|
if timestamp < req.Since {
|
||||||
|
req.ResultCh <- rangeScanResult{
|
||||||
|
ResultCode: QueryDone,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.LastPageNo > 0 {
|
||||||
|
req.ResultCh <- rangeScanResult{
|
||||||
|
ResultCode: UntilFound,
|
||||||
|
LastPageNo: s.LastPageNo,
|
||||||
|
FracDigits: s.FracDigits,
|
||||||
|
}
|
||||||
|
s.RLocks++
|
||||||
|
} else {
|
||||||
|
req.ResultCh <- rangeScanResult{
|
||||||
|
ResultCode: QueryDone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *_metric) StartFullScan(req tryFullScanReq) {
|
||||||
|
if s.Since == 0 {
|
||||||
|
req.ResultCh <- fullScanResult{
|
||||||
|
ResultCode: QueryDone,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
timestampDecompressor := s.Timestamps.CreateDecompressor()
|
||||||
|
valueDecompressor := s.Values.CreateDecompressor(s.FracDigits)
|
||||||
|
|
||||||
|
for {
|
||||||
|
timestamp, done := timestampDecompressor.NextValue()
|
||||||
|
if done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
value, done := valueDecompressor.NextValue()
|
||||||
|
if done {
|
||||||
|
qb.Abort(qb.HasTimestampNoValueBug, ErrNoValueBug)
|
||||||
|
}
|
||||||
|
req.ResponseWriter.FeedNoSend(timestamp, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.LastPageNo > 0 {
|
||||||
|
req.ResultCh <- fullScanResult{
|
||||||
|
ResultCode: UntilFound,
|
||||||
|
LastPageNo: s.LastPageNo,
|
||||||
|
FracDigits: s.FracDigits,
|
||||||
|
}
|
||||||
|
s.RLocks++
|
||||||
|
} else {
|
||||||
|
req.ResultCh <- fullScanResult{
|
||||||
|
ResultCode: QueryDone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
740
database/proc.go
740
database/proc.go
@@ -12,6 +12,11 @@ import (
|
|||||||
"gordenko.dev/dima/qb/txlog"
|
"gordenko.dev/dima/qb/txlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AddMetric - create metric in map and set Xlock, rwad - append to queue
|
||||||
|
// DeleteMetric - set Xlock
|
||||||
|
// DeleteSince - xLock (тупо редкая операция)
|
||||||
|
// Якийсь прапор поставити для pendingDelete, щоб read операції вставали в чергу
|
||||||
|
|
||||||
const (
|
const (
|
||||||
QueryDone = 1
|
QueryDone = 1
|
||||||
UntilFound = 2
|
UntilFound = 2
|
||||||
@@ -49,46 +54,33 @@ func (s *Database) DoWork() {
|
|||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|
||||||
for _, metricID := range rLocksToRelease {
|
for _, metricID := range rLocksToRelease {
|
||||||
lockEntry, ok := s.metricLockEntries[metricID]
|
|
||||||
if !ok {
|
|
||||||
qb.Abort(qb.NoLockEntryBug,
|
|
||||||
fmt.Errorf("drainQueues: lockEntry not found for the metric %d",
|
|
||||||
metricID))
|
|
||||||
}
|
|
||||||
|
|
||||||
if lockEntry.XLock {
|
|
||||||
qb.Abort(qb.XLockBug,
|
|
||||||
fmt.Errorf("drainQueues: xlock is set for the metric %d",
|
|
||||||
metricID))
|
|
||||||
}
|
|
||||||
|
|
||||||
if lockEntry.RLocks <= 0 {
|
|
||||||
qb.Abort(qb.NoRLockBug,
|
|
||||||
fmt.Errorf("drainQueues: rlock not set for the metric %d",
|
|
||||||
metricID))
|
|
||||||
}
|
|
||||||
|
|
||||||
lockEntry.RLocks--
|
|
||||||
|
|
||||||
if len(lockEntry.WaitQueue) > 0 {
|
|
||||||
metric, ok := s.metrics[metricID]
|
metric, ok := s.metrics[metricID]
|
||||||
if !ok {
|
if !ok {
|
||||||
qb.Abort(qb.NoMetricBug,
|
qb.Abort(qb.NoMetricBug,
|
||||||
fmt.Errorf("drainQueues: metric %d not found", metricID))
|
fmt.Errorf("drainQueues: metric %d not found", metricID))
|
||||||
}
|
}
|
||||||
s.processMetricQueue(metricID, metric, lockEntry)
|
|
||||||
} else {
|
if metric.XLock {
|
||||||
if lockEntry.RLocks == 0 {
|
qb.Abort(qb.XLockBug,
|
||||||
delete(s.metricLockEntries, metricID)
|
fmt.Errorf("drainQueues: xlock is set for the metric %d",
|
||||||
|
metricID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if metric.RLocks <= 0 {
|
||||||
|
qb.Abort(qb.NoRLockBug,
|
||||||
|
fmt.Errorf("drainQueues: rlock not set for the metric %d",
|
||||||
|
metricID))
|
||||||
|
}
|
||||||
|
|
||||||
|
metric.RLocks--
|
||||||
|
|
||||||
|
if len(metric.WaitQueue) > 0 {
|
||||||
|
s.processMetricQueue(metricID, metric)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, untyped := range workerQueue {
|
for _, untyped := range workerQueue {
|
||||||
switch req := untyped.(type) {
|
switch req := untyped.(type) {
|
||||||
//case tryAppendMeasureReq:
|
|
||||||
// s.tryAppendMeasure(req)
|
|
||||||
|
|
||||||
case tryAppendMeasuresReq:
|
case tryAppendMeasuresReq:
|
||||||
s.tryAppendMeasures(req)
|
s.tryAppendMeasures(req)
|
||||||
|
|
||||||
@@ -123,44 +115,25 @@ func (s *Database) DoWork() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Database) processMetricQueue(metricID uint32, metric *_metric, lockEntry *metricLockEntry) {
|
// суть у тому що треба запускати запити, пока не зустріну XLock
|
||||||
if len(lockEntry.WaitQueue) == 0 {
|
func (s *Database) processMetricQueue(metricID uint32, metric *_metric) {
|
||||||
|
if len(metric.WaitQueue) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var modificationReqs []any
|
for _, untyped := range metric.WaitQueue {
|
||||||
|
|
||||||
for _, untyped := range lockEntry.WaitQueue {
|
|
||||||
var rLockRequired bool
|
|
||||||
switch req := untyped.(type) {
|
switch req := untyped.(type) {
|
||||||
case tryRangeScanReq:
|
case tryRangeScanReq:
|
||||||
rLockRequired = s.startRangeScan(metric, req)
|
metric.StartRangeScan(req)
|
||||||
|
|
||||||
case tryFullScanReq:
|
case tryFullScanReq:
|
||||||
rLockRequired = s.startFullScan(metric, req)
|
metric.StartFullScan(req)
|
||||||
|
|
||||||
case tryGetMetricReq:
|
case tryGetMetricReq:
|
||||||
s.tryGetMetric(req)
|
s.tryGetMetric(req)
|
||||||
|
|
||||||
default:
|
|
||||||
modificationReqs = append(modificationReqs, untyped)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rLockRequired {
|
|
||||||
lockEntry.RLocks++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lockEntry.WaitQueue = nil
|
|
||||||
if lockEntry.RLocks > 0 {
|
|
||||||
lockEntry.WaitQueue = modificationReqs
|
|
||||||
} else {
|
|
||||||
for idx, untyped := range modificationReqs {
|
|
||||||
switch req := untyped.(type) {
|
|
||||||
//case tryAppendMeasureReq:
|
|
||||||
// s.startAppendMeasure(metric, req, nil)
|
|
||||||
|
|
||||||
case tryAppendMeasuresReq:
|
case tryAppendMeasuresReq:
|
||||||
s.startAppendMeasures(metric, req, nil)
|
metric.StartAppendMeasures(req, s.txlog.Append)
|
||||||
|
|
||||||
case tryDeleteMetricReq:
|
case tryDeleteMetricReq:
|
||||||
s.startDeleteMetric(metric, req)
|
s.startDeleteMetric(metric, req)
|
||||||
@@ -172,16 +145,6 @@ func (s *Database) processMetricQueue(metricID uint32, metric *_metric, lockEntr
|
|||||||
qb.Abort(qb.UnknownMetricWaitQueueItemBug,
|
qb.Abort(qb.UnknownMetricWaitQueueItemBug,
|
||||||
fmt.Errorf("bug: unknown metric wait queue item type %T", req))
|
fmt.Errorf("bug: unknown metric wait queue item type %T", req))
|
||||||
}
|
}
|
||||||
|
|
||||||
lockEntry, ok := s.metricLockEntries[metricID]
|
|
||||||
if ok {
|
|
||||||
start := idx + 1
|
|
||||||
if start < len(modificationReqs) {
|
|
||||||
lockEntry.WaitQueue = append(lockEntry.WaitQueue, modificationReqs[start:]...)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,222 +295,16 @@ func (s *Database) startDeleteMeasures(metric *_metric, req tryDeleteMeasuresReq
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// type tryAppendMeasureReq struct {
|
func (s *Database) finAppendMeasures(rec txlog.AppendMeasuresSummary) {
|
||||||
// MetricID uint32
|
|
||||||
// Timestamp uint32
|
|
||||||
// Value float64
|
|
||||||
// ResultCh chan tryAppendMeasureResult
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (s *Database) tryAppendMeasure(req tryAppendMeasureReq) {
|
|
||||||
// metric, ok := s.metrics[req.MetricID]
|
|
||||||
// if !ok {
|
|
||||||
// req.ResultCh <- tryAppendMeasureResult{
|
|
||||||
// MetricID: req.MetricID,
|
|
||||||
// ResultCode: NoMetric,
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// lockEntry, ok := s.metricLockEntries[req.MetricID]
|
|
||||||
// if ok {
|
|
||||||
// if lockEntry.XLock {
|
|
||||||
// lockEntry.WaitQueue = append(lockEntry.WaitQueue, req)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// s.startAppendMeasure(metric, req, lockEntry)
|
|
||||||
// }
|
|
||||||
|
|
||||||
type ReadBound struct {
|
|
||||||
ValuesPos int
|
|
||||||
ValuesLastValue float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// func (s *Database) startAppendMeasure(metric *_metric, req tryAppendMeasureReq, lockEntry *metricLockEntry) {
|
|
||||||
// if req.Timestamp <= metric.Until {
|
|
||||||
// req.ResultCh <- tryAppendMeasureResult{
|
|
||||||
// MetricID: req.MetricID,
|
|
||||||
// ResultCode: ExpiredMeasure,
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if metric.MetricType == qb.Cumulative && req.Value < metric.UntilValue {
|
|
||||||
// req.ResultCh <- tryAppendMeasureResult{
|
|
||||||
// MetricID: req.MetricID,
|
|
||||||
// ResultCode: NonMonotonicValue,
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// extraSpace := metric.Timestamps.CalcRequiredSpace(req.Timestamp) +
|
|
||||||
// metric.Values.CalcRequiredSpace(req.Value)
|
|
||||||
|
|
||||||
// totalSpace := metric.Timestamps.Size() + metric.Values.Size() + extraSpace
|
|
||||||
|
|
||||||
// if totalSpace <= atree.DataPagePayloadSize {
|
|
||||||
// if lockEntry != nil {
|
|
||||||
// lockEntry.RLocks++
|
|
||||||
// } else {
|
|
||||||
// s.metricLockEntries[req.MetricID] = &metricLockEntry{
|
|
||||||
// RLocks: 1,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// req.ResultCh <- tryAppendMeasureResult{
|
|
||||||
// MetricID: req.MetricID,
|
|
||||||
// Timestamp: req.Timestamp,
|
|
||||||
// Value: req.Value,
|
|
||||||
// ResultCode: CanAppend,
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// if lockEntry != nil {
|
|
||||||
// if lockEntry.RLocks > 0 {
|
|
||||||
// lockEntry.WaitQueue = append(lockEntry.WaitQueue, req)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// lockEntry.XLock = true
|
|
||||||
// } else {
|
|
||||||
// s.metricLockEntries[req.MetricID] = &metricLockEntry{
|
|
||||||
// XLock: true,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// req.ResultCh <- tryAppendMeasureResult{
|
|
||||||
// MetricID: req.MetricID,
|
|
||||||
// Timestamp: req.Timestamp,
|
|
||||||
// Value: req.Value,
|
|
||||||
// ResultCode: NewPage,
|
|
||||||
// FilledPage: &FilledPage{
|
|
||||||
// Since: metric.Since,
|
|
||||||
// RootPageNo: metric.RootPageNo,
|
|
||||||
// PrevPageNo: metric.LastPageNo,
|
|
||||||
// TimestampsChunks: metric.TimestampsBuf.Chunks(),
|
|
||||||
// TimestampsSize: uint16(metric.Timestamps.Size()),
|
|
||||||
// ValuesChunks: metric.ValuesBuf.Chunks(),
|
|
||||||
// ValuesSize: uint16(metric.Values.Size()),
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (s *Database) appendMeasure(rec txlog.AppendedMeasure) {
|
|
||||||
// metric, ok := s.metrics[rec.MetricID]
|
|
||||||
// if !ok {
|
|
||||||
// qb.Abort(qb.NoMetricBug,
|
|
||||||
// fmt.Errorf("appendMeasure: metric %d not found",
|
|
||||||
// rec.MetricID))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// lockEntry, ok := s.metricLockEntries[rec.MetricID]
|
|
||||||
// if !ok {
|
|
||||||
// qb.Abort(qb.NoLockEntryBug,
|
|
||||||
// fmt.Errorf("appendMeasure: lockEntry not found for the metric %d",
|
|
||||||
// rec.MetricID))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if lockEntry.XLock {
|
|
||||||
// qb.Abort(qb.XLockBug,
|
|
||||||
// fmt.Errorf("appendMeasure: xlock is set for the metric %d",
|
|
||||||
// rec.MetricID))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if lockEntry.RLocks <= 0 {
|
|
||||||
// qb.Abort(qb.NoRLockBug,
|
|
||||||
// fmt.Errorf("appendMeasure: rlock not set for the metric %d",
|
|
||||||
// rec.MetricID))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if metric.Since == 0 {
|
|
||||||
// metric.Since = rec.Timestamp
|
|
||||||
// metric.SinceValue = rec.Value
|
|
||||||
// }
|
|
||||||
|
|
||||||
// metric.Timestamps.Append(rec.Timestamp)
|
|
||||||
// metric.Values.Append(rec.Value)
|
|
||||||
// metric.Until = rec.Timestamp
|
|
||||||
// metric.UntilValue = rec.Value
|
|
||||||
|
|
||||||
// lockEntry.RLocks--
|
|
||||||
// if len(lockEntry.WaitQueue) > 0 {
|
|
||||||
// s.processMetricQueue(rec.MetricID, metric, lockEntry)
|
|
||||||
// } else {
|
|
||||||
// if lockEntry.RLocks == 0 {
|
|
||||||
// delete(s.metricLockEntries, rec.MetricID)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (s *Database) appendMeasures(rec txlog.AppendedMeasures) {
|
|
||||||
metric, ok := s.metrics[rec.MetricID]
|
metric, ok := s.metrics[rec.MetricID]
|
||||||
if !ok {
|
if !ok {
|
||||||
qb.Abort(qb.NoMetricBug,
|
qb.Abort(qb.NoMetricBug,
|
||||||
fmt.Errorf("appendMeasureAfterOverflow: metric %d not found",
|
fmt.Errorf("finAppendMeasures: metric %d not found",
|
||||||
rec.MetricID))
|
rec.MetricID))
|
||||||
}
|
}
|
||||||
|
metric.FinAppendMeasures(rec)
|
||||||
metric.Values.Unlock()
|
|
||||||
metric.Timestamps.Unlock()
|
|
||||||
|
|
||||||
// lockEntry, ok := s.metricLockEntries[rec.MetricID]
|
|
||||||
// if !ok {
|
|
||||||
// qb.Abort(qb.NoLockEntryBug,
|
|
||||||
// fmt.Errorf("appendMeasureAfterOverflow: lockEntry not found for the metric %d",
|
|
||||||
// rec.MetricID))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if !lockEntry.XLock {
|
|
||||||
// qb.Abort(qb.NoXLockBug,
|
|
||||||
// fmt.Errorf("appendMeasureAfterOverflow: xlock not set for the metric %d",
|
|
||||||
// rec.MetricID))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// lockEntry.XLock = false
|
|
||||||
// s.doAfterReleaseXLock(rec.MetricID, metric, lockEntry)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (s *Database) appendMeasureAfterOverflow(extended txlog.AppendedMeasureWithOverflowExtended) {
|
|
||||||
// rec := extended.Record
|
|
||||||
// metric, ok := s.metrics[rec.MetricID]
|
|
||||||
// if !ok {
|
|
||||||
// qb.Abort(qb.NoMetricBug,
|
|
||||||
// fmt.Errorf("appendMeasureAfterOverflow: metric %d not found",
|
|
||||||
// rec.MetricID))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// lockEntry, ok := s.metricLockEntries[rec.MetricID]
|
|
||||||
// if !ok {
|
|
||||||
// qb.Abort(qb.NoLockEntryBug,
|
|
||||||
// fmt.Errorf("appendMeasureAfterOverflow: lockEntry not found for the metric %d",
|
|
||||||
// rec.MetricID))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if !lockEntry.XLock {
|
|
||||||
// qb.Abort(qb.NoXLockBug,
|
|
||||||
// fmt.Errorf("appendMeasureAfterOverflow: xlock not set for the metric %d",
|
|
||||||
// rec.MetricID))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// metric.ReinitBy(rec.Timestamp, rec.Value)
|
|
||||||
// if rec.IsRootChanged {
|
|
||||||
// metric.RootPageNo = rec.RootPageNo
|
|
||||||
// }
|
|
||||||
// metric.LastPageNo = rec.DataPageNo
|
|
||||||
|
|
||||||
// if rec.IsDataPageReused {
|
|
||||||
// s.freeList.DeleteReservedPages([]uint32{
|
|
||||||
// rec.DataPageNo,
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if len(rec.ReusedIndexPages) > 0 {
|
|
||||||
// s.freeList.DeleteReservedPages(rec.ReusedIndexPages)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// lockEntry.XLock = false
|
|
||||||
// s.doAfterReleaseXLock(rec.MetricID, metric, lockEntry)
|
|
||||||
// }
|
|
||||||
|
|
||||||
type tryAppendMeasuresReq struct {
|
type tryAppendMeasuresReq struct {
|
||||||
MetricID uint32
|
MetricID uint32
|
||||||
Measures []proto.Measure
|
Measures []proto.Measure
|
||||||
@@ -562,175 +319,11 @@ func (s *Database) tryAppendMeasures(req tryAppendMeasuresReq) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if metric.XLock {
|
||||||
lockEntry, ok := s.metricLockEntries[req.MetricID]
|
metric.WaitQueue = append(metric.WaitQueue, req)
|
||||||
if ok {
|
|
||||||
if lockEntry.XLock {
|
|
||||||
lockEntry.WaitQueue = append(lockEntry.WaitQueue, req)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
metric.StartAppendMeasures(req, s.txlog.Append)
|
||||||
s.startAppendMeasures(metric, req, lockEntry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Database) startAppendMeasures(metric *_metric, req tryAppendMeasuresReq, lockEntry *metricLockEntry) {
|
|
||||||
var (
|
|
||||||
timestamps = metric.Timestamps
|
|
||||||
values = metric.Values
|
|
||||||
|
|
||||||
dataPages []atree.NotLinkedDataPage
|
|
||||||
|
|
||||||
resultCode byte
|
|
||||||
written int
|
|
||||||
)
|
|
||||||
|
|
||||||
metric.Values.Lock()
|
|
||||||
metric.Timestamps.Lock()
|
|
||||||
|
|
||||||
for idx, measure := range req.Measures {
|
|
||||||
if metric.Since == 0 {
|
|
||||||
metric.Since = measure.Timestamp
|
|
||||||
} else {
|
|
||||||
// FIX - у випадку помилки треба в транзакції зберегти що помилка, але також зафіксувати скільки елементів збережено.
|
|
||||||
// якщо idx == 0 - одразу знімаю блокування і нічого не відправляю в txlog
|
|
||||||
if measure.Timestamp <= metric.Until {
|
|
||||||
if idx == 0 {
|
|
||||||
metric.Values.Unlock()
|
|
||||||
metric.Timestamps.Unlock()
|
|
||||||
|
|
||||||
req.ResultCh <- tryAppendMeasuresResult{
|
|
||||||
ResultCode: ExpiredMeasure,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resultCode = ExpiredMeasure
|
|
||||||
written = idx
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if metric.MetricType == qb.Cumulative && measure.Value < metric.UntilValue {
|
|
||||||
if idx == 0 {
|
|
||||||
metric.Values.Unlock()
|
|
||||||
metric.Timestamps.Unlock()
|
|
||||||
|
|
||||||
req.ResultCh <- tryAppendMeasuresResult{
|
|
||||||
ResultCode: NonMonotonicValue,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resultCode = NonMonotonicValue
|
|
||||||
written = idx
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fix - 1 + 8 bytes
|
|
||||||
extraSpace := timestamps.CalcRequiredSpace(measure.Timestamp) +
|
|
||||||
values.CalcRequiredSpace(measure.Value)
|
|
||||||
|
|
||||||
totalSpace := timestamps.Size() + values.Size() + extraSpace
|
|
||||||
|
|
||||||
if totalSpace <= atree.DataPagePayloadSize {
|
|
||||||
// накопичую
|
|
||||||
timestamps.Append(measure.Timestamp)
|
|
||||||
values.Append(measure.Value)
|
|
||||||
} else {
|
|
||||||
// сторінка заповнена
|
|
||||||
pageData := make([]byte, atree.PageSize)
|
|
||||||
|
|
||||||
// prevPageNo - виставляю в txlog, коли забираю номер сторінки із freeList або генерую новий
|
|
||||||
atree.ChunksToNotLinkedDataPage(pageData, atree.ChunksToNotLinkedDataPageReq{
|
|
||||||
TimestampsChunks: timestamps.Chunks(),
|
|
||||||
TimestampsSize: uint16(timestamps.Size()),
|
|
||||||
ValuesChunks: values.Chunks(),
|
|
||||||
ValuesSize: uint16(values.Size()),
|
|
||||||
})
|
|
||||||
|
|
||||||
dataPages = append(dataPages, atree.NotLinkedDataPage{
|
|
||||||
Since: metric.Since,
|
|
||||||
Data: pageData,
|
|
||||||
})
|
|
||||||
|
|
||||||
// FIX
|
|
||||||
// prevPageNo = report.DataPageNo
|
|
||||||
// if report.IsRootChanged {
|
|
||||||
// rootPageNo = report.NewRootPageNo
|
|
||||||
// }
|
|
||||||
// waitCh := s.txlog.WriteAppendedMeasureWithOverflow(
|
|
||||||
// txlog.AppendedMeasureWithOverflow{
|
|
||||||
// MetricID: req.MetricID,
|
|
||||||
// Timestamp: measure.Timestamp,
|
|
||||||
// Value: measure.Value,
|
|
||||||
// IsDataPageReused: report.IsDataPageReused,
|
|
||||||
// DataPageNo: report.DataPageNo,
|
|
||||||
// IsRootChanged: report.IsRootChanged,
|
|
||||||
// RootPageNo: report.NewRootPageNo,
|
|
||||||
// ReusedIndexPages: report.ReusedIndexPages,
|
|
||||||
// },
|
|
||||||
// (idx+1) < len(req.Measures),
|
|
||||||
// )
|
|
||||||
// <-waitCh
|
|
||||||
|
|
||||||
timestamps.Renew()
|
|
||||||
values.Renew()
|
|
||||||
|
|
||||||
timestamps.Append(measure.Timestamp)
|
|
||||||
values.Append(measure.Value)
|
|
||||||
|
|
||||||
metric.Since = measure.Timestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
metric.Until = measure.Timestamp
|
|
||||||
metric.UntilValue = measure.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// виділити змінені байти.
|
|
||||||
// скопіювати. Причому можна скопіювати зрізи chunks
|
|
||||||
|
|
||||||
if len(dataPages) > 0 {
|
|
||||||
// пишу в txlog довгим шляхом через redo файл і запис в data файл
|
|
||||||
} else {
|
|
||||||
// короткий шлях - запис лише в txlog
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitCh := s.txlog.Append(txlog.AppendedPagesReq{
|
|
||||||
// MetricID: req.MetricID,
|
|
||||||
// Timestamp: until,
|
|
||||||
// Value: untilValue,
|
|
||||||
// LastPageNo: prevPageNo,
|
|
||||||
// //Measures: toAppendMeasures,
|
|
||||||
// },
|
|
||||||
// )
|
|
||||||
// <-waitCh
|
|
||||||
|
|
||||||
// if lockEntry != nil {
|
|
||||||
// if lockEntry.RLocks > 0 {
|
|
||||||
// lockEntry.WaitQueue = append(lockEntry.WaitQueue, req)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// lockEntry.XLock = true
|
|
||||||
// } else {
|
|
||||||
// s.metricLockEntries[req.MetricID] = &metricLockEntry{
|
|
||||||
// XLock: true,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
type PartialChange struct {
|
|
||||||
Data [][]byte
|
|
||||||
Size int
|
|
||||||
Offset int
|
|
||||||
}
|
|
||||||
|
|
||||||
type AppendedMeasures struct {
|
|
||||||
RootPageNo uint32
|
|
||||||
LastPageNo uint32
|
|
||||||
DataPages []atree.ChunksToNotLinkedDataPageReq
|
|
||||||
Values PartialChange
|
|
||||||
Timestamps PartialChange
|
|
||||||
ResultCh chan tryAppendMeasuresResult
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type tryRangeScanReq struct {
|
type tryRangeScanReq struct {
|
||||||
@@ -757,102 +350,12 @@ func (s *Database) tryRangeScan(req tryRangeScanReq) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lockEntry, ok := s.metricLockEntries[req.MetricID]
|
if metric.XLock {
|
||||||
if ok {
|
metric.WaitQueue = append(metric.WaitQueue, req)
|
||||||
if lockEntry.XLock {
|
|
||||||
lockEntry.WaitQueue = append(lockEntry.WaitQueue, req)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if s.startRangeScan(metric, req) {
|
metric.StartRangeScan(req)
|
||||||
if lockEntry != nil {
|
|
||||||
lockEntry.RLocks++
|
|
||||||
} else {
|
|
||||||
s.metricLockEntries[req.MetricID] = &metricLockEntry{
|
|
||||||
RLocks: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Database) startRangeScan(metric *_metric, req tryRangeScanReq) bool {
|
|
||||||
if metric.Since == 0 {
|
|
||||||
req.ResultCh <- rangeScanResult{
|
|
||||||
ResultCode: QueryDone,
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Since > metric.Until {
|
|
||||||
req.ResultCh <- rangeScanResult{
|
|
||||||
ResultCode: QueryDone,
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Until < metric.Since {
|
|
||||||
if metric.RootPageNo > 0 {
|
|
||||||
req.ResultCh <- rangeScanResult{
|
|
||||||
ResultCode: UntilNotFound,
|
|
||||||
RootPageNo: metric.RootPageNo,
|
|
||||||
FracDigits: metric.FracDigits,
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
req.ResultCh <- rangeScanResult{
|
|
||||||
ResultCode: QueryDone,
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
timestampDecompressor := chunkenc.NewReverseTimeDeltaDecompressor(
|
|
||||||
metric.TimestampsBuf,
|
|
||||||
metric.Timestamps.Size(),
|
|
||||||
)
|
|
||||||
|
|
||||||
valueDecompressor := chunkenc.NewReverseInstantDeltaDecompressor(
|
|
||||||
metric.ValuesBuf,
|
|
||||||
metric.Values.Size(),
|
|
||||||
metric.FracDigits,
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
timestamp, done := timestampDecompressor.NextValue()
|
|
||||||
if done {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
value, done := valueDecompressor.NextValue()
|
|
||||||
if done {
|
|
||||||
qb.Abort(qb.HasTimestampNoValueBug, ErrNoValueBug)
|
|
||||||
}
|
|
||||||
|
|
||||||
if timestamp <= req.Until {
|
|
||||||
req.ResponseWriter.FeedNoSend(timestamp, value)
|
|
||||||
if timestamp < req.Since {
|
|
||||||
req.ResultCh <- rangeScanResult{
|
|
||||||
ResultCode: QueryDone,
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if metric.LastPageNo > 0 {
|
|
||||||
req.ResultCh <- rangeScanResult{
|
|
||||||
ResultCode: UntilFound,
|
|
||||||
LastPageNo: metric.LastPageNo,
|
|
||||||
FracDigits: metric.FracDigits,
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
req.ResultCh <- rangeScanResult{
|
|
||||||
ResultCode: QueryDone,
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type tryFullScanReq struct {
|
type tryFullScanReq struct {
|
||||||
@@ -877,68 +380,11 @@ func (s *Database) tryFullScan(req tryFullScanReq) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lockEntry, ok := s.metricLockEntries[req.MetricID]
|
if metric.XLock {
|
||||||
if ok {
|
metric.WaitQueue = append(metric.WaitQueue, req)
|
||||||
if lockEntry.XLock {
|
|
||||||
lockEntry.WaitQueue = append(lockEntry.WaitQueue, req)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
metric.StartFullScan(req)
|
||||||
|
|
||||||
if s.startFullScan(metric, req) {
|
|
||||||
if lockEntry != nil {
|
|
||||||
lockEntry.RLocks++
|
|
||||||
} else {
|
|
||||||
s.metricLockEntries[req.MetricID] = &metricLockEntry{
|
|
||||||
RLocks: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Database) startFullScan(metric *_metric, req tryFullScanReq) bool {
|
|
||||||
if metric.Since == 0 {
|
|
||||||
req.ResultCh <- fullScanResult{
|
|
||||||
ResultCode: QueryDone,
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
timestampDecompressor := chunkenc.NewReverseTimeDeltaDecompressor(
|
|
||||||
metric.TimestampsBuf,
|
|
||||||
metric.Timestamps.Size(),
|
|
||||||
)
|
|
||||||
valueDecompressor := chunkenc.NewReverseInstantDeltaDecompressor(
|
|
||||||
metric.ValuesBuf,
|
|
||||||
metric.Values.Size(),
|
|
||||||
metric.FracDigits,
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
timestamp, done := timestampDecompressor.NextValue()
|
|
||||||
if done {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
value, done := valueDecompressor.NextValue()
|
|
||||||
if done {
|
|
||||||
qb.Abort(qb.HasTimestampNoValueBug, ErrNoValueBug)
|
|
||||||
}
|
|
||||||
req.ResponseWriter.FeedNoSend(timestamp, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if metric.LastPageNo > 0 {
|
|
||||||
req.ResultCh <- fullScanResult{
|
|
||||||
ResultCode: UntilFound,
|
|
||||||
LastPageNo: metric.LastPageNo,
|
|
||||||
FracDigits: metric.FracDigits,
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
req.ResultCh <- fullScanResult{
|
|
||||||
ResultCode: QueryDone,
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type tryListCurrentValuesReq struct {
|
type tryListCurrentValuesReq struct {
|
||||||
@@ -975,8 +421,8 @@ func (s *Database) applyChanges(req txlog.Changes) {
|
|||||||
// case txlog.AppendedMeasure:
|
// case txlog.AppendedMeasure:
|
||||||
// s.appendMeasure(rec)
|
// s.appendMeasure(rec)
|
||||||
|
|
||||||
case txlog.AppendedMeasures:
|
case txlog.AppendMeasuresSummary:
|
||||||
s.appendMeasures(rec)
|
s.finAppendMeasures(rec)
|
||||||
|
|
||||||
// case txlog.AppendedMeasureWithOverflowExtended:
|
// case txlog.AppendedMeasureWithOverflowExtended:
|
||||||
// s.appendMeasureAfterOverflow(rec)
|
// s.appendMeasureAfterOverflow(rec)
|
||||||
@@ -986,18 +432,38 @@ func (s *Database) applyChanges(req txlog.Changes) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.ForceSnapshot || req.ExitWaitGroup != nil {
|
if req.MetricsCh != nil {
|
||||||
s.dumpSnapshot(req.LogNumber)
|
waitCh := make(chan struct{})
|
||||||
|
req.MetricsCh <- txlog.MetricsState{
|
||||||
|
Metrics: s.createMetricsState(),
|
||||||
|
WaitCh: waitCh,
|
||||||
|
}
|
||||||
|
// чекаю поки txlog запише снапшот, а отже можна змінювати буфери
|
||||||
|
<-waitCh
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close(req.WaitCh)
|
func (s *Database) createMetricsState() (metrics []txlog.Metric) {
|
||||||
|
for metricID, metric := range s.metrics {
|
||||||
if req.ExitWaitGroup != nil {
|
x := txlog.Metric{
|
||||||
req.ExitWaitGroup.Done()
|
MetricID: metricID,
|
||||||
|
MetricType: metric.MetricType,
|
||||||
|
FracDigits: metric.FracDigits,
|
||||||
|
LastPageNo: metric.LastPageNo,
|
||||||
|
Since: metric.Since,
|
||||||
|
SinceValue: metric.SinceValue,
|
||||||
|
Until: metric.Until, // ?
|
||||||
|
UntilValue: metric.UntilValue, // ?
|
||||||
}
|
}
|
||||||
|
x.Timestamps, x.TimestampsSize = metric.Timestamps.Snapshot()
|
||||||
|
x.Values, x.ValuesSize = metric.Values.Snapshot()
|
||||||
|
metrics = append(metrics, x)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Database) addMetric(rec txlog.AddedMetric) {
|
func (s *Database) addMetric(rec txlog.AddedMetric) {
|
||||||
|
// fix lock
|
||||||
_, ok := s.metrics[rec.MetricID]
|
_, ok := s.metrics[rec.MetricID]
|
||||||
if ok {
|
if ok {
|
||||||
qb.Abort(qb.MetricAddedBug,
|
qb.Abort(qb.MetricAddedBug,
|
||||||
@@ -1005,18 +471,11 @@ func (s *Database) addMetric(rec txlog.AddedMetric) {
|
|||||||
rec.MetricID))
|
rec.MetricID))
|
||||||
}
|
}
|
||||||
|
|
||||||
lockEntry, ok := s.metricLockEntries[rec.MetricID]
|
// if !lockEntry.XLock {
|
||||||
if !ok {
|
// qb.Abort(qb.NoXLockBug,
|
||||||
qb.Abort(qb.NoLockEntryBug,
|
// fmt.Errorf("addMetric: xlock not set for the metric %d",
|
||||||
fmt.Errorf("addMetric: lockEntry not found for the metric %d",
|
// rec.MetricID))
|
||||||
rec.MetricID))
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
if !lockEntry.XLock {
|
|
||||||
qb.Abort(qb.NoXLockBug,
|
|
||||||
fmt.Errorf("addMetric: xlock not set for the metric %d",
|
|
||||||
rec.MetricID))
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
values qb.ValueCompressor
|
values qb.ValueCompressor
|
||||||
@@ -1035,8 +494,6 @@ func (s *Database) addMetric(rec txlog.AddedMetric) {
|
|||||||
s.metrics[rec.MetricID] = &_metric{
|
s.metrics[rec.MetricID] = &_metric{
|
||||||
MetricType: rec.MetricType,
|
MetricType: rec.MetricType,
|
||||||
FracDigits: byte(rec.FracDigits),
|
FracDigits: byte(rec.FracDigits),
|
||||||
TimestampsBuf: timestampsBuf,
|
|
||||||
ValuesBuf: valuesBuf,
|
|
||||||
Timestamps: chunkenc.NewReverseTimeDeltaCompressor(timestampsBuf, 0),
|
Timestamps: chunkenc.NewReverseTimeDeltaCompressor(timestampsBuf, 0),
|
||||||
Values: values,
|
Values: values,
|
||||||
}
|
}
|
||||||
@@ -1046,21 +503,14 @@ func (s *Database) addMetric(rec txlog.AddedMetric) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Database) deleteMetric(rec txlog.DeletedMetric) {
|
func (s *Database) deleteMetric(rec txlog.DeletedMetric) {
|
||||||
_, ok := s.metrics[rec.MetricID]
|
metric, ok := s.metrics[rec.MetricID]
|
||||||
if !ok {
|
if !ok {
|
||||||
qb.Abort(qb.NoMetricBug,
|
qb.Abort(qb.NoMetricBug,
|
||||||
fmt.Errorf("deleteMetric: metric %d not found",
|
fmt.Errorf("deleteMetric: metric %d not found",
|
||||||
rec.MetricID))
|
rec.MetricID))
|
||||||
}
|
}
|
||||||
|
|
||||||
lockEntry, ok := s.metricLockEntries[rec.MetricID]
|
if !metric.XLock {
|
||||||
if !ok {
|
|
||||||
qb.Abort(qb.NoLockEntryBug,
|
|
||||||
fmt.Errorf("deleteMetric: lockEntry not found for the metric %d",
|
|
||||||
rec.MetricID))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !lockEntry.XLock {
|
|
||||||
qb.Abort(qb.NoXLockBug,
|
qb.Abort(qb.NoXLockBug,
|
||||||
fmt.Errorf("deleteMetric: xlock not set for the metric %d",
|
fmt.Errorf("deleteMetric: xlock not set for the metric %d",
|
||||||
rec.MetricID))
|
rec.MetricID))
|
||||||
@@ -1068,8 +518,8 @@ func (s *Database) deleteMetric(rec txlog.DeletedMetric) {
|
|||||||
|
|
||||||
var addMetricReqs []tryAddMetricReq
|
var addMetricReqs []tryAddMetricReq
|
||||||
|
|
||||||
if len(lockEntry.WaitQueue) > 0 {
|
if len(metric.WaitQueue) > 0 {
|
||||||
for _, untyped := range lockEntry.WaitQueue {
|
for _, untyped := range metric.WaitQueue {
|
||||||
switch req := untyped.(type) {
|
switch req := untyped.(type) {
|
||||||
// case tryAppendMeasureReq:
|
// case tryAppendMeasureReq:
|
||||||
// req.ResultCh <- tryAppendMeasureResult{
|
// req.ResultCh <- tryAppendMeasureResult{
|
||||||
@@ -1112,11 +562,11 @@ func (s *Database) deleteMetric(rec txlog.DeletedMetric) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete(s.metrics, rec.MetricID)
|
delete(s.metrics, rec.MetricID)
|
||||||
delete(s.metricLockEntries, rec.MetricID)
|
|
||||||
|
|
||||||
if len(rec.FreePageNumbers) > 0 {
|
// ADD in txlog
|
||||||
s.freeList.AddPages(rec.FreePageNumbers)
|
// if len(rec.FreePageNumbers) > 0 {
|
||||||
}
|
// s.freeList.AddPages(rec.FreePageNumbers)
|
||||||
|
// }
|
||||||
|
|
||||||
if len(addMetricReqs) > 0 {
|
if len(addMetricReqs) > 0 {
|
||||||
s.processTryAddMetricReqsImmediatelyAfterDelete(addMetricReqs)
|
s.processTryAddMetricReqsImmediatelyAfterDelete(addMetricReqs)
|
||||||
@@ -1131,30 +581,22 @@ func (s *Database) deleteMeasures(rec txlog.DeletedMeasures) {
|
|||||||
rec.MetricID))
|
rec.MetricID))
|
||||||
}
|
}
|
||||||
|
|
||||||
lockEntry, ok := s.metricLockEntries[rec.MetricID]
|
if !metric.XLock {
|
||||||
if !ok {
|
|
||||||
qb.Abort(qb.NoLockEntryBug,
|
|
||||||
fmt.Errorf("deleteMeasures: lockEntry not found for the metric %d",
|
|
||||||
rec.MetricID))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !lockEntry.XLock {
|
|
||||||
qb.Abort(qb.NoXLockBug,
|
qb.Abort(qb.NoXLockBug,
|
||||||
fmt.Errorf("deleteMeasures: xlock not set for the metric %d",
|
fmt.Errorf("deleteMeasures: xlock not set for the metric %d",
|
||||||
rec.MetricID))
|
rec.MetricID))
|
||||||
}
|
}
|
||||||
metric.DeleteMeasures()
|
metric.DeleteMeasures()
|
||||||
lockEntry.XLock = false
|
metric.XLock = false
|
||||||
if len(rec.FreePageNumbers) > 0 {
|
// FIX add in txlog
|
||||||
s.freeList.AddPages(rec.FreePageNumbers)
|
// if len(rec.FreePageNumbers) > 0 {
|
||||||
}
|
// s.freeList.AddPages(rec.FreePageNumbers)
|
||||||
s.doAfterReleaseXLock(rec.MetricID, metric, lockEntry)
|
// }
|
||||||
|
s.doAfterReleaseXLock(rec.MetricID, metric)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Database) doAfterReleaseXLock(metricID uint32, metric *_metric, lockEntry *metricLockEntry) {
|
func (s *Database) doAfterReleaseXLock(metricID uint32, metric *_metric) {
|
||||||
if len(lockEntry.WaitQueue) == 0 {
|
if len(metric.WaitQueue) > 0 {
|
||||||
delete(s.metricLockEntries, metricID)
|
s.processMetricQueue(metricID, metric)
|
||||||
} else {
|
|
||||||
s.processMetricQueue(metricID, metric, lockEntry)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,279 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"hash/crc32"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
bin "gordenko.dev/dima/bin/little"
|
|
||||||
"gordenko.dev/dima/qb"
|
|
||||||
"gordenko.dev/dima/qb/atree"
|
|
||||||
"gordenko.dev/dima/qb/chunkenc"
|
|
||||||
"gordenko.dev/dima/qb/conbuf"
|
|
||||||
"gordenko.dev/dima/qb/freelist"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Формат:
|
|
||||||
metricsQty - varuint
|
|
||||||
[metric]*
|
|
||||||
де metric - це:
|
|
||||||
metricID - 4b
|
|
||||||
metricType - 1b
|
|
||||||
fracDigits - 1b
|
|
||||||
lastPageNo - 4b
|
|
||||||
since - 4b
|
|
||||||
sinceValue - 8b
|
|
||||||
until - 4b
|
|
||||||
untilValue - 8b
|
|
||||||
timestamps size - 2b
|
|
||||||
values size - 2b
|
|
||||||
timestams payload - Nb
|
|
||||||
values payload - Nb
|
|
||||||
dataFreeList size - varsize
|
|
||||||
dataFreeList - Nb
|
|
||||||
indexFreeList size - varsize
|
|
||||||
indexFreeList - Nb
|
|
||||||
CRC32 - 4b
|
|
||||||
*/
|
|
||||||
|
|
||||||
const metricHeaderSize = 38
|
|
||||||
|
|
||||||
func (s *Database) dumpSnapshot(logNumber int) (err error) {
|
|
||||||
var (
|
|
||||||
fileName = filepath.Join(s.dir, fmt.Sprintf("%d.snapshot", logNumber))
|
|
||||||
hasher = crc32.NewIEEE()
|
|
||||||
prefix = make([]byte, metricHeaderSize)
|
|
||||||
)
|
|
||||||
|
|
||||||
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY, 0770)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dst := io.MultiWriter(file, hasher)
|
|
||||||
|
|
||||||
_, err = bin.WriteVarUint64(dst, uint64(len(s.metrics)))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for metricID, metric := range s.metrics {
|
|
||||||
tSize := metric.Timestamps.Size()
|
|
||||||
vSize := metric.Values.Size()
|
|
||||||
|
|
||||||
bin.PutUint32(prefix[0:], metricID)
|
|
||||||
prefix[4] = byte(metric.MetricType)
|
|
||||||
prefix[5] = metric.FracDigits
|
|
||||||
bin.PutUint32(prefix[6:], metric.LastPageNo)
|
|
||||||
bin.PutUint32(prefix[10:], metric.Since)
|
|
||||||
bin.PutFloat64(prefix[14:], metric.SinceValue)
|
|
||||||
bin.PutUint32(prefix[22:], metric.Until)
|
|
||||||
bin.PutFloat64(prefix[26:], metric.UntilValue)
|
|
||||||
bin.PutUint16(prefix[34:], uint16(tSize))
|
|
||||||
bin.PutUint16(prefix[36:], uint16(vSize))
|
|
||||||
|
|
||||||
_, err = dst.Write(prefix)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// copy timestamps
|
|
||||||
writeChunks(dst, metric.Timestamps.Chunks(), tSize)
|
|
||||||
// copy values
|
|
||||||
writeChunks(dst, metric.Values.Chunks(), vSize)
|
|
||||||
|
|
||||||
}
|
|
||||||
// free data pages
|
|
||||||
err = freeListWriteTo(s.freeList, dst)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// free index pages
|
|
||||||
err = freeListWriteTo(s.freeList, dst)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bin.WriteUint32(file, hasher.Sum32())
|
|
||||||
|
|
||||||
err = file.Sync()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = file.Close()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// prevLogNumber := logNumber - 1
|
|
||||||
// prevChanges := filepath.Join(s.dir, fmt.Sprintf("%d.changes", prevLogNumber))
|
|
||||||
// prevSnapshot := filepath.Join(s.dir, fmt.Sprintf("%d.snapshot", prevLogNumber))
|
|
||||||
|
|
||||||
// isExist, err := isFileExist(prevChanges)
|
|
||||||
// if err != nil {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if isExist {
|
|
||||||
// err = os.Remove(prevChanges)
|
|
||||||
// if err != nil {
|
|
||||||
// qb.Abort(qb.DeletePrevChangesFileFailed, err)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// isExist, err = isFileExist(prevSnapshot)
|
|
||||||
// if err != nil {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if isExist {
|
|
||||||
// err = os.Remove(prevSnapshot)
|
|
||||||
// if err != nil {
|
|
||||||
// qb.Abort(qb.DeletePrevSnapshotFileFailed, err)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Database) loadSnapshot(fileName string) (err error) {
|
|
||||||
var (
|
|
||||||
hasher = crc32.NewIEEE()
|
|
||||||
header = make([]byte, metricHeaderSize)
|
|
||||||
body = make([]byte, atree.PageSize)
|
|
||||||
)
|
|
||||||
|
|
||||||
file, err := os.Open(fileName)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
src := io.TeeReader(file, hasher)
|
|
||||||
metricsQty, err := bin.ReadVarSize(src)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for range metricsQty {
|
|
||||||
var metric _metric
|
|
||||||
err = bin.ReadNInto(src, header)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
metricID, _ := bin.GetUint32(header[0:])
|
|
||||||
metric.MetricType = qb.MetricType(header[4])
|
|
||||||
metric.FracDigits = header[5]
|
|
||||||
metric.LastPageNo, _ = bin.GetUint32(header[6:])
|
|
||||||
metric.Since, _ = bin.GetUint32(header[10:])
|
|
||||||
metric.SinceValue, _ = bin.GetFloat64(header[14:])
|
|
||||||
metric.Until, _ = bin.GetUint32(header[22:])
|
|
||||||
metric.UntilValue, _ = bin.GetFloat64(header[26:])
|
|
||||||
tSize, _ := bin.GetUint16(header[34:])
|
|
||||||
vSize, _ := bin.GetUint16(header[36:])
|
|
||||||
|
|
||||||
buf := body[:tSize]
|
|
||||||
err = bin.ReadNInto(src, buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
metric.Timestamps = chunkenc.NewReverseTimeDeltaCompressor(
|
|
||||||
conbuf.NewFromBuffer(buf),
|
|
||||||
int(tSize),
|
|
||||||
)
|
|
||||||
|
|
||||||
buf = body[:vSize]
|
|
||||||
err = bin.ReadNInto(src, buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if metric.MetricType == qb.Cumulative {
|
|
||||||
metric.Values = chunkenc.NewReverseCumulativeDeltaCompressor(
|
|
||||||
conbuf.NewFromBuffer(buf),
|
|
||||||
int(vSize),
|
|
||||||
metric.FracDigits,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
metric.Values = chunkenc.NewReverseInstantDeltaCompressor(
|
|
||||||
conbuf.NewFromBuffer(buf),
|
|
||||||
int(vSize),
|
|
||||||
metric.FracDigits,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
s.metrics[metricID] = &metric
|
|
||||||
}
|
|
||||||
|
|
||||||
err = restoreFreeList(s.freeList, src)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("restore dataFreeList: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = restoreFreeList(s.freeList, src)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("restore indexFreeList: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
calculatedChecksum := hasher.Sum32()
|
|
||||||
|
|
||||||
writtenChecksum, err := bin.ReadUint32(file)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if calculatedChecksum != writtenChecksum {
|
|
||||||
return fmt.Errorf("calculated checksum %d not equal written checksum %d", calculatedChecksum, writtenChecksum)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// HELPERS
|
|
||||||
|
|
||||||
func freeListWriteTo(freeList *freelist.FreeList, dst io.Writer) error {
|
|
||||||
serialized, err := freeList.Serialize()
|
|
||||||
if err != nil {
|
|
||||||
qb.Abort(qb.FailedFreeListSerialize, err)
|
|
||||||
}
|
|
||||||
_, err = bin.WriteVarSize(dst, len(serialized))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = dst.Write(serialized)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func restoreFreeList(freeList *freelist.FreeList, src io.Reader) error {
|
|
||||||
size, err := bin.ReadVarSize(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
serialized, err := bin.ReadN(src, size)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
freeList.Restore(serialized)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeChunks(dst io.Writer, chunks [][]byte, size int) (err error) {
|
|
||||||
remaining := size
|
|
||||||
for _, buf := range chunks {
|
|
||||||
if remaining < len(buf) {
|
|
||||||
buf = buf[:remaining]
|
|
||||||
}
|
|
||||||
_, err = dst.Write(buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
remaining -= len(buf)
|
|
||||||
if remaining == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -3,52 +3,82 @@ package freelist
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"gordenko.dev/dima/qb/bin"
|
"gordenko.dev/dima/qb/bin"
|
||||||
"gordenko.dev/dima/qb/util"
|
"gordenko.dev/dima/qb/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Ідея - я фіксую кількість сторінок записаних в снапшоті.
|
||||||
|
Додаю і видаляю сторінки із основного файла, поки їх кількість > кількості зафіксованих.
|
||||||
|
Якщо pageNumbers видалено і кількість сторінок треба зменшити - нові сторінки пишу в delta файл.
|
||||||
|
При створенні снапшота - сторінки із delta файла записую в основний файл і фіксую в снапшоті
|
||||||
|
їх кількість. PageNumbers із буфера пишу в снапшот.
|
||||||
|
*/
|
||||||
|
|
||||||
const (
|
const (
|
||||||
crcSize = 4
|
crcSize = 4
|
||||||
ptrSize = 4
|
ptrSize = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
type FreeList struct {
|
type FreeList struct {
|
||||||
file *os.File
|
baseFile *os.File
|
||||||
|
deltaFile *os.File
|
||||||
pageSize int
|
pageSize int
|
||||||
pointersOnPage int
|
pointersOnPage int
|
||||||
flushTreshold int // кількість вільних сторінок коли вже треба писати на диск
|
flushTreshold int // кількість вільних сторінок коли вже треба писати на диск
|
||||||
pages int
|
frozenPageCount int
|
||||||
|
basePagesCount int
|
||||||
|
deltaPagesCount int
|
||||||
free []uint32
|
free []uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
PageSize int
|
PageSize int
|
||||||
File *os.File
|
BaseFilePath string
|
||||||
|
DeltaFilePath string
|
||||||
|
FrozenPageCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(opt Options) (*FreeList, error) {
|
func New(opt Options) (*FreeList, error) {
|
||||||
s := &FreeList{
|
s := &FreeList{
|
||||||
file: opt.File,
|
|
||||||
pageSize: opt.PageSize,
|
pageSize: opt.PageSize,
|
||||||
pointersOnPage: (opt.PageSize - crcSize) / ptrSize,
|
pointersOnPage: (opt.PageSize - crcSize) / ptrSize,
|
||||||
|
frozenPageCount: opt.FrozenPageCount,
|
||||||
|
basePagesCount: opt.FrozenPageCount,
|
||||||
}
|
}
|
||||||
s.flushTreshold = int(float64(s.pointersOnPage) * 1.3)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
fileSize := info.Size()
|
s.deltaFile, err = os.OpenFile(opt.DeltaFilePath, os.O_RDWR|os.O_CREATE, 0666)
|
||||||
if fileSize > 0 {
|
if err != nil {
|
||||||
if (fileSize % int64(s.pageSize)) > 0 {
|
return nil, err
|
||||||
return nil, fmt.Errorf("file size %d is not a multiple of page size %d", fileSize, s.pageSize)
|
|
||||||
}
|
|
||||||
s.pages = int(fileSize) / s.pageSize
|
|
||||||
}
|
}
|
||||||
|
// 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
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *FreeList) Pages() int {
|
||||||
|
return s.basePagesCount + s.deltaPagesCount
|
||||||
|
}
|
||||||
|
|
||||||
func (s *FreeList) AddPageNumbers(pageNumbers []uint32) (err error) {
|
func (s *FreeList) AddPageNumbers(pageNumbers []uint32) (err error) {
|
||||||
if len(pageNumbers) == 0 {
|
if len(pageNumbers) == 0 {
|
||||||
return
|
return
|
||||||
@@ -63,11 +93,13 @@ func (s *FreeList) AddPageNumbers(pageNumbers []uint32) (err error) {
|
|||||||
// викидаю номери сторінок, які записав на диск
|
// викидаю номери сторінок, які записав на диск
|
||||||
copy(s.free, s.free[s.pointersOnPage:])
|
copy(s.free, s.free[s.pointersOnPage:])
|
||||||
s.free = s.free[:len(s.free)-s.pointersOnPage]
|
s.free = s.free[:len(s.free)-s.pointersOnPage]
|
||||||
|
fmt.Println("free:", s.free)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FreeList) save() error {
|
func (s *FreeList) save() error {
|
||||||
|
fmt.Println("save:", s.free[:s.pointersOnPage])
|
||||||
buf := make([]byte, s.pageSize)
|
buf := make([]byte, s.pageSize)
|
||||||
i := crcSize
|
i := crcSize
|
||||||
for _, pageNo := range s.free[:s.pointersOnPage] {
|
for _, pageNo := range s.free[:s.pointersOnPage] {
|
||||||
@@ -76,28 +108,49 @@ func (s *FreeList) save() error {
|
|||||||
}
|
}
|
||||||
bin.PutUint32(buf, util.CalcChecksum(buf[crcSize:]))
|
bin.PutUint32(buf, util.CalcChecksum(buf[crcSize:]))
|
||||||
//
|
//
|
||||||
off := int64(s.pages * s.pageSize)
|
var (
|
||||||
n, err := s.file.WriteAt(buf, off)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if n != s.pageSize {
|
if n != s.pageSize {
|
||||||
return fmt.Errorf("written size %d bytes not equal page size %d", 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.pages++
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FreeList) loadLastPage() error {
|
func (s *FreeList) loadLastPage() error {
|
||||||
var (
|
var (
|
||||||
|
file *os.File
|
||||||
|
off int64
|
||||||
buf = make([]byte, s.pageSize)
|
buf = make([]byte, s.pageSize)
|
||||||
off = int64((s.pages - 1) * 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 {
|
if err != nil {
|
||||||
return fmt.Errorf("file.Seek: %s", err)
|
return fmt.Errorf("file.Seek: %s", err)
|
||||||
}
|
}
|
||||||
@@ -107,32 +160,49 @@ func (s *FreeList) loadLastPage() error {
|
|||||||
// check crc
|
// check crc
|
||||||
checksum := bin.GetUint32(buf[0:])
|
checksum := bin.GetUint32(buf[0:])
|
||||||
if util.CalcChecksum(buf[crcSize:]) != checksum {
|
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 {
|
for i := crcSize; i < len(buf); i += ptrSize {
|
||||||
s.free = append(s.free, bin.GetUint32(buf[i:]))
|
s.free = append(s.free, bin.GetUint32(buf[i:]))
|
||||||
}
|
}
|
||||||
s.pages--
|
if s.deltaPagesCount > 0 {
|
||||||
err = s.file.Truncate(int64(s.pages * s.pageSize))
|
s.deltaPagesCount--
|
||||||
|
err = file.Truncate(off)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FreeList) GetPageNumber() (pageNo uint32, err error) {
|
func (s *FreeList) GetPageNumber() (pageNo uint32, err error) {
|
||||||
if len(s.free) == 0 {
|
if len(s.free) == 0 {
|
||||||
if s.pages == 0 {
|
if s.basePagesCount > 0 || s.deltaPagesCount > 0 {
|
||||||
return
|
// якщо немає вільних сторінок у буфері, але є у freelist файлах -
|
||||||
}
|
// завантажую сторінку із диску
|
||||||
err = s.loadLastPage()
|
err = s.loadLastPage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if len(s.free) > 0 {
|
||||||
lastIdx := len(s.free) - 1
|
lastIdx := len(s.free) - 1
|
||||||
pageNo = s.free[lastIdx]
|
pageNo = s.free[lastIdx]
|
||||||
s.free = s.free[:lastIdx]
|
s.free = s.free[:lastIdx]
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +214,7 @@ func (s *FreeList) Restore(serialized []byte) error {
|
|||||||
for i := 0; i < len(serialized); i += ptrSize {
|
for i := 0; i < len(serialized); i += ptrSize {
|
||||||
s.free = append(s.free, bin.GetUint32(serialized[i:]))
|
s.free = append(s.free, bin.GetUint32(serialized[i:]))
|
||||||
}
|
}
|
||||||
|
fmt.Println(s.free)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,3 +226,49 @@ func (s *FreeList) Serialize() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
return w.Bytes(), nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,22 +2,39 @@ package freelist
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createFreeList() (_ *FreeList, err error) {
|
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{
|
return New(Options{
|
||||||
PageSize: 20,
|
PageSize: 20,
|
||||||
File: file,
|
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()
|
freeList, err := createFreeList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -29,16 +46,67 @@ func TestAddToFreeList(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = freeList.Merge()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetFromFreeList(t *testing.T) {
|
// for range len(pageNumbers) + 1 {
|
||||||
|
// pageNo, err := freeList.GetPageNumber()
|
||||||
|
// fmt.Println(pageNo, err)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelta(t *testing.T) {
|
||||||
freeList, err := createFreeList()
|
freeList, err := createFreeList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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()
|
pageNo, err := freeList.GetPageNumber()
|
||||||
fmt.Println(pageNo, err)
|
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
BIN
freelist/test.basefree
Normal file
Binary file not shown.
0
freelist/test.deltafree
Normal file
0
freelist/test.deltafree
Normal file
Binary file not shown.
2
go.mod
2
go.mod
@@ -4,6 +4,6 @@ go 1.24.2
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
gopkg.in/ini.v1 v1.67.1
|
gopkg.in/ini.v1 v1.67.1
|
||||||
gordenko.dev/dima/bin v0.0.0-20251101161620-7fa2d0f2923a
|
gordenko.dev/dima/bin v0.0.0-20260528204801-12b890585248
|
||||||
gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69
|
gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69
|
||||||
)
|
)
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -18,7 +18,7 @@ gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gordenko.dev/dima/bin v0.0.0-20251101161620-7fa2d0f2923a h1:IAJvA+4IxPAUVXqXAIGrlfljoAbbzek4jPgrLwWJm9E=
|
gordenko.dev/dima/bin v0.0.0-20260528204801-12b890585248 h1:APYOkErjDz3hykfcddvmABsexoyKUgdRPU3XiM16F34=
|
||||||
gordenko.dev/dima/bin v0.0.0-20251101161620-7fa2d0f2923a/go.mod h1:/I+9fvRUzXHgXSwGEwOK1mBBM4BJ3AtCmoCZ2upDiwk=
|
gordenko.dev/dima/bin v0.0.0-20260528204801-12b890585248/go.mod h1:/I+9fvRUzXHgXSwGEwOK1mBBM4BJ3AtCmoCZ2upDiwk=
|
||||||
gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69 h1:nyJ3mzTQ46yUeMZCdLyYcs7B5JCS54c67v84miyhq2E=
|
gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69 h1:nyJ3mzTQ46yUeMZCdLyYcs7B5JCS54c67v84miyhq2E=
|
||||||
gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69/go.mod h1:AxgKDktpqBVyIOhIcP+nlCpK+EsJyjN5kPdqyd8euVU=
|
gordenko.dev/dima/pretty v0.0.0-20221225212746-0c27d8c0ac69/go.mod h1:AxgKDktpqBVyIOhIcP+nlCpK+EsJyjN5kPdqyd8euVU=
|
||||||
|
|||||||
9
qb.go
9
qb.go
@@ -31,6 +31,9 @@ type TimestampCompressor interface {
|
|||||||
Renew()
|
Renew()
|
||||||
Lock()
|
Lock()
|
||||||
Unlock()
|
Unlock()
|
||||||
|
CreateDecompressor() TimestampDecompressor
|
||||||
|
Snapshot() ([][]byte, int) // chunks, size
|
||||||
|
Offset() int
|
||||||
//LastTimestamp() uint32
|
//LastTimestamp() uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +46,10 @@ type ValueCompressor interface {
|
|||||||
Renew() // створює новий conbuf, але не чіпає state
|
Renew() // створює новий conbuf, але не чіпає state
|
||||||
Lock()
|
Lock()
|
||||||
Unlock()
|
Unlock()
|
||||||
|
// fracDigits
|
||||||
|
CreateDecompressor(byte) ValueDecompressor
|
||||||
|
Snapshot() ([][]byte, int) // chunks, size
|
||||||
|
Offset() int
|
||||||
//LastValue() float64
|
//LastValue() float64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +73,7 @@ const (
|
|||||||
WrongResultCodeBug AbortCode = 6
|
WrongResultCodeBug AbortCode = 6
|
||||||
RemoveREDOFileFailed AbortCode = 7
|
RemoveREDOFileFailed AbortCode = 7
|
||||||
FailedAtreeRequest AbortCode = 8
|
FailedAtreeRequest AbortCode = 8
|
||||||
|
FailedGetPageNumber AbortCode = 9
|
||||||
UnknownTxLogRecordTypeBug AbortCode = 11
|
UnknownTxLogRecordTypeBug AbortCode = 11
|
||||||
HasTimestampNoValueBug AbortCode = 12
|
HasTimestampNoValueBug AbortCode = 12
|
||||||
NoMetricBug AbortCode = 13
|
NoMetricBug AbortCode = 13
|
||||||
@@ -77,6 +85,7 @@ const (
|
|||||||
FailedFreeListSerialize AbortCode = 19
|
FailedFreeListSerialize AbortCode = 19
|
||||||
UnknownWorkerQueueItemBug AbortCode = 20
|
UnknownWorkerQueueItemBug AbortCode = 20
|
||||||
UnknownMetricWaitQueueItemBug AbortCode = 21
|
UnknownMetricWaitQueueItemBug AbortCode = 21
|
||||||
|
RepeatableLock AbortCode = 22
|
||||||
//
|
//
|
||||||
GetRecoveryRecipeFailed AbortCode = 26
|
GetRecoveryRecipeFailed AbortCode = 26
|
||||||
LoadSnapshotFailed AbortCode = 27
|
LoadSnapshotFailed AbortCode = 27
|
||||||
|
|||||||
122
txlog/snapshot.go
Normal file
122
txlog/snapshot.go
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
package txlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
bin "gordenko.dev/dima/bin/little"
|
||||||
|
"gordenko.dev/dima/qb"
|
||||||
|
"gordenko.dev/dima/qb/atree"
|
||||||
|
"gordenko.dev/dima/qb/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadSnapshot(fileName string) (snapshot Snapshot, err error) {
|
||||||
|
var (
|
||||||
|
hasher = util.NewHasher()
|
||||||
|
header = make([]byte, metricHeaderSize)
|
||||||
|
body = make([]byte, atree.PageSize)
|
||||||
|
)
|
||||||
|
|
||||||
|
file, err := os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
src := io.TeeReader(file, hasher)
|
||||||
|
metricsQty, err := bin.ReadVarSize(src)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for range metricsQty {
|
||||||
|
var metric Metric
|
||||||
|
err = bin.ReadNInto(src, header)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
metric.MetricID, _ = bin.GetUint32(header[0:])
|
||||||
|
metric.MetricType = qb.MetricType(header[4])
|
||||||
|
metric.FracDigits = header[5]
|
||||||
|
metric.LastPageNo, _ = bin.GetUint32(header[6:])
|
||||||
|
metric.Since, _ = bin.GetUint32(header[10:])
|
||||||
|
metric.SinceValue, _ = bin.GetFloat64(header[14:])
|
||||||
|
metric.Until, _ = bin.GetUint32(header[22:])
|
||||||
|
metric.UntilValue, _ = bin.GetFloat64(header[26:])
|
||||||
|
tSize, _ := bin.GetUint16(header[34:])
|
||||||
|
vSize, _ := bin.GetUint16(header[36:])
|
||||||
|
|
||||||
|
buf := body[:tSize]
|
||||||
|
err = bin.ReadNInto(src, buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// FIX
|
||||||
|
// metric.Timestamps = chunkenc.NewReverseTimeDeltaCompressor(
|
||||||
|
// conbuf.NewFromBuffer(buf),
|
||||||
|
// int(tSize),
|
||||||
|
// )
|
||||||
|
|
||||||
|
buf = body[:vSize]
|
||||||
|
err = bin.ReadNInto(src, buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIX
|
||||||
|
// if metric.MetricType == qb.Cumulative {
|
||||||
|
// metric.Values = chunkenc.NewReverseCumulativeDeltaCompressor(
|
||||||
|
// conbuf.NewFromBuffer(buf),
|
||||||
|
// int(vSize),
|
||||||
|
// metric.FracDigits,
|
||||||
|
// )
|
||||||
|
// } else {
|
||||||
|
// metric.Values = chunkenc.NewReverseInstantDeltaCompressor(
|
||||||
|
// conbuf.NewFromBuffer(buf),
|
||||||
|
// int(vSize),
|
||||||
|
// metric.FracDigits,
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// s.metrics[metricID] = &metric
|
||||||
|
}
|
||||||
|
// FIX
|
||||||
|
// err = restoreFreeList(s.dataFreeList, src)
|
||||||
|
// if err != nil {
|
||||||
|
// return fmt.Errorf("restore dataFreeList: %s", err)
|
||||||
|
// }
|
||||||
|
// FIX
|
||||||
|
// err = restoreFreeList(s.indexFreeList, src)
|
||||||
|
// if err != nil {
|
||||||
|
// return fmt.Errorf("restore indexFreeList: %s", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
calculatedChecksum := hasher.Sum32()
|
||||||
|
|
||||||
|
writtenChecksum, err := bin.ReadUint32(file)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if calculatedChecksum != writtenChecksum {
|
||||||
|
err = fmt.Errorf("calculated checksum %d not equal written checksum %d",
|
||||||
|
calculatedChecksum, writtenChecksum)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialized + frozen pages count
|
||||||
|
|
||||||
|
// func restoreFreeList(freeList *freelist.FreeList, src io.Reader) error {
|
||||||
|
// size, err := bin.ReadVarSize(src)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// serialized, err := bin.ReadN(src, size)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// freeList.Restore(serialized)
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
207
txlog/txlog.go
207
txlog/txlog.go
@@ -1,11 +1,13 @@
|
|||||||
package txlog
|
package txlog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
bin "gordenko.dev/dima/bin/little"
|
bin "gordenko.dev/dima/bin/little"
|
||||||
"gordenko.dev/dima/qb"
|
"gordenko.dev/dima/qb"
|
||||||
"gordenko.dev/dima/qb/conbuf"
|
"gordenko.dev/dima/qb/conbuf"
|
||||||
|
"gordenko.dev/dima/qb/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AddedMetric struct {
|
type AddedMetric struct {
|
||||||
@@ -75,6 +77,89 @@ func (s *DeletedMetric) Parse(src io.Reader) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeChunkedPayloadTo(chunks [][]byte, size int, w io.Writer) {
|
||||||
|
bin.WriteVarSize(w, size)
|
||||||
|
for _, chunk := range chunks {
|
||||||
|
if size >= len(chunk) {
|
||||||
|
w.Write(chunk)
|
||||||
|
size -= len(chunk)
|
||||||
|
} else {
|
||||||
|
w.Write(chunk[:size])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readChunkedPayload(src io.Reader) (_ [][]byte, _ int, err error) {
|
||||||
|
var (
|
||||||
|
chunks [][]byte
|
||||||
|
size int
|
||||||
|
remainingSize = size
|
||||||
|
)
|
||||||
|
size, err = bin.ReadVarSize(src)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for remainingSize > 0 {
|
||||||
|
var chunk []byte
|
||||||
|
if remainingSize >= conbuf.ChunkSize {
|
||||||
|
chunk, err = bin.ReadN(src, conbuf.ChunkSize)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chunk = make([]byte, conbuf.ChunkSize)
|
||||||
|
err = bin.ReadNInto(src, chunk[:remainingSize])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chunks = append(chunks, chunk)
|
||||||
|
remainingSize -= conbuf.ChunkSize
|
||||||
|
}
|
||||||
|
return chunks, size, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fix - add changed pages
|
||||||
|
type DeletedMeasures struct {
|
||||||
|
MetricID uint32
|
||||||
|
FreePageNumbers []uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s DeletedMeasures) Pack(w io.Writer) {
|
||||||
|
arr := []byte{
|
||||||
|
CodeDeletedMeasures,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
}
|
||||||
|
bin.PutUint32(arr[1:], s.MetricID)
|
||||||
|
w.Write(arr)
|
||||||
|
bin.WriteVarSize(w, len(s.FreePageNumbers))
|
||||||
|
for _, pageNo := range s.FreePageNumbers {
|
||||||
|
bin.WriteUint32(w, pageNo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DeletedMeasures) Parse(src io.Reader) (err error) {
|
||||||
|
s.MetricID, err = bin.ReadUint32(src)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// free pages
|
||||||
|
qty, err := bin.ReadVarSize(src)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for range qty {
|
||||||
|
var pageNo uint32
|
||||||
|
pageNo, err = bin.ReadUint32(src)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.FreePageNumbers = append(s.FreePageNumbers, pageNo)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// type AppendedMeasure struct {
|
// type AppendedMeasure struct {
|
||||||
// MetricID uint32
|
// MetricID uint32
|
||||||
// Timestamp uint32
|
// Timestamp uint32
|
||||||
@@ -273,85 +358,63 @@ Nb - values payload
|
|||||||
// return nil
|
// return nil
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func writeChunkedPayloadTo(chunks [][]byte, size int, w io.Writer) {
|
// PACK
|
||||||
bin.WriteVarSize(w, size)
|
|
||||||
for _, chunk := range chunks {
|
type PreparedData struct {
|
||||||
if size >= len(chunk) {
|
Packet []byte // пакет для запису в WAL
|
||||||
w.Write(chunk)
|
WriteToIndex []IndexPayloadToWrite
|
||||||
size -= len(chunk)
|
WriteToData []DataPayloadToWrite
|
||||||
} else {
|
ToWorker []any
|
||||||
w.Write(chunk[:size])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readChunkedPayload(src io.Reader) (_ [][]byte, _ int, err error) {
|
func prepareData(input []any) PreparedData {
|
||||||
|
buffer := bytes.NewBuffer(nil)
|
||||||
|
buffer.Write([]byte{
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, // size (max 8 byte)
|
||||||
|
0, 0, 0, 0, // crc32
|
||||||
|
})
|
||||||
|
|
||||||
|
hasher := util.NewHasher()
|
||||||
|
|
||||||
|
w := io.MultiWriter(buffer, hasher)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
chunks [][]byte
|
writeToIndex []IndexPayloadToWrite
|
||||||
size int
|
writeToData []DataPayloadToWrite
|
||||||
remainingSize = size
|
|
||||||
)
|
)
|
||||||
size, err = bin.ReadVarSize(src)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for remainingSize > 0 {
|
|
||||||
var chunk []byte
|
|
||||||
if remainingSize >= conbuf.ChunkSize {
|
|
||||||
chunk, err = bin.ReadN(src, conbuf.ChunkSize)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
chunk = make([]byte, conbuf.ChunkSize)
|
|
||||||
err = bin.ReadNInto(src, chunk[:remainingSize])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chunks = append(chunks, chunk)
|
|
||||||
remainingSize -= conbuf.ChunkSize
|
|
||||||
}
|
|
||||||
return chunks, size, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fix - add changed pages
|
// 1. Пакую всі дані в WAL буфер запису
|
||||||
type DeletedMeasures struct {
|
for _, untyped := range input {
|
||||||
MetricID uint32
|
switch x := untyped.(type) {
|
||||||
FreePageNumbers []uint32
|
case AddedMetric:
|
||||||
|
x.Pack(w)
|
||||||
|
// case DeletedMetric:
|
||||||
|
// if len(x.FreePageNumbers) > 0 {
|
||||||
|
// s.freeList.AddPageNumbers(x.FreePageNumbers)
|
||||||
|
// }
|
||||||
|
case AppendedMeasures:
|
||||||
|
//s.packMeasuresIntoWALBuffer(x)
|
||||||
|
//case DeletedMeasures:
|
||||||
|
//case DeletedMeasuresSince:
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// 2. Додаю розмір пакету і чексуму
|
||||||
|
|
||||||
func (s DeletedMeasures) Pack(w io.Writer) {
|
//s.written += int64(len(packet)) + 12
|
||||||
arr := []byte{
|
|
||||||
CodeDeletedMeasures,
|
|
||||||
0, 0, 0, 0,
|
|
||||||
}
|
|
||||||
bin.PutUint32(arr[1:], s.MetricID)
|
|
||||||
w.Write(arr)
|
|
||||||
bin.WriteVarSize(w, len(s.FreePageNumbers))
|
|
||||||
for _, pageNo := range s.FreePageNumbers {
|
|
||||||
bin.WriteUint32(w, pageNo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DeletedMeasures) Parse(src io.Reader) (err error) {
|
packet := buffer.Bytes()
|
||||||
s.MetricID, err = bin.ReadUint32(src)
|
// write size
|
||||||
if err != nil {
|
payloadSize := len(packet) - 12
|
||||||
return
|
start := 8 - bin.CountVarSize(payloadSize)
|
||||||
|
bin.PutVarSize(packet[start:], payloadSize)
|
||||||
|
// checksum
|
||||||
|
bin.PutUint32(packet[8:], hasher.Sum32())
|
||||||
|
//
|
||||||
|
//totalSize := (12 - start) + payloadSize
|
||||||
|
|
||||||
|
return PreparedData{
|
||||||
|
Packet: packet[start:],
|
||||||
|
WriteToIndex: writeToIndex,
|
||||||
|
WriteToData: writeToData,
|
||||||
}
|
}
|
||||||
// free pages
|
|
||||||
qty, err := bin.ReadVarSize(src)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for range qty {
|
|
||||||
var pageNo uint32
|
|
||||||
pageNo, err = bin.ReadUint32(src)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.FreePageNumbers = append(s.FreePageNumbers, pageNo)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
545
txlog/writer.go
545
txlog/writer.go
@@ -1,11 +1,13 @@
|
|||||||
package txlog
|
package txlog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/crc32"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -13,6 +15,8 @@ import (
|
|||||||
bin "gordenko.dev/dima/bin/little"
|
bin "gordenko.dev/dima/bin/little"
|
||||||
"gordenko.dev/dima/qb"
|
"gordenko.dev/dima/qb"
|
||||||
"gordenko.dev/dima/qb/atree"
|
"gordenko.dev/dima/qb/atree"
|
||||||
|
"gordenko.dev/dima/qb/freelist"
|
||||||
|
"gordenko.dev/dima/qb/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -26,6 +30,8 @@ const (
|
|||||||
filePerm = 0770
|
filePerm = 0770
|
||||||
|
|
||||||
dumpSnapshotAfterNBytes = 1024 * 1024 * 1024 // 1 GB
|
dumpSnapshotAfterNBytes = 1024 * 1024 * 1024 // 1 GB
|
||||||
|
|
||||||
|
writeBufferSize = 4 * 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -46,17 +52,22 @@ func JoinChangesFileName(dir string, logNumber int) string {
|
|||||||
return filepath.Join(dir, fmt.Sprintf("%d.changes", logNumber))
|
return filepath.Join(dir, fmt.Sprintf("%d.changes", logNumber))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MetricsState struct {
|
||||||
|
Metrics []Metric
|
||||||
|
WaitCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
type Changes struct {
|
type Changes struct {
|
||||||
Records []any
|
Records []any
|
||||||
LogNumber int
|
MetricsCh chan MetricsState
|
||||||
ForceSnapshot bool
|
|
||||||
ExitWaitGroup *sync.WaitGroup
|
|
||||||
WaitCh chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Writer struct {
|
type Writer struct {
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
freelist FreeList
|
dataPagesCount uint32
|
||||||
|
indexPagesCount uint32
|
||||||
|
dataFreeList *freelist.FreeList
|
||||||
|
indexFreeList *freelist.FreeList
|
||||||
atree *atree.Atree
|
atree *atree.Atree
|
||||||
logNumber int
|
logNumber int
|
||||||
dir string
|
dir string
|
||||||
@@ -64,13 +75,9 @@ type Writer struct {
|
|||||||
wal *os.File
|
wal *os.File
|
||||||
dataFile *os.File
|
dataFile *os.File
|
||||||
indexFile *os.File
|
indexFile *os.File
|
||||||
dataPage []byte
|
// буфер для складання payload на сторінку, розрахунку CRC32 та інше
|
||||||
indexPage []byte
|
pageBuffer []byte
|
||||||
input []any
|
input []any
|
||||||
//buf *bytes.Buffer
|
|
||||||
//pagesToWrite []atree.PageToWrite
|
|
||||||
workerReqs []any
|
|
||||||
//waitCh chan struct{}
|
|
||||||
appendToWorkerQueue func(any)
|
appendToWorkerQueue func(any)
|
||||||
//lsn uint32
|
//lsn uint32
|
||||||
written int64
|
written int64
|
||||||
@@ -84,7 +91,8 @@ type WriterOptions struct {
|
|||||||
Dir string
|
Dir string
|
||||||
LogNumber int // номер журнала
|
LogNumber int // номер журнала
|
||||||
AppendToWorkerQueue func(any)
|
AppendToWorkerQueue func(any)
|
||||||
FreeList FreeList
|
DataFreeList *freelist.FreeList
|
||||||
|
IndexFreeList *freelist.FreeList
|
||||||
Atree *atree.Atree
|
Atree *atree.Atree
|
||||||
ExitCh chan struct{}
|
ExitCh chan struct{}
|
||||||
WaitGroup *sync.WaitGroup
|
WaitGroup *sync.WaitGroup
|
||||||
@@ -97,8 +105,11 @@ func NewWriter(opt WriterOptions) (*Writer, error) {
|
|||||||
if opt.AppendToWorkerQueue == nil {
|
if opt.AppendToWorkerQueue == nil {
|
||||||
return nil, errors.New("AppendToWorkerQueue option is required")
|
return nil, errors.New("AppendToWorkerQueue option is required")
|
||||||
}
|
}
|
||||||
if opt.FreeList == nil {
|
if opt.DataFreeList == nil {
|
||||||
return nil, errors.New("FreeList option is required")
|
return nil, errors.New("DataFreeList option is required")
|
||||||
|
}
|
||||||
|
if opt.IndexFreeList == nil {
|
||||||
|
return nil, errors.New("IndexFreeList option is required")
|
||||||
}
|
}
|
||||||
if opt.Atree == nil {
|
if opt.Atree == nil {
|
||||||
return nil, errors.New("Atree option is required")
|
return nil, errors.New("Atree option is required")
|
||||||
@@ -113,7 +124,8 @@ func NewWriter(opt WriterOptions) (*Writer, error) {
|
|||||||
s := &Writer{
|
s := &Writer{
|
||||||
dir: opt.Dir,
|
dir: opt.Dir,
|
||||||
appendToWorkerQueue: opt.AppendToWorkerQueue,
|
appendToWorkerQueue: opt.AppendToWorkerQueue,
|
||||||
freelist: opt.FreeList,
|
dataFreeList: opt.DataFreeList,
|
||||||
|
indexFreeList: opt.IndexFreeList,
|
||||||
atree: opt.Atree,
|
atree: opt.Atree,
|
||||||
logNumber: opt.LogNumber,
|
logNumber: opt.LogNumber,
|
||||||
exitCh: opt.ExitCh,
|
exitCh: opt.ExitCh,
|
||||||
@@ -161,53 +173,140 @@ func (s *Writer) Run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Writer) getDataPageNumber() (uint32, bool, error) {
|
||||||
|
pageNo, err := s.dataFreeList.GetPageNumber()
|
||||||
|
if err != nil {
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
if pageNo > 0 {
|
||||||
|
return pageNo, true, nil
|
||||||
|
}
|
||||||
|
if s.dataPagesCount < math.MaxUint32 {
|
||||||
|
s.dataPagesCount++
|
||||||
|
return s.dataPagesCount, false, nil
|
||||||
|
}
|
||||||
|
return 0, false, errors.New("no space")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Writer) getIndexPageNumber() (uint32, bool, error) {
|
||||||
|
pageNo, err := s.indexFreeList.GetPageNumber()
|
||||||
|
if err != nil {
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
if pageNo > 0 {
|
||||||
|
return pageNo, true, nil
|
||||||
|
}
|
||||||
|
if s.indexPagesCount < math.MaxUint32 {
|
||||||
|
s.indexPagesCount++
|
||||||
|
return s.indexPagesCount, false, nil
|
||||||
|
}
|
||||||
|
return 0, false, errors.New("no space")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Writer) packAndWrite() (err error) {
|
func (s *Writer) packAndWrite() (err error) {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
|
//isExited := s.isExited
|
||||||
input := s.input
|
input := s.input
|
||||||
s.input = nil
|
s.input = nil
|
||||||
|
|
||||||
|
// var exitWaitGroup *sync.WaitGroup
|
||||||
|
// if s.isExited {
|
||||||
|
// exitWaitGroup = s.waitGroup
|
||||||
|
// }
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|
||||||
w := bytes.NewBuffer(nil)
|
prepared := prepareData(input)
|
||||||
|
|
||||||
// 1. Пакую всі дані в WAL буфер запису
|
|
||||||
for _, untyped := range input {
|
|
||||||
switch x := untyped.(type) {
|
|
||||||
case AddedMetric:
|
|
||||||
x.Pack(w)
|
|
||||||
// case DeletedMetric:
|
|
||||||
// if len(x.FreePageNumbers) > 0 {
|
|
||||||
// s.freeList.AddPageNumbers(x.FreePageNumbers)
|
|
||||||
// }
|
|
||||||
case AppendedMeasures:
|
|
||||||
//
|
|
||||||
|
|
||||||
s.packMeasuresIntoWALBuffer(x)
|
|
||||||
//case DeletedMeasures:
|
|
||||||
//case DeletedMeasuresSince:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 2. Додаю розмір пакету і чексуму
|
|
||||||
|
|
||||||
// 3. Пишу на диск WAL (append)
|
// 3. Пишу на диск WAL (append)
|
||||||
|
n, err := s.wal.Write(prepared.Packet)
|
||||||
// 4. Пишу в atree сторінки
|
|
||||||
for range 1 {
|
|
||||||
err = s.writePagesToAtree(nil, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if n != len(prepared.Packet) {
|
||||||
|
return fmt.Errorf("written %d != total size %d", n, len(prepared.Packet))
|
||||||
|
}
|
||||||
|
if err = s.wal.Sync(); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Пишу зміни в free list
|
// 4. Пишу в atree сторінки
|
||||||
|
err = s.writePagesToAtree(prepared.WriteToIndex, prepared.WriteToData)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 6. відправляю input - worker-у
|
// 6. відправляю input - worker-у
|
||||||
|
|
||||||
// flush
|
var (
|
||||||
err = s.flush(w)
|
forceSnapshot bool
|
||||||
if err != nil {
|
)
|
||||||
return err
|
|
||||||
|
if s.written > dumpSnapshotAfterNBytes {
|
||||||
|
forceSnapshot = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if isExited && s.written > 0 {
|
||||||
|
// forceSnapshot = true
|
||||||
|
// }
|
||||||
|
|
||||||
|
if forceSnapshot {
|
||||||
|
metricsStateCh := make(chan MetricsState)
|
||||||
|
|
||||||
|
s.appendToWorkerQueue(Changes{
|
||||||
|
Records: prepared.ToWorker,
|
||||||
|
MetricsCh: metricsStateCh,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := s.wal.Close(); err != nil {
|
||||||
|
return fmt.Errorf("close changes file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
state := <-metricsStateCh
|
||||||
|
|
||||||
|
s.logNumber++
|
||||||
|
|
||||||
|
// write snapshot FIX
|
||||||
|
err = writeSnapshot(Snapshot{
|
||||||
|
LogNumber: s.logNumber,
|
||||||
|
Dir: s.dir,
|
||||||
|
WriteBufferSize: writeBufferSize,
|
||||||
|
Metrics: state.Metrics,
|
||||||
|
DataFreeList: s.dataFreeList,
|
||||||
|
IndexFreeList: s.indexFreeList,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("write snapshot file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
s.wal, err = os.OpenFile(
|
||||||
|
JoinChangesFileName(s.dir, s.logNumber),
|
||||||
|
os.O_CREATE|os.O_WRONLY,
|
||||||
|
filePerm,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create new changes file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.written = 0
|
||||||
|
// release Worker
|
||||||
|
close(state.WaitCh)
|
||||||
|
} else {
|
||||||
|
s.appendToWorkerQueue(Changes{
|
||||||
|
Records: prepared.ToWorker,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Якщо потрібен снапшот - відправляю канал із буфером розміру 1,
|
||||||
|
// в який воркер має покласти снапшот отриманий після застосування змін (records).
|
||||||
|
// Після чого Writer створює нові файли snapshot і changes і працює далі.
|
||||||
|
|
||||||
|
// flush
|
||||||
|
// err = s.flush(w)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
// FIX send to worker workerReqs
|
// FIX send to worker workerReqs
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -215,7 +314,6 @@ func (s *Writer) packAndWrite() (err error) {
|
|||||||
func (s *Writer) Append(req any) {
|
func (s *Writer) Append(req any) {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
s.input = append(s.input, req)
|
s.input = append(s.input, req)
|
||||||
s.workerReqs = append(s.workerReqs, req)
|
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@@ -237,87 +335,10 @@ func (s *Writer) Append(req any) {
|
|||||||
// s.waitCh = make(chan struct{})
|
// s.waitCh = make(chan struct{})
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func (s *Writer) flush(w *bytes.Buffer) error {
|
// func (s *Writer) flush(w *bytes.Buffer) error {
|
||||||
s.mutex.Lock()
|
|
||||||
|
|
||||||
//pagesToWrite := s.pagesToWrite
|
// return nil
|
||||||
workerReqs := s.workerReqs
|
|
||||||
isExited := s.isExited
|
|
||||||
|
|
||||||
var exitWaitGroup *sync.WaitGroup
|
|
||||||
if s.isExited {
|
|
||||||
exitWaitGroup = s.waitGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.Len() > packetPrefixSize {
|
|
||||||
//s.lsn++
|
|
||||||
//lsn := s.lsn
|
|
||||||
packet := make([]byte, w.Len())
|
|
||||||
copy(packet, w.Bytes())
|
|
||||||
//s.reset() fix
|
|
||||||
|
|
||||||
s.written += int64(len(packet)) + 12
|
|
||||||
s.mutex.Unlock()
|
|
||||||
|
|
||||||
bin.PutUint32(packet[lengthIdx:], uint32(len(packet)-packetPrefixSize))
|
|
||||||
//bin.PutUint32(packet[lsnIdx:], lsn)
|
|
||||||
bin.PutUint32(packet[checksumIdx:], crc32.ChecksumIEEE(packet[8:]))
|
|
||||||
|
|
||||||
n, err := s.wal.Write(packet)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("TxLog write: %s", err)
|
|
||||||
}
|
|
||||||
if n != len(packet) {
|
|
||||||
return fmt.Errorf("TxLog written %d != packet size %d", n, len(packet))
|
|
||||||
}
|
|
||||||
if err := s.wal.Sync(); err != nil {
|
|
||||||
return fmt.Errorf("TxLog sync: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// err = s.writePagesToAtree(pagesToWrite)
|
|
||||||
// if err != nil {
|
|
||||||
// return fmt.Errorf("TxLog writePagesToAtree: %s", err)
|
|
||||||
// }
|
// }
|
||||||
} else {
|
|
||||||
s.mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
var forceSnapshot bool
|
|
||||||
|
|
||||||
if s.written > dumpSnapshotAfterNBytes {
|
|
||||||
forceSnapshot = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if isExited && s.written > 0 {
|
|
||||||
forceSnapshot = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if forceSnapshot {
|
|
||||||
if err := s.wal.Close(); err != nil {
|
|
||||||
return fmt.Errorf("close changes file: %s", err)
|
|
||||||
}
|
|
||||||
s.logNumber++
|
|
||||||
var err error
|
|
||||||
s.wal, err = os.OpenFile(
|
|
||||||
JoinChangesFileName(s.dir, s.logNumber),
|
|
||||||
os.O_CREATE|os.O_WRONLY,
|
|
||||||
filePerm,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create new changes file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.written = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
s.appendToWorkerQueue(Changes{
|
|
||||||
Records: workerReqs,
|
|
||||||
ForceSnapshot: forceSnapshot,
|
|
||||||
LogNumber: s.logNumber,
|
|
||||||
ExitWaitGroup: exitWaitGroup,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Writer) exit() {
|
func (s *Writer) exit() {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
@@ -329,45 +350,79 @@ func (s *Writer) exit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IndexPayloadToWrite struct {
|
||||||
|
PageNo uint32
|
||||||
|
Payload []byte
|
||||||
|
ZeroLevel bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataPayloadToWrite struct {
|
||||||
|
PrevPageNo uint32
|
||||||
|
Timestamps [][]byte // chunks
|
||||||
|
TimestampsSize int
|
||||||
|
Values [][]byte // chunks
|
||||||
|
ValuesSize int
|
||||||
|
PageNo uint32
|
||||||
|
}
|
||||||
|
|
||||||
// writePagesToAtree - записує сторінки в .data та .index файли
|
// writePagesToAtree - записує сторінки в .data та .index файли
|
||||||
func (s *Writer) writePagesToAtree(levels []*atree.IndexLevel, dataPages []*atree.DataPage) (err error) {
|
func (s *Writer) writePagesToAtree(indexItems []IndexPayloadToWrite, dataItems []DataPayloadToWrite) (err error) {
|
||||||
for _, p := range dataPages {
|
for _, p := range dataItems {
|
||||||
p.Checksum = atree.ChunksToDataPage(s.dataPage, atree.ChunksToDataPageReq{
|
atree.ChunksToDataPage(s.pageBuffer, atree.ChunksToDataPageReq{
|
||||||
PrevPageNo: p.PrevPageNo,
|
PrevPageNo: p.PrevPageNo,
|
||||||
Timestamps: p.Timestamps,
|
Timestamps: p.Timestamps,
|
||||||
TimestampsSize: p.TimestampsSize,
|
TimestampsSize: p.TimestampsSize,
|
||||||
Values: p.Values,
|
Values: p.Values,
|
||||||
ValuesSize: p.ValuesSize,
|
ValuesSize: p.ValuesSize,
|
||||||
})
|
})
|
||||||
off := (p.PageNo - 1) * atree.DataPageSize
|
var (
|
||||||
n, err := s.dataFile.WriteAt(s.dataPage, int64(off))
|
off = (p.PageNo - 1) * atree.DataPageSize
|
||||||
|
n int
|
||||||
|
)
|
||||||
|
n, err = s.dataFile.WriteAt(s.pageBuffer, int64(off))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
if n != atree.DataPageSize {
|
if n != atree.DataPageSize {
|
||||||
return fmt.Errorf("write %d instead of %d", n, atree.DataPageSize)
|
return fmt.Errorf("write %d instead of %d", n, atree.DataPageSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for levelIdx, level := range levels {
|
for _, p := range indexItems {
|
||||||
for _, p := range level.Filled {
|
// if len(p.Payload) > atree.MaxIndexPayload {
|
||||||
// if len(p.Data) != atree.PageSize {
|
// return fmt.Errorf("wrong index payload size: %d", len(p.Payload))
|
||||||
// return fmt.Errorf("wrong page %d size: %d",
|
|
||||||
// p.PageNo, len(p.Data))
|
|
||||||
// }
|
// }
|
||||||
p.Checksum = atree.DataToIndexPage(s.indexPage, p.Data, levelIdx == 0)
|
indexPageBuffer := s.pageBuffer[:atree.IndexPageSize]
|
||||||
off := (p.PageNo - 1) * atree.IndexPageSize
|
atree.DataToIndexPage(indexPageBuffer, p.Payload, p.ZeroLevel)
|
||||||
n, err := s.indexFile.WriteAt(s.indexPage, int64(off))
|
var (
|
||||||
|
off = (p.PageNo - 1) * atree.IndexPageSize
|
||||||
|
n int
|
||||||
|
)
|
||||||
|
n, err = s.indexFile.WriteAt(indexPageBuffer, int64(off))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
if n != atree.IndexPageSize {
|
if n != atree.IndexPageSize {
|
||||||
return fmt.Errorf("write %d instead of %d", n, atree.IndexPageSize)
|
return fmt.Errorf("write %d instead of %d", n, atree.IndexPageSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AppendMeasuresSummary struct {
|
||||||
|
MetricID uint32
|
||||||
|
//TimestampsOffset int // (заповнені одразу)
|
||||||
|
//ValuesOffset int // (заповнені одразу)
|
||||||
|
// Timestamps [][]byte
|
||||||
|
// TimestampsSize int
|
||||||
|
// Values [][]byte
|
||||||
|
// ValuesSize int
|
||||||
|
LastPageNo uint32
|
||||||
|
Index [][]byte
|
||||||
|
ResultCode int
|
||||||
|
WrittenCount int
|
||||||
|
ResultCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
type AppendedMeasures struct {
|
type AppendedMeasures struct {
|
||||||
MetricID uint32
|
MetricID uint32
|
||||||
TimestampsOffset int // (заповнені одразу)
|
TimestampsOffset int // (заповнені одразу)
|
||||||
@@ -378,6 +433,9 @@ type AppendedMeasures struct {
|
|||||||
ValuesSize int
|
ValuesSize int
|
||||||
IndexLevels []*atree.IndexLevel
|
IndexLevels []*atree.IndexLevel
|
||||||
DataPages []*atree.DataPage // fix - prevPageNo for the 1st data page
|
DataPages []*atree.DataPage // fix - prevPageNo for the 1st data page
|
||||||
|
ResultCode int
|
||||||
|
WrittenCount int
|
||||||
|
ResultCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Writer) packMeasuresIntoWALBuffer(req AppendedMeasures) (err error) {
|
func (s *Writer) packMeasuresIntoWALBuffer(req AppendedMeasures) (err error) {
|
||||||
@@ -392,7 +450,7 @@ func (s *Writer) packMeasuresIntoWALBuffer(req AppendedMeasures) (err error) {
|
|||||||
}
|
}
|
||||||
s.w.WriteByte(reused)
|
s.w.WriteByte(reused)
|
||||||
bin.WriteUint32(s.w, p.PrevPageNo)
|
bin.WriteUint32(s.w, p.PrevPageNo)
|
||||||
bin.WriteUint32(s.w, p.Checksum)
|
//bin.WriteUint32(s.w, p.Checksum)
|
||||||
var (
|
var (
|
||||||
timestampsOffset int
|
timestampsOffset int
|
||||||
valuesOffset int
|
valuesOffset int
|
||||||
@@ -430,7 +488,7 @@ func (s *Writer) packMeasuresIntoWALBuffer(req AppendedMeasures) (err error) {
|
|||||||
reused = 1
|
reused = 1
|
||||||
}
|
}
|
||||||
s.w.WriteByte(reused)
|
s.w.WriteByte(reused)
|
||||||
bin.WriteUint32(s.w, p.Checksum)
|
//bin.WriteUint32(s.w, p.Checksum)
|
||||||
records := p.Data
|
records := p.Data
|
||||||
if idx == 0 {
|
if idx == 0 {
|
||||||
records = p.Data[level.Offset:]
|
records = p.Data[level.Offset:]
|
||||||
@@ -502,3 +560,204 @@ func (s *Writer) sendSignal() {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
|
||||||
|
/*
|
||||||
|
Формат:
|
||||||
|
metricsQty - varuint
|
||||||
|
[metric]*
|
||||||
|
де metric - це:
|
||||||
|
metricID - 4b
|
||||||
|
metricType - 1b
|
||||||
|
fracDigits - 1b
|
||||||
|
lastPageNo - 4b
|
||||||
|
since - 4b
|
||||||
|
sinceValue - 8b
|
||||||
|
until - 4b
|
||||||
|
untilValue - 8b
|
||||||
|
timestamps size - 2b
|
||||||
|
values size - 2b
|
||||||
|
timestams payload - Nb
|
||||||
|
values payload - Nb
|
||||||
|
data free list frozen pages - varsize
|
||||||
|
dataFreeList size - varsize
|
||||||
|
dataFreeList - Nb
|
||||||
|
index free list frozen pages - varsize
|
||||||
|
indexFreeList size - varsize
|
||||||
|
indexFreeList - Nb
|
||||||
|
CRC32 - 4b
|
||||||
|
*/
|
||||||
|
|
||||||
|
const metricHeaderSize = 38
|
||||||
|
|
||||||
|
type Metric struct {
|
||||||
|
MetricID uint32
|
||||||
|
MetricType qb.MetricType
|
||||||
|
FracDigits byte
|
||||||
|
LastPageNo uint32
|
||||||
|
Since uint32
|
||||||
|
SinceValue float64
|
||||||
|
Until uint32
|
||||||
|
UntilValue float64
|
||||||
|
Timestamps [][]byte
|
||||||
|
TimestampsSize int
|
||||||
|
Values [][]byte
|
||||||
|
ValuesSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Snapshot struct {
|
||||||
|
LogNumber int
|
||||||
|
Dir string
|
||||||
|
WriteBufferSize int
|
||||||
|
Metrics []Metric
|
||||||
|
IndexFreeList *freelist.FreeList
|
||||||
|
DataFreeList *freelist.FreeList
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeSnapshot(snapshot Snapshot) (err error) {
|
||||||
|
var (
|
||||||
|
fileName = filepath.Join(snapshot.Dir, fmt.Sprintf("%d.snapshot", snapshot.LogNumber))
|
||||||
|
hasher = util.NewHasher()
|
||||||
|
prefix = make([]byte, metricHeaderSize)
|
||||||
|
)
|
||||||
|
|
||||||
|
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY, 0770)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := io.MultiWriter(bufio.NewWriterSize(file, snapshot.WriteBufferSize), hasher)
|
||||||
|
|
||||||
|
_, err = bin.WriteVarSize(dst, len(snapshot.Metrics))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, metric := range snapshot.Metrics {
|
||||||
|
tSize := metric.TimestampsSize
|
||||||
|
vSize := metric.ValuesSize
|
||||||
|
|
||||||
|
bin.PutUint32(prefix[0:], metric.MetricID)
|
||||||
|
prefix[4] = byte(metric.MetricType)
|
||||||
|
prefix[5] = metric.FracDigits
|
||||||
|
bin.PutUint32(prefix[6:], metric.LastPageNo)
|
||||||
|
bin.PutUint32(prefix[10:], metric.Since)
|
||||||
|
bin.PutFloat64(prefix[14:], metric.SinceValue)
|
||||||
|
bin.PutUint32(prefix[22:], metric.Until)
|
||||||
|
bin.PutFloat64(prefix[26:], metric.UntilValue)
|
||||||
|
bin.PutUint16(prefix[34:], uint16(tSize))
|
||||||
|
bin.PutUint16(prefix[36:], uint16(vSize))
|
||||||
|
|
||||||
|
_, err = dst.Write(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// copy timestamps
|
||||||
|
writeChunks(dst, metric.Timestamps, tSize)
|
||||||
|
// copy values
|
||||||
|
writeChunks(dst, metric.Values, vSize)
|
||||||
|
}
|
||||||
|
// free data pages
|
||||||
|
_, err = bin.WriteVarSize(dst, snapshot.DataFreeList.Pages())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = freeListWriteTo(snapshot.DataFreeList, dst)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// free index pages
|
||||||
|
_, err = bin.WriteVarSize(dst, snapshot.IndexFreeList.Pages())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = freeListWriteTo(snapshot.IndexFreeList, dst)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bin.WriteUint32(file, hasher.Sum32())
|
||||||
|
|
||||||
|
err = file.Sync()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// копіюю сторінки із delta файла в base файл і потім роблю Truncate
|
||||||
|
err = snapshot.DataFreeList.Merge() // fix - get frozen pages count + unfilled
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = snapshot.IndexFreeList.Merge()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevLogNumber := logNumber - 1
|
||||||
|
// prevChanges := filepath.Join(s.dir, fmt.Sprintf("%d.changes", prevLogNumber))
|
||||||
|
// prevSnapshot := filepath.Join(s.dir, fmt.Sprintf("%d.snapshot", prevLogNumber))
|
||||||
|
|
||||||
|
// isExist, err := isFileExist(prevChanges)
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if isExist {
|
||||||
|
// err = os.Remove(prevChanges)
|
||||||
|
// if err != nil {
|
||||||
|
// qb.Abort(qb.DeletePrevChangesFileFailed, err)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// isExist, err = isFileExist(prevSnapshot)
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if isExist {
|
||||||
|
// err = os.Remove(prevSnapshot)
|
||||||
|
// if err != nil {
|
||||||
|
// qb.Abort(qb.DeletePrevSnapshotFileFailed, err)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HELPERS
|
||||||
|
|
||||||
|
func freeListWriteTo(freeList *freelist.FreeList, dst io.Writer) error {
|
||||||
|
serialized, err := freeList.Serialize()
|
||||||
|
if err != nil {
|
||||||
|
qb.Abort(qb.FailedFreeListSerialize, err)
|
||||||
|
}
|
||||||
|
_, err = bin.WriteVarSize(dst, len(serialized))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = dst.Write(serialized)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeChunks(dst io.Writer, chunks [][]byte, size int) (err error) {
|
||||||
|
remaining := size
|
||||||
|
for _, buf := range chunks {
|
||||||
|
if remaining < len(buf) {
|
||||||
|
buf = buf[:remaining]
|
||||||
|
}
|
||||||
|
_, err = dst.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remaining -= len(buf)
|
||||||
|
if remaining == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"hash"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,3 +16,7 @@ func CalcChecksum(page []byte) uint32 {
|
|||||||
func IsValidChecksum(page []byte, checksum uint32) bool {
|
func IsValidChecksum(page []byte, checksum uint32) bool {
|
||||||
return crc32.Checksum(page, castagnoliTable) == checksum
|
return crc32.Checksum(page, castagnoliTable) == checksum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewHasher() hash.Hash32 {
|
||||||
|
return crc32.New(castagnoliTable)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user