mirror of https://github.com/tidwall/tile38.git
Optimization for non-cross geofence detection
This commit fixes a performance issue with the algorithm that determines with geofences are potential candidates for notifications following a SET operation. Details Prior to commitb471873
(10 commits ago) there was a bug where the "cross" detection was not firing in all cases. This happened because when looking for candidates for "cross" due to a SET operation, only the geofences that overlapped the previous position of the object and the geofences that overlapped the new position where searched. But, in fac, all of the geofences that overlapped the union rectangle of the old and new position should have been searched. That commit fixed the problem by searching a union rect of the old and new positions. While this is an accurate solution, it caused a slowdown on systems that have big/wild position changes that might cross a huge number of geofences, even when those geofences did not need actually need "cross" detection. The fix With this commit the geofences that have a "cross" detection are stored in a seperated tree from those that do not. This allows for a hybrid of the functionality prior and postb471873
. Fixes #583
This commit is contained in:
parent
100be7be3c
commit
9998e03f6f
|
@ -15,7 +15,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tidwall/buntdb"
|
"github.com/tidwall/buntdb"
|
||||||
"github.com/tidwall/geojson/geometry"
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
"github.com/tidwall/redcon"
|
"github.com/tidwall/redcon"
|
||||||
"github.com/tidwall/resp"
|
"github.com/tidwall/resp"
|
||||||
|
@ -227,55 +226,70 @@ func (s *Server) writeAOF(args []string, d *commandDetails) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getQueueCandidates(d *commandDetails) []*Hook {
|
func (s *Server) getQueueCandidates(d *commandDetails) []*Hook {
|
||||||
var candidates []*Hook
|
candidates := make(map[*Hook]bool)
|
||||||
// add the hooks with "outside" detection
|
// add the hooks with "outside" detection
|
||||||
if len(s.hooksOut) > 0 {
|
|
||||||
for _, hook := range s.hooksOut {
|
for _, hook := range s.hooksOut {
|
||||||
if hook.Key == d.key {
|
if hook.Key == d.key {
|
||||||
candidates = append(candidates, hook)
|
candidates[hook] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// look for candidates that might "cross" geofences
|
||||||
// search the hook spatial tree
|
if d.oldObj != nil && d.obj != nil && s.hookCross.Len() > 0 {
|
||||||
// build a rectangle that fills the old and new which will be enough to
|
r1, r2 := d.oldObj.Rect(), d.obj.Rect()
|
||||||
// handle "enter", "inside", "exit", and "cross" detections.
|
s.hookCross.Search(
|
||||||
var rect geometry.Rect
|
[2]float64{
|
||||||
if d.oldObj != nil {
|
math.Min(r1.Min.X, r2.Min.X),
|
||||||
rect = d.oldObj.Rect()
|
math.Min(r1.Min.Y, r2.Min.Y),
|
||||||
if d.obj != nil {
|
|
||||||
r2 := d.obj.Rect()
|
|
||||||
rect.Min.X = math.Min(rect.Min.X, r2.Min.X)
|
|
||||||
rect.Min.Y = math.Min(rect.Min.Y, r2.Min.Y)
|
|
||||||
rect.Max.X = math.Max(rect.Max.X, r2.Max.X)
|
|
||||||
rect.Max.Y = math.Max(rect.Max.Y, r2.Max.Y)
|
|
||||||
}
|
|
||||||
} else if d.obj != nil {
|
|
||||||
rect = d.obj.Rect()
|
|
||||||
} else {
|
|
||||||
return candidates
|
|
||||||
}
|
|
||||||
s.hookTree.Search(
|
|
||||||
[2]float64{rect.Min.X, rect.Min.Y},
|
|
||||||
[2]float64{rect.Max.X, rect.Max.Y},
|
|
||||||
func(_, _ [2]float64, value interface{}) bool {
|
|
||||||
hook := value.(*Hook)
|
|
||||||
if hook.Key != d.key {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
var found bool
|
|
||||||
for _, candidate := range candidates {
|
|
||||||
if candidate == hook {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
candidates = append(candidates, hook)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
},
|
||||||
)
|
[2]float64{
|
||||||
return candidates
|
math.Max(r1.Max.X, r2.Max.X),
|
||||||
|
math.Max(r1.Max.Y, r2.Max.Y),
|
||||||
|
},
|
||||||
|
func(min, max [2]float64, value interface{}) bool {
|
||||||
|
hook := value.(*Hook)
|
||||||
|
if hook.Key == d.key {
|
||||||
|
candidates[hook] = true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// look for candidates that overlap the old object
|
||||||
|
if d.oldObj != nil {
|
||||||
|
r1 := d.oldObj.Rect()
|
||||||
|
s.hookTree.Search(
|
||||||
|
[2]float64{r1.Min.X, r1.Min.Y},
|
||||||
|
[2]float64{r1.Max.X, r1.Max.Y},
|
||||||
|
func(min, max [2]float64, value interface{}) bool {
|
||||||
|
hook := value.(*Hook)
|
||||||
|
if hook.Key == d.key {
|
||||||
|
candidates[hook] = true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// look for candidates that overlap the new object
|
||||||
|
if d.obj != nil {
|
||||||
|
r1 := d.obj.Rect()
|
||||||
|
s.hookTree.Search(
|
||||||
|
[2]float64{r1.Min.X, r1.Min.Y},
|
||||||
|
[2]float64{r1.Max.X, r1.Max.Y},
|
||||||
|
func(min, max [2]float64, value interface{}) bool {
|
||||||
|
hook := value.(*Hook)
|
||||||
|
if hook.Key == d.key {
|
||||||
|
candidates[hook] = true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(candidates) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// return the candidates as a slice
|
||||||
|
ret := make([]*Hook, 0, len(candidates))
|
||||||
|
for hook := range candidates {
|
||||||
|
ret = append(ret, hook)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) queueHooks(d *commandDetails) error {
|
func (s *Server) queueHooks(d *commandDetails) error {
|
||||||
|
|
|
@ -514,6 +514,7 @@ func (server *Server) cmdFlushDB(msg *Message) (res resp.Value, d commandDetails
|
||||||
server.hooks = make(map[string]*Hook)
|
server.hooks = make(map[string]*Hook)
|
||||||
server.hooksOut = make(map[string]*Hook)
|
server.hooksOut = make(map[string]*Hook)
|
||||||
server.hookTree = rbang.RTree{}
|
server.hookTree = rbang.RTree{}
|
||||||
|
server.hookCross = rbang.RTree{}
|
||||||
d.command = "flushdb"
|
d.command = "flushdb"
|
||||||
d.updated = true
|
d.updated = true
|
||||||
d.timestamp = time.Now()
|
d.timestamp = time.Now()
|
||||||
|
|
|
@ -197,6 +197,12 @@ func (s *Server) cmdSetHook(msg *Message, chanCmd bool) (
|
||||||
[2]float64{rect.Min.X, rect.Min.Y},
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
[2]float64{rect.Max.X, rect.Max.Y},
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
prevHook)
|
prevHook)
|
||||||
|
if prevHook.Fence.detect["cross"] {
|
||||||
|
s.hookCross.Delete(
|
||||||
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
|
prevHook)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// add hook to spatial index
|
// add hook to spatial index
|
||||||
if hook != nil && hook.Fence != nil && hook.Fence.obj != nil {
|
if hook != nil && hook.Fence != nil && hook.Fence.obj != nil {
|
||||||
|
@ -205,6 +211,12 @@ func (s *Server) cmdSetHook(msg *Message, chanCmd bool) (
|
||||||
[2]float64{rect.Min.X, rect.Min.Y},
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
[2]float64{rect.Max.X, rect.Max.Y},
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
hook)
|
hook)
|
||||||
|
if hook.Fence.detect["cross"] {
|
||||||
|
s.hookCross.Insert(
|
||||||
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
|
hook)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hook.Open() // Opens a goroutine to notify the hook
|
hook.Open() // Opens a goroutine to notify the hook
|
||||||
|
@ -246,6 +258,12 @@ func (s *Server) cmdDelHook(msg *Message, chanCmd bool) (
|
||||||
[2]float64{rect.Min.X, rect.Min.Y},
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
[2]float64{rect.Max.X, rect.Max.Y},
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
hook)
|
hook)
|
||||||
|
if hook.Fence.detect["cross"] {
|
||||||
|
s.hookCross.Delete(
|
||||||
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
|
hook)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
d.updated = true
|
d.updated = true
|
||||||
}
|
}
|
||||||
|
@ -298,6 +316,12 @@ func (s *Server) cmdPDelHook(msg *Message, channel bool) (
|
||||||
[2]float64{rect.Min.X, rect.Min.Y},
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
[2]float64{rect.Max.X, rect.Max.Y},
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
hook)
|
hook)
|
||||||
|
if hook.Fence.detect["cross"] {
|
||||||
|
s.hookCross.Delete(
|
||||||
|
[2]float64{rect.Min.X, rect.Min.Y},
|
||||||
|
[2]float64{rect.Max.X, rect.Max.Y},
|
||||||
|
hook)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
d.updated = true
|
d.updated = true
|
||||||
count++
|
count++
|
||||||
|
|
|
@ -115,7 +115,8 @@ type Server struct {
|
||||||
shrinking bool // aof shrinking flag
|
shrinking bool // aof shrinking flag
|
||||||
shrinklog [][]string // aof shrinking log
|
shrinklog [][]string // aof shrinking log
|
||||||
hooks map[string]*Hook // hook name
|
hooks map[string]*Hook // hook name
|
||||||
hookTree rbang.RTree // hook spatial tree containing all
|
hookCross rbang.RTree // hook spatial tree for "cross" geofences
|
||||||
|
hookTree rbang.RTree // hook spatial tree for all
|
||||||
hooksOut map[string]*Hook // hooks with "outside" detection
|
hooksOut map[string]*Hook // hooks with "outside" detection
|
||||||
aofconnM map[net.Conn]bool
|
aofconnM map[net.Conn]bool
|
||||||
luascripts *lScriptMap
|
luascripts *lScriptMap
|
||||||
|
|
Loading…
Reference in New Issue