Files
qb/atree/atree.go

439 lines
13 KiB
Go
Raw Normal View History

2026-02-10 14:02:11 +00:00
package atree
import (
"os"
"sync"
)
const (
filePerm = 0770
)
type _page struct {
PageNo uint32
Buf []byte
ReferenceCount int
}
type Atree struct {
2026-02-23 19:37:05 +00:00
file *os.File
mutex sync.Mutex
allocatedPagesQty uint32
pages map[uint32]*_page
pageWaits map[uint32][]chan readResult
pagesToRead []uint32
readSignalCh chan struct{}
writeSignalCh chan struct{}
2026-05-10 00:59:47 +00:00
//writeTasksQueue []WriteTask
2026-02-10 14:02:11 +00:00
}
type Options struct {
2026-02-23 19:37:05 +00:00
Dir string
DatabaseName string
2026-02-10 14:02:11 +00:00
}
func New(opt Options) (*Atree, error) {
2026-06-10 06:18:45 +03:00
// if opt.Dir == "" {
// return nil, errors.New("Dir option is required")
// }
// if opt.DatabaseName == "" {
// return nil, errors.New("DatabaseName option is required")
// }
// // открываю или создаю dbName.data и dbName.index файлы
// var (
// fileName = filepath.Join(opt.Dir, opt.DatabaseName+".db")
// file *os.File
// allocatedPagesQty uint32
// )
// // При создании data файла сразу создается индекс, поэтому корректное
// // состояние БД: либо оба файла есть, либо ни одного файла нет.
// isDataExist, err := isFileExist(fileName)
// if err != nil {
// return nil, fmt.Errorf("check data file is exist: %s", err)
// }
// if isDataExist {
// file, allocatedPagesQty, err = openFile(fileName, PageSize)
// if err != nil {
// return nil, fmt.Errorf("open data file: %s", err)
// }
// } else {
// // нет файла
// file, err = os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, filePerm)
// if err != nil {
// return nil, err
// }
// }
// tree := &Atree{
// file: file,
// allocatedPagesQty: allocatedPagesQty,
// pages: make(map[uint32]*_page),
// pageWaits: make(map[uint32][]chan readResult),
// readSignalCh: make(chan struct{}, 1),
// writeSignalCh: make(chan struct{}, 1),
// }
// return tree, nil
return nil, nil
2026-02-10 14:02:11 +00:00
}
func (s *Atree) Run() {
2026-05-10 00:59:47 +00:00
//go s.pageWriter()
2026-02-10 14:02:11 +00:00
go s.pageReader()
}
// FIND
func (s *Atree) findDataPage(rootPageNo uint32, timestamp uint32) (uint32, []byte, error) {
2026-06-10 06:18:45 +03:00
// indexPageNo := rootPageNo
// for {
// buf, err := s.fetchIndexPage(indexPageNo)
// if err != nil {
// return 0, nil, fmt.Errorf("fetchIndexPage(%d): %s", indexPageNo, err)
// }
// foundPageNo := findPageNo(buf, timestamp)
// s.releasePage(indexPageNo)
// // fix
// if buf[isLastLevelIdx] == 1 {
// buf, err := s.fetchDataPage(foundPageNo)
// if err != nil {
// return 0, nil, fmt.Errorf("fetchDataPage(%d): %s", foundPageNo, err)
// }
// return foundPageNo, buf, nil
// }
// // вглубь
// indexPageNo = foundPageNo
// }
return 0, nil, nil
2026-02-10 14:02:11 +00:00
}
2026-05-10 00:59:47 +00:00
type PathLeg struct {
2026-02-10 14:02:11 +00:00
PageNo uint32
Data []byte
}
2026-05-10 00:59:47 +00:00
type PathToDataPage struct {
Legs []PathLeg
2026-02-10 14:02:11 +00:00
LastPageNo uint32
}
2026-05-10 00:59:47 +00:00
func (s *Atree) FindPathToLastPage(rootPageNo uint32) (_ PathToDataPage, err error) {
2026-06-10 06:18:45 +03:00
// var (
// pageNo = rootPageNo
// legs []PathLeg
// )
// for {
// var buf []byte
// buf, err = s.fetchIndexPage(pageNo)
// if err != nil {
// err = fmt.Errorf("FetchIndexPage(%d): %s", pageNo, err)
// return
// }
// legs = append(legs, PathLeg{
// PageNo: pageNo,
// Data: buf,
// // childIdx не нужен
// })
// foundPageNo := getLastPageNo(buf)
// // fix
// if buf[isLastLevelIdx] == 1 {
// return PathToDataPage{
// Legs: legs,
// LastPageNo: foundPageNo,
// }, nil
// }
// // вглубь
// pageNo = foundPageNo
// }
return
2026-02-10 14:02:11 +00:00
}
// DELETE
type Level struct {
PageNo uint32
PageData []byte
Idx int
ChildQty int
}
2026-05-10 00:59:47 +00:00
func (s *Atree) GetAllPages(rootPageNo uint32) (_ []uint32, err error) {
2026-06-10 06:18:45 +03:00
// var (
// pageNumbers []uint32
// levels []*Level
// )
// buf, err := s.fetchIndexPage(rootPageNo)
// if err != nil {
// return nil, fmt.Errorf("fetchIndexPage(%d): %s", rootPageNo, err)
2026-05-10 00:59:47 +00:00
// }
2026-06-10 06:18:45 +03:00
// pageNumbers = append(pageNumbers, rootPageNo)
// // if buf[isDataPageNumbersIdx] == 1 {
// // pageNumbers := listPageNumbers(buf)
// // dataPages = append(dataPages, pageNumbers...)
// // s.releasePage(rootPageNo)
// // return PageLists{
// // DataPages: dataPages,
// // IndexPages: indexPages,
// // }, nil
// // }
// childQty, _ := bin.GetUint16(buf[indexRecordsQtyIdx:])
// levels = append(levels, &Level{
// PageNo: rootPageNo,
// PageData: buf,
// Idx: 0,
// ChildQty: int(childQty),
// })
// for {
// if len(levels) == 0 {
// return pageNumbers, nil
// }
// lastIdx := len(levels) - 1
// level := levels[lastIdx]
// if level.Idx < level.ChildQty {
// pageNo := getPageNo(level.PageData, level.Idx)
// level.Idx++
// var buf []byte
// buf, err = s.fetchPage(pageNo)
// if err != nil {
// return nil, fmt.Errorf("fetchPage(%d): %s", pageNo, err)
// }
// pageNumbers = append(pageNumbers, pageNo)
// if buf[pageTypeIdx] == PageTypeData {
// //pageNumbers := listPageNumbers(buf)
// //dataPages = append(dataPages, pageNumbers...)
// s.releasePage(pageNo)
// } else {
// childQty, _ = bin.GetUint16(buf[indexRecordsQtyIdx:])
// levels = append(levels, &Level{
// PageNo: pageNo,
// PageData: buf,
// Idx: 0,
// ChildQty: int(childQty),
// })
// }
// } else {
// s.releasePage(level.PageNo)
// levels = levels[:lastIdx]
// }
// }
return
2026-02-10 14:02:11 +00:00
}
2026-05-31 20:01:28 +00:00
// 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]))
// }
//