mirror of https://github.com/tidwall/tile38.git
Change hooks collection type from hashmap to btree
This commit changes the collection type that holds all of the hooks from a hashmap to a btree. This allows for better flexibility for operations that need to perform range searches and scanning of the collection.
This commit is contained in:
parent
decafae2d7
commit
7ff0d18868
|
@ -224,11 +224,13 @@ func (s *Server) writeAOF(args []string, d *commandDetails) error {
|
|||
func (s *Server) getQueueCandidates(d *commandDetails) []*Hook {
|
||||
candidates := make(map[*Hook]bool)
|
||||
// add the hooks with "outside" detection
|
||||
for _, hook := range s.hooksOut {
|
||||
s.hooksOut.Ascend(nil, func(v interface{}) bool {
|
||||
hook := v.(*Hook)
|
||||
if hook.Key == d.key {
|
||||
candidates[hook] = true
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
// look for candidates that might "cross" geofences
|
||||
if d.oldObj != nil && d.obj != nil && s.hookCross.Len() > 0 {
|
||||
r1, r2 := d.oldObj.Rect(), d.obj.Rect()
|
||||
|
|
|
@ -3,11 +3,11 @@ package server
|
|||
import (
|
||||
"math"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/btree"
|
||||
"github.com/tidwall/geojson"
|
||||
"github.com/tidwall/tile38/core"
|
||||
"github.com/tidwall/tile38/internal/collection"
|
||||
|
@ -169,17 +169,19 @@ func (server *Server) aofshrink() {
|
|||
func() {
|
||||
server.mu.Lock()
|
||||
defer server.mu.Unlock()
|
||||
for name := range server.hooks {
|
||||
hnames = append(hnames, name)
|
||||
}
|
||||
hnames = make([]string, 0, server.hooks.Len())
|
||||
server.hooks.Walk(func(v []interface{}) {
|
||||
for _, v := range v {
|
||||
hnames = append(hnames, v.(*Hook).Name)
|
||||
}
|
||||
})
|
||||
}()
|
||||
// sort the names for consistency
|
||||
sort.Strings(hnames)
|
||||
var hookHint btree.PathHint
|
||||
for _, name := range hnames {
|
||||
func() {
|
||||
server.mu.Lock()
|
||||
defer server.mu.Unlock()
|
||||
hook := server.hooks[name]
|
||||
hook, _ := server.hooks.GetHint(name, &hookHint).(*Hook)
|
||||
if hook == nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -462,12 +462,14 @@ func (server *Server) cmdRename(msg *Message, nx bool) (res resp.Value, d comman
|
|||
err = errKeyNotFound
|
||||
return
|
||||
}
|
||||
for _, h := range server.hooks {
|
||||
server.hooks.Ascend(nil, func(v interface{}) bool {
|
||||
h := v.(*Hook)
|
||||
if h.Key == d.key || h.Key == d.newKey {
|
||||
err = errKeyHasHooksSet
|
||||
return
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
d.command = "rename"
|
||||
newCol := server.getCol(d.newKey)
|
||||
if newCol == nil {
|
||||
|
@ -505,14 +507,17 @@ func (server *Server) cmdFlushDB(msg *Message) (res resp.Value, d commandDetails
|
|||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
|
||||
// clear the entire database
|
||||
server.cols = btree.NewNonConcurrent(byCollectionKey)
|
||||
server.groupHooks = btree.NewNonConcurrent(byGroupHook)
|
||||
server.groupObjects = btree.NewNonConcurrent(byGroupObject)
|
||||
server.hookExpires = btree.NewNonConcurrent(byHookExpires)
|
||||
server.hooks = make(map[string]*Hook)
|
||||
server.hooksOut = make(map[string]*Hook)
|
||||
server.hooks = btree.NewNonConcurrent(byHookName)
|
||||
server.hooksOut = btree.NewNonConcurrent(byHookName)
|
||||
server.hookTree = &rtree.RTree{}
|
||||
server.hookCross = &rtree.RTree{}
|
||||
|
||||
d.command = "flushdb"
|
||||
d.updated = true
|
||||
d.timestamp = time.Now()
|
||||
|
|
|
@ -22,18 +22,8 @@ var hookLogSetDefaults = &buntdb.SetOptions{
|
|||
TTL: time.Second * 30,
|
||||
}
|
||||
|
||||
type hooksByName []*Hook
|
||||
|
||||
func (a hooksByName) Len() int {
|
||||
return len(a)
|
||||
}
|
||||
|
||||
func (a hooksByName) Less(i, j int) bool {
|
||||
return a[i].Name < a[j].Name
|
||||
}
|
||||
|
||||
func (a hooksByName) Swap(i, j int) {
|
||||
a[i], a[j] = a[j], a[i]
|
||||
func byHookName(a, b interface{}) bool {
|
||||
return a.(*Hook).Name < b.(*Hook).Name
|
||||
}
|
||||
|
||||
func (s *Server) cmdSetHook(msg *Message, chanCmd bool) (
|
||||
|
@ -159,7 +149,7 @@ func (s *Server) cmdSetHook(msg *Message, chanCmd bool) (
|
|||
|
||||
return NOMessage, d, err
|
||||
}
|
||||
prevHook := s.hooks[name]
|
||||
prevHook, _ := s.hooks.Get(&Hook{Name: name}).(*Hook)
|
||||
if prevHook != nil {
|
||||
if prevHook.channel != chanCmd {
|
||||
return NOMessage, d,
|
||||
|
@ -180,8 +170,8 @@ func (s *Server) cmdSetHook(msg *Message, chanCmd bool) (
|
|||
}
|
||||
}
|
||||
prevHook.Close()
|
||||
delete(s.hooks, name)
|
||||
delete(s.hooksOut, name)
|
||||
s.hooks.Delete(prevHook)
|
||||
s.hooksOut.Delete(prevHook)
|
||||
if !prevHook.expires.IsZero() {
|
||||
s.hookExpires.Delete(prevHook)
|
||||
}
|
||||
|
@ -191,9 +181,9 @@ func (s *Server) cmdSetHook(msg *Message, chanCmd bool) (
|
|||
d.updated = true
|
||||
d.timestamp = time.Now()
|
||||
|
||||
s.hooks[name] = hook
|
||||
s.hooks.Set(hook)
|
||||
if hook.Fence.detect == nil || hook.Fence.detect["outside"] {
|
||||
s.hooksOut[name] = hook
|
||||
s.hooksOut.Set(hook)
|
||||
}
|
||||
|
||||
// remove previous hook from spatial index
|
||||
|
@ -264,11 +254,12 @@ func (s *Server) cmdDelHook(msg *Message, chanCmd bool) (
|
|||
if len(vs) != 0 {
|
||||
return NOMessage, d, errInvalidNumberOfArguments
|
||||
}
|
||||
if hook, ok := s.hooks[name]; ok && hook.channel == chanCmd {
|
||||
hook, _ := s.hooks.Get(&Hook{Name: name}).(*Hook)
|
||||
if hook != nil && hook.channel == chanCmd {
|
||||
hook.Close()
|
||||
// remove hook from maps
|
||||
delete(s.hooks, hook.Name)
|
||||
delete(s.hooksOut, hook.Name)
|
||||
s.hooks.Delete(hook)
|
||||
s.hooksOut.Delete(hook)
|
||||
if !hook.expires.IsZero() {
|
||||
s.hookExpires.Delete(hook)
|
||||
}
|
||||
|
@ -320,18 +311,20 @@ func (s *Server) cmdPDelHook(msg *Message, channel bool) (
|
|||
}
|
||||
|
||||
count := 0
|
||||
for name, hook := range s.hooks {
|
||||
var hooks []*Hook
|
||||
s.forEachHookByPattern(pattern, channel, func(hook *Hook) bool {
|
||||
hooks = append(hooks, hook)
|
||||
return true
|
||||
})
|
||||
|
||||
for _, hook := range hooks {
|
||||
if hook.channel != channel {
|
||||
continue
|
||||
}
|
||||
match, _ := glob.Match(pattern, name)
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
hook.Close()
|
||||
// remove hook from maps
|
||||
delete(s.hooks, hook.Name)
|
||||
delete(s.hooksOut, hook.Name)
|
||||
s.hooks.Delete(hook)
|
||||
s.hooksOut.Delete(hook)
|
||||
if !hook.expires.IsZero() {
|
||||
s.hookExpires.Delete(hook)
|
||||
}
|
||||
|
@ -365,6 +358,26 @@ func (s *Server) cmdPDelHook(msg *Message, channel bool) (
|
|||
return
|
||||
}
|
||||
|
||||
func (s *Server) forEachHookByPattern(
|
||||
pattern string, channel bool, iter func(hook *Hook) bool,
|
||||
) {
|
||||
g := glob.Parse(pattern, false)
|
||||
hasUpperLimit := g.Limits[1] != ""
|
||||
s.hooks.Ascend(&Hook{Name: g.Limits[0]}, func(v interface{}) bool {
|
||||
hook := v.(*Hook)
|
||||
if hasUpperLimit && hook.Name > g.Limits[1] {
|
||||
return false
|
||||
}
|
||||
if hook.channel == channel {
|
||||
match, _ := glob.Match(pattern, hook.Name)
|
||||
if match {
|
||||
return iter(hook)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) cmdHooks(msg *Message, channel bool) (
|
||||
res resp.Value, err error,
|
||||
) {
|
||||
|
@ -381,18 +394,6 @@ func (s *Server) cmdHooks(msg *Message, channel bool) (
|
|||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
|
||||
var hooks []*Hook
|
||||
for name, hook := range s.hooks {
|
||||
if hook.channel != channel {
|
||||
continue
|
||||
}
|
||||
match, _ := glob.Match(pattern, name)
|
||||
if match {
|
||||
hooks = append(hooks, hook)
|
||||
}
|
||||
}
|
||||
sort.Sort(hooksByName(hooks))
|
||||
|
||||
switch msg.OutputType {
|
||||
case JSON:
|
||||
buf := &bytes.Buffer{}
|
||||
|
@ -402,7 +403,8 @@ func (s *Server) cmdHooks(msg *Message, channel bool) (
|
|||
} else {
|
||||
buf.WriteString(`"hooks":[`)
|
||||
}
|
||||
for i, hook := range hooks {
|
||||
var i int
|
||||
s.forEachHookByPattern(pattern, channel, func(hook *Hook) bool {
|
||||
var ttl = -1
|
||||
if !hook.expires.IsZero() {
|
||||
ttl = int(hook.expires.Sub(start).Seconds())
|
||||
|
@ -444,13 +446,15 @@ func (s *Server) cmdHooks(msg *Message, channel bool) (
|
|||
buf.WriteString(jsonString(meta.Value))
|
||||
}
|
||||
buf.WriteString(`}}`)
|
||||
}
|
||||
i++
|
||||
return true
|
||||
})
|
||||
buf.WriteString(`],"elapsed":"` +
|
||||
time.Since(start).String() + "\"}")
|
||||
return resp.StringValue(buf.String()), nil
|
||||
case RESP:
|
||||
var vals []resp.Value
|
||||
for _, hook := range hooks {
|
||||
s.forEachHookByPattern(pattern, channel, func(hook *Hook) bool {
|
||||
var hvals []resp.Value
|
||||
hvals = append(hvals, resp.StringValue(hook.Name))
|
||||
hvals = append(hvals, resp.StringValue(hook.Key))
|
||||
|
@ -471,7 +475,8 @@ func (s *Server) cmdHooks(msg *Message, channel bool) (
|
|||
}
|
||||
hvals = append(hvals, resp.ArrayValue(metas))
|
||||
vals = append(vals, resp.ArrayValue(hvals))
|
||||
}
|
||||
return true
|
||||
})
|
||||
return resp.ArrayValue(vals), nil
|
||||
}
|
||||
return resp.SimpleStringValue(""), nil
|
||||
|
|
|
@ -112,17 +112,17 @@ type Server struct {
|
|||
lstack []*commandDetails
|
||||
lives map[*liveBuffer]bool
|
||||
lcond *sync.Cond
|
||||
fcup bool // follow caught up
|
||||
fcuponce bool // follow caught up once
|
||||
shrinking bool // aof shrinking flag
|
||||
shrinklog [][]string // aof shrinking log
|
||||
hooks map[string]*Hook // hook name
|
||||
hookCross *rtree.RTree // hook spatial tree for "cross" geofences
|
||||
hookTree *rtree.RTree // hook spatial tree for all
|
||||
hooksOut map[string]*Hook // hooks with "outside" detection
|
||||
groupHooks *btree.BTree // hooks that are connected to objects
|
||||
groupObjects *btree.BTree // objects that are connected to hooks
|
||||
hookExpires *btree.BTree // queue of all hooks marked for expiration
|
||||
fcup bool // follow caught up
|
||||
fcuponce bool // follow caught up once
|
||||
shrinking bool // aof shrinking flag
|
||||
shrinklog [][]string // aof shrinking log
|
||||
hooks *btree.BTree // hook name -- [string]*Hook
|
||||
hookCross *rtree.RTree // hook spatial tree for "cross" geofences
|
||||
hookTree *rtree.RTree // hook spatial tree for all
|
||||
hooksOut *btree.BTree // hooks with "outside" detection -- [string]*Hook
|
||||
groupHooks *btree.BTree // hooks that are connected to objects
|
||||
groupObjects *btree.BTree // objects that are connected to hooks
|
||||
hookExpires *btree.BTree // queue of all hooks marked for expiration
|
||||
|
||||
aofconnM map[net.Conn]io.Closer
|
||||
luascripts *lScriptMap
|
||||
|
@ -164,8 +164,8 @@ func Serve(opts Options) error {
|
|||
fcond: sync.NewCond(&sync.Mutex{}),
|
||||
lives: make(map[*liveBuffer]bool),
|
||||
lcond: sync.NewCond(&sync.Mutex{}),
|
||||
hooks: make(map[string]*Hook),
|
||||
hooksOut: make(map[string]*Hook),
|
||||
hooks: btree.NewNonConcurrent(byHookName),
|
||||
hooksOut: btree.NewNonConcurrent(byHookName),
|
||||
hookCross: &rtree.RTree{},
|
||||
hookTree: &rtree.RTree{},
|
||||
aofconnM: make(map[net.Conn]io.Closer),
|
||||
|
|
|
@ -157,7 +157,7 @@ func (s *Server) basicStats(m map[string]interface{}) {
|
|||
m["pid"] = os.Getpid()
|
||||
m["aof_size"] = s.aofsz
|
||||
m["num_collections"] = s.cols.Len()
|
||||
m["num_hooks"] = len(s.hooks)
|
||||
m["num_hooks"] = s.hooks.Len()
|
||||
sz := 0
|
||||
s.cols.Ascend(nil, func(v interface{}) bool {
|
||||
col := v.(*collectionKeyContainer).col
|
||||
|
@ -337,7 +337,7 @@ func (s *Server) extStats(m map[string]interface{}) {
|
|||
// Number of collections in the database
|
||||
m["tile38_num_collections"] = s.cols.Len()
|
||||
// Number of hooks in the database
|
||||
m["tile38_num_hooks"] = len(s.hooks)
|
||||
m["tile38_num_hooks"] = s.hooks.Len()
|
||||
// Number of hook groups in the database
|
||||
m["tile38_num_hook_groups"] = s.groupHooks.Len()
|
||||
// Number of object groups in the database
|
||||
|
|
Loading…
Reference in New Issue