mirror of
https://github.com/cubefs/cubefs.git
synced 2026-08-02 02:00:56 +00:00
1848 lines
54 KiB
Go
1848 lines
54 KiB
Go
// Copyright 2018 The CubeFS Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
// implied. See the License for the specific language governing
|
|
// permissions and limitations under the License.
|
|
|
|
package storage
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"hash/crc32"
|
|
"io"
|
|
"io/fs"
|
|
"math/rand"
|
|
"os"
|
|
"path"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/cubefs/cubefs/proto"
|
|
"github.com/cubefs/cubefs/util"
|
|
"github.com/cubefs/cubefs/util/errors"
|
|
"github.com/cubefs/cubefs/util/fileutil"
|
|
"github.com/cubefs/cubefs/util/log"
|
|
"github.com/cubefs/cubefs/util/stat"
|
|
"github.com/cubefs/cubefs/util/strutil"
|
|
)
|
|
|
|
const (
|
|
ExtCrcHeaderFileName = "EXTENT_CRC"
|
|
ExtBaseExtentIDFileName = "EXTENT_META"
|
|
TinyDeleteFileOpt = os.O_CREATE | os.O_RDWR | os.O_APPEND
|
|
TinyExtDeletedFileName = "TINYEXTENT_DELETE"
|
|
NormalExtDeletedFileName = "NORMALEXTENT_DELETE"
|
|
MaxExtentCount = 20000
|
|
TinyExtentCount = 64
|
|
TinyExtentStartID = 1
|
|
MinExtentID = 1024
|
|
DeleteTinyRecordSize = 24
|
|
UpdateCrcInterval = 600
|
|
RepairInterval = 60
|
|
RandomWriteType = 2
|
|
AppendWriteType = 1
|
|
AppendRandomWriteType = 4
|
|
MaxExtentID = 1 << 30
|
|
|
|
NormalExtentDeleteRetainTime = 3600 * 4
|
|
CacheFlushMinInterval = 5 * time.Minute
|
|
CacheFlushMaxInterval = 15 * time.Minute
|
|
ExtentReadDirHint = "READDIR_HINT"
|
|
ExtentReadDirHintV2 = "READDIR_HINT_V2"
|
|
ExtentReadDirHintTemp = "READDIR_HINT.tmp"
|
|
|
|
StaleExtStoreBackupSuffix = ".old"
|
|
StaleExtStoreTimeFormat = "20060102150405.000000000"
|
|
)
|
|
|
|
var ErrStoreAlreadyClosed = errors.New("extent store already closed")
|
|
|
|
var (
|
|
RegexpExtentFile, _ = regexp.Compile(`^(\d)+$`)
|
|
SnapShotFilePool = &sync.Pool{New: func() interface{} {
|
|
return new(proto.File)
|
|
}}
|
|
emptyHeader = make([]byte, util.BlockHeaderSize)
|
|
)
|
|
|
|
func GetSnapShotFileFromPool() (f *proto.File) {
|
|
f = SnapShotFilePool.Get().(*proto.File)
|
|
return
|
|
}
|
|
|
|
func PutSnapShotFileToPool(f *proto.File) {
|
|
SnapShotFilePool.Put(f)
|
|
}
|
|
|
|
type ExtentFilter func(info *ExtentInfo) bool
|
|
|
|
// Filters
|
|
var (
|
|
NormalExtentFilter = func() ExtentFilter {
|
|
now := time.Now()
|
|
return func(ei *ExtentInfo) bool {
|
|
return !IsTinyExtent(ei.FileID) && now.Unix()-ei.ModifyTime > RepairInterval && !ei.IsDeleted
|
|
}
|
|
}
|
|
|
|
TinyExtentFilter = func(filters []uint64) ExtentFilter {
|
|
return func(ei *ExtentInfo) bool {
|
|
if !IsTinyExtent(ei.FileID) {
|
|
return false
|
|
}
|
|
for _, filterID := range filters {
|
|
if filterID == ei.FileID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
)
|
|
|
|
// ExtentStore defines fields used in the storage engine.
|
|
// Packets smaller than 128K are stored in the "tinyExtent", a place to persist the small files.
|
|
// packets larger than or equal to 128K are stored in the normal "extent", a place to persist large files.
|
|
// The difference between them is that the extentID of a tinyExtent starts at 5000000 and ends at 5000128.
|
|
// Multiple small files can be appended to the same tinyExtent.
|
|
// In addition, the deletion of small files is implemented by the punch hole from the underlying file system.
|
|
type ExtentStore struct {
|
|
dataPath string
|
|
baseExtentID uint64 // TODO what is baseExtentID
|
|
extentInfoMap map[uint64]*ExtentInfo // map that stores all the extent information
|
|
eiMutex sync.RWMutex
|
|
cache *ExtentCache // extent cache
|
|
mutex sync.Mutex
|
|
storeSize int // size of the extent store
|
|
metadataFp *os.File // metadata file pointer?
|
|
tinyExtentDeleteFp *os.File
|
|
normalExtentDeleteFp *os.File
|
|
closed int32
|
|
availableTinyExtentC chan uint64 // available tinyExtent channel
|
|
availableTinyExtentMap sync.Map
|
|
brokenTinyExtentC chan uint64 // broken tinyExtent channel
|
|
brokenTinyExtentMap sync.Map
|
|
// blockSize int
|
|
partitionID uint64
|
|
verifyExtentFp *os.File
|
|
|
|
verifyExtentFpAppend []*os.File
|
|
hasAllocSpaceExtentIDOnVerfiyFile uint64
|
|
hasDeleteNormalExtentsCache sync.Map
|
|
partitionType int
|
|
extentLockMap map[uint64]proto.GcFlag
|
|
elMutex sync.RWMutex
|
|
extentLock bool
|
|
stopMutex sync.RWMutex
|
|
stopC chan interface{}
|
|
ApplyId uint64
|
|
DirectRead bool
|
|
IgnoreTinyRecover bool
|
|
IsEnableSnapshot bool
|
|
extIDLock sync.Mutex
|
|
}
|
|
|
|
func MkdirAll(name string) (err error) {
|
|
return os.MkdirAll(name, 0o755)
|
|
}
|
|
|
|
func NewExtentStore(dataDir string, partitionID uint64, storeSize, dpType, cap int, isCreate bool) (s *ExtentStore, err error) {
|
|
begin := time.Now()
|
|
defer func() {
|
|
log.LogInfof("[NewExtentStore] load dp(%v) new extent store using time(%v)", partitionID, time.Since(begin))
|
|
}()
|
|
s = new(ExtentStore)
|
|
s.dataPath = dataDir
|
|
s.partitionType = dpType
|
|
s.partitionID = partitionID
|
|
|
|
if isCreate {
|
|
if err = s.renameStaleExtentStore(); err != nil {
|
|
return
|
|
}
|
|
if err = MkdirAll(dataDir); err != nil {
|
|
return nil, fmt.Errorf("NewExtentStore [%v] err[%v]", dataDir, err)
|
|
}
|
|
|
|
if s.tinyExtentDeleteFp, err = os.OpenFile(path.Join(s.dataPath, TinyExtDeletedFileName), TinyDeleteFileOpt, 0o666); err != nil {
|
|
return
|
|
}
|
|
if s.verifyExtentFp, err = os.OpenFile(path.Join(s.dataPath, ExtCrcHeaderFileName), os.O_CREATE|os.O_RDWR, 0o666); err != nil {
|
|
return
|
|
}
|
|
if s.metadataFp, err = os.OpenFile(path.Join(s.dataPath, ExtBaseExtentIDFileName), os.O_CREATE|os.O_RDWR, 0o666); err != nil {
|
|
return
|
|
}
|
|
if s.normalExtentDeleteFp, err = os.OpenFile(path.Join(s.dataPath, NormalExtDeletedFileName), os.O_CREATE|os.O_RDWR|os.O_APPEND, 0o666); err != nil {
|
|
return
|
|
}
|
|
} else {
|
|
if err = MkdirAll(dataDir); err != nil {
|
|
return nil, fmt.Errorf("NewExtentStore [%v] err[%v]", dataDir, err)
|
|
}
|
|
if s.tinyExtentDeleteFp, err = os.OpenFile(path.Join(s.dataPath, TinyExtDeletedFileName), os.O_RDWR|os.O_APPEND, 0o666); err != nil {
|
|
return
|
|
}
|
|
if s.verifyExtentFp, err = os.OpenFile(path.Join(s.dataPath, ExtCrcHeaderFileName), os.O_RDWR, 0o666); err != nil {
|
|
return
|
|
}
|
|
if s.metadataFp, err = os.OpenFile(path.Join(s.dataPath, ExtBaseExtentIDFileName), os.O_RDWR, 0o666); err != nil {
|
|
return
|
|
}
|
|
if s.normalExtentDeleteFp, err = os.OpenFile(path.Join(s.dataPath, NormalExtDeletedFileName), os.O_RDWR|os.O_APPEND, 0o666); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
aId := 0
|
|
var vFp *os.File
|
|
for {
|
|
dataPath := path.Join(s.dataPath, ExtCrcHeaderFileName+"_"+strconv.Itoa(aId))
|
|
if _, err = os.Stat(dataPath); err != nil {
|
|
log.LogDebugf("NewExtentStore. partitionID [%v] dataPath not exist err %v. verifyExtentFpAppend init return", partitionID, err)
|
|
break
|
|
}
|
|
if vFp, err = os.OpenFile(dataPath, os.O_CREATE|os.O_RDWR, 0o666); err != nil {
|
|
log.LogErrorf("NewExtentStore. partitionID [%v] dataPath exist but open err %v. verifyExtentFpAppend init return", partitionID, err)
|
|
return
|
|
}
|
|
log.LogDebugf("NewExtentStore. partitionID [%v] dataPath exist and opened id %v", partitionID, aId)
|
|
s.verifyExtentFpAppend = append(s.verifyExtentFpAppend, vFp)
|
|
aId++
|
|
}
|
|
|
|
stat, err := s.tinyExtentDeleteFp.Stat()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if stat.Size()%DeleteTinyRecordSize != 0 {
|
|
needWriteEmpty := DeleteTinyRecordSize - (stat.Size() % DeleteTinyRecordSize)
|
|
data := make([]byte, needWriteEmpty)
|
|
s.tinyExtentDeleteFp.Write(data)
|
|
log.LogInfof("[NewExtentStore] load dp(%v) write zero buffer", partitionID)
|
|
}
|
|
|
|
s.extentInfoMap = make(map[uint64]*ExtentInfo)
|
|
s.extentLockMap = make(map[uint64]proto.GcFlag)
|
|
s.cache = NewExtentCache(cap)
|
|
if err = s.initBaseFileID(); err != nil {
|
|
err = fmt.Errorf("init base field ID: %v", err)
|
|
return
|
|
}
|
|
s.hasAllocSpaceExtentIDOnVerfiyFile = s.GetPreAllocSpaceExtentIDOnVerifyFile()
|
|
s.storeSize = storeSize
|
|
s.closed = 0
|
|
err = s.initTinyExtent()
|
|
if err != nil {
|
|
return
|
|
}
|
|
s.stopC = make(chan interface{})
|
|
go func() {
|
|
time.Sleep(15 * time.Minute)
|
|
s.startFlushCache()
|
|
}()
|
|
return
|
|
}
|
|
|
|
func (ei *ExtentInfo) UpdateExtentInfo(extent *Extent, crc uint32) {
|
|
extent.Lock()
|
|
defer extent.Unlock()
|
|
|
|
if time.Now().Unix()-extent.ModifyTime() <= UpdateCrcInterval {
|
|
crc = 0
|
|
}
|
|
|
|
ei.Size = uint64(extent.dataSize)
|
|
ei.SnapshotDataOff = extent.snapshotDataOff
|
|
|
|
log.LogDebugf("action[ExtentInfo.UpdateExtentInfo] ei info [%v]", ei.String())
|
|
|
|
if !IsTinyExtent(ei.FileID) {
|
|
atomic.StoreUint32(&ei.Crc, crc)
|
|
ei.ModifyTime = extent.ModifyTime()
|
|
}
|
|
}
|
|
|
|
func (s *ExtentStore) SetDirectRead(enable bool) {
|
|
if s.DirectRead != enable {
|
|
log.LogWarnf("SetDirectRead: update direct info, new %v, old %v, id %d",
|
|
enable, s.DirectRead, s.partitionID)
|
|
}
|
|
s.DirectRead = enable
|
|
}
|
|
|
|
func (s *ExtentStore) SetIgnoreTinyRecover(enable bool) {
|
|
if s.IgnoreTinyRecover != enable {
|
|
log.LogWarnf("SetTinyRecover: update checkTinyRecover, new %v, old %v, id %d",
|
|
enable, s.IgnoreTinyRecover, s.partitionID)
|
|
}
|
|
s.IgnoreTinyRecover = enable
|
|
}
|
|
|
|
// SnapShot returns the information of all the extents on the current data partition.
|
|
// When the master sends the loadDataPartition request, the snapshot is used to compare the replicas.
|
|
func (s *ExtentStore) SnapShot() (files []*proto.File, err error) {
|
|
var normalExtentSnapshot, tinyExtentSnapshot []*ExtentInfo
|
|
if normalExtentSnapshot, _, err = s.GetAllWatermarks(NormalExtentFilter()); err != nil {
|
|
log.LogErrorf("SnapShot GetAllWatermarks err %v", err)
|
|
return
|
|
}
|
|
|
|
files = make([]*proto.File, 0, len(normalExtentSnapshot))
|
|
for _, ei := range normalExtentSnapshot {
|
|
file := GetSnapShotFileFromPool()
|
|
file.Name = strconv.FormatUint(ei.FileID, 10)
|
|
|
|
file.Size = uint32(ei.Size)
|
|
|
|
file.Modified = ei.ModifyTime
|
|
file.Crc = atomic.LoadUint32(&ei.Crc)
|
|
file.ApplyID = ei.ApplyID
|
|
log.LogDebugf("partitionID %v ExtentStore set applyid %v partition %v", s.partitionID, s.ApplyId, s.partitionID)
|
|
files = append(files, file)
|
|
}
|
|
tinyExtentSnapshot = s.getTinyExtentInfo()
|
|
for _, ei := range tinyExtentSnapshot {
|
|
file := GetSnapShotFileFromPool()
|
|
file.Name = strconv.FormatUint(ei.FileID, 10)
|
|
file.Size = uint32(ei.Size)
|
|
file.Modified = ei.ModifyTime
|
|
file.Crc = 0
|
|
files = append(files, file)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) startFlushCache() {
|
|
// NOTE: flush extents in cache
|
|
randGen := rand.New(rand.NewSource(time.Now().UnixMicro()))
|
|
for {
|
|
select {
|
|
case <-s.stopC:
|
|
return
|
|
default:
|
|
log.LogInfof("[startFlushCache] flush extent cache")
|
|
s.cache.CopyAndFlush(30 * time.Second)
|
|
}
|
|
tmp := randGen.Int63n(int64(CacheFlushMaxInterval - CacheFlushMinInterval))
|
|
randSleep := time.Duration(tmp) + CacheFlushMinInterval
|
|
log.LogDebugf("[startFlushCache] startFlushCache sleep time(%v)", randSleep)
|
|
time.Sleep(randSleep)
|
|
}
|
|
}
|
|
|
|
func (s *ExtentStore) IsClosed() (closed bool) {
|
|
closed = atomic.LoadInt32(&s.closed) == 1
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) setClosed(v bool) {
|
|
closed := int32(0)
|
|
if v {
|
|
closed = 1
|
|
}
|
|
atomic.StoreInt32(&s.closed, closed)
|
|
}
|
|
|
|
// Create creates an extent.
|
|
func (s *ExtentStore) Create(extentID uint64) (err error) {
|
|
s.stopMutex.RLock()
|
|
defer s.stopMutex.RUnlock()
|
|
|
|
if s.IsClosed() {
|
|
err = ErrStoreAlreadyClosed
|
|
log.LogErrorf("[Create] store(%v) failed to create extent(%v), err(%v)", s.dataPath, extentID, err)
|
|
return
|
|
}
|
|
|
|
var e *Extent
|
|
name := path.Join(s.dataPath, strconv.Itoa(int(extentID)))
|
|
if s.HasExtent(extentID) {
|
|
err = ExtentExistsError
|
|
return err
|
|
}
|
|
|
|
stat.RecordStat(s.partitionID, "Create", s.dataPath)
|
|
|
|
e = NewExtentInCore(name, extentID)
|
|
e.header = make([]byte, util.BlockHeaderSize)
|
|
err = e.InitToFS()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.cache.Put(e)
|
|
extInfo := &ExtentInfo{FileID: extentID}
|
|
extInfo.UpdateExtentInfo(e, 0)
|
|
|
|
s.eiMutex.Lock()
|
|
s.extentInfoMap[extentID] = extInfo
|
|
s.eiMutex.Unlock()
|
|
|
|
s.UpdateBaseExtentID(extentID)
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) GetExtentInfoFromDisk(id uint64) (ei *ExtentInfo, err error) {
|
|
retry := 0
|
|
const maxRetry = 3
|
|
|
|
for retry < maxRetry {
|
|
var stat fs.FileInfo
|
|
name := path.Join(s.dataPath, fmt.Sprint(id))
|
|
stat, err = os.Stat(name)
|
|
if err != nil {
|
|
retry++
|
|
continue
|
|
}
|
|
|
|
ei = &ExtentInfo{
|
|
FileID: id,
|
|
Size: uint64(stat.Size()),
|
|
Crc: 0,
|
|
IsDeleted: false,
|
|
ModifyTime: stat.ModTime().Unix(),
|
|
SnapshotDataOff: util.ExtentSize,
|
|
}
|
|
if IsTinyExtent(id) {
|
|
watermark := ei.Size
|
|
if watermark%util.PageSize != 0 {
|
|
watermark = watermark + (util.PageSize - watermark%util.PageSize)
|
|
}
|
|
ei.Size = watermark
|
|
}
|
|
// NOTE: init snapshot offset
|
|
if !IsTinyExtent(id) {
|
|
if stat.Size() > int64(ei.SnapshotDataOff) {
|
|
ei.SnapshotDataOff = uint64(stat.Size())
|
|
}
|
|
}
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) GetExtentInfo(id uint64) (ei *ExtentInfo, ok bool) {
|
|
s.eiMutex.RLock()
|
|
defer s.eiMutex.RUnlock()
|
|
ei, ok = s.extentInfoMap[id]
|
|
return
|
|
}
|
|
|
|
// func (s *ExtentStore) SetExtentInfo(id uint64, ei *ExtentInfo) {
|
|
// s.eiMutex.Lock()
|
|
// defer s.eiMutex.Unlock()
|
|
// s.extentInfoMap[id] = ei
|
|
// }
|
|
|
|
func (s *ExtentStore) RangeExtentInfo(iter func(id uint64, ei *ExtentInfo) (ok bool, err error)) (err error) {
|
|
s.eiMutex.RLock()
|
|
defer s.eiMutex.RUnlock()
|
|
|
|
var ok bool
|
|
for id, v := range s.extentInfoMap {
|
|
ok, err = iter(id, v)
|
|
if err != nil || !ok {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) DeleteExtentInfo(id uint64) {
|
|
s.eiMutex.Lock()
|
|
defer s.eiMutex.Unlock()
|
|
|
|
stat.RecordStat(s.partitionID, "DeleteExtentInfo", s.dataPath)
|
|
delete(s.extentInfoMap, id)
|
|
}
|
|
|
|
func (s *ExtentStore) GetExtentInfoCount() (count int) {
|
|
s.eiMutex.RLock()
|
|
defer s.eiMutex.RUnlock()
|
|
count = len(s.extentInfoMap)
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) writeReadDirHint() (err error) {
|
|
begin := time.Now()
|
|
defer func() {
|
|
log.LogInfof("[writeReadDirHint] store(%v) write dir hint extent cnt(%v) using time(%v), slow(%v)", s.dataPath, s.GetExtentCount(), time.Since(begin), time.Since(begin) > 100*time.Millisecond)
|
|
}()
|
|
|
|
hintTempPath := path.Join(s.dataPath, ExtentReadDirHintTemp)
|
|
hintPath := path.Join(s.dataPath, ExtentReadDirHintV2)
|
|
buff := bytes.NewBuffer([]byte{})
|
|
err = s.RangeExtentInfo(func(id uint64, ei *ExtentInfo) (ok bool, err error) {
|
|
err = ei.MarshalBinaryWithBuffer(buff)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return true, nil
|
|
})
|
|
if err != nil {
|
|
log.LogErrorf("[writeReadDirHint] store(%v) failed to marshal hint, err(%v)", s.dataPath, err)
|
|
return
|
|
}
|
|
if err = os.WriteFile(hintTempPath, buff.Bytes(), 0o666); err != nil {
|
|
log.LogErrorf("[writeReadDirHint] store(%v) failed to write readdir hint, err(%v)", s.dataPath, err)
|
|
return
|
|
}
|
|
err = os.Rename(hintTempPath, hintPath)
|
|
if err != nil {
|
|
log.LogErrorf("[writeReadDirHint] store(%v) failed to rename readdir hint, err(%v)", s.dataPath, err)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) readReadDirHint() (extMap map[uint64]*ExtentInfo, err error) {
|
|
var data []byte
|
|
begin := time.Now()
|
|
defer func() {
|
|
size := 0
|
|
cnt := 0
|
|
if data != nil {
|
|
size = len(data)
|
|
}
|
|
if extMap != nil {
|
|
cnt = len(extMap)
|
|
}
|
|
slow := time.Since(begin) > 1*time.Second
|
|
log.LogInfof("[readReadDirHint] store(%v) read hint file using time(%v), read size(%v), cnt(%v), slow(%v)", s.dataPath, time.Since(begin), size, cnt, slow)
|
|
}()
|
|
|
|
hintPath := path.Join(s.dataPath, ExtentReadDirHintV2)
|
|
data, err = os.ReadFile(hintPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
err = nil
|
|
return
|
|
}
|
|
log.LogErrorf("[readReadDirHint] store(%v) failed to read hint file, err(%v)", s.dataPath, err)
|
|
return
|
|
}
|
|
extMap = make(map[uint64]*ExtentInfo)
|
|
buff := bytes.NewBuffer(data)
|
|
for buff.Len() != 0 {
|
|
ei := &ExtentInfo{}
|
|
err = ei.UnmarshalBinaryWithBuffer(buff)
|
|
if err != nil {
|
|
log.LogErrorf("[readReadDirHint] store(%v) failed to unmarshal hint, err(%v)", s.dataPath, err)
|
|
return
|
|
}
|
|
extMap[ei.FileID] = ei
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) removeReadDirHint() (err error) {
|
|
hintPath := path.Join(s.dataPath, ExtentReadDirHintV2)
|
|
err = os.Remove(hintPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
err = nil
|
|
return
|
|
}
|
|
log.LogErrorf("[removeReadDirHint] store(%v) failed to remove read dir hint(%v), err(%v)", s.dataPath, hintPath, err)
|
|
return
|
|
}
|
|
// NOTE: delete old version
|
|
hintPath = path.Join(s.dataPath, ExtentReadDirHint)
|
|
err = os.Remove(hintPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
err = nil
|
|
return
|
|
}
|
|
log.LogErrorf("[removeReadDirHint] store(%v) failed to remove read dir hint(%v), err(%v)", s.dataPath, hintPath, err)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) initBaseFileID() error {
|
|
var extNum int
|
|
begin := time.Now()
|
|
defer func() {
|
|
log.LogInfof("[initBaseFileID] store(%v) init base file id using time(%v), count(%v)", s.dataPath, time.Since(begin), extNum)
|
|
}()
|
|
|
|
// NOTE: try to read hint
|
|
var baseFileID uint64
|
|
var err error
|
|
var extMap map[uint64]*ExtentInfo
|
|
extMap, err = s.readReadDirHint()
|
|
if err != nil {
|
|
log.LogErrorf("[initBaseFileID] store(%v) failed to read hint, err(%v)", s.dataPath, err)
|
|
}
|
|
// NOTE: remove hint
|
|
if err = s.removeReadDirHint(); err != nil {
|
|
log.LogErrorf("[initBaseFileID] store(%v) failed to remove hint, err(%v)", s.dataPath, err)
|
|
return err
|
|
}
|
|
|
|
if len(extMap) != 0 {
|
|
log.LogInfof("[initBaseFileID] store(%v) init base file to read hint using time(%v)", s.dataPath, time.Since(begin))
|
|
// NOTE: fast path
|
|
for id := range extMap {
|
|
if !IsTinyExtent(id) && id > baseFileID {
|
|
baseFileID = id
|
|
}
|
|
}
|
|
s.extentInfoMap = extMap
|
|
} else {
|
|
// NOTE: slow path
|
|
files, err := fileutil.ReadDir(s.dataPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.LogInfof("[initBaseFileID] store(%v) init base file to read dir using time(%v)", s.dataPath, time.Since(begin))
|
|
|
|
var ei *ExtentInfo
|
|
for _, f := range files {
|
|
extentID, isExtent := s.ExtentID(f)
|
|
if !isExtent {
|
|
continue
|
|
}
|
|
|
|
extNum++
|
|
ei, err = s.GetExtentInfoFromDisk(extentID)
|
|
if err != nil {
|
|
log.LogErrorf("[initBaseFileID] store(%v) failed to load extent(%v), err(%v)", s.dataPath, extentID, err)
|
|
return err
|
|
}
|
|
s.extentInfoMap[extentID] = ei
|
|
|
|
if !IsTinyExtent(extentID) && extentID > baseFileID {
|
|
baseFileID = extentID
|
|
}
|
|
}
|
|
}
|
|
log.LogInfof("[initBaseFileID] store(%v) init base file to load loop using time(%v)", s.dataPath, time.Since(begin))
|
|
if baseFileID < MinExtentID {
|
|
baseFileID = MinExtentID
|
|
}
|
|
|
|
diskBaseFileID, _ := s.GetPersistenceBaseExtentID()
|
|
log.LogInfof("[initBaseFileID] store(%v) init base file to persistence base extent id using time(%v)", s.dataPath, time.Since(begin))
|
|
|
|
if errTmp := s.CheckBaseExtentCrc(); errTmp != nil && errTmp != io.EOF {
|
|
log.LogWarnf("[initBaseFileID] store(%v) init base file but not consistent baseFileID %v diskBaseFileID %v err %v", s.dataPath, baseFileID, diskBaseFileID, errTmp)
|
|
}
|
|
if baseFileID != diskBaseFileID {
|
|
// no extent is created before dn reboot
|
|
if diskBaseFileID != 0 {
|
|
log.LogWarnf("[initBaseFileID] store(%v) init base file but not consistent baseFileID %v diskBaseFileID %v", s.dataPath, baseFileID, diskBaseFileID)
|
|
}
|
|
if diskBaseFileID > baseFileID {
|
|
baseFileID = diskBaseFileID
|
|
}
|
|
}
|
|
atomic.StoreUint64(&s.baseExtentID, baseFileID)
|
|
log.LogInfof("datadir(%v) maxBaseId(%v)", s.dataPath, baseFileID)
|
|
return nil
|
|
}
|
|
|
|
// Write writes the given extent to the disk.
|
|
func (s *ExtentStore) Write(param *WriteParam) (status uint8, err error) {
|
|
s.stopMutex.RLock()
|
|
defer s.stopMutex.RUnlock()
|
|
if s.IsClosed() {
|
|
err = ErrStoreAlreadyClosed
|
|
log.LogErrorf("[Write] store(%v) failed to write param(%v), err(%v)", s.dataPath, param, err)
|
|
return
|
|
}
|
|
|
|
var (
|
|
e *Extent
|
|
ei *ExtentInfo
|
|
)
|
|
|
|
s.elMutex.RLock()
|
|
if param.IsBackupWrite {
|
|
// NOTE: meet an error is impossible
|
|
_, ok := s.GetExtentInfo(param.ExtentID)
|
|
if !ok {
|
|
s.elMutex.RUnlock()
|
|
err = fmt.Errorf("extent(%v) is not locked", param.ExtentID)
|
|
log.LogErrorf("[Write] gc_extent[%d] is not locked", param.ExtentID)
|
|
return
|
|
}
|
|
} else {
|
|
if s.extentLock {
|
|
if flag, ok := s.extentLockMap[param.ExtentID]; ok {
|
|
log.LogErrorf("[Write] gc_extent_lock[%d] is locked, path %s", param.ExtentID, s.dataPath)
|
|
if flag == proto.GcDeleteFlag {
|
|
s.elMutex.RUnlock()
|
|
err = fmt.Errorf("extent(%v) is locked", param.ExtentID)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
s.elMutex.RUnlock()
|
|
|
|
s.eiMutex.Lock()
|
|
status = proto.OpOk
|
|
ei = s.extentInfoMap[param.ExtentID]
|
|
s.eiMutex.Unlock()
|
|
e, err = s.extentWithHeader(ei)
|
|
if err != nil {
|
|
return status, err
|
|
}
|
|
|
|
log.LogDebugf("action[Write] dp %v write param(%v)", s.partitionID, param)
|
|
if err = s.checkOffsetAndSize(param); err != nil {
|
|
log.LogInfof("action[Write] path %v err %v", e.filePath, err)
|
|
return status, err
|
|
}
|
|
|
|
op := "Write"
|
|
if param.IsRepair {
|
|
op = "WriteRepair"
|
|
}
|
|
stat.RecordStat(s.partitionID, op, s.dataPath)
|
|
|
|
status, err = e.Write(param, s.PersistenceBlockCrc)
|
|
if err != nil {
|
|
log.LogInfof("action[Write] path %v err %v", e.filePath, err)
|
|
return status, err
|
|
}
|
|
|
|
ei.UpdateExtentInfo(e, 0)
|
|
return status, nil
|
|
}
|
|
|
|
func (s *ExtentStore) checkOffsetAndSize(param *WriteParam) error {
|
|
if IsTinyExtent(param.ExtentID) {
|
|
return nil
|
|
}
|
|
// random write pos can happen on modAppend partition of extent
|
|
if param.WriteType == RandomWriteType {
|
|
return nil
|
|
}
|
|
if param.WriteType == AppendRandomWriteType {
|
|
if param.Offset < util.ExtentSize {
|
|
return newParameterError("Write param error(%v)", param)
|
|
}
|
|
return nil
|
|
}
|
|
if param.Size == 0 ||
|
|
param.Offset >= util.BlockCount*util.BlockSize ||
|
|
param.Offset+param.Size > util.BlockCount*util.BlockSize {
|
|
return newParameterError("offset=%d size=%d", param.Offset, param.Size)
|
|
}
|
|
if !param.IsRepair && param.Size > util.BlockSize {
|
|
return newParameterError("offset=%d size=%d", param.Offset, param.Size)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IsTinyExtent checks if the given extent is tiny extent.
|
|
func IsTinyExtent(extentID uint64) bool {
|
|
return extentID >= TinyExtentStartID && extentID < TinyExtentStartID+TinyExtentCount
|
|
}
|
|
|
|
// Read reads the extent based on the given id.
|
|
func (s *ExtentStore) Read(extentID uint64, offset, size int64, nbuf []byte, isRepairRead bool, isBackupRead bool) (crc uint32, err error) {
|
|
var e *Extent
|
|
begin := time.Now()
|
|
log.LogDebugf("[Read] dp %v extent[%d] offset[%d] size[%d] isRepairRead[%v] extentLock[%v]",
|
|
s.partitionID, extentID, offset, size, isRepairRead, s.extentLock)
|
|
defer func() {
|
|
if log.EnableDebug() {
|
|
log.LogDebugf("[Read] dp %v extent[%d] offset[%d] size[%d] isRepairRead[%v] extentLock[%v] crc[%v] err[%v] cost %v",
|
|
s.partitionID, extentID, offset, size, isRepairRead, s.extentLock, crc, err, time.Since(begin).String())
|
|
}
|
|
}()
|
|
|
|
ei, _ := s.GetExtentInfo(extentID)
|
|
if ei == nil {
|
|
return 0, errors.Trace(ExtentHasBeenDeletedError, "[Read] dp %v extent[%d] is already been deleted", s.partitionID, extentID)
|
|
}
|
|
|
|
s.elMutex.RLock()
|
|
if isBackupRead {
|
|
if _, ok := s.extentLockMap[extentID]; !ok {
|
|
s.elMutex.RUnlock()
|
|
err = fmt.Errorf("extent(%v) is not locked", extentID)
|
|
log.LogErrorf("[Read]dp %v gc_extent_no_lock[%d] is not locked", s.partitionID, extentID)
|
|
return
|
|
}
|
|
} else {
|
|
if s.extentLock {
|
|
if _, ok := s.extentLockMap[extentID]; ok && !isRepairRead {
|
|
log.LogErrorf("[Read]dp %v gc_extent_lock[%d] is locked, should not be read.", s.partitionID, extentID)
|
|
}
|
|
}
|
|
}
|
|
s.elMutex.RUnlock()
|
|
|
|
if e, err = s.extentWithHeader(ei); err != nil {
|
|
return
|
|
}
|
|
|
|
op := "Read"
|
|
if isRepairRead {
|
|
op = "ReadRepair"
|
|
}
|
|
stat.RecordStat(s.partitionID, op, s.dataPath)
|
|
|
|
begin2 := time.Now()
|
|
log.LogDebugf("[Read]dp %v extent %v offset %v size %v ei.Size %v e.dataSize %v isRepairRead %v",
|
|
s.partitionID, extentID, offset, size, ei.Size, e.dataSize, isRepairRead)
|
|
crc, err = e.Read(nbuf, offset, size, isRepairRead, s.DirectRead)
|
|
if log.EnableDebug() {
|
|
log.LogDebugf("[Read]dp %v extent %v offset %v size %v ei.Size %v e.dataSize %v isRepairRead %v crc %v err %v,cost %v",
|
|
s.partitionID, extentID, offset, size, ei.Size, e.dataSize, isRepairRead, crc, err, time.Since(begin2).String())
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) punchDelete(extentID uint64, offset, size int64) (err error) {
|
|
e, err := s.extentWithHeaderByExtentID(extentID)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
if offset+size > e.dataSize {
|
|
log.LogWarnf("punchDelete: tiny extent maybe recovering, retry later. extId %d, offset %d, size %d, eSize %d, ignore %v", extentID, offset, size, e.dataSize, s.IgnoreTinyRecover)
|
|
if s.IgnoreTinyRecover {
|
|
return
|
|
}
|
|
return TinyRecoverError
|
|
}
|
|
|
|
var hasDelete bool
|
|
if hasDelete, err = e.punchDelete(offset, size); err != nil {
|
|
return
|
|
}
|
|
if hasDelete {
|
|
return
|
|
}
|
|
if err = s.RecordTinyDelete(e.extentID, offset, size); err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) CanGcDelete(extId uint64) bool {
|
|
ei, _ := s.GetExtentInfo(extId)
|
|
if ei == nil || ei.IsDeleted {
|
|
return true
|
|
}
|
|
|
|
s.elMutex.RLock()
|
|
defer s.elMutex.RUnlock()
|
|
|
|
flag, ok := s.extentLockMap[extId]
|
|
return ok && flag == proto.GcDeleteFlag
|
|
}
|
|
|
|
func (s *ExtentStore) GetGcFlag(extId uint64) proto.GcFlag {
|
|
s.elMutex.RLock()
|
|
defer s.elMutex.RUnlock()
|
|
|
|
flag, ok := s.extentLockMap[extId]
|
|
if !ok {
|
|
return proto.GcNormal
|
|
}
|
|
return flag
|
|
}
|
|
|
|
// MarkDelete marks the given extent as deleted.
|
|
func (s *ExtentStore) MarkDelete(extentID uint64, offset, size int64) (err error) {
|
|
s.stopMutex.RLock()
|
|
defer s.stopMutex.RUnlock()
|
|
|
|
if s.IsClosed() {
|
|
err = ErrStoreAlreadyClosed
|
|
log.LogErrorf("[MarkDelete] store(%v) failed to mark delete extent(%v), err(%v)", s.dataPath, extentID, err)
|
|
return
|
|
}
|
|
|
|
var ei *ExtentInfo
|
|
|
|
if IsTinyExtent(extentID) {
|
|
return s.punchDelete(extentID, offset, size)
|
|
}
|
|
|
|
ei, _ = s.GetExtentInfo(extentID)
|
|
if err != nil {
|
|
log.LogErrorf("[MarkDelete] failed to mark delete extent(%v), err(%v)", extentID, err)
|
|
return
|
|
}
|
|
if ei == nil || ei.IsDeleted {
|
|
return
|
|
}
|
|
log.LogDebugf("action[MarkDelete] extentID %v offset %v size %v ei(size %v snapshotSize %v)",
|
|
extentID, offset, size, ei.Size, ei.SnapshotDataOff)
|
|
|
|
funcNeedPunchDel := func() bool {
|
|
if offset != 0 {
|
|
return true
|
|
}
|
|
if size != 0 {
|
|
if ei.Size != uint64(size) && ei.SnapshotDataOff == util.ExtentSize {
|
|
return true
|
|
}
|
|
|
|
if ei.SnapshotDataOff != uint64(size) && ei.SnapshotDataOff > util.ExtentSize {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
stat.RecordStat(s.partitionID, "MarkDelete", s.dataPath)
|
|
|
|
log.LogInfof("[MarkDelete] store(%v) mark del extent(%v) offset(%v) size(%v), ei size(%v) ei snapshotOff(%v), tiny(%v), funcNeedPunchDel(%v)", s.dataPath, extentID, offset, size, ei.Size, ei.SnapshotDataOff, IsTinyExtent(extentID), funcNeedPunchDel())
|
|
if IsTinyExtent(extentID) || funcNeedPunchDel() {
|
|
log.LogDebugf("action[MarkDelete] extentID %v offset %v size %v ei(size %v snapshotSize %v), tiny(%v), snapshot punch(%v)",
|
|
extentID, offset, size, ei.Size, ei.SnapshotDataOff, IsTinyExtent(extentID), funcNeedPunchDel())
|
|
return s.punchDelete(extentID, offset, size)
|
|
}
|
|
|
|
extentFilePath := path.Join(s.dataPath, strconv.FormatUint(extentID, 10))
|
|
log.LogDebugf("action[MarkDelete] extentID %v offset %v size %v ei(size %v extentFilePath %v)",
|
|
extentID, offset, size, ei.Size, extentFilePath)
|
|
if err = os.Remove(extentFilePath); err != nil && !os.IsNotExist(err) {
|
|
// NOTE: if remove failed
|
|
// we meet a disk error
|
|
err = BrokenDiskError
|
|
return
|
|
}
|
|
if err = s.PersistenceHasDeleteExtent(extentID); err != nil {
|
|
err = BrokenDiskError
|
|
return
|
|
}
|
|
ei.IsDeleted = true
|
|
ei.ModifyTime = time.Now().Unix()
|
|
s.cache.Del(extentID)
|
|
if err = s.DeleteBlockCrc(extentID); err != nil {
|
|
err = BrokenDiskError
|
|
return
|
|
}
|
|
s.PutNormalExtentToDeleteCache(extentID)
|
|
|
|
s.eiMutex.Lock()
|
|
delete(s.extentInfoMap, extentID)
|
|
s.eiMutex.Unlock()
|
|
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) PutNormalExtentToDeleteCache(extentID uint64) {
|
|
s.hasDeleteNormalExtentsCache.Store(extentID, time.Now().Unix())
|
|
}
|
|
|
|
func (s *ExtentStore) IsDeletedNormalExtent(extentID uint64) (ok bool) {
|
|
_, ok = s.hasDeleteNormalExtentsCache.Load(extentID)
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) Flush() {
|
|
begin := time.Now()
|
|
defer func() {
|
|
log.LogInfof("[Flush] flush extent store using time(%v)", time.Since(begin))
|
|
}()
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
if s.IsClosed() {
|
|
return
|
|
}
|
|
s.cache.Flush()
|
|
}
|
|
|
|
// Close closes the extent store.
|
|
func (s *ExtentStore) Close() {
|
|
begin := time.Now()
|
|
defer func() {
|
|
log.LogInfof("[Close] store(%v) close extent store using time(%v), slow(%v)", s.dataPath, time.Since(begin), time.Since(begin) > 100*time.Millisecond)
|
|
}()
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
if s.IsClosed() {
|
|
return
|
|
}
|
|
close(s.stopC)
|
|
|
|
// Release cache
|
|
s.cache.Flush()
|
|
s.cache.Clear()
|
|
s.tinyExtentDeleteFp.Sync()
|
|
s.tinyExtentDeleteFp.Close()
|
|
s.normalExtentDeleteFp.Sync()
|
|
s.normalExtentDeleteFp.Close()
|
|
s.verifyExtentFp.Sync()
|
|
s.verifyExtentFp.Close()
|
|
for _, vFp := range s.verifyExtentFpAppend {
|
|
if vFp != nil {
|
|
vFp.Sync()
|
|
vFp.Close()
|
|
}
|
|
}
|
|
|
|
s.stopMutex.Lock()
|
|
defer s.stopMutex.Unlock()
|
|
s.setClosed(true)
|
|
if err := s.writeReadDirHint(); err != nil {
|
|
log.LogErrorf("[Close] store(%v) failed to write extent hint, err(%v)", s.dataPath, err)
|
|
}
|
|
}
|
|
|
|
// Watermark returns the extent info of the given extent on the record.
|
|
func (s *ExtentStore) Watermark(extentID uint64) (ei *ExtentInfo, err error) {
|
|
var has bool
|
|
ei, has = s.GetExtentInfo(extentID)
|
|
if !has {
|
|
err = fmt.Errorf("e %v not exist", s.getExtentKey(extentID))
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetTinyExtentOffset returns the offset of the given extent.
|
|
func (s *ExtentStore) GetTinyExtentOffset(extentID uint64) (watermark int64, err error) {
|
|
einfo, err := s.Watermark(extentID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
watermark = int64(einfo.Size)
|
|
if watermark%util.PageSize != 0 {
|
|
watermark = watermark + (util.PageSize - watermark%util.PageSize)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// GetTinyExtentOffset returns the offset of the given extent.
|
|
func (s *ExtentStore) GetExtentSnapshotModOffset(extentID uint64, allocSize uint32) (watermark int64, err error) {
|
|
einfo, err := s.Watermark(extentID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
log.LogDebugf("action[ExtentStore.GetExtentSnapshotModOffset] extId %v SnapshotDataOff %v SnapPreAllocDataOff %v allocSize %v",
|
|
extentID, einfo.SnapshotDataOff, einfo.SnapPreAllocDataOff, allocSize)
|
|
|
|
if einfo.SnapPreAllocDataOff == 0 {
|
|
einfo.SnapPreAllocDataOff = einfo.SnapshotDataOff
|
|
}
|
|
watermark = int64(einfo.SnapPreAllocDataOff)
|
|
// if watermark%util.PageSize != 0 {
|
|
// watermark = watermark + (util.PageSize - watermark%util.PageSize)
|
|
// }
|
|
einfo.SnapPreAllocDataOff += uint64(allocSize)
|
|
|
|
return
|
|
}
|
|
|
|
// Sector size
|
|
const (
|
|
DiskSectorSize = 512
|
|
)
|
|
|
|
func (s *ExtentStore) getFileDiskUsed(name string) (size int64, err error) {
|
|
stat := syscall.Stat_t{}
|
|
err = syscall.Stat(name, &stat)
|
|
if err != nil {
|
|
return
|
|
}
|
|
size = stat.Blocks * DiskSectorSize
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) GetStoreUsedSize() (used int64) {
|
|
extentInfoSlice := make([]*ExtentInfo, 0, s.GetExtentCount())
|
|
s.eiMutex.RLock()
|
|
for _, extentID := range s.extentInfoMap {
|
|
extentInfoSlice = append(extentInfoSlice, extentID)
|
|
}
|
|
s.eiMutex.RUnlock()
|
|
tinyTotal := uint64(0)
|
|
normalTotal := uint64(0)
|
|
for _, einfo := range extentInfoSlice {
|
|
if einfo.IsDeleted {
|
|
continue
|
|
}
|
|
if IsTinyExtent(einfo.FileID) {
|
|
size, err := s.getFileDiskUsed(path.Join(s.dataPath, strconv.FormatInt(int64(einfo.FileID), 10)))
|
|
if err != nil {
|
|
log.LogErrorf("[GetStoreUsedSize] store(%v) failed to get tiny extent(%v) disk used", s.dataPath, einfo.FileID)
|
|
continue
|
|
}
|
|
if log.EnableDebug() {
|
|
log.LogDebugf("[GetStoreUsedSize] store(%v) tiny extent(%v) size(%v) raw(%v)", s.dataPath, einfo.FileID, strutil.FormatSize(uint64(size)), size)
|
|
}
|
|
used += size
|
|
tinyTotal += uint64(size)
|
|
} else {
|
|
// NOTE: for debug
|
|
size := int64(einfo.TotalSize())
|
|
if log.EnableDebug() {
|
|
if size < 0 {
|
|
log.LogErrorf("[GetStoreUsedSize] store(%v) extent(%v) size(%v) is < 0, extent size(%v) snap off(%v)", s.dataPath, einfo.FileID, size, einfo.Size, einfo.SnapshotDataOff)
|
|
}
|
|
actualSize, err := s.getFileDiskUsed(path.Join(s.dataPath, strconv.FormatInt(int64(einfo.FileID), 10)))
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
log.LogInfof("[GetStoreUsedSize] store(%v) extent(%v) already be deleted, skip", s.dataPath, einfo.FileID)
|
|
continue
|
|
}
|
|
log.LogErrorf("[GetStoreUsedSize] store(%v) failed to get normal extent(%v) disk used, err(%v)", s.dataPath, einfo.FileID, err)
|
|
continue
|
|
}
|
|
log.LogDebugf("[GetStoreUsedSize] store(%v) normal extent(%v) size(%v) raw(%v), actual size(%v) raw(%v)", s.dataPath, einfo.FileID, strutil.FormatSize(uint64(size)), size, strutil.FormatSize(uint64(actualSize)), actualSize)
|
|
if actualSize < size {
|
|
log.LogWarnf("[GetStoreUsedSize] store(%v) normal extent(%v) actual size(%v), size(%v) einfo size(%v) snapshot off(%v)", s.dataPath, einfo.FileID, strutil.FormatSize(uint64(actualSize)), strutil.FormatSize(uint64(size)), strutil.FormatSize(einfo.Size), strutil.FormatSize(einfo.SnapshotDataOff))
|
|
}
|
|
}
|
|
used += size
|
|
normalTotal += uint64(size)
|
|
}
|
|
}
|
|
if log.EnableInfo() {
|
|
log.LogInfof("[GetStoreUsedSize] store(%v) total size(%v) raw(%v) tiny total(%v) raw(%v) normal total(%v) raw(%v)", s.dataPath, strutil.FormatSize(uint64(used)), used, strutil.FormatSize(tinyTotal), tinyTotal, strutil.FormatSize(normalTotal), normalTotal)
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetAllWatermarks returns all the watermarks.
|
|
func (s *ExtentStore) GetAllWatermarks(filter ExtentFilter) (extents []*ExtentInfo, tinyDeleteFileSize int64, err error) {
|
|
extents = make([]*ExtentInfo, 0, len(s.extentInfoMap))
|
|
extentInfoSlice := make([]*ExtentInfo, 0, len(s.extentInfoMap))
|
|
s.eiMutex.RLock()
|
|
for _, extentID := range s.extentInfoMap {
|
|
extentInfoSlice = append(extentInfoSlice, extentID)
|
|
}
|
|
s.eiMutex.RUnlock()
|
|
|
|
for _, extentInfo := range extentInfoSlice {
|
|
if filter != nil && !filter(extentInfo) {
|
|
continue
|
|
}
|
|
if extentInfo.IsDeleted {
|
|
continue
|
|
}
|
|
extents = append(extents, extentInfo)
|
|
}
|
|
tinyDeleteFileSize, err = s.LoadTinyDeleteFileOffset()
|
|
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) getTinyExtentInfo() (extents []*ExtentInfo) {
|
|
extents = make([]*ExtentInfo, 0)
|
|
var extentID uint64
|
|
for extentID = TinyExtentStartID; extentID < TinyExtentCount+TinyExtentStartID; extentID++ {
|
|
var ei *ExtentInfo
|
|
ei, _ = s.GetExtentInfo(extentID)
|
|
if ei == nil {
|
|
continue
|
|
}
|
|
extents = append(extents, ei)
|
|
}
|
|
return
|
|
}
|
|
|
|
// ExtentID return the extent ID.
|
|
func (s *ExtentStore) ExtentID(filename string) (extentID uint64, isExtent bool) {
|
|
if isExtent = RegexpExtentFile.MatchString(filename); !isExtent {
|
|
return
|
|
}
|
|
var err error
|
|
if extentID, err = strconv.ParseUint(filename, 10, 64); err != nil {
|
|
isExtent = false
|
|
return
|
|
}
|
|
isExtent = true
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) initTinyExtent() (err error) {
|
|
begin := time.Now()
|
|
defer func() {
|
|
log.LogInfof("[initTinyExtent] init tiny extent using time(%v)", time.Since(begin))
|
|
}()
|
|
s.availableTinyExtentC = make(chan uint64, TinyExtentCount)
|
|
s.brokenTinyExtentC = make(chan uint64, TinyExtentCount)
|
|
var extentID uint64
|
|
|
|
for extentID = TinyExtentStartID; extentID < TinyExtentStartID+TinyExtentCount; extentID++ {
|
|
err = s.Create(extentID)
|
|
if err == nil || strings.Contains(err.Error(), syscall.EEXIST.Error()) || err == ExtentExistsError {
|
|
err = nil
|
|
s.brokenTinyExtentC <- extentID
|
|
s.brokenTinyExtentMap.Store(extentID, true)
|
|
continue
|
|
}
|
|
return err
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// GetAvailableTinyExtent returns the available tiny extent from the channel.
|
|
func (s *ExtentStore) GetAvailableTinyExtent() (extentID uint64, err error) {
|
|
select {
|
|
case extentID = <-s.availableTinyExtentC:
|
|
log.LogDebugf("dp %v GetAvailableTinyExtent. extentID %v", s.partitionID, extentID)
|
|
s.availableTinyExtentMap.Delete(extentID)
|
|
return
|
|
default:
|
|
log.LogDebugf("dp %v GetAvailableTinyExtent not found", s.partitionID)
|
|
return 0, NoAvailableExtentError
|
|
}
|
|
}
|
|
|
|
// SendToAvailableTinyExtentC sends the extent to the channel that stores the available tiny extents.
|
|
func (s *ExtentStore) SendToAvailableTinyExtentC(extentID uint64) {
|
|
log.LogDebugf("dp %v action[SendToAvailableTinyExtentC] extentid %v", s.partitionID, extentID)
|
|
if _, ok := s.availableTinyExtentMap.Load(extentID); !ok {
|
|
log.LogDebugf("dp %v SendToAvailableTinyExtentC. extentID %v", s.partitionID, extentID)
|
|
s.availableTinyExtentC <- extentID
|
|
s.availableTinyExtentMap.Store(extentID, true)
|
|
} else {
|
|
log.LogDebugf("dp %v action[SendToAvailableTinyExtentC] extentid %v already exist", s.partitionID, extentID)
|
|
}
|
|
}
|
|
|
|
// SendAllToBrokenTinyExtentC sends all the extents to the channel that stores the broken extents.
|
|
func (s *ExtentStore) SendAllToBrokenTinyExtentC(extentIds []uint64) {
|
|
for _, extentID := range extentIds {
|
|
if _, ok := s.brokenTinyExtentMap.Load(extentID); !ok {
|
|
s.brokenTinyExtentC <- extentID
|
|
s.brokenTinyExtentMap.Store(extentID, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
// AvailableTinyExtentCnt returns the count of the available tiny extents.
|
|
func (s *ExtentStore) AvailableTinyExtentCnt() int {
|
|
return len(s.availableTinyExtentC)
|
|
}
|
|
|
|
// BrokenTinyExtentCnt returns the count of the broken tiny extents.
|
|
func (s *ExtentStore) BrokenTinyExtentCnt() int {
|
|
return len(s.brokenTinyExtentC)
|
|
}
|
|
|
|
// MoveAllToBrokenTinyExtentC moves all the tiny extents to the channel stores the broken extents.
|
|
func (s *ExtentStore) MoveAllToBrokenTinyExtentC(cnt int) {
|
|
for i := 0; i < cnt; i++ {
|
|
extentID, err := s.GetAvailableTinyExtent()
|
|
if err != nil {
|
|
return
|
|
}
|
|
s.SendToBrokenTinyExtentC(extentID)
|
|
}
|
|
}
|
|
|
|
// SendToBrokenTinyExtentC sends the given extent id to the channel.
|
|
func (s *ExtentStore) SendToBrokenTinyExtentC(extentID uint64) {
|
|
if _, ok := s.brokenTinyExtentMap.Load(extentID); !ok {
|
|
s.brokenTinyExtentC <- extentID
|
|
s.brokenTinyExtentMap.Store(extentID, true)
|
|
}
|
|
}
|
|
|
|
// GetBrokenTinyExtent returns the first broken extent in the channel.
|
|
func (s *ExtentStore) GetBrokenTinyExtent() (extentID uint64, err error) {
|
|
select {
|
|
case extentID = <-s.brokenTinyExtentC:
|
|
s.brokenTinyExtentMap.Delete(extentID)
|
|
return
|
|
default:
|
|
return 0, NoBrokenExtentError
|
|
|
|
}
|
|
}
|
|
|
|
// StoreSizeExtentID returns the size of the extent store
|
|
func (s *ExtentStore) StoreSizeExtentID(maxExtentID uint64) (totalSize uint64) {
|
|
extentInfos := make([]*ExtentInfo, 0)
|
|
s.eiMutex.RLock()
|
|
for _, extentInfo := range s.extentInfoMap {
|
|
if extentInfo.FileID <= maxExtentID {
|
|
extentInfos = append(extentInfos, extentInfo)
|
|
}
|
|
}
|
|
s.eiMutex.RUnlock()
|
|
for _, extentInfo := range extentInfos {
|
|
totalSize += extentInfo.TotalSize()
|
|
log.LogDebugf("ExtentStore.StoreSizeExtentID dp %v extentInfo %v totalSize %v", s.partitionID, extentInfo, extentInfo.TotalSize())
|
|
}
|
|
|
|
return totalSize
|
|
}
|
|
|
|
// StoreSizeExtentID returns the size of the extent store
|
|
func (s *ExtentStore) GetMaxExtentIDAndPartitionSize() (maxExtentID, totalSize uint64) {
|
|
extentInfos := make([]*ExtentInfo, 0)
|
|
s.eiMutex.RLock()
|
|
for _, extentInfo := range s.extentInfoMap {
|
|
extentInfos = append(extentInfos, extentInfo)
|
|
}
|
|
s.eiMutex.RUnlock()
|
|
for _, extentInfo := range extentInfos {
|
|
if extentInfo.FileID > maxExtentID {
|
|
maxExtentID = extentInfo.FileID
|
|
}
|
|
totalSize += extentInfo.TotalSize()
|
|
log.LogDebugf("GetMaxExtentIDAndPartitionSize dp %v add extentInfo %v size %v", s.partitionID,
|
|
extentInfo.FileID, extentInfo.TotalSize())
|
|
}
|
|
return maxExtentID, totalSize
|
|
}
|
|
|
|
func MarshalTinyExtent(extentID uint64, offset, size int64) (data []byte) {
|
|
data = make([]byte, DeleteTinyRecordSize)
|
|
binary.BigEndian.PutUint64(data[0:8], extentID)
|
|
binary.BigEndian.PutUint64(data[8:16], uint64(offset))
|
|
binary.BigEndian.PutUint64(data[16:DeleteTinyRecordSize], uint64(size))
|
|
return data
|
|
}
|
|
|
|
func UnMarshalTinyExtent(data []byte) (extentID, offset, size uint64) {
|
|
extentID = binary.BigEndian.Uint64(data[0:8])
|
|
offset = binary.BigEndian.Uint64(data[8:16])
|
|
size = binary.BigEndian.Uint64(data[16:DeleteTinyRecordSize])
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) RecordTinyDelete(extentID uint64, offset, size int64) (err error) {
|
|
record := MarshalTinyExtent(extentID, offset, size)
|
|
stat, err := s.tinyExtentDeleteFp.Stat()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if stat.Size()%DeleteTinyRecordSize != 0 {
|
|
needWriteEmpty := DeleteTinyRecordSize - (stat.Size() % DeleteTinyRecordSize)
|
|
data := make([]byte, needWriteEmpty)
|
|
s.tinyExtentDeleteFp.Write(data)
|
|
}
|
|
_, err = s.tinyExtentDeleteFp.Write(record)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) ReadTinyDeleteRecords(offset, size int64, data []byte) (crc uint32, err error) {
|
|
_, err = s.tinyExtentDeleteFp.ReadAt(data[:size], offset)
|
|
if err == nil || err == io.EOF {
|
|
err = nil
|
|
crc = crc32.ChecksumIEEE(data[:size])
|
|
}
|
|
return
|
|
}
|
|
|
|
type ExtentDeleted struct {
|
|
ExtentID uint64 `json:"extentID"`
|
|
Offset uint64 `json:"offset"`
|
|
Size uint64 `json:"size"`
|
|
}
|
|
|
|
func (s *ExtentStore) GetHasDeleteTinyRecords() (extentDes []ExtentDeleted, err error) {
|
|
data := make([]byte, DeleteTinyRecordSize)
|
|
offset := int64(0)
|
|
|
|
for {
|
|
_, err = s.tinyExtentDeleteFp.ReadAt(data, offset)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
err = nil
|
|
}
|
|
return
|
|
}
|
|
|
|
extent := ExtentDeleted{}
|
|
extent.ExtentID, extent.Offset, extent.Size = UnMarshalTinyExtent(data)
|
|
extentDes = append(extentDes, extent)
|
|
offset += DeleteTinyRecordSize
|
|
}
|
|
}
|
|
|
|
// NextExtentID returns the next extentID. When the client sends the request to create an extent,
|
|
// this function generates an unique extentID within the current partition.
|
|
// This function can only be called by the leader.
|
|
func (s *ExtentStore) NextExtentID() (extentID uint64, err error) {
|
|
extentID = atomic.AddUint64(&s.baseExtentID, 1)
|
|
s.PreAllocSpaceOnVerfiyFile()
|
|
err = s.PersistenceBaseExtentID(extentID)
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) LoadTinyDeleteFileOffset() (offset int64, err error) {
|
|
stat, err := s.tinyExtentDeleteFp.Stat()
|
|
if err == nil {
|
|
offset = stat.Size()
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) getExtentKey(extent uint64) string {
|
|
return fmt.Sprintf("extent %v_%v", s.partitionID, extent)
|
|
}
|
|
|
|
// UpdateBaseExtentID updates the base extent ID.
|
|
func (s *ExtentStore) UpdateBaseExtentID(id uint64) (err error) {
|
|
if IsTinyExtent(id) {
|
|
return
|
|
}
|
|
needPer := s.PreAllocSpaceOnVerfiyFile()
|
|
if id > atomic.LoadUint64(&s.baseExtentID) || needPer {
|
|
atomic.StoreUint64(&s.baseExtentID, id)
|
|
err = s.PersistenceBaseExtentID(atomic.LoadUint64(&s.baseExtentID))
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) extentWithHeader(ei *ExtentInfo) (e *Extent, err error) {
|
|
var ok bool
|
|
if ei == nil || ei.IsDeleted {
|
|
err = ExtentNotFoundError
|
|
return
|
|
}
|
|
if e, ok = s.cache.Get(ei.FileID); !ok {
|
|
if e, err = s.LoadExtentFromDisk(ei.FileID, true); err != nil {
|
|
err = fmt.Errorf("load %v from disk: %v", s.getExtentKey(ei.FileID), err)
|
|
return nil, err
|
|
}
|
|
ei.UpdateExtentInfo(e, 0)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) extentWithHeaderByExtentID(extentID uint64) (e *Extent, err error) {
|
|
var ok bool
|
|
if e, ok = s.cache.Get(extentID); !ok {
|
|
if e, err = s.LoadExtentFromDisk(extentID, true); err != nil {
|
|
err = fmt.Errorf("load %v from disk: %v", s.getExtentKey(extentID), err)
|
|
return nil, err
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// HasExtent tells if the extent store has the extent with the given ID
|
|
func (s *ExtentStore) HasExtent(extentID uint64) (exist bool) {
|
|
_, exist = s.GetExtentInfo(extentID)
|
|
return
|
|
}
|
|
|
|
// GetExtentCount returns the number of extents in the extentInfoMap
|
|
func (s *ExtentStore) GetExtentCount() (count int) {
|
|
s.eiMutex.RLock()
|
|
defer s.eiMutex.RUnlock()
|
|
return len(s.extentInfoMap)
|
|
}
|
|
|
|
func (s *ExtentStore) LoadExtentFromDisk(extentID uint64, putCache bool) (e *Extent, err error) {
|
|
name := path.Join(s.dataPath, fmt.Sprintf("%v", extentID))
|
|
e = NewExtentInCore(name, extentID)
|
|
if err = e.RestoreFromFS(); err != nil {
|
|
if strings.Contains(err.Error(), ExtentNotFoundError.Error()) {
|
|
s.DeleteExtentInfo(extentID)
|
|
log.LogWarnf("LoadExtentFromDisk. partition id %v delete missed extentId %v",
|
|
s.partitionID, extentID)
|
|
}
|
|
err = fmt.Errorf("restore from file %v putCache %v system: %v", name, putCache, err)
|
|
return
|
|
}
|
|
|
|
if !putCache {
|
|
return
|
|
}
|
|
|
|
if !IsTinyExtent(extentID) && proto.IsNormalDp(s.partitionType) {
|
|
e.header = make([]byte, util.BlockHeaderSize)
|
|
|
|
if _, err1 := s.verifyExtentFp.ReadAt(e.header, int64(extentID*util.BlockHeaderSize)); err1 != nil && err1 != io.EOF {
|
|
log.LogWarnf("LoadExtentFromDisk. partition id %v extent %v err %v", s.partitionID, e, err1)
|
|
}
|
|
|
|
log.LogDebugf("LoadExtentFromDisk. partition id %v extentId %v, snapshotOff %v, append fp cnt %v",
|
|
s.partitionID, extentID, e.snapshotDataOff, len(s.verifyExtentFpAppend))
|
|
if e.snapshotDataOff > util.ExtentSize {
|
|
for id, vFp := range s.verifyExtentFpAppend {
|
|
if uint64(id) > (e.snapshotDataOff-util.ExtentSize)/util.ExtentSize {
|
|
log.LogDebugf("LoadExtentFromDisk. partition id %v extentId %v, snapshotOff %v id %v out of extent range",
|
|
s.partitionID, extentID, e.snapshotDataOff, id)
|
|
break
|
|
}
|
|
log.LogDebugf("LoadExtentFromDisk. partition id %v extentId %v, snapshotOff %v id %v", s.partitionID, extentID, e.snapshotDataOff, id)
|
|
header := make([]byte, util.BlockHeaderSize)
|
|
if _, err = vFp.ReadAt(header, int64(extentID*util.BlockHeaderSize)); err != nil && err != io.EOF {
|
|
log.LogDebugf("LoadExtentFromDisk. partition id %v extentId %v, read at %v err %v",
|
|
s.partitionID, extentID, extentID*util.BlockHeaderSize, err)
|
|
return
|
|
}
|
|
if bytes.Equal(emptyHeader, header) {
|
|
log.LogErrorf("LoadExtentFromDisk. partition id %v extent %v hole at id %v", s.partitionID, e, id)
|
|
}
|
|
e.header = append(e.header, header...)
|
|
}
|
|
if len(s.verifyExtentFpAppend) < int(e.snapshotDataOff-1)/util.ExtentSize {
|
|
log.LogErrorf("LoadExtentFromDisk. extent %v need fp %v out of range %v", e, int(e.snapshotDataOff-1)/util.ExtentSize, len(s.verifyExtentFpAppend))
|
|
}
|
|
}
|
|
}
|
|
|
|
err = nil
|
|
s.cache.Put(e)
|
|
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) ScanBlocks(extentID uint64) (bcs []*BlockCrc, err error) {
|
|
if !proto.IsNormalDp(s.partitionType) {
|
|
return
|
|
}
|
|
|
|
var blockCnt int
|
|
bcs = make([]*BlockCrc, 0)
|
|
ei, _ := s.GetExtentInfo(extentID)
|
|
e, err := s.extentWithHeader(ei)
|
|
if err != nil {
|
|
return bcs, err
|
|
}
|
|
|
|
extSize := e.Size()
|
|
if e.snapshotDataOff > util.ExtentSize {
|
|
extSize = int64(e.snapshotDataOff)
|
|
}
|
|
blockCnt = int(extSize / util.BlockSize)
|
|
|
|
if e.Size()%util.BlockSize != 0 {
|
|
blockCnt += 1
|
|
}
|
|
for blockNo := 0; blockNo < blockCnt; blockNo++ {
|
|
blockCrc := binary.BigEndian.Uint32(e.header[blockNo*util.PerBlockCrcSize : (blockNo+1)*util.PerBlockCrcSize])
|
|
bcs = append(bcs, &BlockCrc{BlockNo: blockNo, Crc: blockCrc})
|
|
}
|
|
sort.Sort(BlockCrcArr(bcs))
|
|
|
|
return
|
|
}
|
|
|
|
type ExtentInfoArr []*ExtentInfo
|
|
|
|
func (arr ExtentInfoArr) Len() int { return len(arr) }
|
|
func (arr ExtentInfoArr) Less(i, j int) bool { return arr[i].FileID < arr[j].FileID }
|
|
func (arr ExtentInfoArr) Swap(i, j int) { arr[i], arr[j] = arr[j], arr[i] }
|
|
|
|
func (s *ExtentStore) BackendTask() {
|
|
s.autoComputeExtentCrc()
|
|
s.cleanExpiredNormalExtentDeleteCache()
|
|
}
|
|
|
|
func (s *ExtentStore) cleanExpiredNormalExtentDeleteCache() {
|
|
s.hasDeleteNormalExtentsCache.Range(func(key, value interface{}) bool {
|
|
deleteTime := value.(int64)
|
|
extentID := key.(uint64)
|
|
if time.Now().Unix()-deleteTime > NormalExtentDeleteRetainTime {
|
|
s.hasDeleteNormalExtentsCache.Delete(extentID)
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
func (s *ExtentStore) autoComputeExtentCrc() {
|
|
if !proto.IsNormalDp(s.partitionType) {
|
|
return
|
|
}
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
return
|
|
}
|
|
}()
|
|
|
|
extentInfos := make([]*ExtentInfo, 0)
|
|
deleteExtents := make([]*ExtentInfo, 0)
|
|
s.eiMutex.RLock()
|
|
for _, ei := range s.extentInfoMap {
|
|
extentInfos = append(extentInfos, ei)
|
|
if ei.IsDeleted && time.Now().Unix()-ei.ModifyTime > UpdateCrcInterval {
|
|
deleteExtents = append(deleteExtents, ei)
|
|
}
|
|
}
|
|
s.eiMutex.RUnlock()
|
|
|
|
if len(deleteExtents) > 0 {
|
|
s.eiMutex.Lock()
|
|
for _, ei := range deleteExtents {
|
|
delete(s.extentInfoMap, ei.FileID)
|
|
}
|
|
s.eiMutex.Unlock()
|
|
}
|
|
|
|
sort.Sort(ExtentInfoArr(extentInfos))
|
|
|
|
for _, ei := range extentInfos {
|
|
if ei == nil {
|
|
continue
|
|
}
|
|
|
|
if !IsTinyExtent(ei.FileID) && time.Now().Unix()-ei.ModifyTime > UpdateCrcInterval &&
|
|
!ei.IsDeleted && ei.Size > 0 && ei.Crc == 0 {
|
|
|
|
e, err := s.extentWithHeader(ei)
|
|
if err != nil {
|
|
log.LogWarnf("[autoComputeExtentCrc] get extent error:%+v", err)
|
|
continue
|
|
}
|
|
extSize := e.Size()
|
|
if e.snapshotDataOff > util.ExtentSize {
|
|
extSize = int64(e.snapshotDataOff)
|
|
}
|
|
extentCrc, err := e.autoComputeExtentCrc(extSize, s.PersistenceBlockCrc)
|
|
if err != nil {
|
|
log.LogError("[autoComputeExtentCrc] compute crc fail", err)
|
|
continue
|
|
}
|
|
ei.UpdateExtentInfo(e, extentCrc)
|
|
atomic.StoreUint64(&ei.ApplyID, s.ApplyId)
|
|
time.Sleep(time.Millisecond * 100)
|
|
}
|
|
}
|
|
|
|
time.Sleep(time.Second)
|
|
}
|
|
|
|
func (s *ExtentStore) TinyExtentRecover(extentID uint64, offset, size int64, data []byte, crc uint32, isEmptyPacket bool) (err error) {
|
|
if !IsTinyExtent(extentID) {
|
|
return fmt.Errorf("extent %v not tinyExtent", extentID)
|
|
}
|
|
|
|
s.stopMutex.RLock()
|
|
defer s.stopMutex.RUnlock()
|
|
if s.IsClosed() {
|
|
err = ErrStoreAlreadyClosed
|
|
log.LogErrorf("[TinyExtentRecover] store(%v) failed to tiny recover, extId %d, off %d, size %d, empyt %v", s.dataPath, extentID, offset, size, isEmptyPacket)
|
|
return
|
|
}
|
|
|
|
var (
|
|
e *Extent
|
|
ei *ExtentInfo
|
|
)
|
|
|
|
ei, _ = s.GetExtentInfo(extentID)
|
|
if e, err = s.extentWithHeader(ei); err != nil {
|
|
return nil
|
|
}
|
|
log.LogDebugf("[TinyExtentRecover] dp %v extent %v offset %v size %v: ei.Size %v cache.dataSize %v",
|
|
s.partitionID, extentID, offset, size, ei.Size, e.dataSize)
|
|
if err = e.TinyExtentRecover(data, offset, size, crc, isEmptyPacket); err != nil {
|
|
return err
|
|
}
|
|
ei.UpdateExtentInfo(e, 0)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ExtentStore) TinyExtentGetFinfoSize(extentID uint64) (size uint64, err error) {
|
|
var e *Extent
|
|
if !IsTinyExtent(extentID) {
|
|
return 0, fmt.Errorf("unavali extent id (%v)", extentID)
|
|
}
|
|
ei, _ := s.GetExtentInfo(extentID)
|
|
if err != nil {
|
|
log.LogErrorf("[TinyExtentGetFinfoSize] failed to get extent(%v) info, err(%v)", extentID, err)
|
|
return
|
|
}
|
|
if e, err = s.extentWithHeader(ei); err != nil {
|
|
return
|
|
}
|
|
|
|
finfo, err := e.file.Stat()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
size = uint64(finfo.Size())
|
|
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) GetExtentFinfoSize(extentID uint64) (size uint64, err error) {
|
|
var e *Extent
|
|
s.eiMutex.RLock()
|
|
ei := s.extentInfoMap[extentID]
|
|
s.eiMutex.RUnlock()
|
|
if e, err = s.extentWithHeader(ei); err != nil {
|
|
return
|
|
}
|
|
|
|
finfo, err := e.file.Stat()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
size = uint64(finfo.Size())
|
|
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) GetExtentWithHoleAvailableOffset(extentID uint64, offset int64) (newOffset, newEnd int64, err error) {
|
|
var e *Extent
|
|
s.eiMutex.RLock()
|
|
ei := s.extentInfoMap[extentID]
|
|
s.eiMutex.RUnlock()
|
|
if e, err = s.extentWithHeader(ei); err != nil {
|
|
return
|
|
}
|
|
|
|
defer func() {
|
|
if err != nil && strings.Contains(err.Error(), syscall.ENXIO.Error()) {
|
|
newOffset = e.dataSize
|
|
newEnd = e.dataSize
|
|
err = nil
|
|
}
|
|
}()
|
|
newOffset, newEnd, err = e.getExtentWithHoleAvailableOffset(offset)
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) renameStaleExtentStore() (err error) {
|
|
// create: move current folder to .old and create a new folder
|
|
if _, err = os.Stat(s.dataPath); err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
curTime := time.Now().Format(StaleExtStoreTimeFormat)
|
|
staleExtStoreDirName := s.dataPath + "_" + curTime + StaleExtStoreBackupSuffix
|
|
|
|
if err = os.Rename(s.dataPath, staleExtStoreDirName); err != nil {
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) GetAllExtents(beforeTime int64) (extents []*ExtentInfo, err error) {
|
|
start := time.Now()
|
|
files, err := os.ReadDir(s.dataPath)
|
|
if err != nil {
|
|
log.LogErrorf("GetAllExtents: read dir extents failed, path %s, err %s", s.dataPath, err.Error())
|
|
return nil, err
|
|
}
|
|
|
|
extents = make([]*ExtentInfo, 0, len(files))
|
|
for _, f := range files {
|
|
extId, isExtent := s.ExtentID(f.Name())
|
|
if !isExtent {
|
|
continue
|
|
}
|
|
|
|
ifo, err1 := f.Info()
|
|
if err1 != nil {
|
|
log.LogWarnf("GetAllExtents: get extent info failed, path %s, ext %s, err %s", s.dataPath, f.Name(), err1.Error())
|
|
continue
|
|
}
|
|
|
|
modTime := ifo.ModTime().Unix()
|
|
if modTime >= beforeTime {
|
|
continue
|
|
}
|
|
|
|
extents = append(extents, &ExtentInfo{
|
|
FileID: extId,
|
|
Size: uint64(ifo.Size()),
|
|
ModifyTime: modTime,
|
|
})
|
|
}
|
|
|
|
log.LogWarnf("GetAllExtents: path:%v, beforeTime:%v, extents len:%v, cost %d",
|
|
s.dataPath, beforeTime, len(extents), time.Since(start).Milliseconds())
|
|
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) ExtentBatchLockNormalExtent(gcLockEks *proto.GcLockExtents) (err error) {
|
|
s.elMutex.Lock()
|
|
s.extentLock = true
|
|
s.elMutex.Unlock()
|
|
|
|
s.elMutex.Lock()
|
|
defer s.elMutex.Unlock()
|
|
|
|
if gcLockEks.IsCreate {
|
|
for _, e := range gcLockEks.Eks {
|
|
s.extentLockMap[e.ExtentId] = gcLockEks.Flag
|
|
log.LogDebugf("[ExtentBatchLockNormalExtent] lock extent(%v)", e.ExtentId)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// return all extents
|
|
exts, err := s.GetAllExtents(time.Now().Unix() + 1000)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
extMap := make(map[uint64]*ExtentInfo, len(exts))
|
|
for _, e := range exts {
|
|
extMap[e.FileID] = e
|
|
}
|
|
|
|
for _, e := range gcLockEks.Eks {
|
|
extent, ok := extMap[e.ExtentId]
|
|
if !ok {
|
|
log.LogWarnf("[ExtentBatchLockNormalExtent] extent already not exist %s, eId %d", s.dataPath, e.ExtentId)
|
|
continue
|
|
}
|
|
|
|
if e.Size != uint32(extent.Size) {
|
|
err = fmt.Errorf("extent size not match, path %s, extentID(%v), extentSize(%v), extentKeySize(%v)",
|
|
s.dataPath, e.ExtentId, extent.Size, e.Size)
|
|
log.LogErrorf("[ExtentBatchLockNormalExtent] msg %s", err.Error())
|
|
return err
|
|
}
|
|
|
|
s.extentLockMap[e.ExtentId] = gcLockEks.Flag
|
|
if log.EnableDebug() {
|
|
log.LogDebugf("[ExtentBatchLockNormalExtent] path %s, lock extent(%v)", s.dataPath, e.ExtentId)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *ExtentStore) ExtentBatchUnlockNormalExtent(ext []*proto.ExtentKey) {
|
|
s.elMutex.Lock()
|
|
defer s.elMutex.Unlock()
|
|
|
|
s.extentLockMap = make(map[uint64]proto.GcFlag)
|
|
s.extentLock = false
|
|
}
|
|
|
|
func (s *ExtentStore) GetExtentCountWithoutLock() (count int) {
|
|
return len(s.extentInfoMap)
|
|
}
|
|
|
|
func (s *ExtentStore) DoExtentCacheTtl(ttl int) error {
|
|
return s.cache.EvictTTL(ttl)
|
|
}
|