234 lines
6.0 KiB
Go
234 lines
6.0 KiB
Go
package txlog
|
||
|
||
import (
|
||
"bytes"
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
|
||
bin "gordenko.dev/dima/bin/little"
|
||
"gordenko.dev/dima/qb/atree"
|
||
)
|
||
|
||
const recordSize = 8
|
||
|
||
type LogWriter struct {
|
||
w *bytes.Buffer
|
||
wal *os.File
|
||
dataFile *os.File
|
||
indexFile *os.File
|
||
dataPage []byte
|
||
indexPage []byte
|
||
}
|
||
|
||
func (s *LogWriter) writePagesToAtree(levels []*atree.IndexLevel, dataPages []*atree.DataPage) (err error) {
|
||
for _, p := range dataPages {
|
||
atree.ChunksToDataPage(s.dataPage, atree.ChunksToDataPageReq{
|
||
PrevPageNo: p.PrevPageNo,
|
||
Timestamps: p.Timestamps,
|
||
TimestampsSize: p.TimestampsSize,
|
||
Values: p.Values,
|
||
ValuesSize: p.ValuesSize,
|
||
})
|
||
off := (p.PageNo - 1) * atree.DataPageSize
|
||
n, err := s.dataFile.WriteAt(s.dataPage, int64(off))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if n != atree.DataPageSize {
|
||
return fmt.Errorf("write %d instead of %d", n, atree.DataPageSize)
|
||
}
|
||
}
|
||
for levelIdx, level := range levels {
|
||
for _, p := range level.Filled {
|
||
// if len(p.Data) != atree.PageSize {
|
||
// return fmt.Errorf("wrong page %d size: %d",
|
||
// p.PageNo, len(p.Data))
|
||
// }
|
||
atree.DataToIndexPage(s.indexPage, p.Data, levelIdx == 0)
|
||
off := (p.PageNo - 1) * atree.IndexPageSize
|
||
n, err := s.indexFile.WriteAt(s.indexPage, int64(off))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if n != atree.IndexPageSize {
|
||
return fmt.Errorf("write %d instead of %d", n, atree.IndexPageSize)
|
||
}
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
type ToWrite 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
|
||
}
|
||
|
||
type DataPage struct {
|
||
PageNo uint32
|
||
Reused bool
|
||
PrevPageNo uint32
|
||
Checksum uint32
|
||
Timestamps []byte
|
||
Values []byte
|
||
}
|
||
|
||
type IndexPage struct {
|
||
PageNo uint32
|
||
Reused bool
|
||
Checksum uint32
|
||
Records []byte
|
||
}
|
||
|
||
type IndexLevel struct {
|
||
Pages []IndexPage
|
||
// offset потрібен тому що дані не додаються в кінець, а перезаписують кілька
|
||
// останніх байтів unfilled даних. Потрібно для коректного recovery.
|
||
// Якщо є Pages, то застосовуються для 1-ї Index сторінки, інакше - до records (unfilled)
|
||
RecordsOffset int
|
||
Records []byte // unfilled
|
||
}
|
||
|
||
type TxAppendedMeasures struct {
|
||
MetricID uint32
|
||
DataPages []DataPage
|
||
// offsets потрібні тому що дані не додаються в кінець, а перезаписують кілька
|
||
// останніх байтів unfilled даних. Потрібно для коректного recovery.
|
||
// Якщо є DataPages, то застосовуються для 1-ї Data сторінки, інакше - до timestamps і values (unfilled)
|
||
TimestampsOffset int
|
||
ValuesOffset int
|
||
Timestamps []byte // unfilled
|
||
Values []byte // unfilled
|
||
IndexLevels []DataPage
|
||
}
|
||
|
||
/*
|
||
Format appended measures:
|
||
1b - tx type
|
||
4b - metricID
|
||
Nb - qty of filled data pages (varsize)
|
||
[
|
||
4b - pageNo
|
||
1b - reused
|
||
4b - prevPageNo
|
||
4b - page crc32
|
||
Nb - timestamps size (varsize)
|
||
Nb - timestamps payload
|
||
Nb - values size (varsize)
|
||
Nb - values payload
|
||
]
|
||
2b - timestamps offset (for the 1st data page only)
|
||
2b - values offset (for the 1st data page only)
|
||
Nb - timestamps size unfilled (varsize)
|
||
Nb - timestamps payload unfilled
|
||
Nb - values size unfilled (varsize)
|
||
Nb - values payload unfilled
|
||
Nb - qty of index levels (varsize)
|
||
[
|
||
Nb - qty of level filled pages (varsize)
|
||
[
|
||
4b - pageNo
|
||
1b - reused
|
||
4b - page crc32
|
||
Nb - records qty (varsize)
|
||
Nb - records payload
|
||
]
|
||
Nb - records offset (for the 1st index page only)
|
||
Nb - records qty unfilled (varsize)
|
||
Nb - records payload unfilled
|
||
]
|
||
*/
|
||
|
||
func (s *LogWriter) writeToWAL(req ToWrite) (err error) {
|
||
// fix write code
|
||
bin.WriteUint32(s.w, req.MetricID)
|
||
bin.WriteVarSize(s.w, len(req.DataPages))
|
||
for idx, p := range req.DataPages {
|
||
bin.WriteUint32(s.w, p.PageNo)
|
||
var reused byte
|
||
if p.IsReused {
|
||
reused = 1
|
||
}
|
||
s.w.WriteByte(reused)
|
||
bin.WriteUint32(s.w, p.PrevPageNo)
|
||
//bin.WriteUint32(s.w, p.CRC32) fix
|
||
var (
|
||
timestampsOffset int
|
||
valuesOffset int
|
||
)
|
||
if idx == 0 {
|
||
timestampsOffset = req.TimestampsOffset
|
||
valuesOffset = req.ValuesOffset
|
||
}
|
||
copyPayloadFromChunks(s.w, p.Timestamps, p.TimestampsSize, timestampsOffset)
|
||
copyPayloadFromChunks(s.w, p.Values, p.ValuesSize, valuesOffset)
|
||
}
|
||
bin.WriteUint16(s.w, uint16(req.TimestampsOffset))
|
||
bin.WriteUint16(s.w, uint16(req.ValuesOffset))
|
||
// fix offset
|
||
copyPayloadFromChunks(s.w, req.Timestamps, req.TimestampsSize, req.TimestampsOffset)
|
||
copyPayloadFromChunks(s.w, req.Values, req.ValuesSize, req.ValuesOffset)
|
||
if len(req.DataPages) == 0 {
|
||
return
|
||
}
|
||
bin.WriteVarSize(s.w, len(req.IndexLevels))
|
||
for _, level := range req.IndexLevels {
|
||
bin.WriteVarSize(s.w, len(level.Filled))
|
||
for idx, p := range level.Filled {
|
||
bin.WriteUint32(s.w, p.PageNo)
|
||
var reused byte
|
||
if p.IsReused {
|
||
reused = 1
|
||
}
|
||
s.w.WriteByte(reused)
|
||
//bin.WriteUint32(s.w, p.CRC32) fix
|
||
records := p.Data
|
||
if idx == 0 {
|
||
records = p.Data[level.Offset:]
|
||
}
|
||
if (len(records) % recordSize) != 0 {
|
||
log.Fatalf("wrong records length: %d", len(records))
|
||
}
|
||
bin.WriteVarSize(s.w, len(records)/recordSize)
|
||
s.w.Write(records)
|
||
}
|
||
if len(level.Filled) > 0 {
|
||
s.w.Write(level.Data)
|
||
} else {
|
||
s.w.Write(level.Data[level.Offset:])
|
||
}
|
||
}
|
||
return
|
||
}
|
||
|
||
const chunkSize = 24
|
||
|
||
func copyPayloadFromChunks(w *bytes.Buffer, chunks [][]byte, size int, offset int) {
|
||
bin.WriteVarSize(w, size)
|
||
var (
|
||
chunkIdx = offset / chunkSize
|
||
byteIdx = offset % chunkSize
|
||
)
|
||
for _, chunk := range chunks[chunkIdx:] {
|
||
available := len(chunk) - byteIdx
|
||
if available <= size {
|
||
w.Write(chunk[byteIdx:])
|
||
size -= available
|
||
if size == 0 {
|
||
return
|
||
}
|
||
byteIdx = 0
|
||
} else {
|
||
w.Write(chunk[byteIdx : byteIdx+size])
|
||
return
|
||
}
|
||
}
|
||
}
|