Compare commits

...

31 commits

Author SHA1 Message Date
Travis
0ce5245e5a turn on bitmapCache support in openCache() 2017-03-07 08:34:29 -06:00
Travis
1980978847 fix logic in needsSlices() 2017-03-06 22:20:39 -06:00
Travis
8bb45b8bec Refactor fragment.bitmap() so that it leverages bitmapCache and so that
it's no longer reponsible for updating the count cache.
This commit also helps SetBit/ClearBit performance by allowing them
to work against data from `bitmapCache` instead of loading bitmaps
from fragment.storage every time.
2017-03-06 22:16:37 -06:00
Travis
a42da68899 bug fix in SimpleCache.Fetch (really just reverting my change) 2017-03-06 22:16:37 -06:00
Travis
bfc0e56dc2 move BitmapCache into cache.go. adjust some variable names for clarity 2017-03-06 22:16:37 -06:00
Todd Gruben
e27c1a6d33 rank cache update after count
heap sort order backwards

WIP TopN accuracy

adjusted first phase topn to collect all slices id's

incorrect handling of large topns

cache performance enhancement

fix for failed test TestMain_FrameRestore

remove unused code and fix some variable names
2017-03-06 22:16:36 -06:00
Todd Gruben
23bc227bf3 fix for failed test TestMain_FrameRestore 2017-03-06 22:02:49 -06:00
Todd Gruben
cabe0fef24 cache performance enhancement 2017-03-06 22:02:49 -06:00
Todd Gruben
283dfef1d9 WIP TopN accuracy 2017-03-06 21:59:46 -06:00
Todd Gruben
57122e043f heap sort order backwards 2017-03-06 18:02:59 -06:00
Todd Gruben
29ccab9624 rank cache update after count 2017-03-06 18:02:59 -06:00
Travis
144d64c250 fix BulkAdd comment 2017-03-06 18:02:58 -06:00
Travis
53a8f660a7 add TODO to make MaxIdleConnsPerHost configurable 2017-03-06 18:02:58 -06:00
Travis
34cb92861b remove unused MaxSlice from DB 2017-03-06 18:02:58 -06:00
Travis
307b5884d3 change the names of Simple, turbo to SimpleCache, bitmapCache 2017-03-06 18:02:57 -06:00
Travis
bafdf7744f no longer need to implement Refresh() since that was removed` 2017-03-06 18:02:57 -06:00
Todd Gruben
b1ce3ab751 WIP TopN optimization 2017-03-06 18:02:57 -06:00
Todd Gruben
a21610ed82 handle empty Union/Intersect 2017-03-06 18:02:56 -06:00
Todd Gruben
e7d71a8f47 WIP increase caching and topn adjustments 2017-03-06 18:02:56 -06:00
Todd Gruben
5716006717 top bug in cache refresh 2017-03-06 18:02:55 -06:00
Todd Gruben
a1b9dc05b8 added simple cache to fragment 2017-03-06 18:02:55 -06:00
Todd Gruben
a02716dadb added max slice to schema 2017-03-06 18:02:55 -06:00
Todd Gruben
d61e8e14a4 WIP snapshot optimization 2017-03-06 18:02:54 -06:00
Todd Gruben
0b4f640444 added handling for float attribute 2017-03-06 17:59:16 -06:00
Travis
25e4a681bd add comments to the MaxIdleConnsPerHost tweak 2017-03-06 17:59:16 -06:00
Travis
85a1a71024 run gofmt on the previous commit 2017-03-06 17:59:15 -06:00
Todd Gruben
ce3b797172 limit the number of connections to single host 2017-03-06 17:59:15 -06:00
Todd Gruben
a0d3b67bc8 removed maxslice from setbit path 2017-03-06 17:59:14 -06:00
Travis
7052809504 Adds Todd's performance improvements:
- Ingore asserts in fragment container.
- Only log queries that take longer than 90 seconds.

TODO:
- address the TODOs that make the asserts configurable.
2017-03-06 17:59:14 -06:00
Travis
6518a7381e have roaring clone() return the clone (as opposed to the original) 2017-03-06 17:59:13 -06:00
Travis
f92e880751 handle unionArrayBitmap width of bitmaps correctly 2017-03-06 17:59:12 -06:00
8 changed files with 302 additions and 111 deletions

View file

@ -265,6 +265,8 @@ func txUpdateAttrs(tx *bolt.Tx, id uint64, m map[string]interface{}) (map[string
attr[k] = uint64(v)
case uint:
attr[k] = uint64(v)
case float64:
attr[k] = uint64(v)
case int64:
attr[k] = uint64(v)
case string, uint64, bool:

View file

@ -174,6 +174,26 @@ func (b *Bitmap) InvalidateCount() {
}
}
//increment the bitmap cached counter, note this is an optimization that assumes that the caller is aware the size increased
func (b *Bitmap) IncrementCount(i uint64) {
seg := b.segment(i / SliceWidth)
if seg != nil {
seg.n++
}
}
func (b *Bitmap) DecrementCount(i uint64) {
seg := b.segment(i / SliceWidth)
if seg != nil {
if seg.n > 0 {
seg.n--
}
}
}
func (b *Bitmap) SetCount(i uint64, count uint64) {
seg := b.segment(i / SliceWidth)
seg.n = count
}
// Count returns the number of set bits in the bitmap.
func (b *Bitmap) Count() uint64 {
var n uint64
@ -312,7 +332,6 @@ func (s *BitmapSegment) Difference(other *BitmapSegment) *BitmapSegment {
// SetBit sets the i-th bit of the bitmap.
func (s *BitmapSegment) SetBit(i uint64) (changed bool) {
s.ensureWritable()
changed, _ = s.data.Add(i)
if changed {
s.n++

121
cache.go
View file

@ -5,6 +5,7 @@ import (
"fmt"
"io"
"sort"
"sync"
"time"
"github.com/golang/groupcache/lru"
@ -14,6 +15,7 @@ import (
// Cache represents a cache for bitmap counts.
type Cache interface {
Add(bitmapID uint64, n uint64)
BulkAdd(bitmapID uint64, n uint64)
Get(bitmapID uint64) uint64
Len() int
@ -43,6 +45,10 @@ func NewLRUCache(maxEntries int) *LRUCache {
return c
}
func (c *LRUCache) BulkAdd(bitmapID, n uint64) {
c.Add(bitmapID, n)
}
// Add adds a bitmap to the cache.
func (c *LRUCache) Add(bitmapID, n uint64) {
c.cache.Add(bitmapID, n)
@ -92,6 +98,7 @@ var _ Cache = &LRUCache{}
// RankCache represents a cache with sorted entries.
type RankCache struct {
mu sync.Mutex
entries map[uint64]uint64
rankings []BitmapPair // cached, ordered list
@ -112,33 +119,47 @@ func NewRankCache() *RankCache {
// Add adds a bitmap to the cache.
func (c *RankCache) Add(bitmapID uint64, n uint64) {
c.mu.Lock()
defer c.mu.Unlock()
// Ignore if the bit count on the bitmap is below the threshold.
if n < c.ThresholdValue {
return
}
// Add to cache.
c.entries[bitmapID] = n
// If size is larger than the threshold then trim it.
if len(c.entries) > c.ThresholdLength {
c.update()
for id, n := range c.entries {
if n <= c.ThresholdValue {
delete(c.entries, id)
}
}
c.invalidate()
}
// BulkAdd adds a bitmap to the cache unsorted. You should Invalidate after completion.
func (c *RankCache) BulkAdd(bitmapID uint64, n uint64) {
c.mu.Lock()
defer c.mu.Unlock()
if n < c.ThresholdValue {
return
}
c.entries[bitmapID] = n
}
// Get returns a bitmap with a given id.
func (c *RankCache) Get(bitmapID uint64) uint64 { return c.entries[bitmapID] }
func (c *RankCache) Get(bitmapID uint64) uint64 {
c.mu.Lock()
defer c.mu.Unlock()
return c.entries[bitmapID]
}
// Len returns the number of items in the cache.
func (c *RankCache) Len() int { return len(c.entries) }
func (c *RankCache) Len() int {
c.mu.Lock()
defer c.mu.Unlock()
return len(c.entries)
}
// BitmapIDs returns a list of all bitmap IDs in the cache.
func (c *RankCache) BitmapIDs() []uint64 {
c.mu.Lock()
defer c.mu.Unlock()
a := make([]uint64, 0, len(c.entries))
for id := range c.entries {
a = append(a, id)
@ -147,22 +168,25 @@ func (c *RankCache) BitmapIDs() []uint64 {
return a
}
// Invalidate reorders the entries, if necessary.
func (c *RankCache) Invalidate() {
// Update if there aren't many items or it hasn't been updated recently.
if len(c.rankings) < 50 || (c.updateN > 0 && time.Since(c.updateTime) > 5*time.Minute) {
c.update()
}
}
// update reorders the entries by rank.
func (c *RankCache) update() {
func (c *RankCache) Invalidate() {
c.mu.Lock()
defer c.mu.Unlock()
c.invalidate()
}
func (c *RankCache) invalidate() {
// Don't invalidate more than once every X seconds.
// TODO: consider making this configurable.
if time.Now().Sub(c.updateTime).Seconds() < 10 {
return
}
// Convert cache to a sorted list.
rankings := make([]BitmapPair, 0, len(c.entries))
for id, n := range c.entries {
for id, cnt := range c.entries {
rankings = append(rankings, BitmapPair{
ID: id,
Count: n,
Count: cnt,
})
}
sort.Sort(BitmapPairs(rankings))
@ -177,6 +201,15 @@ func (c *RankCache) update() {
// Reset counters.
c.updateTime, c.updateN = time.Now(), 0
// If size is larger than the threshold then trim it.
if len(c.entries) > c.ThresholdLength {
for id, cnt := range c.entries {
if cnt <= c.ThresholdValue {
delete(c.entries, id)
}
}
}
}
// Top returns an ordered list of bitmaps.
@ -233,6 +266,26 @@ func (p Pairs) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p Pairs) Len() int { return len(p) }
func (p Pairs) Less(i, j int) bool { return p[i].Count > p[j].Count }
type PairHeap struct {
Pairs
}
func (p PairHeap) Less(i, j int) bool { return p.Pairs[i].Count < p.Pairs[j].Count }
func (h *Pairs) Push(x interface{}) {
// Push and Pop use pointer receivers because they modify the slice's length,
// not just its contents.
*h = append(*h, x.(Pair))
}
func (h *Pairs) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
// Add merges other into p and returns a new slice.
func (p Pairs) Add(other []Pair) []Pair {
// Create lookup of key/counts.
@ -327,3 +380,27 @@ func (p uint64Slice) merge(other []uint64) []uint64 {
return ret
}
// BitmapCache provides an interface for caching full bitmaps.
type BitmapCache interface {
Fetch(id uint64) (*Bitmap, bool)
Add(id uint64, b *Bitmap)
}
// SimpleCache implements BitmapCache
// it is meant to be a short-lived cache for cases where writes are continuing to access
// the same bit within a short time frame (i.e. good for write-heavy loads)
// A read-heavy use case would cause the cache to get bigger, potentially causing the
// node to run out of memory.
type SimpleCache struct {
cache map[uint64]*Bitmap
}
func (s *SimpleCache) Fetch(id uint64) (*Bitmap, bool) {
m, ok := s.cache[id]
return m, ok
}
func (s *SimpleCache) Add(id uint64, b *Bitmap) {
s.cache[id] = b
}

View file

@ -6,6 +6,7 @@ import (
"fmt"
"io"
"math/rand"
"net/http"
"os"
"os/signal"
"path/filepath"
@ -34,6 +35,11 @@ const (
)
func main() {
// Limit the number of connections that a server can make to a single node
// in order to prevent a cluster storm.
// TODO: make this configurable
http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = 64
m := NewMain()
m.Server.Handler.Version = Version
fmt.Fprintf(m.Stderr, "Pilosa %s\n", Version)

View file

@ -52,13 +52,15 @@ func (e *Executor) Execute(ctx context.Context, db string, q *pql.Query, slices
// If slices aren't specified, then include all of them.
if len(slices) == 0 {
// Round up the number of slices.
maxSlice := e.Index.DB(db).MaxSlice()
if needsSlices(q.Calls) {
// Round up the number of slices.
maxSlice := e.Index.DB(db).MaxSlice()
// Generate a slices of all slices.
slices = make([]uint64, maxSlice+1)
for i := range slices {
slices[i] = uint64(i)
// Generate a slices of all slices.
slices = make([]uint64, maxSlice+1)
for i := range slices {
slices[i] = uint64(i)
}
}
}
@ -174,7 +176,6 @@ func (e *Executor) executeTopN(ctx context.Context, db string, c *pql.TopN, slic
if len(pairs) == 0 || len(c.BitmapIDs) > 0 || opt.Remote {
return pairs, nil
}
// Only the original caller should refetch the full counts.
other := *c
other.N = 0
@ -279,7 +280,7 @@ func (e *Executor) executeBitmapSlice(ctx context.Context, db string, c *pql.Bit
// executeIntersectSlice executes a intersect() call for a local slice.
func (e *Executor) executeIntersectSlice(ctx context.Context, db string, c *pql.Intersect, slice uint64) (*Bitmap, error) {
var other *Bitmap
other := &Bitmap{}
for i, input := range c.Inputs {
bm, err := e.executeBitmapCallSlice(ctx, db, input, slice)
if err != nil {
@ -329,7 +330,7 @@ func (e *Executor) executeRangeSlice(ctx context.Context, db string, c *pql.Rang
// executeUnionSlice executes a union() call for a local slice.
func (e *Executor) executeUnionSlice(ctx context.Context, db string, c *pql.Union, slice uint64) (*Bitmap, error) {
var other *Bitmap
other := &Bitmap{}
for i, input := range c.Inputs {
bm, err := e.executeBitmapCallSlice(ctx, db, input, slice)
if err != nil {
@ -876,3 +877,21 @@ func hasOnlySetBitmapAttrs(calls pql.Calls) bool {
}
return true
}
func needsSlices(calls pql.Calls) bool {
if len(calls) == 0 {
return false
}
for _, call := range calls {
if _, ok := call.(pql.BitmapCall); ok {
return true
} else if _, ok := call.(*pql.Count); ok {
return true
} else if _, ok := call.(*pql.TopN); ok {
return true
}
}
return false
}

View file

@ -4,6 +4,7 @@ import (
"archive/tar"
"bufio"
"bytes"
"container/heap"
"context"
"crypto/sha1"
"encoding/binary"
@ -49,7 +50,7 @@ const (
const (
// DefaultFragmentMaxOpN is the default value for Fragment.MaxOpN.
DefaultFragmentMaxOpN = 1000
DefaultFragmentMaxOpN = 2000
)
// Fragment represents the intersection of a frame and slice in a database.
@ -68,9 +69,12 @@ type Fragment struct {
storageData []byte
opN int // number of ops since snapshot
// Bitmap cache.
// Cache for bitmap counts.
cache Cache
// Cache containing full bitmaps (not just counts).
bitmapCache BitmapCache
// Cached checksums for each block.
checksums map[int][]byte
@ -203,6 +207,7 @@ func (f *Fragment) openStorage() error {
// Attach the file to the bitmap to act as a write-ahead log.
f.storage.OpWriter = f.file
f.bitmapCache = &SimpleCache{make(map[uint64]*Bitmap)}
return nil
@ -239,9 +244,11 @@ func (f *Fragment) openCache() error {
// Read in all bitmaps by ID.
// This will cause them to be added to the cache.
for _, bitmapID := range pb.BitmapIDs {
n := f.storage.CountRange(bitmapID*SliceWidth, (bitmapID+1)*SliceWidth)
f.cache.Add(bitmapID, n)
//n := f.storage.CountRange(bitmapID*SliceWidth, (bitmapID+1)*SliceWidth)
n := f.bitmap(bitmapID, true, true).Count()
f.cache.BulkAdd(bitmapID, n)
}
f.cache.Invalidate()
return nil
}
@ -305,26 +312,37 @@ func (f *Fragment) logger() *log.Logger { return log.New(f.LogOutput, "", log.Ls
func (f *Fragment) Bitmap(bitmapID uint64) *Bitmap {
f.mu.Lock()
defer f.mu.Unlock()
return f.bitmap(bitmapID)
return f.bitmap(bitmapID, true, true)
}
func (f *Fragment) bitmap(bitmapID uint64) *Bitmap {
func (f *Fragment) bitmap(bitmapID uint64, checkBitmapCache bool, updateBitmapCache bool) *Bitmap {
if checkBitmapCache {
r, ok := f.bitmapCache.Fetch(bitmapID)
if ok && r != nil {
return r
}
}
// Only use a subset of the containers.
// NOTE: The start & end ranges must be divisible by
data := f.storage.OffsetRange(f.slice*SliceWidth, bitmapID*SliceWidth, (bitmapID+1)*SliceWidth)
// Reference bitmap subrange in storage.
// We Clone() data because otherwise bm will contains pointers to containers in storage.
// This causes unexpected results when we cache the bitmap and try to use it later.
bm := &Bitmap{
segments: []BitmapSegment{{
data: *data,
data: *data.Clone(),
slice: f.slice,
writable: false,
}},
}
bm.InvalidateCount()
// Update cache.
f.cache.Add(bitmapID, bm.Count())
if updateBitmapCache {
f.bitmapCache.Add(bitmapID, bm)
}
return bm
}
@ -337,9 +355,9 @@ func (f *Fragment) SetBit(bitmapID, profileID uint64) (changed bool, err error)
return f.setBit(bitmapID, profileID)
}
func (f *Fragment) setBit(bitmapID, profileID uint64) (changed bool, bool error) {
// Determine the position of the bit in the storage.
func (f *Fragment) setBit(bitmapID, profileID uint64) (changed bool, err error) {
changed = false
// Determine the position of the bit in the storage.
pos, err := f.pos(bitmapID, profileID)
if err != nil {
return false, err
@ -350,18 +368,25 @@ func (f *Fragment) setBit(bitmapID, profileID uint64) (changed bool, bool error)
return false, err
}
// Don't update the cache if nothing changed.
if !changed {
return changed, nil
}
// Invalidate block checksum.
delete(f.checksums, int(bitmapID/HashBlockSize))
// If the number of operations exceeds the limit then snapshot.
// Increment number of operations until snapshot is required.
if err := f.incrementOpN(); err != nil {
return false, err
}
// Get the bitmap from bitmapCache or fragment.storage.
bm := f.bitmap(bitmapID, true, true)
bm.SetBit(profileID)
// Update the cache.
if f.bitmap(bitmapID).SetBit(profileID) {
changed = true
}
f.cache.Add(bitmapID, bm.Count())
f.stats.Count("setN", 1)
@ -376,7 +401,8 @@ func (f *Fragment) ClearBit(bitmapID, profileID uint64) (bool, error) {
return f.clearBit(bitmapID, profileID)
}
func (f *Fragment) clearBit(bitmapID, profileID uint64) (bool, error) {
func (f *Fragment) clearBit(bitmapID, profileID uint64) (changed bool, err error) {
changed = false
// Determine the position of the bit in the storage.
pos, err := f.pos(bitmapID, profileID)
if err != nil {
@ -384,11 +410,15 @@ func (f *Fragment) clearBit(bitmapID, profileID uint64) (bool, error) {
}
// Write to storage.
changed, err := f.storage.Remove(pos)
if err != nil {
if changed, err = f.storage.Remove(pos); err != nil {
return false, err
}
// Don't update the cache if nothing changed.
if !changed {
return changed, nil
}
// Invalidate block checksum.
delete(f.checksums, int(bitmapID/HashBlockSize))
@ -397,10 +427,12 @@ func (f *Fragment) clearBit(bitmapID, profileID uint64) (bool, error) {
return false, err
}
// Get the bitmap from bitmapCache or fragment.storage.
bm := f.bitmap(bitmapID, true, true)
bm.ClearBit(profileID)
// Update the cache.
if f.bitmap(bitmapID).ClearBit(profileID) {
return true, nil
}
f.cache.Add(bitmapID, bm.Count())
f.stats.Count("clearN", 1)
@ -453,7 +485,8 @@ func (f *Fragment) Top(opt TopOptions) ([]Pair, error) {
}
// Iterate over rankings and add to results until we have enough.
results := make([]Pair, 0, opt.N)
//results := make(PairHeap, 0, opt.N)
results := &PairHeap{}
for _, pair := range pairs {
bitmapID, n := pair.ID, pair.Count
@ -477,7 +510,7 @@ func (f *Fragment) Top(opt TopOptions) ([]Pair, error) {
}
// The initial n pairs should simply be added to the results.
if opt.N == 0 || len(results) < opt.N {
if opt.N == 0 || results.Len() < opt.N {
// Calculate count and append.
count := n
if opt.Src != nil {
@ -486,26 +519,22 @@ func (f *Fragment) Top(opt TopOptions) ([]Pair, error) {
if count == 0 {
continue
}
results = append(results, Pair{Key: bitmapID, Count: count})
heap.Push(results, Pair{Key: bitmapID, Count: count})
// If we reach the requested number of pairs and we are not computing
// intersections then simply exit. If we are intersecting then sort
// and then only keep pairs that are higher than the lowest count.
if opt.N > 0 && len(results) == opt.N {
if opt.N > 0 && results.Len() == opt.N {
if opt.Src == nil {
break
}
sort.Sort(Pairs(results))
}
continue
}
// Retrieve the lowest count we have.
// If it's too low then don't try finding anymore pairs.
threshold := results[len(results)-1].Count
if threshold < MinThreshold {
break
}
threshold := results.Pairs[0].Count
// If the bitmap doesn't have enough bits set before the intersection
// then we can assume that any remaing bitmaps also have a count too low.
@ -515,22 +544,22 @@ func (f *Fragment) Top(opt TopOptions) ([]Pair, error) {
// Calculate the intersecting bit count and skip if it's below our
// last bitmap in our current result set.
count := opt.Src.IntersectionCount(f.Bitmap(bitmapID))
if count < threshold {
continue
}
// Swap out the last pair for this new count.
results[len(results)-1] = Pair{Key: bitmapID, Count: count}
// If it's count is also higher than the second to last item then resort.
if len(results) >= 2 && count > results[len(results)-2].Count {
sort.Sort(Pairs(results))
}
heap.Push(results, Pair{Key: bitmapID, Count: count})
}
sort.Sort(Pairs(results))
return results, nil
r := make(Pairs, results.Len(), results.Len())
x := results.Len()
i := 1
for results.Len() > 0 {
r[x-i] = heap.Pop(results).(Pair)
i++
}
return r, nil
}
func (f *Fragment) topBitmapPairs(bitmapIDs []uint64) []BitmapPair {
@ -543,23 +572,27 @@ func (f *Fragment) topBitmapPairs(bitmapIDs []uint64) []BitmapPair {
}
// Otherwise retrieve specific bitmaps.
pairs := make([]BitmapPair, len(bitmapIDs))
for i, bitmapID := range bitmapIDs {
pairs := make([]BitmapPair, 0, len(bitmapIDs))
for _, bitmapID := range bitmapIDs {
// Look up cache first, if available.
if n := f.cache.Get(bitmapID); n > 0 {
pairs[i] = BitmapPair{
pairs = append(pairs, BitmapPair{
ID: bitmapID,
Count: n,
}
})
continue
}
// Otherwise load from storage.
pairs[i] = BitmapPair{
ID: bitmapID,
Count: f.Bitmap(bitmapID).Count(),
bm := f.Bitmap(bitmapID)
if bm.Count() > 0 {
// Otherwise load from storage.
pairs = append(pairs, BitmapPair{
ID: bitmapID,
Count: bm.Count(),
})
}
}
sort.Sort(BitmapPairs(pairs))
return pairs
}
@ -838,7 +871,6 @@ func (f *Fragment) Import(bitmapIDs, profileIDs []uint64) error {
// Process every bit.
// If an error occurs then reopen the storage.
lastID := uint64(0)
bmCounter := 0
if err := func() error {
set := make(map[uint64]struct{})
for i := range bitmapIDs {
@ -851,7 +883,7 @@ func (f *Fragment) Import(bitmapIDs, profileIDs []uint64) error {
}
// Write to storage.
changed, err := f.storage.Add(pos)
_, err = f.storage.Add(pos)
if err != nil {
return err
}
@ -863,9 +895,6 @@ func (f *Fragment) Import(bitmapIDs, profileIDs []uint64) error {
lastID = bitmapID
set[bitmapID] = struct{}{}
}
if changed {
bmCounter += 1
}
// Invalidate block checksum.
delete(f.checksums, int(bitmapID/HashBlockSize))
@ -873,7 +902,10 @@ func (f *Fragment) Import(bitmapIDs, profileIDs []uint64) error {
// Update cache counts for all bitmaps.
for bitmapID := range set {
f.cache.Add(bitmapID, f.bitmap(bitmapID).Count())
// Import should ALWAYS have bitmap() load a new bm from fragment.storage
// because the bitmap that's in bitmapCache hasn't been updated with
// this import's data.
f.cache.BulkAdd(bitmapID, f.bitmap(bitmapID, false, false).Count())
}
f.cache.Invalidate()
@ -912,10 +944,15 @@ func (f *Fragment) Snapshot() error {
defer f.mu.Unlock()
return f.snapshot()
}
func track(start time.Time, name string, logger *log.Logger) {
elapsed := time.Since(start)
logger.Printf("%s took %s", name, elapsed)
}
func (f *Fragment) snapshot() error {
logger := f.logger()
logger.Printf("fragment: snapshotting %s/%s/%d", f.db, f.frame, f.slice)
defer track(time.Now(), fmt.Sprintf("fragment: snapshot complete %s/%s/%d", f.db, f.frame, f.slice), logger)
// Create a temporary file to snapshot to.
snapshotPath := f.path + SnapshotExt
@ -1216,7 +1253,6 @@ func (s *FragmentSyncer) SyncFragment() error {
// Determine replica set.
nodes := s.Cluster.FragmentNodes(s.Fragment.DB(), s.Fragment.Slice())
if len(nodes) == 1 {
//fmt.Println("no place to replicate", s.Fragment.DB(), s.Fragment.Frame(), s.Fragment.Slice())
return nil
}

View file

@ -201,7 +201,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
}
h.logger().Printf("%s %s %.03fs", r.Method, r.URL.String(), time.Since(t).Seconds())
dif := time.Since(t).Seconds()
if dif > 90 {
h.logger().Printf("%s %s %.03fs", r.Method, r.URL.String(), dif)
}
}
// handleGetSchema handles GET /schema requests.

View file

@ -444,33 +444,56 @@ func (b *Bitmap) removeEmptyContainers() {
i++
}
}
func (b *Bitmap) countEmptyContainers() int {
result := 0
for i := 0; i < len(b.containers); {
c := b.containers[i]
if c.n == 0 {
result++
}
i++
}
return result
}
// WriteTo writes b to w.
func (b *Bitmap) WriteTo(w io.Writer) (n int64, err error) {
// Remove empty containers before persisting.
b.removeEmptyContainers()
//b.removeEmptyContainers()
containerCount := len(b.keys) - b.countEmptyContainers()
// Build header before writing individual container blocks.
buf := make([]byte, headerSize+(len(b.keys)*(4+8+4)))
buf := make([]byte, headerSize+(containerCount*(4+8+4)))
binary.LittleEndian.PutUint32(buf[0:], cookie)
binary.LittleEndian.PutUint32(buf[4:], uint32(len(b.keys)))
binary.LittleEndian.PutUint32(buf[4:], uint32(containerCount))
empty := 0
// Encode keys and cardinality.
for i, key := range b.keys {
c := b.containers[i]
// Verify container count before writing.
count := c.count()
assert(c.count() == c.n, "cannot write container count, mismatch: count=%d, n=%d", count, c.n)
binary.LittleEndian.PutUint64(buf[headerSize+i*12:], uint64(key))
binary.LittleEndian.PutUint32(buf[headerSize+i*12+8:], uint32(c.n-1))
// TODO: instead of commenting this out, we need to make it a configuration option
//count := c.count()
//assert(c.count() == c.n, "cannot write container count, mismatch: count=%d, n=%d", count, c.n)
if c.n > 0 {
binary.LittleEndian.PutUint64(buf[headerSize+(i-empty)*12:], uint64(key))
binary.LittleEndian.PutUint32(buf[headerSize+(i-empty)*12+8:], uint32(c.n-1))
} else {
empty++
}
}
// Write the offset for each container block.
offset := uint32(len(buf))
empty = 0
for i, c := range b.containers {
binary.LittleEndian.PutUint32(buf[headerSize+(len(b.keys)*12)+(i*4):], uint32(offset))
if c.n > 0 {
binary.LittleEndian.PutUint32(buf[headerSize+(containerCount*12)+((i-empty)*4):], uint32(offset))
} else {
empty++
}
offset += uint32(c.size())
}
@ -483,10 +506,12 @@ func (b *Bitmap) WriteTo(w io.Writer) (n int64, err error) {
// Write each container block.
for _, c := range b.containers {
nn, err := c.WriteTo(w)
n += nn
if err != nil {
return n, err
if c.n > 0 {
nn, err := c.WriteTo(w)
n += nn
if err != nil {
return n, err
}
}
}
@ -532,9 +557,10 @@ func (b *Bitmap) UnmarshalBinary(data []byte) error {
c := b.containers[i]
if c.n <= ArrayMaxSize {
c.array = (*[0xFFFFFFF]uint32)(unsafe.Pointer(&data[offset]))[:c.n]
for _, v := range c.array {
assert(lowbits(uint64(v)) == v, "array value out of range: %d", v)
}
// TODO: instead of commenting this out, we need to make it a configuration option
//for _, v := range c.array {
// assert(lowbits(uint64(v)) == v, "array value out of range: %d", v)
//}
opsOffset = int(offset) + len(c.array)*4
} else {
c.bitmap = (*[0xFFFFFFF]uint64)(unsafe.Pointer(&data[offset]))[:bitmapN]
@ -542,8 +568,9 @@ func (b *Bitmap) UnmarshalBinary(data []byte) error {
}
// Verify container count on load.
count := c.count()
assert(c.count() == c.n, "container count mismatch: count=%d, n=%d", count, c.n)
// TODO: instead of commenting this out, we need to make it a configuration option
//count := c.count()
//assert(c.count() == c.n, "container count mismatch: count=%d, n=%d", count, c.n)
}
// Read ops log until the end of the file.
@ -1057,7 +1084,7 @@ func (c *container) clone() *container {
copy(other.bitmap, c.bitmap)
}
return c
return other
}
// WriteTo writes c to w.
@ -1074,9 +1101,10 @@ func (c *container) arrayWriteTo(w io.Writer) (n int64, err error) {
}
// Verify all elements are valid.
for _, v := range c.array {
assert(lowbits(uint64(v)) == v, "cannot write array value out of range: %d", v)
}
// TODO: instead of commenting this out, we need to make it a configuration option
//for _, v := range c.array {
// assert(lowbits(uint64(v)) == v, "cannot write array value out of range: %d", v)
//}
nn, err := w.Write((*[0xFFFFFFF]byte)(unsafe.Pointer(&c.array[0]))[:4*c.n])
return int64(nn), err
@ -1364,6 +1392,7 @@ func unionArrayBitmap(a, b *container) *container {
break
} else if i >= len(a.array) {
output.add(vb)
continue
} else if eof {
output.add(a.array[i])
i++