more solid weed mount (#4089)
* compare chunks by timestamp * fix slab clearing error * fix test compilation * move oldest chunk to sealed, instead of by fullness * lock on fh.entryViewCache * remove verbose logs * revert slat clearing * less logs * less logs * track write and read by timestamp * remove useless logic * add entry lock on file handle release * use mem chunk only, swap file chunk has problems * comment out code that maybe used later * add debug mode to compare data read and write * more efficient readResolvedChunks with linked list * small optimization * fix test compilation * minor fix on writer * add SeparateGarbageChunks * group chunks into sections * turn off debug mode * fix tests * fix tests * tmp enable swap file chunk * Revert "tmp enable swap file chunk" This reverts commit 985137ec472924e4815f258189f6ca9f2168a0a7. * simple refactoring * simple refactoring * do not re-use swap file chunk. Sealed chunks should not be re-used. * comment out debugging facilities * either mem chunk or swap file chunk is fine now * remove orderedMutex as *semaphore.Weighted not found impactful * optimize size calculation for changing large files * optimize performance to avoid going through the long list of chunks * still problems with swap file chunk * rename * tiny optimization * swap file chunk save only successfully read data * fix * enable both mem and swap file chunk * resolve chunks with range * rename * fix chunk interval list * also change file handle chunk group when adding chunks * pick in-active chunk with time-decayed counter * fix compilation * avoid nil with empty fh.entry * refactoring * rename * rename * refactor visible intervals to *list.List * refactor chunkViews to *list.List * add IntervalList for generic interval list * change visible interval to use IntervalList in generics * cahnge chunkViews to *IntervalList[*ChunkView] * use NewFileChunkSection to create * rename variables * refactor * fix renaming leftover * renaming * renaming * add insert interval * interval list adds lock * incrementally add chunks to readers Fixes: 1. set start and stop offset for the value object 2. clone the value object 3. use pointer instead of copy-by-value when passing to interval.Value 4. use insert interval since adding chunk could be out of order * fix tests compilation * fix tests compilation
This commit is contained in:
39
weed/mount/page_writer/activity_score.go
Normal file
39
weed/mount/page_writer/activity_score.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package page_writer
|
||||
|
||||
import "time"
|
||||
|
||||
type ActivityScore struct {
|
||||
lastActiveTsNs int64
|
||||
decayedActivenessScore int64
|
||||
}
|
||||
|
||||
func NewActivityScore() *ActivityScore {
|
||||
return &ActivityScore{}
|
||||
}
|
||||
|
||||
func (as ActivityScore) MarkRead() {
|
||||
now := time.Now().UnixNano()
|
||||
deltaTime := (now - as.lastActiveTsNs) >> 30 // about number of seconds
|
||||
as.lastActiveTsNs = now
|
||||
|
||||
as.decayedActivenessScore = as.decayedActivenessScore>>deltaTime + 256
|
||||
if as.decayedActivenessScore < 0 {
|
||||
as.decayedActivenessScore = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (as ActivityScore) MarkWrite() {
|
||||
now := time.Now().UnixNano()
|
||||
deltaTime := (now - as.lastActiveTsNs) >> 30 // about number of seconds
|
||||
as.lastActiveTsNs = now
|
||||
|
||||
as.decayedActivenessScore = as.decayedActivenessScore>>deltaTime + 1024
|
||||
if as.decayedActivenessScore < 0 {
|
||||
as.decayedActivenessScore = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (as ActivityScore) ActivityScore() int64 {
|
||||
deltaTime := (time.Now().UnixNano() - as.lastActiveTsNs) >> 30 // about number of seconds
|
||||
return as.decayedActivenessScore >> deltaTime
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
type ChunkWrittenInterval struct {
|
||||
StartOffset int64
|
||||
stopOffset int64
|
||||
TsNs int64
|
||||
prev *ChunkWrittenInterval
|
||||
next *ChunkWrittenInterval
|
||||
}
|
||||
@@ -42,10 +43,14 @@ func newChunkWrittenIntervalList() *ChunkWrittenIntervalList {
|
||||
return list
|
||||
}
|
||||
|
||||
func (list *ChunkWrittenIntervalList) MarkWritten(startOffset, stopOffset int64) {
|
||||
func (list *ChunkWrittenIntervalList) MarkWritten(startOffset, stopOffset, tsNs int64) {
|
||||
if startOffset >= stopOffset {
|
||||
return
|
||||
}
|
||||
interval := &ChunkWrittenInterval{
|
||||
StartOffset: startOffset,
|
||||
stopOffset: stopOffset,
|
||||
TsNs: tsNs,
|
||||
}
|
||||
list.addInterval(interval)
|
||||
}
|
||||
@@ -62,50 +67,54 @@ func (list *ChunkWrittenIntervalList) WrittenSize() (writtenByteCount int64) {
|
||||
|
||||
func (list *ChunkWrittenIntervalList) addInterval(interval *ChunkWrittenInterval) {
|
||||
|
||||
//t := list.head
|
||||
//for ; t.next != nil; t = t.next {
|
||||
// if t.TsNs > interval.TsNs {
|
||||
// println("writes is out of order", t.TsNs-interval.TsNs, "ns")
|
||||
// }
|
||||
//}
|
||||
|
||||
p := list.head
|
||||
for ; p.next != nil && p.next.StartOffset <= interval.StartOffset; p = p.next {
|
||||
for ; p.next != nil && p.next.stopOffset <= interval.StartOffset; p = p.next {
|
||||
}
|
||||
q := list.tail
|
||||
for ; q.prev != nil && q.prev.stopOffset >= interval.stopOffset; q = q.prev {
|
||||
for ; q.prev != nil && q.prev.StartOffset >= interval.stopOffset; q = q.prev {
|
||||
}
|
||||
|
||||
if interval.StartOffset <= p.stopOffset && q.StartOffset <= interval.stopOffset {
|
||||
// merge p and q together
|
||||
p.stopOffset = q.stopOffset
|
||||
unlinkNodesBetween(p, q.next)
|
||||
return
|
||||
}
|
||||
if interval.StartOffset <= p.stopOffset {
|
||||
// merge new interval into p
|
||||
p.stopOffset = interval.stopOffset
|
||||
unlinkNodesBetween(p, q)
|
||||
return
|
||||
}
|
||||
if q.StartOffset <= interval.stopOffset {
|
||||
// merge new interval into q
|
||||
q.StartOffset = interval.StartOffset
|
||||
unlinkNodesBetween(p, q)
|
||||
return
|
||||
// left side
|
||||
// interval after p.next start
|
||||
if p.next.StartOffset < interval.StartOffset {
|
||||
t := &ChunkWrittenInterval{
|
||||
StartOffset: p.next.StartOffset,
|
||||
stopOffset: interval.StartOffset,
|
||||
TsNs: p.next.TsNs,
|
||||
}
|
||||
p.next = t
|
||||
t.prev = p
|
||||
t.next = interval
|
||||
interval.prev = t
|
||||
} else {
|
||||
p.next = interval
|
||||
interval.prev = p
|
||||
}
|
||||
|
||||
// add the new interval between p and q
|
||||
unlinkNodesBetween(p, q)
|
||||
p.next = interval
|
||||
interval.prev = p
|
||||
q.prev = interval
|
||||
interval.next = q
|
||||
|
||||
}
|
||||
|
||||
// unlinkNodesBetween remove all nodes after start and before stop, exclusive
|
||||
func unlinkNodesBetween(start *ChunkWrittenInterval, stop *ChunkWrittenInterval) {
|
||||
if start.next == stop {
|
||||
return
|
||||
// right side
|
||||
// interval ends before p.prev
|
||||
if interval.stopOffset < q.prev.stopOffset {
|
||||
t := &ChunkWrittenInterval{
|
||||
StartOffset: interval.stopOffset,
|
||||
stopOffset: q.prev.stopOffset,
|
||||
TsNs: q.prev.TsNs,
|
||||
}
|
||||
q.prev = t
|
||||
t.next = q
|
||||
interval.next = t
|
||||
t.prev = interval
|
||||
} else {
|
||||
q.prev = interval
|
||||
interval.next = q
|
||||
}
|
||||
start.next.prev = nil
|
||||
start.next = stop
|
||||
stop.prev.next = nil
|
||||
stop.prev = start
|
||||
|
||||
}
|
||||
|
||||
func (list *ChunkWrittenIntervalList) size() int {
|
||||
|
||||
@@ -10,40 +10,72 @@ func Test_PageChunkWrittenIntervalList(t *testing.T) {
|
||||
|
||||
assert.Equal(t, 0, list.size(), "empty list")
|
||||
|
||||
list.MarkWritten(0, 5)
|
||||
list.MarkWritten(0, 5, 1)
|
||||
assert.Equal(t, 1, list.size(), "one interval")
|
||||
|
||||
list.MarkWritten(0, 5)
|
||||
list.MarkWritten(0, 5, 2)
|
||||
assert.Equal(t, 1, list.size(), "duplicated interval2")
|
||||
|
||||
list.MarkWritten(95, 100)
|
||||
list.MarkWritten(95, 100, 3)
|
||||
assert.Equal(t, 2, list.size(), "two intervals")
|
||||
|
||||
list.MarkWritten(50, 60)
|
||||
list.MarkWritten(50, 60, 4)
|
||||
assert.Equal(t, 3, list.size(), "three intervals")
|
||||
|
||||
list.MarkWritten(50, 55)
|
||||
assert.Equal(t, 3, list.size(), "three intervals merge")
|
||||
list.MarkWritten(50, 55, 5)
|
||||
assert.Equal(t, 4, list.size(), "three intervals merge")
|
||||
|
||||
list.MarkWritten(40, 50)
|
||||
assert.Equal(t, 3, list.size(), "three intervals grow forward")
|
||||
list.MarkWritten(40, 50, 6)
|
||||
assert.Equal(t, 5, list.size(), "three intervals grow forward")
|
||||
|
||||
list.MarkWritten(50, 65)
|
||||
assert.Equal(t, 3, list.size(), "three intervals grow backward")
|
||||
list.MarkWritten(50, 65, 7)
|
||||
assert.Equal(t, 4, list.size(), "three intervals grow backward")
|
||||
|
||||
list.MarkWritten(70, 80)
|
||||
assert.Equal(t, 4, list.size(), "four intervals")
|
||||
list.MarkWritten(70, 80, 8)
|
||||
assert.Equal(t, 5, list.size(), "four intervals")
|
||||
|
||||
list.MarkWritten(60, 70)
|
||||
assert.Equal(t, 3, list.size(), "three intervals merged")
|
||||
list.MarkWritten(60, 70, 9)
|
||||
assert.Equal(t, 6, list.size(), "three intervals merged")
|
||||
|
||||
list.MarkWritten(59, 71)
|
||||
assert.Equal(t, 3, list.size(), "covered three intervals")
|
||||
list.MarkWritten(59, 71, 10)
|
||||
assert.Equal(t, 6, list.size(), "covered three intervals")
|
||||
|
||||
list.MarkWritten(5, 59)
|
||||
assert.Equal(t, 2, list.size(), "covered two intervals")
|
||||
list.MarkWritten(5, 59, 11)
|
||||
assert.Equal(t, 5, list.size(), "covered two intervals")
|
||||
|
||||
list.MarkWritten(70, 99)
|
||||
assert.Equal(t, 1, list.size(), "covered one intervals")
|
||||
list.MarkWritten(70, 99, 12)
|
||||
assert.Equal(t, 5, list.size(), "covered one intervals")
|
||||
|
||||
}
|
||||
|
||||
type interval struct {
|
||||
start int64
|
||||
stop int64
|
||||
expected bool
|
||||
}
|
||||
|
||||
func Test_PageChunkWrittenIntervalList1(t *testing.T) {
|
||||
list := newChunkWrittenIntervalList()
|
||||
inputs := []interval{
|
||||
{1, 5, true},
|
||||
{2, 3, true},
|
||||
}
|
||||
for i, input := range inputs {
|
||||
list.MarkWritten(input.start, input.stop, int64(i)+1)
|
||||
actual := hasData(list, 0, 4)
|
||||
if actual != input.expected {
|
||||
t.Errorf("input [%d,%d) expected %v actual %v", input.start, input.stop, input.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hasData(usage *ChunkWrittenIntervalList, chunkStartOffset, x int64) bool {
|
||||
for t := usage.head.next; t != usage.tail; t = t.next {
|
||||
logicStart := chunkStartOffset + t.StartOffset
|
||||
logicStop := chunkStartOffset + t.stopOffset
|
||||
if logicStart <= x && x < logicStop {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package page_writer
|
||||
|
||||
type DirtyPages interface {
|
||||
AddPage(offset int64, data []byte, isSequential bool)
|
||||
AddPage(offset int64, data []byte, isSequential bool, tsNs int64)
|
||||
FlushData() error
|
||||
ReadDirtyDataAt(data []byte, startOffset int64) (maxStop int64)
|
||||
ReadDirtyDataAt(data []byte, startOffset int64, tsNs int64) (maxStop int64)
|
||||
Destroy()
|
||||
LockForRead(startOffset, stopOffset int64)
|
||||
UnlockForRead(startOffset, stopOffset int64)
|
||||
|
||||
@@ -4,13 +4,13 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type SaveToStorageFunc func(reader io.Reader, offset int64, size int64, cleanupFn func())
|
||||
type SaveToStorageFunc func(reader io.Reader, offset int64, size int64, modifiedTsNs int64, cleanupFn func())
|
||||
|
||||
type PageChunk interface {
|
||||
FreeResource()
|
||||
WriteDataAt(src []byte, offset int64) (n int)
|
||||
ReadDataAt(p []byte, off int64) (maxStop int64)
|
||||
WriteDataAt(src []byte, offset int64, tsNs int64) (n int)
|
||||
ReadDataAt(p []byte, off int64, tsNs int64) (maxStop int64)
|
||||
IsComplete() bool
|
||||
WrittenSize() int64
|
||||
ActivityScore() int64
|
||||
SaveContent(saveFn SaveToStorageFunc)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ type MemChunk struct {
|
||||
usage *ChunkWrittenIntervalList
|
||||
chunkSize int64
|
||||
logicChunkIndex LogicChunkIndex
|
||||
activityScore *ActivityScore
|
||||
}
|
||||
|
||||
func NewMemChunk(logicChunkIndex LogicChunkIndex, chunkSize int64) *MemChunk {
|
||||
@@ -28,6 +29,7 @@ func NewMemChunk(logicChunkIndex LogicChunkIndex, chunkSize int64) *MemChunk {
|
||||
chunkSize: chunkSize,
|
||||
buf: mem.Allocate(int(chunkSize)),
|
||||
usage: newChunkWrittenIntervalList(),
|
||||
activityScore: NewActivityScore(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,29 +41,37 @@ func (mc *MemChunk) FreeResource() {
|
||||
mem.Free(mc.buf)
|
||||
}
|
||||
|
||||
func (mc *MemChunk) WriteDataAt(src []byte, offset int64) (n int) {
|
||||
func (mc *MemChunk) WriteDataAt(src []byte, offset int64, tsNs int64) (n int) {
|
||||
mc.Lock()
|
||||
defer mc.Unlock()
|
||||
|
||||
innerOffset := offset % mc.chunkSize
|
||||
n = copy(mc.buf[innerOffset:], src)
|
||||
mc.usage.MarkWritten(innerOffset, innerOffset+int64(n))
|
||||
mc.usage.MarkWritten(innerOffset, innerOffset+int64(n), tsNs)
|
||||
mc.activityScore.MarkWrite()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (mc *MemChunk) ReadDataAt(p []byte, off int64) (maxStop int64) {
|
||||
func (mc *MemChunk) ReadDataAt(p []byte, off int64, tsNs int64) (maxStop int64) {
|
||||
mc.RLock()
|
||||
defer mc.RUnlock()
|
||||
|
||||
memChunkBaseOffset := int64(mc.logicChunkIndex) * mc.chunkSize
|
||||
for t := mc.usage.head.next; t != mc.usage.tail; t = t.next {
|
||||
logicStart := max(off, int64(mc.logicChunkIndex)*mc.chunkSize+t.StartOffset)
|
||||
logicStart := max(off, memChunkBaseOffset+t.StartOffset)
|
||||
logicStop := min(off+int64(len(p)), memChunkBaseOffset+t.stopOffset)
|
||||
if logicStart < logicStop {
|
||||
copy(p[logicStart-off:logicStop-off], mc.buf[logicStart-memChunkBaseOffset:logicStop-memChunkBaseOffset])
|
||||
maxStop = max(maxStop, logicStop)
|
||||
if t.TsNs >= tsNs {
|
||||
copy(p[logicStart-off:logicStop-off], mc.buf[logicStart-memChunkBaseOffset:logicStop-memChunkBaseOffset])
|
||||
maxStop = max(maxStop, logicStop)
|
||||
} else {
|
||||
println("read old data1", tsNs-t.TsNs, "ns")
|
||||
}
|
||||
}
|
||||
}
|
||||
mc.activityScore.MarkRead()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -72,11 +82,8 @@ func (mc *MemChunk) IsComplete() bool {
|
||||
return mc.usage.IsComplete(mc.chunkSize)
|
||||
}
|
||||
|
||||
func (mc *MemChunk) WrittenSize() int64 {
|
||||
mc.RLock()
|
||||
defer mc.RUnlock()
|
||||
|
||||
return mc.usage.WrittenSize()
|
||||
func (mc *MemChunk) ActivityScore() int64 {
|
||||
return mc.activityScore.ActivityScore()
|
||||
}
|
||||
|
||||
func (mc *MemChunk) SaveContent(saveFn SaveToStorageFunc) {
|
||||
@@ -88,7 +95,7 @@ func (mc *MemChunk) SaveContent(saveFn SaveToStorageFunc) {
|
||||
}
|
||||
for t := mc.usage.head.next; t != mc.usage.tail; t = t.next {
|
||||
reader := util.NewBytesReader(mc.buf[t.StartOffset:t.stopOffset])
|
||||
saveFn(reader, int64(mc.logicChunkIndex)*mc.chunkSize+t.StartOffset, t.Size(), func() {
|
||||
saveFn(reader, int64(mc.logicChunkIndex)*mc.chunkSize+t.StartOffset, t.Size(), t.TsNs, func() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@ var (
|
||||
type ActualChunkIndex int
|
||||
|
||||
type SwapFile struct {
|
||||
dir string
|
||||
file *os.File
|
||||
logicToActualChunkIndex map[LogicChunkIndex]ActualChunkIndex
|
||||
logicToActualChunkIndexLock sync.Mutex
|
||||
chunkSize int64
|
||||
freeActualChunkList []ActualChunkIndex
|
||||
dir string
|
||||
file *os.File
|
||||
chunkSize int64
|
||||
chunkTrackingLock sync.Mutex
|
||||
activeChunkCount int
|
||||
freeActualChunkList []ActualChunkIndex
|
||||
}
|
||||
|
||||
type SwapFileChunk struct {
|
||||
@@ -29,14 +29,15 @@ type SwapFileChunk struct {
|
||||
usage *ChunkWrittenIntervalList
|
||||
logicChunkIndex LogicChunkIndex
|
||||
actualChunkIndex ActualChunkIndex
|
||||
activityScore *ActivityScore
|
||||
//memChunk *MemChunk
|
||||
}
|
||||
|
||||
func NewSwapFile(dir string, chunkSize int64) *SwapFile {
|
||||
return &SwapFile{
|
||||
dir: dir,
|
||||
file: nil,
|
||||
logicToActualChunkIndex: make(map[LogicChunkIndex]ActualChunkIndex),
|
||||
chunkSize: chunkSize,
|
||||
dir: dir,
|
||||
file: nil,
|
||||
chunkSize: chunkSize,
|
||||
}
|
||||
}
|
||||
func (sf *SwapFile) FreeResource() {
|
||||
@@ -46,7 +47,7 @@ func (sf *SwapFile) FreeResource() {
|
||||
}
|
||||
}
|
||||
|
||||
func (sf *SwapFile) NewTempFileChunk(logicChunkIndex LogicChunkIndex) (tc *SwapFileChunk) {
|
||||
func (sf *SwapFile) NewSwapFileChunk(logicChunkIndex LogicChunkIndex) (tc *SwapFileChunk) {
|
||||
if sf.file == nil {
|
||||
var err error
|
||||
sf.file, err = os.CreateTemp(sf.dir, "")
|
||||
@@ -55,70 +56,98 @@ func (sf *SwapFile) NewTempFileChunk(logicChunkIndex LogicChunkIndex) (tc *SwapF
|
||||
return nil
|
||||
}
|
||||
}
|
||||
sf.logicToActualChunkIndexLock.Lock()
|
||||
defer sf.logicToActualChunkIndexLock.Unlock()
|
||||
actualChunkIndex, found := sf.logicToActualChunkIndex[logicChunkIndex]
|
||||
if !found {
|
||||
if len(sf.freeActualChunkList) > 0 {
|
||||
actualChunkIndex = sf.freeActualChunkList[0]
|
||||
sf.freeActualChunkList = sf.freeActualChunkList[1:]
|
||||
} else {
|
||||
actualChunkIndex = ActualChunkIndex(len(sf.logicToActualChunkIndex))
|
||||
}
|
||||
sf.logicToActualChunkIndex[logicChunkIndex] = actualChunkIndex
|
||||
sf.chunkTrackingLock.Lock()
|
||||
defer sf.chunkTrackingLock.Unlock()
|
||||
|
||||
sf.activeChunkCount++
|
||||
|
||||
// assign a new physical chunk
|
||||
var actualChunkIndex ActualChunkIndex
|
||||
if len(sf.freeActualChunkList) > 0 {
|
||||
actualChunkIndex = sf.freeActualChunkList[0]
|
||||
sf.freeActualChunkList = sf.freeActualChunkList[1:]
|
||||
} else {
|
||||
actualChunkIndex = ActualChunkIndex(sf.activeChunkCount)
|
||||
}
|
||||
|
||||
return &SwapFileChunk{
|
||||
swapFileChunk := &SwapFileChunk{
|
||||
swapfile: sf,
|
||||
usage: newChunkWrittenIntervalList(),
|
||||
logicChunkIndex: logicChunkIndex,
|
||||
actualChunkIndex: actualChunkIndex,
|
||||
activityScore: NewActivityScore(),
|
||||
// memChunk: NewMemChunk(logicChunkIndex, sf.chunkSize),
|
||||
}
|
||||
|
||||
// println(logicChunkIndex, "|", "++++", swapFileChunk.actualChunkIndex, swapFileChunk, sf)
|
||||
return swapFileChunk
|
||||
}
|
||||
|
||||
func (sc *SwapFileChunk) FreeResource() {
|
||||
sc.swapfile.logicToActualChunkIndexLock.Lock()
|
||||
defer sc.swapfile.logicToActualChunkIndexLock.Unlock()
|
||||
|
||||
sc.Lock()
|
||||
defer sc.Unlock()
|
||||
|
||||
sc.swapfile.chunkTrackingLock.Lock()
|
||||
defer sc.swapfile.chunkTrackingLock.Unlock()
|
||||
|
||||
sc.swapfile.freeActualChunkList = append(sc.swapfile.freeActualChunkList, sc.actualChunkIndex)
|
||||
delete(sc.swapfile.logicToActualChunkIndex, sc.logicChunkIndex)
|
||||
sc.swapfile.activeChunkCount--
|
||||
// println(sc.logicChunkIndex, "|", "----", sc.actualChunkIndex, sc, sc.swapfile)
|
||||
}
|
||||
|
||||
func (sc *SwapFileChunk) WriteDataAt(src []byte, offset int64) (n int) {
|
||||
func (sc *SwapFileChunk) WriteDataAt(src []byte, offset int64, tsNs int64) (n int) {
|
||||
sc.Lock()
|
||||
defer sc.Unlock()
|
||||
|
||||
// println(sc.logicChunkIndex, "|", tsNs, "write at", offset, len(src), sc.actualChunkIndex)
|
||||
|
||||
innerOffset := offset % sc.swapfile.chunkSize
|
||||
var err error
|
||||
n, err = sc.swapfile.file.WriteAt(src, int64(sc.actualChunkIndex)*sc.swapfile.chunkSize+innerOffset)
|
||||
if err == nil {
|
||||
sc.usage.MarkWritten(innerOffset, innerOffset+int64(n))
|
||||
} else {
|
||||
sc.usage.MarkWritten(innerOffset, innerOffset+int64(n), tsNs)
|
||||
if err != nil {
|
||||
glog.Errorf("failed to write swap file %s: %v", sc.swapfile.file.Name(), err)
|
||||
}
|
||||
//sc.memChunk.WriteDataAt(src, offset, tsNs)
|
||||
sc.activityScore.MarkWrite()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (sc *SwapFileChunk) ReadDataAt(p []byte, off int64) (maxStop int64) {
|
||||
func (sc *SwapFileChunk) ReadDataAt(p []byte, off int64, tsNs int64) (maxStop int64) {
|
||||
sc.RLock()
|
||||
defer sc.RUnlock()
|
||||
|
||||
// println(sc.logicChunkIndex, "|", tsNs, "read at", off, len(p), sc.actualChunkIndex)
|
||||
|
||||
//memCopy := make([]byte, len(p))
|
||||
//copy(memCopy, p)
|
||||
|
||||
chunkStartOffset := int64(sc.logicChunkIndex) * sc.swapfile.chunkSize
|
||||
for t := sc.usage.head.next; t != sc.usage.tail; t = t.next {
|
||||
logicStart := max(off, chunkStartOffset+t.StartOffset)
|
||||
logicStop := min(off+int64(len(p)), chunkStartOffset+t.stopOffset)
|
||||
if logicStart < logicStop {
|
||||
actualStart := logicStart - chunkStartOffset + int64(sc.actualChunkIndex)*sc.swapfile.chunkSize
|
||||
if _, err := sc.swapfile.file.ReadAt(p[logicStart-off:logicStop-off], actualStart); err != nil {
|
||||
glog.Errorf("failed to reading swap file %s: %v", sc.swapfile.file.Name(), err)
|
||||
break
|
||||
if t.TsNs >= tsNs {
|
||||
actualStart := logicStart - chunkStartOffset + int64(sc.actualChunkIndex)*sc.swapfile.chunkSize
|
||||
if _, err := sc.swapfile.file.ReadAt(p[logicStart-off:logicStop-off], actualStart); err != nil {
|
||||
glog.Errorf("failed to reading swap file %s: %v", sc.swapfile.file.Name(), err)
|
||||
break
|
||||
}
|
||||
maxStop = max(maxStop, logicStop)
|
||||
} else {
|
||||
println("read old data2", tsNs-t.TsNs, "ns")
|
||||
}
|
||||
maxStop = max(maxStop, logicStop)
|
||||
}
|
||||
}
|
||||
//sc.memChunk.ReadDataAt(memCopy, off, tsNs)
|
||||
//if bytes.Compare(memCopy, p) != 0 {
|
||||
// println("read wrong data from swap file", off, sc.logicChunkIndex)
|
||||
//}
|
||||
|
||||
sc.activityScore.MarkRead()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -128,27 +157,27 @@ func (sc *SwapFileChunk) IsComplete() bool {
|
||||
return sc.usage.IsComplete(sc.swapfile.chunkSize)
|
||||
}
|
||||
|
||||
func (sc *SwapFileChunk) WrittenSize() int64 {
|
||||
sc.RLock()
|
||||
defer sc.RUnlock()
|
||||
return sc.usage.WrittenSize()
|
||||
func (sc *SwapFileChunk) ActivityScore() int64 {
|
||||
return sc.activityScore.ActivityScore()
|
||||
}
|
||||
|
||||
func (sc *SwapFileChunk) SaveContent(saveFn SaveToStorageFunc) {
|
||||
sc.RLock()
|
||||
defer sc.RUnlock()
|
||||
|
||||
if saveFn == nil {
|
||||
return
|
||||
}
|
||||
sc.Lock()
|
||||
defer sc.Unlock()
|
||||
|
||||
// println(sc.logicChunkIndex, "|", "save")
|
||||
for t := sc.usage.head.next; t != sc.usage.tail; t = t.next {
|
||||
data := mem.Allocate(int(t.Size()))
|
||||
sc.swapfile.file.ReadAt(data, t.StartOffset+int64(sc.actualChunkIndex)*sc.swapfile.chunkSize)
|
||||
reader := util.NewBytesReader(data)
|
||||
saveFn(reader, int64(sc.logicChunkIndex)*sc.swapfile.chunkSize+t.StartOffset, t.Size(), func() {
|
||||
})
|
||||
n, _ := sc.swapfile.file.ReadAt(data, t.StartOffset+int64(sc.actualChunkIndex)*sc.swapfile.chunkSize)
|
||||
if n > 0 {
|
||||
reader := util.NewBytesReader(data[:n])
|
||||
saveFn(reader, int64(sc.logicChunkIndex)*sc.swapfile.chunkSize+t.StartOffset, int64(n), t.TsNs, func() {
|
||||
})
|
||||
}
|
||||
mem.Free(data)
|
||||
}
|
||||
|
||||
sc.usage = newChunkWrittenIntervalList()
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
@@ -55,7 +56,8 @@ func NewUploadPipeline(writers *util.LimitedConcurrentExecutor, chunkSize int64,
|
||||
return t
|
||||
}
|
||||
|
||||
func (up *UploadPipeline) SaveDataAt(p []byte, off int64, isSequential bool) (n int) {
|
||||
func (up *UploadPipeline) SaveDataAt(p []byte, off int64, isSequential bool, tsNs int64) (n int) {
|
||||
|
||||
up.chunksLock.Lock()
|
||||
defer up.chunksLock.Unlock()
|
||||
|
||||
@@ -65,33 +67,39 @@ func (up *UploadPipeline) SaveDataAt(p []byte, off int64, isSequential bool) (n
|
||||
if !found {
|
||||
if len(up.writableChunks) > up.writableChunkLimit {
|
||||
// if current file chunks is over the per file buffer count limit
|
||||
fullestChunkIndex, fullness := LogicChunkIndex(-1), int64(0)
|
||||
for lci, mc := range up.writableChunks {
|
||||
chunkFullness := mc.WrittenSize()
|
||||
if fullness < chunkFullness {
|
||||
fullestChunkIndex = lci
|
||||
fullness = chunkFullness
|
||||
laziestChunkIndex, lowestActivityScore := LogicChunkIndex(-1), int64(math.MaxInt64)
|
||||
for wci, wc := range up.writableChunks {
|
||||
activityScore := wc.ActivityScore()
|
||||
if lowestActivityScore > activityScore {
|
||||
laziestChunkIndex = wci
|
||||
lowestActivityScore = activityScore
|
||||
}
|
||||
}
|
||||
up.moveToSealed(up.writableChunks[fullestChunkIndex], fullestChunkIndex)
|
||||
// fmt.Printf("flush chunk %d with %d bytes written\n", logicChunkIndex, fullness)
|
||||
up.moveToSealed(up.writableChunks[laziestChunkIndex], laziestChunkIndex)
|
||||
// fmt.Printf("flush chunk %d with %d bytes written\n", logicChunkIndex, oldestTs)
|
||||
}
|
||||
if isSequential &&
|
||||
len(up.writableChunks) < up.writableChunkLimit &&
|
||||
atomic.LoadInt64(&memChunkCounter) < 4*int64(up.writableChunkLimit) {
|
||||
pageChunk = NewMemChunk(logicChunkIndex, up.ChunkSize)
|
||||
} else {
|
||||
pageChunk = up.swapFile.NewTempFileChunk(logicChunkIndex)
|
||||
pageChunk = up.swapFile.NewSwapFileChunk(logicChunkIndex)
|
||||
}
|
||||
up.writableChunks[logicChunkIndex] = pageChunk
|
||||
}
|
||||
n = pageChunk.WriteDataAt(p, off)
|
||||
//if _, foundSealed := up.sealedChunks[logicChunkIndex]; foundSealed {
|
||||
// println("found already sealed chunk", logicChunkIndex)
|
||||
//}
|
||||
//if _, foundReading := up.activeReadChunks[logicChunkIndex]; foundReading {
|
||||
// println("found active read chunk", logicChunkIndex)
|
||||
//}
|
||||
n = pageChunk.WriteDataAt(p, off, tsNs)
|
||||
up.maybeMoveToSealed(pageChunk, logicChunkIndex)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (up *UploadPipeline) MaybeReadDataAt(p []byte, off int64) (maxStop int64) {
|
||||
func (up *UploadPipeline) MaybeReadDataAt(p []byte, off int64, tsNs int64) (maxStop int64) {
|
||||
logicChunkIndex := LogicChunkIndex(off / up.ChunkSize)
|
||||
|
||||
up.chunksLock.Lock()
|
||||
@@ -103,12 +111,8 @@ func (up *UploadPipeline) MaybeReadDataAt(p []byte, off int64) (maxStop int64) {
|
||||
// read from sealed chunks first
|
||||
sealedChunk, found := up.sealedChunks[logicChunkIndex]
|
||||
if found {
|
||||
sealedChunk.referenceCounter++
|
||||
}
|
||||
if found {
|
||||
maxStop = sealedChunk.chunk.ReadDataAt(p, off)
|
||||
maxStop = sealedChunk.chunk.ReadDataAt(p, off, tsNs)
|
||||
glog.V(4).Infof("%s read sealed memchunk [%d,%d)", up.filepath, off, maxStop)
|
||||
sealedChunk.FreeReference(fmt.Sprintf("%s finish reading chunk %d", up.filepath, logicChunkIndex))
|
||||
}
|
||||
|
||||
// read from writable chunks last
|
||||
@@ -116,7 +120,7 @@ func (up *UploadPipeline) MaybeReadDataAt(p []byte, off int64) (maxStop int64) {
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
writableMaxStop := writableChunk.ReadDataAt(p, off)
|
||||
writableMaxStop := writableChunk.ReadDataAt(p, off, tsNs)
|
||||
glog.V(4).Infof("%s read writable memchunk [%d,%d)", up.filepath, off, writableMaxStop)
|
||||
maxStop = max(maxStop, writableMaxStop)
|
||||
|
||||
|
||||
@@ -31,14 +31,14 @@ func writeRange(uploadPipeline *UploadPipeline, startOff, stopOff int64) {
|
||||
p := make([]byte, 4)
|
||||
for i := startOff / 4; i < stopOff/4; i += 4 {
|
||||
util.Uint32toBytes(p, uint32(i))
|
||||
uploadPipeline.SaveDataAt(p, i, false)
|
||||
uploadPipeline.SaveDataAt(p, i, false, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func confirmRange(t *testing.T, uploadPipeline *UploadPipeline, startOff, stopOff int64) {
|
||||
p := make([]byte, 4)
|
||||
for i := startOff; i < stopOff/4; i += 4 {
|
||||
uploadPipeline.MaybeReadDataAt(p, i)
|
||||
uploadPipeline.MaybeReadDataAt(p, i, 0)
|
||||
x := util.BytesToUint32(p)
|
||||
if x != uint32(i) {
|
||||
t.Errorf("expecting %d found %d at offset [%d,%d)", i, x, i, i+4)
|
||||
|
||||
Reference in New Issue
Block a user