Commit 1f481eb8 authored by Kirill Smelkov's avatar Kirill Smelkov

.

parent 05e3d50a
......@@ -612,6 +612,7 @@ type BigFile struct {
// //
// // XXX computationally expensive to start - see "Invalidations to wcfs
// // clients are delayed ..." in notes.txt
// //go:generate ./gen-δtail I64 int64 zδtail_i64.go
// δtail *ΔTailI64 // [](rev↑, []#blk)
// inflight loadings of ZBigFile from ZODB.
......
// Code generated by gen-δtail I64 int64; DO NOT EDIT.
// (from lab.nexedi.com/kirr/neo/go/zodb @ v1.9-2339-gc2d001e1)
// Copyright (C) 2018-2020 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 3, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// You can also Link and Combine this program with other software covered by
// the terms of any of the Free Software licenses or any of the Open Source
// Initiative approved licenses and Convey the resulting work. Corresponding
// source of such a combination shall include the source code for all other
// software used.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
// See https://www.nexedi.com/licensing for rationale and options.
package main
import "lab.nexedi.com/kirr/neo/go/zodb"
// XXX do we really need ΔTailI64 to be exported from zodb?
// (other users are low level caches + maybe ZEO/NEO -> zplumbing? but then import cycle)
import (
"fmt"
)
// ΔTailI64 represents tail of revisional changes.
//
// It semantically consists of
//
// [](rev↑, []id) ; rev ∈ (tail, head]
//
// and index
//
// {} id -> max(rev: rev changed id)
//
// where
//
// rev - is ZODB revision,
// id - is an identifier of what has been changed(*), and
// (tail, head] - is covered revision range
//
// It provides operations to
//
// - append information to the tail about next revision,
// - forget information in the tail past specified revision,
// - query the tail for slice with rev ∈ (lo, hi],
// - query the tail about what is last revision that changed an id,
// - query the tail for len and (tail, head].
//
// ΔTailI64 is safe to access for multiple-readers / single writer.
//
// (*) examples of id:
//
// oid - ZODB object identifier, when ΔTailI64 represents changes to ZODB objects,
// #blk - file block number, when ΔTailI64 represents changes to a file.
type ΔTailI64 struct {
head zodb.Tid
tail zodb.Tid
tailv []ΔRevEntry
lastRevOf map[int64]zodb.Tid // index for LastRevOf queries
// XXX -> lastRevOf = {} oid -> []rev↑ if linear scan in LastRevOf starts to eat cpu
}
// ΔRevEntry represents information of what have been changed in one revision.
type ΔRevEntry struct {
Rev zodb.Tid
Changev []int64
}
// NewΔTailI64 creates new ΔTailI64 object.
//
// Initial coverage of created ΔTailI64 is (at₀, at₀].
func NewΔTailI64(at0 zodb.Tid) *ΔTailI64 {
return &ΔTailI64{
head: at0,
tail: at0,
lastRevOf: make(map[int64]zodb.Tid),
}
}
// Len returns number of revisions.
func (δtail *ΔTailI64) Len() int {
return len(δtail.tailv)
}
// Data returns slice with δtail data.
//
// The caller must not modify returned slice.
func (δtail *ΔTailI64) Data() /*readonly*/ []ΔRevEntry {
return δtail.tailv
}
// Head returns newest database state for which δtail has history coverage.
//
// Head is ↑ on Append, in particular it does not ↓ on Forget even if δtail becomes empty.
func (δtail *ΔTailI64) Head() zodb.Tid {
return δtail.head
}
// Tail returns lower-bound of database state for which δtail has history coverage.
//
// Tail is ↑= on Forget, even if δtail becomes empty.
func (δtail *ΔTailI64) Tail() zodb.Tid {
return δtail.tail
}
// SliceByRev returns δtail slice of elements with .rev ∈ (low, high].
//
// it must be called with the following condition:
//
// tail ≤ low ≤ high ≤ head
//
// the caller must not modify returned slice.
//
// Note: contrary to regular go slicing, low is exclusive while high is inclusive.
func (δtail *ΔTailI64) SliceByRev(low, high zodb.Tid) /*readonly*/ []ΔRevEntry {
tail := δtail.tail
head := δtail.head
if !(tail <= low && low <= high && high <= head) {
panic(fmt.Sprintf("δtail.Slice: invalid query: (%s, %s]; (tail, head] = (%s, %s]", low, high, tail, head))
}
tailv := δtail.tailv
// ex (0,0] tail..head = 0..0
if len(tailv) == 0 {
return tailv
}
// find max j : [j].rev ≤ high XXX linear scan -> binary search
j := len(tailv)-1
for ; j >= 0 && tailv[j].Rev > high; j-- {}
if j < 0 {
return nil // ø
}
// find max i : [i].rev > low XXX linear scan -> binary search
i := j
for ; i >= 0 && tailv[i].Rev > low; i-- {}
i++
return tailv[i:j+1]
}
// XXX add way to extend coverage without appending changed data? (i.e. if a
// txn did not change file at all) -> but then it is simply .Append(rev, nil)?
// Append appends to δtail information about what have been changed in next revision.
//
// rev must be ↑.
func (δtail *ΔTailI64) Append(rev zodb.Tid, changev []int64) {
// check rev↑
if δtail.head >= rev {
panic(fmt.Sprintf("δtail.Append: rev not ↑: %s -> %s", δtail.head, rev))
}
δtail.head = rev
δtail.tailv = append(δtail.tailv, ΔRevEntry{rev, changev})
for _, id := range changev {
δtail.lastRevOf[id] = rev
}
}
// ForgetPast discards all δtail entries with rev ≤ revCut.
func (δtail *ΔTailI64) ForgetPast(revCut zodb.Tid) {
// revCut ≤ tail: nothing to do; don't let .tail go ↓
if revCut <= δtail.tail {
return
}
icut := 0
for i, δ := range δtail.tailv {
rev := δ.Rev
if rev > revCut {
break
}
icut = i+1
// 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
// 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
δtail.tail = revCut
}
// XXX -> RevAt ?
// 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:
//
// # 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)
//
// 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 *ΔTailI64) LastRevOf(id int64, 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 {
return δtail.tailv[0].Rev, false
}
if rev <= at {
return rev, true
}
// what's in index is after at - scan tailv back to find appropriate entry
// XXX linear scan - see .lastRevOf comment.
for i := l - 1; i >= 0; i-- {
δ := δtail.tailv[i]
if δ.Rev > at {
continue
}
for _, δid := range δ.Changev {
if id == δid {
return δ.Rev, true
}
}
}
// nothing found
return δtail.tailv[0].Rev, false
}
// Copyright (C) 2018-2020 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 3, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// You can also Link and Combine this program with other software covered by
// the terms of any of the Free Software licenses or any of the Open Source
// Initiative approved licenses and Convey the resulting work. Corresponding
// source of such a combination shall include the source code for all other
// software used.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
// See https://www.nexedi.com/licensing for rationale and options.
// XXX -> internal/δtail/ ?
package main
// δtail maintenance
//go:generate ./gen-δtail I64 int64 zδtail_i64.go
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