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

X ΔTail draftly done

parent 6e679a97
......@@ -77,6 +77,7 @@ digraph {
wcfsRead [label="read(#blk)"]
blktabGet [label="blktab.Get(#blk):\nmanually + → ⌈rev(#blk)⌉"]
δFtail [style=filled fillcolor=grey95]
mappingRegister [label="mmappings:\nregister/maint"]
clientInvHandle [label="process\n#blk invalidations"]
......
......@@ -162,7 +162,7 @@
<!-- δFtail -->
<g id="node10" class="node">
<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>
</g>
<!-- wcfsInvProcess&#45;&gt;δFtail -->
......
......@@ -268,7 +268,7 @@ package main
// - if retrieved successfully -> store retrieved data back into OS file
// 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.
//
......@@ -279,16 +279,14 @@ package main
//
// 5) for every file δFtail invalidation info about head/data is maintained:
//
// - tail: of [](rev↑, []#blk)
// - by: {} #blk -> []rev↑ in tail
// - tailv: [](rev↑, []#blk)
// - by: {} #blk -> []rev↑ in tail
//
// δFtail.tail describes invalidations to file we learned from ZODB invalidation.
// δ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).
//
// 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.1) load blkdata for head/data[blk] @zconn.at .
......@@ -307,7 +305,7 @@ package main
//
// 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):
......@@ -320,7 +318,7 @@ package main
// - rev'(blk) ≤ at: -> do nothing XXX || blk ∉ mapping
// - rev'(blk) > at:
// - 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)
// - mmapping.pinned += blk
//
......
......@@ -20,19 +20,21 @@
// XXX -> internal/δtail/ ?
package main
// δtail maintenance XXX
// δtail maintenance
import (
"fmt"
"lab.nexedi.com/kirr/neo/go/zodb"
)
type ID int64 // XXX -> template
type ID int64 // XXX -> template ?
// ΔTail represents tail of revisional changes.
//
// It semantically consists of
//
// [] of (rev↑, []id)
// [](rev↑, []id)
//
// where
//
......@@ -46,24 +48,24 @@ type ID int64 // XXX -> template
// - 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 safe to perform multiple simultaneous queries.
// It is safe to perform multiple simultaneous read-kind operations.
//
// (*) examples of id:
//
// oid - ZODB object identifier, when ΔTail represents changes to ZODB objects,
// #blk - file block number, when ΔTail represents changes to a file.
type ΔTail struct {
tailv []δRevEntry
lastRevOf map[ID]zodb.Tid
tailv []δRevEntry
lastRevOf map[ID]zodb.Tid // index for LastRevOf queries
// TODO also add either tailv idx <-> rev index, or lastRevOf -> tailv idx
// (if linear back-scan of δRevEntry starts eat cpu).
}
// δRevEntry represents information of what have been changed in one revision.
// XXX naming
type δRevEntry struct {
rev zodb.Tid
δv []ID
changev []ID
}
// NewΔTail creates new ΔTail object.
......@@ -75,10 +77,17 @@ func NewΔTail() *ΔTail {
// Append appends to δtail information about what have been changed in next revision.
//
// rev must be ↑.
func (δtail *ΔTail) Append(rev zodb.Tid, δv []ID) {
// XXX check rev↑
δtail.tailv = append(δtail.tailv, δRevEntry{rev, δv})
for _, id := range δv {
func (δtail *ΔTail) Append(rev zodb.Tid, changev []ID) {
// check rev↑
// XXX better also check even when δtail is ø (after forget)
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
}
}
......@@ -93,51 +102,86 @@ func (δtail *ΔTail) ForgetBefore(revCut zodb.Tid) {
}
icut = i+1
// if forgottent revision was last for id, we have to update lastRevOf index
for _, id := range δ.δv {
// if forgotten revision was last for id, we have to update lastRevOf index
for _, id := range δ.changev {
if δtail.lastRevOf[id] == rev {
delete(δtail.lastRevOf, id)
}
}
}
// tailv = tailv[icut:] but without growing underlying storage array indefinetely
copy(δtail.tailv, δtail.tailv[icut:])
δtail.tailv = δtail.tailv[:len(δtail.tailv)-icut]
// tailv = tailv[icut:] but without
// 1) growing underlying storage array indefinitely
// 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)
//
// XXX if δtail does not contain records with id -> what is returned?
func (δtail *ΔTail) LastRevOf(id ID, at zodb.Tid) zodb.Tid {
// 3) if δtail does not contain appropriate record with id - it returns δtail's
// 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]
if !ok {
panic("TODO") // XXX
return δtail.tailv[0].rev, false
}
if rev <= at {
return rev
return rev, true
}
// what's in index is after at - scan tailv back to find appropriate entry
// XXX linear scan
for i := len(δtail.tailv) - 1; i >= 0; i-- {
for i := l - 1; i >= 0; i-- {
δ := δtail.tailv[i]
if δ.rev > at {
continue
}
for _, δid := range δ.δv {
for _, δid := range δ.changev {
if id == δid {
return δ.rev
return δ.rev, true
}
}
}
// nothing found
panic("TODO") // XXX
return δtail.tailv[0].rev, false
}
......@@ -20,6 +20,7 @@
package main
import (
"fmt"
"reflect"
"testing"
......@@ -33,14 +34,14 @@ func TestΔTail(t *testing.T) {
δtail := NewΔTail()
// R is syntic sugar to create 1 δRevEntry
R := func(rev zodb.Tid, δv ...ID) δRevEntry {
return δRevEntry{rev, δv}
// R is syntactic sugar to create 1 δRevEntry
R := func(rev zodb.Tid, changev ...ID) δRevEntry {
return δRevEntry{rev, changev}
}
// δAppend is syntatic sugar for δtail.Append
// δAppend is syntactic sugar for δtail.Append
δAppend := func(δ δRevEntry) {
δtail.Append(δ.rev, δ.δv)
δtail.Append(δ.rev, δ.changev)
}
// δCheck verifies that δtail state corresponds to provided tailv
......@@ -60,10 +61,10 @@ func TestΔTail(t *testing.T) {
// verify lastRevOf query / index
lastRevOf := make(map[ID]zodb.Tid)
for _, δ := range tailv {
for _, id := range δ.δv {
idRev := δtail.LastRevOf(id, δ.rev)
if idRev != δ.rev {
t.Fatalf("LastRevOf(%v, at=%s) -> %s ; want %s", id, δ.rev, idRev, δ.rev)
for _, id := range δ.changev {
idRev, exact := δtail.LastRevOf(id, δ.rev)
if !(idRev == δ.rev && exact) {
t.Fatalf("LastRevOf(%v, at=%s) -> %s, %v ; want %s, %v", id, δ.rev, idRev, exact, δ.rev, true)
}
lastRevOf[id] = δ.rev
......@@ -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()
δCheckLastUP(4, 12, 12) // δtail = ø
δAppend(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))
δCheck(R(10, 3,5), R(11, 7))
δAppend(R(12, 7))
δCheck(R(10, 3,5), R(11, 7), R(12, 7))
δAppend(R(14, 3,7))
δCheck(R(10, 3,5), R(11, 7), R(12, 7), R(14, 3,7))
δAppend(R(14, 3,8))
δ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)
δ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)
δ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)
δCheck(R(14, 3,7))
δCheck(R(14, 3,8))
δtail.ForgetBefore(15)
δ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)
// XXX edge cases
// XXX .tailv underlying storage does not grow indefinitely
// .tailv underlying storage does not grow indefinitely
// XXX cannot test as the growth here goes to left and we cannot get
// access to whole underlying array from a slice.
}
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