package atree import ( "os" "sync" ) const ( filePerm = 0770 ) type _page struct { PageNo uint32 Buf []byte ReferenceCount int } type Atree struct { 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{} //writeTasksQueue []WriteTask } type Options struct { Dir string DatabaseName string } func New(opt Options) (*Atree, error) { // 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 } func (s *Atree) Run() { //go s.pageWriter() go s.pageReader() } // FIND func (s *Atree) findDataPage(rootPageNo uint32, timestamp uint32) (uint32, []byte, error) { // 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 } type PathLeg struct { PageNo uint32 Data []byte } type PathToDataPage struct { Legs []PathLeg LastPageNo uint32 } func (s *Atree) FindPathToLastPage(rootPageNo uint32) (_ PathToDataPage, err error) { // 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 } // DELETE type Level struct { PageNo uint32 PageData []byte Idx int ChildQty int } func (s *Atree) GetAllPages(rootPageNo uint32) (_ []uint32, err error) { // var ( // pageNumbers []uint32 // levels []*Level // ) // buf, err := s.fetchIndexPage(rootPageNo) // if err != nil { // return nil, fmt.Errorf("fetchIndexPage(%d): %s", rootPageNo, err) // } // 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 } // 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])) // } //