Files
zapdb/y/watermark.go
Manish R JainandGitHub 88340c27d7 Introduce NumVersionsToKeep (#474)
Allow users to specify how many versions to store per key. By default, this is set to one, because most users are only interested in the latest version of the key.

Fix: Instead of using the readTs by Oracle, we should be using the minimum readTs of any pending transaction. We should never discard any versions > minReadTs, only versions which are <= minReadTs.
2018-05-03 19:26:18 -07:00

131 lines
3.6 KiB
Go

/*
* Copyright 2018 Dgraph Labs, Inc. and Contributors
*
* 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 y
import (
"container/heap"
"sync/atomic"
"golang.org/x/net/trace"
)
type uint64Heap []uint64
func (u uint64Heap) Len() int { return len(u) }
func (u uint64Heap) Less(i int, j int) bool { return u[i] < u[j] }
func (u uint64Heap) Swap(i int, j int) { u[i], u[j] = u[j], u[i] }
func (u *uint64Heap) Push(x interface{}) { *u = append(*u, x.(uint64)) }
func (u *uint64Heap) Pop() interface{} {
old := *u
n := len(old)
x := old[n-1]
*u = old[0 : n-1]
return x
}
type mark struct {
readTs uint64
done bool // Set to true if the pending mutation is done.
}
type WaterMark struct {
markCh chan mark
minReadTs uint64
elog trace.EventLog
}
// Init initializes a WaterMark struct. MUST be called before using it.
func (w *WaterMark) Init() {
w.markCh = make(chan mark, 1000)
w.elog = trace.NewEventLog("Badger", "MinReadTs")
go w.process()
}
func (w *WaterMark) Begin(readTs uint64) {
w.markCh <- mark{readTs: readTs, done: false}
}
func (w *WaterMark) Done(readTs uint64) {
w.markCh <- mark{readTs: readTs, done: true}
}
// DoneUntil returns the maximum index until which all tasks are done.
func (w *WaterMark) MinReadTs() uint64 {
return atomic.LoadUint64(&w.minReadTs)
}
// process is used to process the Mark channel. This is not thread-safe,
// so only run one goroutine for process. One is sufficient, because
// all ops in the goroutine use only memory and cpu.
func (w *WaterMark) process() {
var reads uint64Heap
// pending maps raft proposal index to the number of pending mutations for this proposal.
pending := make(map[uint64]int)
heap.Init(&reads)
var loop uint64
processOne := func(readTs uint64, done bool) {
// If not already done, then set. Otherwise, don't undo a done entry.
prev, present := pending[readTs]
if !present {
heap.Push(&reads, readTs)
}
delta := 1
if done {
delta = -1
}
pending[readTs] = prev + delta
loop++
if len(reads) > 0 && loop%1000 == 0 {
min := reads[0]
w.elog.Printf("ReadTs: %4d. Size: %4d MinReadTs: %-4d Looking for: %-4d. Value: %d\n",
readTs, len(reads), w.MinReadTs(), min, pending[min])
}
// Update mark by going through all reads in order; and checking if they have
// been done. Stop at the first readTs, which isn't done.
minReadTs := w.MinReadTs()
// Don't assert that minReadTs < readTs, to avoid any inconsistencies caused by managed
// transactions, or testing where we explicitly set the readTs for transactions like in
// TestTxnVersions.
until := minReadTs
loops := 0
for len(reads) > 0 {
min := reads[0]
if done := pending[min]; done != 0 {
break // len(reads) will be > 0.
}
heap.Pop(&reads)
delete(pending, min)
until = min
loops++
}
if until != minReadTs {
AssertTrue(atomic.CompareAndSwapUint64(&w.minReadTs, minReadTs, until))
w.elog.Printf("MinReadTs: %d. Loops: %d\n", until, loops)
}
}
for mark := range w.markCh {
if mark.readTs > 0 {
processOne(mark.readTs, mark.done)
}
}
}