New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement a Cache For Monitor Updates #75
Conversation
47f59c2
to
4cdfd00
Compare
@vtolstov to your comment in #78
calling the handlers synchronously doesn't seem like a good idea because if a library user writes a handler that is slow, then all cache reads will be blocked until all handlers are done processing events. calling the handler async gets past the locking issues, but as you mentioned, having unbounded numbers of goroutines isn't great either. one other thing i can think of is to create buffered channels for Add/Update/Delete operations and have a goroutine that reads those channels and dispatches events synchronously. worst case, the channel buffer fills and we block on sending updates to the channel until there is room. to avoid blocking in that case, we could use a ring buffer for the channels, and then we'd drop events when the channels are full. i'm curious as to what behaviour would be preferred? |
I'm prefer something like worker pool of goroutines. So user of library can decide how much resources can be used and how fast updates processed. Another thing that may be we can have some deadline for event processing and if consumer is slow - log some warning and drop some events. |
Ack. ants looks really cool. thanks for sharing! Kubernetes has a similar problem in the cache in client-go, and I think perhaps we could follow the same pattern.
This way, we can keep the libovsdb footprint small - one additional goroutine to dispatch events to listeners - and give the user complete control of the thread pool for event handling. Now I just need to cobble together a quick ring buffer and we should be good to go 😉 |
I think that this will be good. |
9a56679
to
eb7eff9
Compare
@vtolstov done. the ring buffer isn't very well optimised, but works well enough for the single writer, multiple reader we have currently:
I've used @hzhou8 @amorenoz I think this is pretty ready for review now if you have a spare minute. |
Pull Request Test Coverage Report for Build 792357558
💛 - Coveralls |
@amorenoz comments addressed. Please take another look. Thanks to your comments I made some improvements to the ring buffer impl and added some additional docs. |
While RFC 7047 allows for a database server to have multiple databases, in the wild, this is not the case for Open vSwitch or OVN. There are cases where supporting multiple databases would be odd. For example, if you were to Monitor the address_set table of the OVN North and South databases, the client would get no indication in the corresponding update notifiction of which DB the update was for. As such, it seems sensible to only support one database per client Signed-off-by: Dave Tucker <dave@dtucker.co.uk>
Since the Client can only have one database, we no longer need to provide the client methods with database name. This provides a much more simple API Signed-off-by: Dave Tucker <dave@dtucker.co.uk>
This can be handled client side with a channel Signed-off-by: Dave Tucker <dave@dtucker.co.uk>
Also use log.Fatal for errors as it's less verbose Signed-off-by: Dave Tucker <dave@dtucker.co.uk>
The client now includes a cache, and also registers a handler to ensure the cache is updated once Monitor or MonitorAll is called. It also permits clients to register callbacks for Add/Update/Delete cache events, which is a nicer API that registering your own handler and unpacking the updates yourself. Signed-off-by: Dave Tucker <dave@dtucker.co.uk>
@amorenoz suggested that we use channels instead. The only drawback is that when the buffer is full, we drop events vs in the ring buffer we would overwrite older events. In both cases you're losing events, and it's up to the library user to make sure that doesn't happen. As neither has a clear advantage over the other I suggest we stick with the simple implementation for now, and we can re-visit the buffer again if we need it in future. |
# CHANNELS
$ time go run ./example/stress/stress.go -ovsdb tcp::49154 -ninserts 100 Summary:
Insertions: 202
Deletions: 100
go run ./example/stress/stress.go -ovsdb tcp::49154 -ninserts 100 0.68s user 0.15s system 129% cpu 0.645 total
# BUFFER
$ time go run ./example/stress/stress.go -ovsdb tcp::49154 -ninserts 100 Summary:
Insertions: 188
Deletions: 90
go run ./example/stress/stress.go -ovsdb tcp::49154 -ninserts 100 1.27s user 0.18s system 177% cpu 0.822 total |
LGTM |
(See Original Comments on eBay#35)
This makes a few significant API changes in order to provide a cache for monitor updates.
Not only does it improve the API, but it also makes it possible to implement a
Get
(from cache) in the ORM API proposed in #78The API changes are as follows:
database
argument from theTransact
,Monitor
... APIsDisconnected(*OvsClient)
withDisconnected()
.The client needs to know about a handler to register it, but the handler shouldn't need to know about the client. Even if it did, that could be achieved through channels. Either way, the Go compiler didn't seem to like passing that client around once I'd added the cache feature.The feature itself can be seen in operation in the
play_with_ovs
example.In short, the
OvsClient
exposes aCache
and associated handler to ensure the cache is updated.As such, a user can call
Monitor
without a handler defined and the initial state and all updates will populate in to the cache.The cache API is very simple:
In addition, a user may choose to register a calback so they can perform an action when a cache item is updated:
Misc changes included in this PR to make sure CI is green:
encoding_test.go
s/"ovsTable"/ovsTable/g
/cc @amorenoz @hzhou8
Closes: #85