2019-05-22 21:16:55 +00:00
|
|
|
package mergeset
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/hex"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2022-08-21 20:51:13 +00:00
|
|
|
"os"
|
2023-03-25 20:39:38 +00:00
|
|
|
"path/filepath"
|
2019-05-22 21:16:55 +00:00
|
|
|
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
2023-03-19 08:36:05 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
2019-05-22 21:16:55 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type partHeader struct {
|
|
|
|
// The number of items the part contains.
|
|
|
|
itemsCount uint64
|
|
|
|
|
|
|
|
// The number of blocks the part contains.
|
|
|
|
blocksCount uint64
|
|
|
|
|
|
|
|
// The first item in the part.
|
|
|
|
firstItem []byte
|
|
|
|
|
|
|
|
// The last item in the part.
|
|
|
|
lastItem []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
type partHeaderJSON struct {
|
|
|
|
ItemsCount uint64
|
|
|
|
BlocksCount uint64
|
|
|
|
FirstItem hexString
|
|
|
|
LastItem hexString
|
|
|
|
}
|
|
|
|
|
|
|
|
type hexString []byte
|
|
|
|
|
|
|
|
func (hs hexString) MarshalJSON() ([]byte, error) {
|
|
|
|
h := hex.EncodeToString(hs)
|
|
|
|
b := make([]byte, 0, len(h)+2)
|
|
|
|
b = append(b, '"')
|
|
|
|
b = append(b, h...)
|
|
|
|
b = append(b, '"')
|
|
|
|
return b, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (hs *hexString) UnmarshalJSON(data []byte) error {
|
|
|
|
if len(data) < 2 {
|
|
|
|
return fmt.Errorf("too small data string: got %q; must be at least 2 bytes", data)
|
|
|
|
}
|
|
|
|
if data[0] != '"' || data[len(data)-1] != '"' {
|
|
|
|
return fmt.Errorf("missing heading and/or tailing quotes in the data string %q", data)
|
|
|
|
}
|
|
|
|
data = data[1 : len(data)-1]
|
|
|
|
b, err := hex.DecodeString(string(data))
|
|
|
|
if err != nil {
|
2020-06-30 19:58:18 +00:00
|
|
|
return fmt.Errorf("cannot hex-decode %q: %w", data, err)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
*hs = b
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ph *partHeader) Reset() {
|
|
|
|
ph.itemsCount = 0
|
|
|
|
ph.blocksCount = 0
|
|
|
|
ph.firstItem = ph.firstItem[:0]
|
|
|
|
ph.lastItem = ph.lastItem[:0]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ph *partHeader) String() string {
|
|
|
|
return fmt.Sprintf("partHeader{itemsCount: %d, blocksCount: %d, firstItem: %X, lastItem: %X}",
|
|
|
|
ph.itemsCount, ph.blocksCount, ph.firstItem, ph.lastItem)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ph *partHeader) CopyFrom(src *partHeader) {
|
|
|
|
ph.itemsCount = src.itemsCount
|
|
|
|
ph.blocksCount = src.blocksCount
|
|
|
|
ph.firstItem = append(ph.firstItem[:0], src.firstItem...)
|
|
|
|
ph.lastItem = append(ph.lastItem[:0], src.lastItem...)
|
|
|
|
}
|
|
|
|
|
2023-03-19 08:36:05 +00:00
|
|
|
func (ph *partHeader) ReadMetadata(partPath string) error {
|
2019-05-22 21:16:55 +00:00
|
|
|
ph.Reset()
|
|
|
|
|
2023-03-19 08:36:05 +00:00
|
|
|
// Read ph fields from metadata.
|
2023-03-25 20:39:38 +00:00
|
|
|
metadataPath := filepath.Join(partPath, metadataFilename)
|
2022-08-21 20:51:13 +00:00
|
|
|
metadata, err := os.ReadFile(metadataPath)
|
2019-05-22 21:16:55 +00:00
|
|
|
if err != nil {
|
2020-06-30 19:58:18 +00:00
|
|
|
return fmt.Errorf("cannot read %q: %w", metadataPath, err)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var phj partHeaderJSON
|
|
|
|
if err := json.Unmarshal(metadata, &phj); err != nil {
|
2020-06-30 19:58:18 +00:00
|
|
|
return fmt.Errorf("cannot parse %q: %w", metadataPath, err)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
2023-03-19 08:36:05 +00:00
|
|
|
|
|
|
|
if phj.ItemsCount <= 0 {
|
|
|
|
return fmt.Errorf("part %q cannot contain zero items", partPath)
|
|
|
|
}
|
|
|
|
ph.itemsCount = phj.ItemsCount
|
|
|
|
|
|
|
|
if phj.BlocksCount <= 0 {
|
|
|
|
return fmt.Errorf("part %q cannot contain zero blocks", partPath)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
2023-03-19 08:36:05 +00:00
|
|
|
if phj.BlocksCount > phj.ItemsCount {
|
|
|
|
return fmt.Errorf("the number of blocks cannot exceed the number of items in the part %q; got blocksCount=%d, itemsCount=%d",
|
|
|
|
partPath, phj.BlocksCount, phj.ItemsCount)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
2023-03-19 08:36:05 +00:00
|
|
|
ph.blocksCount = phj.BlocksCount
|
2019-05-22 21:16:55 +00:00
|
|
|
|
|
|
|
ph.firstItem = append(ph.firstItem[:0], phj.FirstItem...)
|
|
|
|
ph.lastItem = append(ph.lastItem[:0], phj.LastItem...)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-04-14 04:33:15 +00:00
|
|
|
func (ph *partHeader) MustWriteMetadata(partPath string) {
|
2019-05-22 21:16:55 +00:00
|
|
|
phj := &partHeaderJSON{
|
|
|
|
ItemsCount: ph.itemsCount,
|
|
|
|
BlocksCount: ph.blocksCount,
|
|
|
|
FirstItem: append([]byte{}, ph.firstItem...),
|
|
|
|
LastItem: append([]byte{}, ph.lastItem...),
|
|
|
|
}
|
2023-03-19 08:36:05 +00:00
|
|
|
metadata, err := json.Marshal(&phj)
|
2019-05-22 21:16:55 +00:00
|
|
|
if err != nil {
|
2023-03-19 08:36:05 +00:00
|
|
|
logger.Panicf("BUG: cannot marshal partHeader metadata: %s", err)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|
2023-03-25 20:39:38 +00:00
|
|
|
metadataPath := filepath.Join(partPath, metadataFilename)
|
2023-04-14 05:41:12 +00:00
|
|
|
// There is no need in calling fs.MustWriteAtomic() here,
|
2023-04-14 04:03:06 +00:00
|
|
|
// since the file is created only once during part creatinng
|
|
|
|
// and the part directory is synced aftewards.
|
2023-04-14 04:42:11 +00:00
|
|
|
fs.MustWriteSync(metadataPath, metadata)
|
2019-05-22 21:16:55 +00:00
|
|
|
}
|