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) attr[k] = uint64(v)
case uint: case uint:
attr[k] = uint64(v) attr[k] = uint64(v)
case float64:
attr[k] = uint64(v)
case int64: case int64:
attr[k] = uint64(v) attr[k] = uint64(v)
case string, uint64, bool: 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. // Count returns the number of set bits in the bitmap.
func (b *Bitmap) Count() uint64 { func (b *Bitmap) Count() uint64 {
var n uint64 var n uint64
@ -312,7 +332,6 @@ func (s *BitmapSegment) Difference(other *BitmapSegment) *BitmapSegment {
// SetBit sets the i-th bit of the bitmap. // SetBit sets the i-th bit of the bitmap.
func (s *BitmapSegment) SetBit(i uint64) (changed bool) { func (s *BitmapSegment) SetBit(i uint64) (changed bool) {
s.ensureWritable() s.ensureWritable()
changed, _ = s.data.Add(i) changed, _ = s.data.Add(i)
if changed { if changed {
s.n++ s.n++

121
cache.go
View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"sort" "sort"
"sync"
"time" "time"
"github.com/golang/groupcache/lru" "github.com/golang/groupcache/lru"
@ -14,6 +15,7 @@ import (
// Cache represents a cache for bitmap counts. // Cache represents a cache for bitmap counts.
type Cache interface { type Cache interface {
Add(bitmapID uint64, n uint64) Add(bitmapID uint64, n uint64)
BulkAdd(bitmapID uint64, n uint64)
Get(bitmapID uint64) uint64 Get(bitmapID uint64) uint64
Len() int Len() int
@ -43,6 +45,10 @@ func NewLRUCache(maxEntries int) *LRUCache {
return c return c
} }
func (c *LRUCache) BulkAdd(bitmapID, n uint64) {
c.Add(bitmapID, n)
}
// Add adds a bitmap to the cache. // Add adds a bitmap to the cache.
func (c *LRUCache) Add(bitmapID, n uint64) { func (c *LRUCache) Add(bitmapID, n uint64) {
c.cache.Add(bitmapID, n) c.cache.Add(bitmapID, n)
@ -92,6 +98,7 @@ var _ Cache = &LRUCache{}
// RankCache represents a cache with sorted entries. // RankCache represents a cache with sorted entries.
type RankCache struct { type RankCache struct {
mu sync.Mutex
entries map[uint64]uint64 entries map[uint64]uint64
rankings []BitmapPair // cached, ordered list rankings []BitmapPair // cached, ordered list
@ -112,33 +119,47 @@ func NewRankCache() *RankCache {
// Add adds a bitmap to the cache. // Add adds a bitmap to the cache.
func (c *RankCache) Add(bitmapID uint64, n uint64) { 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. // Ignore if the bit count on the bitmap is below the threshold.
if n < c.ThresholdValue { if n < c.ThresholdValue {
return return
} }
// Add to cache.
c.entries[bitmapID] = n c.entries[bitmapID] = n
// If size is larger than the threshold then trim it. c.invalidate()
if len(c.entries) > c.ThresholdLength { }
c.update()
for id, n := range c.entries { // BulkAdd adds a bitmap to the cache unsorted. You should Invalidate after completion.
if n <= c.ThresholdValue { func (c *RankCache) BulkAdd(bitmapID uint64, n uint64) {
delete(c.entries, id) c.mu.Lock()
} defer c.mu.Unlock()
} if n < c.ThresholdValue {
return
} }
c.entries[bitmapID] = n
} }
// Get returns a bitmap with a given id. // 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. // 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. // BitmapIDs returns a list of all bitmap IDs in the cache.
func (c *RankCache) BitmapIDs() []uint64 { func (c *RankCache) BitmapIDs() []uint64 {
c.mu.Lock()
defer c.mu.Unlock()
a := make([]uint64, 0, len(c.entries)) a := make([]uint64, 0, len(c.entries))
for id := range c.entries { for id := range c.entries {
a = append(a, id) a = append(a, id)
@ -147,22 +168,25 @@ func (c *RankCache) BitmapIDs() []uint64 {
return a 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. // 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. // Convert cache to a sorted list.
rankings := make([]BitmapPair, 0, len(c.entries)) rankings := make([]BitmapPair, 0, len(c.entries))
for id, n := range c.entries { for id, cnt := range c.entries {
rankings = append(rankings, BitmapPair{ rankings = append(rankings, BitmapPair{
ID: id, ID: id,
Count: n, Count: cnt,
}) })
} }
sort.Sort(BitmapPairs(rankings)) sort.Sort(BitmapPairs(rankings))
@ -177,6 +201,15 @@ func (c *RankCache) update() {
// Reset counters. // Reset counters.
c.updateTime, c.updateN = time.Now(), 0 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. // 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) Len() int { return len(p) }
func (p Pairs) Less(i, j int) bool { return p[i].Count > p[j].Count } 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. // Add merges other into p and returns a new slice.
func (p Pairs) Add(other []Pair) []Pair { func (p Pairs) Add(other []Pair) []Pair {
// Create lookup of key/counts. // Create lookup of key/counts.
@ -327,3 +380,27 @@ func (p uint64Slice) merge(other []uint64) []uint64 {
return ret 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" "fmt"
"io" "io"
"math/rand" "math/rand"
"net/http"
"os" "os"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
@ -34,6 +35,11 @@ const (
) )
func main() { 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 := NewMain()
m.Server.Handler.Version = Version m.Server.Handler.Version = Version
fmt.Fprintf(m.Stderr, "Pilosa %s\n", 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 slices aren't specified, then include all of them.
if len(slices) == 0 { if len(slices) == 0 {
// Round up the number of slices. if needsSlices(q.Calls) {
maxSlice := e.Index.DB(db).MaxSlice() // Round up the number of slices.
maxSlice := e.Index.DB(db).MaxSlice()
// Generate a slices of all slices. // Generate a slices of all slices.
slices = make([]uint64, maxSlice+1) slices = make([]uint64, maxSlice+1)
for i := range slices { for i := range slices {
slices[i] = uint64(i) 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 { if len(pairs) == 0 || len(c.BitmapIDs) > 0 || opt.Remote {
return pairs, nil return pairs, nil
} }
// Only the original caller should refetch the full counts. // Only the original caller should refetch the full counts.
other := *c other := *c
other.N = 0 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. // 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) { 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 { for i, input := range c.Inputs {
bm, err := e.executeBitmapCallSlice(ctx, db, input, slice) bm, err := e.executeBitmapCallSlice(ctx, db, input, slice)
if err != nil { 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. // 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) { 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 { for i, input := range c.Inputs {
bm, err := e.executeBitmapCallSlice(ctx, db, input, slice) bm, err := e.executeBitmapCallSlice(ctx, db, input, slice)
if err != nil { if err != nil {
@ -876,3 +877,21 @@ func hasOnlySetBitmapAttrs(calls pql.Calls) bool {
} }
return true 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" "archive/tar"
"bufio" "bufio"
"bytes" "bytes"
"container/heap"
"context" "context"
"crypto/sha1" "crypto/sha1"
"encoding/binary" "encoding/binary"
@ -49,7 +50,7 @@ const (
const ( const (
// DefaultFragmentMaxOpN is the default value for Fragment.MaxOpN. // DefaultFragmentMaxOpN is the default value for Fragment.MaxOpN.
DefaultFragmentMaxOpN = 1000 DefaultFragmentMaxOpN = 2000
) )
// Fragment represents the intersection of a frame and slice in a database. // Fragment represents the intersection of a frame and slice in a database.
@ -68,9 +69,12 @@ type Fragment struct {
storageData []byte storageData []byte
opN int // number of ops since snapshot opN int // number of ops since snapshot
// Bitmap cache. // Cache for bitmap counts.
cache Cache cache Cache
// Cache containing full bitmaps (not just counts).
bitmapCache BitmapCache
// Cached checksums for each block. // Cached checksums for each block.
checksums map[int][]byte 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. // Attach the file to the bitmap to act as a write-ahead log.
f.storage.OpWriter = f.file f.storage.OpWriter = f.file
f.bitmapCache = &SimpleCache{make(map[uint64]*Bitmap)}
return nil return nil
@ -239,9 +244,11 @@ func (f *Fragment) openCache() error {
// Read in all bitmaps by ID. // Read in all bitmaps by ID.
// This will cause them to be added to the cache. // This will cause them to be added to the cache.
for _, bitmapID := range pb.BitmapIDs { for _, bitmapID := range pb.BitmapIDs {
n := f.storage.CountRange(bitmapID*SliceWidth, (bitmapID+1)*SliceWidth) //n := f.storage.CountRange(bitmapID*SliceWidth, (bitmapID+1)*SliceWidth)
f.cache.Add(bitmapID, n) n := f.bitmap(bitmapID, true, true).Count()
f.cache.BulkAdd(bitmapID, n)
} }
f.cache.Invalidate()
return nil 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 { func (f *Fragment) Bitmap(bitmapID uint64) *Bitmap {
f.mu.Lock() f.mu.Lock()
defer f.mu.Unlock() 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. // Only use a subset of the containers.
// NOTE: The start & end ranges must be divisible by // NOTE: The start & end ranges must be divisible by
data := f.storage.OffsetRange(f.slice*SliceWidth, bitmapID*SliceWidth, (bitmapID+1)*SliceWidth) data := f.storage.OffsetRange(f.slice*SliceWidth, bitmapID*SliceWidth, (bitmapID+1)*SliceWidth)
// Reference bitmap subrange in storage. // 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{ bm := &Bitmap{
segments: []BitmapSegment{{ segments: []BitmapSegment{{
data: *data, data: *data.Clone(),
slice: f.slice, slice: f.slice,
writable: false, writable: false,
}}, }},
} }
bm.InvalidateCount() bm.InvalidateCount()
// Update cache. if updateBitmapCache {
f.cache.Add(bitmapID, bm.Count()) f.bitmapCache.Add(bitmapID, bm)
}
return bm return bm
} }
@ -337,9 +355,9 @@ func (f *Fragment) SetBit(bitmapID, profileID uint64) (changed bool, err error)
return f.setBit(bitmapID, profileID) return f.setBit(bitmapID, profileID)
} }
func (f *Fragment) setBit(bitmapID, profileID uint64) (changed bool, bool error) { func (f *Fragment) setBit(bitmapID, profileID uint64) (changed bool, err error) {
// Determine the position of the bit in the storage.
changed = false changed = false
// Determine the position of the bit in the storage.
pos, err := f.pos(bitmapID, profileID) pos, err := f.pos(bitmapID, profileID)
if err != nil { if err != nil {
return false, err return false, err
@ -350,18 +368,25 @@ func (f *Fragment) setBit(bitmapID, profileID uint64) (changed bool, bool error)
return false, err return false, err
} }
// Don't update the cache if nothing changed.
if !changed {
return changed, nil
}
// Invalidate block checksum. // Invalidate block checksum.
delete(f.checksums, int(bitmapID/HashBlockSize)) 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 { if err := f.incrementOpN(); err != nil {
return false, err return false, err
} }
// Get the bitmap from bitmapCache or fragment.storage.
bm := f.bitmap(bitmapID, true, true)
bm.SetBit(profileID)
// Update the cache. // Update the cache.
if f.bitmap(bitmapID).SetBit(profileID) { f.cache.Add(bitmapID, bm.Count())
changed = true
}
f.stats.Count("setN", 1) f.stats.Count("setN", 1)
@ -376,7 +401,8 @@ func (f *Fragment) ClearBit(bitmapID, profileID uint64) (bool, error) {
return f.clearBit(bitmapID, profileID) 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. // Determine the position of the bit in the storage.
pos, err := f.pos(bitmapID, profileID) pos, err := f.pos(bitmapID, profileID)
if err != nil { if err != nil {
@ -384,11 +410,15 @@ func (f *Fragment) clearBit(bitmapID, profileID uint64) (bool, error) {
} }
// Write to storage. // Write to storage.
changed, err := f.storage.Remove(pos) if changed, err = f.storage.Remove(pos); err != nil {
if err != nil {
return false, err return false, err
} }
// Don't update the cache if nothing changed.
if !changed {
return changed, nil
}
// Invalidate block checksum. // Invalidate block checksum.
delete(f.checksums, int(bitmapID/HashBlockSize)) delete(f.checksums, int(bitmapID/HashBlockSize))
@ -397,10 +427,12 @@ func (f *Fragment) clearBit(bitmapID, profileID uint64) (bool, error) {
return false, err return false, err
} }
// Get the bitmap from bitmapCache or fragment.storage.
bm := f.bitmap(bitmapID, true, true)
bm.ClearBit(profileID)
// Update the cache. // Update the cache.
if f.bitmap(bitmapID).ClearBit(profileID) { f.cache.Add(bitmapID, bm.Count())
return true, nil
}
f.stats.Count("clearN", 1) 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. // 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 { for _, pair := range pairs {
bitmapID, n := pair.ID, pair.Count 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. // 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. // Calculate count and append.
count := n count := n
if opt.Src != nil { if opt.Src != nil {
@ -486,26 +519,22 @@ func (f *Fragment) Top(opt TopOptions) ([]Pair, error) {
if count == 0 { if count == 0 {
continue 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 // If we reach the requested number of pairs and we are not computing
// intersections then simply exit. If we are intersecting then sort // intersections then simply exit. If we are intersecting then sort
// and then only keep pairs that are higher than the lowest count. // 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 { if opt.Src == nil {
break break
} }
sort.Sort(Pairs(results))
} }
continue continue
} }
// Retrieve the lowest count we have. // Retrieve the lowest count we have.
// If it's too low then don't try finding anymore pairs. // If it's too low then don't try finding anymore pairs.
threshold := results[len(results)-1].Count threshold := results.Pairs[0].Count
if threshold < MinThreshold {
break
}
// If the bitmap doesn't have enough bits set before the intersection // 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. // 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 // Calculate the intersecting bit count and skip if it's below our
// last bitmap in our current result set. // last bitmap in our current result set.
count := opt.Src.IntersectionCount(f.Bitmap(bitmapID)) count := opt.Src.IntersectionCount(f.Bitmap(bitmapID))
if count < threshold { if count < threshold {
continue continue
} }
// Swap out the last pair for this new count. heap.Push(results, Pair{Key: bitmapID, Count: 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))
}
} }
r := make(Pairs, results.Len(), results.Len())
sort.Sort(Pairs(results)) x := results.Len()
return results, nil i := 1
for results.Len() > 0 {
r[x-i] = heap.Pop(results).(Pair)
i++
}
return r, nil
} }
func (f *Fragment) topBitmapPairs(bitmapIDs []uint64) []BitmapPair { func (f *Fragment) topBitmapPairs(bitmapIDs []uint64) []BitmapPair {
@ -543,23 +572,27 @@ func (f *Fragment) topBitmapPairs(bitmapIDs []uint64) []BitmapPair {
} }
// Otherwise retrieve specific bitmaps. // Otherwise retrieve specific bitmaps.
pairs := make([]BitmapPair, len(bitmapIDs)) pairs := make([]BitmapPair, 0, len(bitmapIDs))
for i, bitmapID := range bitmapIDs { for _, bitmapID := range bitmapIDs {
// Look up cache first, if available. // Look up cache first, if available.
if n := f.cache.Get(bitmapID); n > 0 { if n := f.cache.Get(bitmapID); n > 0 {
pairs[i] = BitmapPair{ pairs = append(pairs, BitmapPair{
ID: bitmapID, ID: bitmapID,
Count: n, Count: n,
} })
continue continue
} }
// Otherwise load from storage. bm := f.Bitmap(bitmapID)
pairs[i] = BitmapPair{ if bm.Count() > 0 {
ID: bitmapID, // Otherwise load from storage.
Count: f.Bitmap(bitmapID).Count(), pairs = append(pairs, BitmapPair{
ID: bitmapID,
Count: bm.Count(),
})
} }
} }
sort.Sort(BitmapPairs(pairs))
return pairs return pairs
} }
@ -838,7 +871,6 @@ func (f *Fragment) Import(bitmapIDs, profileIDs []uint64) error {
// Process every bit. // Process every bit.
// If an error occurs then reopen the storage. // If an error occurs then reopen the storage.
lastID := uint64(0) lastID := uint64(0)
bmCounter := 0
if err := func() error { if err := func() error {
set := make(map[uint64]struct{}) set := make(map[uint64]struct{})
for i := range bitmapIDs { for i := range bitmapIDs {
@ -851,7 +883,7 @@ func (f *Fragment) Import(bitmapIDs, profileIDs []uint64) error {
} }
// Write to storage. // Write to storage.
changed, err := f.storage.Add(pos) _, err = f.storage.Add(pos)
if err != nil { if err != nil {
return err return err
} }
@ -863,9 +895,6 @@ func (f *Fragment) Import(bitmapIDs, profileIDs []uint64) error {
lastID = bitmapID lastID = bitmapID
set[bitmapID] = struct{}{} set[bitmapID] = struct{}{}
} }
if changed {
bmCounter += 1
}
// Invalidate block checksum. // Invalidate block checksum.
delete(f.checksums, int(bitmapID/HashBlockSize)) delete(f.checksums, int(bitmapID/HashBlockSize))
@ -873,7 +902,10 @@ func (f *Fragment) Import(bitmapIDs, profileIDs []uint64) error {
// Update cache counts for all bitmaps. // Update cache counts for all bitmaps.
for bitmapID := range set { 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() f.cache.Invalidate()
@ -912,10 +944,15 @@ func (f *Fragment) Snapshot() error {
defer f.mu.Unlock() defer f.mu.Unlock()
return f.snapshot() 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 { func (f *Fragment) snapshot() error {
logger := f.logger() logger := f.logger()
logger.Printf("fragment: snapshotting %s/%s/%d", f.db, f.frame, f.slice) 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. // Create a temporary file to snapshot to.
snapshotPath := f.path + SnapshotExt snapshotPath := f.path + SnapshotExt
@ -1216,7 +1253,6 @@ func (s *FragmentSyncer) SyncFragment() error {
// Determine replica set. // Determine replica set.
nodes := s.Cluster.FragmentNodes(s.Fragment.DB(), s.Fragment.Slice()) nodes := s.Cluster.FragmentNodes(s.Fragment.DB(), s.Fragment.Slice())
if len(nodes) == 1 { if len(nodes) == 1 {
//fmt.Println("no place to replicate", s.Fragment.DB(), s.Fragment.Frame(), s.Fragment.Slice())
return nil return nil
} }

View file

@ -201,7 +201,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r) 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. // handleGetSchema handles GET /schema requests.

View file

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