Commit 33e0dfce authored by Kirill Smelkov's avatar Kirill Smelkov

X ΔTail draftly done

parent 6e679a97
...@@ -77,6 +77,7 @@ digraph { ...@@ -77,6 +77,7 @@ digraph {
wcfsRead [label="read(#blk)"] wcfsRead [label="read(#blk)"]
blktabGet [label="blktab.Get(#blk):\nmanually + → ⌈rev(#blk)⌉"] blktabGet [label="blktab.Get(#blk):\nmanually + → ⌈rev(#blk)⌉"]
δFtail [style=filled fillcolor=grey95]
mappingRegister [label="mmappings:\nregister/maint"] mappingRegister [label="mmappings:\nregister/maint"]
clientInvHandle [label="process\n#blk invalidations"] clientInvHandle [label="process\n#blk invalidations"]
......
...@@ -162,7 +162,7 @@ ...@@ -162,7 +162,7 @@
<!-- δFtail --> <!-- δFtail -->
<g id="node10" class="node"> <g id="node10" class="node">
<title>δFtail</title> <title>δFtail</title>
<ellipse fill="none" stroke="#000000" cx="797.8112" cy="-188.6102" rx="31.6951" ry="18"/> <ellipse fill="#f2f2f2" stroke="#000000" cx="797.8112" cy="-188.6102" rx="31.6951" ry="18"/>
<text text-anchor="middle" x="797.8112" y="-184.9102" font-family="Times,serif" font-size="14.00" fill="#000000">δFtail</text> <text text-anchor="middle" x="797.8112" y="-184.9102" font-family="Times,serif" font-size="14.00" fill="#000000">δFtail</text>
</g> </g>
<!-- wcfsInvProcess&#45;&gt;δFtail --> <!-- wcfsInvProcess&#45;&gt;δFtail -->
......
...@@ -268,7 +268,7 @@ package main ...@@ -268,7 +268,7 @@ package main
// - if retrieved successfully -> store retrieved data back into OS file // - if retrieved successfully -> store retrieved data back into OS file
// cache for file/@<rev>/data[blk], where // cache for file/@<rev>/data[blk], where
// //
// rev = max(δFtail.by(#blk)) || zconn.at ; see below about δFtail // rev = max(δFtail.by(#blk)) || min(rev ∈ δFtail) || zconn.at ; see below about δFtail
// //
// - invalidate file/head/data[blk] in OS file cache. // - invalidate file/head/data[blk] in OS file cache.
// //
...@@ -279,16 +279,14 @@ package main ...@@ -279,16 +279,14 @@ package main
// //
// 5) for every file δFtail invalidation info about head/data is maintained: // 5) for every file δFtail invalidation info about head/data is maintained:
// //
// - tail: of [](rev↑, []#blk) // - tailv: [](rev↑, []#blk)
// - by: {} #blk -> []rev↑ in tail // - by: {} #blk -> []rev↑ in tail
// //
// δFtail.tail describes invalidations to file we learned from ZODB invalidation. // δFtail.tail describes invalidations to file we learned from ZODB invalidation.
// δFtail.by allows to quickly lookup information by #blk. // δFtail.by allows to quickly lookup information by #blk.
// //
// min(rev) in δFtail is min(@at) at which head/data is currently mmapped (see below). // min(rev) in δFtail is min(@at) at which head/data is currently mmapped (see below).
// //
// TODO invariant -> δFtail - which entries are present?
//
// 6) when we receive a FUSE read(#blk) request to a file/head/data we process it as follows: // 6) when we receive a FUSE read(#blk) request to a file/head/data we process it as follows:
// //
// 6.1) load blkdata for head/data[blk] @zconn.at . // 6.1) load blkdata for head/data[blk] @zconn.at .
...@@ -307,7 +305,7 @@ package main ...@@ -307,7 +305,7 @@ package main
// //
// or another upper bound if #blk ∉ δFtail: // or another upper bound if #blk ∉ δFtail:
// //
// rev(blk) ≤ min(rev) in δFtail ; #blk ∉ δFtail // rev(blk) ≤ min(rev ∈ δFtail) ; #blk ∉ δFtail
// //
// //
// below rev'(blk) is min(of the estimates found): // below rev'(blk) is min(of the estimates found):
...@@ -320,7 +318,7 @@ package main ...@@ -320,7 +318,7 @@ package main
// - rev'(blk) ≤ at: -> do nothing XXX || blk ∉ mapping // - rev'(blk) ≤ at: -> do nothing XXX || blk ∉ mapping
// - rev'(blk) > at: // - rev'(blk) > at:
// - if blk ∈ mmapping.pinned -> do nothing // - if blk ∈ mmapping.pinned -> do nothing
// - rev = max(δFtail.by(#blk) : _ ≤ at) || at // - rev = max(δFtail.by(#blk) : _ ≤ at) || min(rev ∈ δFtail : rev ≤ at) || at
// - client.remmap(addr[blk], file/@rev/data) // - client.remmap(addr[blk], file/@rev/data)
// - mmapping.pinned += blk // - mmapping.pinned += blk
// //
......
...@@ -20,19 +20,21 @@ ...@@ -20,19 +20,21 @@
// XXX -> internal/δtail/ ? // XXX -> internal/δtail/ ?
package main package main
// δtail maintenance XXX // δtail maintenance
import ( import (
"fmt"
"lab.nexedi.com/kirr/neo/go/zodb" "lab.nexedi.com/kirr/neo/go/zodb"
) )
type ID int64 // XXX -> template type ID int64 // XXX -> template ?
// ΔTail represents tail of revisional changes. // ΔTail represents tail of revisional changes.
// //
// It semantically consists of // It semantically consists of
// //
// [] of (rev↑, []id) // [](rev↑, []id)
// //
// where // where
// //
...@@ -46,24 +48,24 @@ type ID int64 // XXX -> template ...@@ -46,24 +48,24 @@ type ID int64 // XXX -> template
// - query the tail about what is last revision that changed an id. // - query the tail about what is last revision that changed an id.
// //
// It is generally not safe to use ΔTail from multiple goroutines simultaneously. // It is generally not safe to use ΔTail from multiple goroutines simultaneously.
// It is safe to perform multiple simultaneous queries. // It is safe to perform multiple simultaneous read-kind operations.
// //
// (*) examples of id: // (*) examples of id:
// //
// oid - ZODB object identifier, when ΔTail represents changes to ZODB objects, // oid - ZODB object identifier, when ΔTail represents changes to ZODB objects,
// #blk - file block number, when ΔTail represents changes to a file. // #blk - file block number, when ΔTail represents changes to a file.
type ΔTail struct { type ΔTail struct {
tailv []δRevEntry tailv []δRevEntry
lastRevOf map[ID]zodb.Tid
lastRevOf map[ID]zodb.Tid // index for LastRevOf queries
// TODO also add either tailv idx <-> rev index, or lastRevOf -> tailv idx // TODO also add either tailv idx <-> rev index, or lastRevOf -> tailv idx
// (if linear back-scan of δRevEntry starts eat cpu). // (if linear back-scan of δRevEntry starts eat cpu).
} }
// δRevEntry represents information of what have been changed in one revision. // δRevEntry represents information of what have been changed in one revision.
// XXX naming
type δRevEntry struct { type δRevEntry struct {
rev zodb.Tid rev zodb.Tid
δv []ID changev []ID
} }
// NewΔTail creates new ΔTail object. // NewΔTail creates new ΔTail object.
...@@ -75,10 +77,17 @@ func NewΔTail() *ΔTail { ...@@ -75,10 +77,17 @@ func NewΔTail() *ΔTail {
// Append appends to δtail information about what have been changed in next revision. // Append appends to δtail information about what have been changed in next revision.
// //
// rev must be ↑. // rev must be ↑.
func (δtail *ΔTail) Append(rev zodb.Tid, δv []ID) { func (δtail *ΔTail) Append(rev zodb.Tid, changev []ID) {
// XXX check rev↑ // check rev↑
δtail.tailv = append(δtail.tailv, δRevEntry{rev, δv}) // XXX better also check even when δtail is ø (after forget)
for _, id := range δv { if l := len(δtail.tailv); l > 0 {
if revPrev := δtail.tailv[l-1].rev; revPrev >= rev {
panic(fmt.Sprintf("δtail.Append: rev not ↑: %s -> %s", revPrev, rev))
}
}
δtail.tailv = append(δtail.tailv, δRevEntry{rev, changev})
for _, id := range changev {
δtail.lastRevOf[id] = rev δtail.lastRevOf[id] = rev
} }
} }
...@@ -93,51 +102,86 @@ func (δtail *ΔTail) ForgetBefore(revCut zodb.Tid) { ...@@ -93,51 +102,86 @@ func (δtail *ΔTail) ForgetBefore(revCut zodb.Tid) {
} }
icut = i+1 icut = i+1
// if forgottent revision was last for id, we have to update lastRevOf index // if forgotten revision was last for id, we have to update lastRevOf index
for _, id := range δ.δv { for _, id := range δ.changev {
if δtail.lastRevOf[id] == rev { if δtail.lastRevOf[id] == rev {
delete(δtail.lastRevOf, id) delete(δtail.lastRevOf, id)
} }
} }
} }
// tailv = tailv[icut:] but without growing underlying storage array indefinetely // tailv = tailv[icut:] but without
copy(δtail.tailv, δtail.tailv[icut:]) // 1) growing underlying storage array indefinitely
δtail.tailv = δtail.tailv[:len(δtail.tailv)-icut] // 2) keeping underlying storage after forget
l := len(δtail.tailv)-icut
tailv := make([]δRevEntry, l)
copy(tailv, δtail.tailv[icut:])
δtail.tailv = tailv
} }
// LastRevOf returns what was the last revision that changed id as of at database state. // LastRevOf tries to return what was the last revision that changed id as of at database state.
//
// Depending on current information in δtail it returns either exact result, or
// an upper-bound estimate for the last id revision, assuming id was changed ≤ at:
//
// 1) if δtail does not cover at, at is returned:
// //
// in other words // # at ∉ [min(rev ∈ δtail), max(rev ∈ δtail)]
// LastRevOf(id, at) = at
// //
// 2) if δtail has an entry corresponding to id change, it gives exactly the
// last revision that changed id:
//
// # at ∈ [min(rev ∈ δtail), max(rev ∈ δtail)]
// # ∃ rev ∈ δtail: rev changed id && rev ≤ at
// LastRevOf(id, at) = max(rev: rev changed id && rev ≤ at) // LastRevOf(id, at) = max(rev: rev changed id && rev ≤ at)
// //
// XXX if δtail does not contain records with id -> what is returned? // 3) if δtail does not contain appropriate record with id - it returns δtail's
func (δtail *ΔTail) LastRevOf(id ID, at zodb.Tid) zodb.Tid { // lower bound as the estimate for the upper bound of the last id revision:
//
// # at ∈ [min(rev ∈ δtail), max(rev ∈ δtail)]
// # ∄ rev ∈ δtail: rev changed id && rev ≤ at
// LastRevOf(id, at) = min(rev ∈ δtail)
//
// On return exact indicates whether returned revision is exactly the last
// revision of id, or only an upper-bound estimate of it.
func (δtail *ΔTail) LastRevOf(id ID, at zodb.Tid) (_ zodb.Tid, exact bool) {
// check if we have no coverage at all
l := len(δtail.tailv)
if l == 0 {
return at, false
}
revMin := δtail.tailv[0].rev
revMax := δtail.tailv[l-1].rev
if !(revMin <= at && at <= revMax) {
return at, false
}
// we have the coverage
rev, ok := δtail.lastRevOf[id] rev, ok := δtail.lastRevOf[id]
if !ok { if !ok {
panic("TODO") // XXX return δtail.tailv[0].rev, false
} }
if rev <= at { if rev <= at {
return rev return rev, true
} }
// what's in index is after at - scan tailv back to find appropriate entry // what's in index is after at - scan tailv back to find appropriate entry
// XXX linear scan // XXX linear scan
for i := len(δtail.tailv) - 1; i >= 0; i-- { for i := l - 1; i >= 0; i-- {
δ := δtail.tailv[i] δ := δtail.tailv[i]
if δ.rev > at { if δ.rev > at {
continue continue
} }
for _, δid := range δ.δv { for _, δid := range δ.changev {
if id == δid { if id == δid {
return δ.rev return δ.rev, true
} }
} }
} }
// nothing found // nothing found
panic("TODO") // XXX return δtail.tailv[0].rev, false
} }
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
package main package main
import ( import (
"fmt"
"reflect" "reflect"
"testing" "testing"
...@@ -33,14 +34,14 @@ func TestΔTail(t *testing.T) { ...@@ -33,14 +34,14 @@ func TestΔTail(t *testing.T) {
δtail := NewΔTail() δtail := NewΔTail()
// R is syntic sugar to create 1 δRevEntry // R is syntactic sugar to create 1 δRevEntry
R := func(rev zodb.Tid, δv ...ID) δRevEntry { R := func(rev zodb.Tid, changev ...ID) δRevEntry {
return δRevEntry{rev, δv} return δRevEntry{rev, changev}
} }
// δAppend is syntatic sugar for δtail.Append // δAppend is syntactic sugar for δtail.Append
δAppend := func(δ δRevEntry) { δAppend := func(δ δRevEntry) {
δtail.Append(δ.rev, δ.δv) δtail.Append(δ.rev, δ.changev)
} }
// δCheck verifies that δtail state corresponds to provided tailv // δCheck verifies that δtail state corresponds to provided tailv
...@@ -60,10 +61,10 @@ func TestΔTail(t *testing.T) { ...@@ -60,10 +61,10 @@ func TestΔTail(t *testing.T) {
// verify lastRevOf query / index // verify lastRevOf query / index
lastRevOf := make(map[ID]zodb.Tid) lastRevOf := make(map[ID]zodb.Tid)
for _, δ := range tailv { for _, δ := range tailv {
for _, id := range δ.δv { for _, id := range δ.changev {
idRev := δtail.LastRevOf(id, δ.rev) idRev, exact := δtail.LastRevOf(id, δ.rev)
if idRev != δ.rev { if !(idRev == δ.rev && exact) {
t.Fatalf("LastRevOf(%v, at=%s) -> %s ; want %s", id, δ.rev, idRev, δ.rev) t.Fatalf("LastRevOf(%v, at=%s) -> %s, %v ; want %s, %v", id, δ.rev, idRev, exact, δ.rev, true)
} }
lastRevOf[id] = δ.rev lastRevOf[id] = δ.rev
...@@ -76,37 +77,87 @@ func TestΔTail(t *testing.T) { ...@@ -76,37 +77,87 @@ func TestΔTail(t *testing.T) {
} }
// δCheckLastUP verifies that δtail.LastRevOf(id, at) gives lastOk and exact=false.
// (we don't need to check for exact=true as those cases are covered in δCheck)
δCheckLastUP := func(id ID, at, lastOk zodb.Tid) {
t.Helper()
last, exact := δtail.LastRevOf(id, at)
if !(last == lastOk && exact == false) {
t.Fatalf("LastRevOf(%v, at=%s) -> %s, %v ; want %s, %v", id, at, last, exact, lastOk, false)
}
}
δCheck() δCheck()
δCheckLastUP(4, 12, 12) // δtail = ø
δAppend(R(10, 3,5)) δAppend(R(10, 3,5))
δCheck(R(10, 3,5)) δCheck(R(10, 3,5))
δCheckLastUP(3, 9, 9) // at < δtail
δCheckLastUP(3, 12, 12) // at > δtail
δCheckLastUP(4, 10, 10) // id ∉ δtail
δAppend(R(11, 7)) δAppend(R(11, 7))
δCheck(R(10, 3,5), R(11, 7)) δCheck(R(10, 3,5), R(11, 7))
δAppend(R(12, 7)) δAppend(R(12, 7))
δCheck(R(10, 3,5), R(11, 7), R(12, 7)) δCheck(R(10, 3,5), R(11, 7), R(12, 7))
δAppend(R(14, 3,7)) δAppend(R(14, 3,8))
δCheck(R(10, 3,5), R(11, 7), R(12, 7), R(14, 3,7)) δCheck(R(10, 3,5), R(11, 7), R(12, 7), R(14, 3,8))
δCheckLastUP(8, 12, 10) // id ∈ δtail, but has no entry with rev ≤ at
δtail.ForgetBefore(10) δtail.ForgetBefore(10)
δCheck(R(10, 3,5), R(11, 7), R(12, 7), R(14, 3,7)) δCheck(R(10, 3,5), R(11, 7), R(12, 7), R(14, 3,8))
δtail.ForgetBefore(11) δtail.ForgetBefore(11)
δCheck(R(11, 7), R(12, 7), R(14, 3,7)) δCheck(R(11, 7), R(12, 7), R(14, 3,8))
δtail.ForgetBefore(13) δtail.ForgetBefore(13)
δCheck(R(14, 3,7)) δCheck(R(14, 3,8))
δtail.ForgetBefore(15) δtail.ForgetBefore(15)
δCheck() δCheck()
// Append panics on non-↑ rev
δAppend(R(15, 1))
func() {
defer func() {
r := recover()
if r == nil {
t.Fatal("append non-↑: not panicked")
}
rev := zodb.Tid(15)
want := fmt.Sprintf("δtail.Append: rev not ↑: %s -> %s", rev, rev)
if r != want {
t.Fatalf("append non-↑:\nhave: %q\nwant: %q", r, want)
}
}()
δAppend(R(15, 1))
}()
// .tailv underlying storage is not kept after forget
δtail.ForgetBefore(16)
const N = 1E3
for rev, i := zodb.Tid(16), 0; i < N; i, rev = i+1, rev+1 {
δAppend(R(rev, 1))
}
capN := cap(δtail.tailv)
δtail.ForgetBefore(N)
if c := cap(δtail.tailv); !(c < capN/10) {
t.Fatalf("forget: tailv storage did not shrink: cap%v: %d -> cap: %d", N, capN, c)
}
// XXX .Append(rev not ↑ - panic) // .tailv underlying storage does not grow indefinitely
// XXX edge cases // XXX cannot test as the growth here goes to left and we cannot get
// XXX .tailv underlying storage does not grow indefinitely // access to whole underlying array from a slice.
} }
func tailvEqual(a, b []δRevEntry) bool { func tailvEqual(a, b []δRevEntry) bool {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment