-
Notifications
You must be signed in to change notification settings - Fork 94
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
feat(spooler): Add capacity check for the envelope buffer #3925
Conversation
} | ||
|
||
/// A provider of [`EnvelopeStack`] instances that is responsible for creating them. | ||
pub trait StacksManager: std::fmt::Debug { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to StacksManager
to make it more meaningful w.r.t. the mansions of the trait implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Manager
in a type name is usually a red flag for unclear separation of concerns. In this case it creates new stacks parametrized with the underlying storage backend, and queries the storage backend for information. Maybe we should call this StorageBackend
, and / or unite it with the other EnvelopeStore
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thing is that for me they are two separate entities, it's a coincidence that some of them forward the methods.
The dependency is:
flowchart TD
StacksManager --> EnvelopeStack --> EnvelopeStore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StacksManager
holds a reference to EnvelopeStore
, right? So there is already a coupling between the manager and the store:
classDiagram
StacksManager "1" o-- "1" EnvelopeStore
StacksManager --> "creates" EnvelopeStack
EnvelopeStack "n" o-- "1" EnvelopeStore
IMO this can be simplified to
classDiagram
EnvelopeStack o-- "uses" EnvelopeStore
EnvelopeStore --> "creates" EnvelopeStack
(store creates stack, stack uses store)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like having the store interact with the stack is not ideal, for me the store is just offering data to some consumers, the stack is dealing with the data in a domain-specific way and the stack manager deals with creating stacks.
/// Enum representing the current capacity of the [`StacksManager`] to accept new [`Envelope`]s. | ||
pub enum Capacity { | ||
Free, | ||
Full, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we don't expect capacity to ever have more than two unit variants, I would remove this and give the trait a has_capacity
method instead. That makes the intent more clear IMO.
} | ||
|
||
/// A provider of [`EnvelopeStack`] instances that is responsible for creating them. | ||
pub trait StacksManager: std::fmt::Debug { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Manager
in a type name is usually a red flag for unclear separation of concerns. In this case it creates new stacks parametrized with the underlying storage backend, and queries the storage backend for information. Maybe we should call this StorageBackend
, and / or unite it with the other EnvelopeStore
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment about await
, rest are nits.
relay-server/src/endpoints/common.rs
Outdated
@@ -307,12 +307,14 @@ fn queue_envelope( | |||
|
|||
match state.envelope_buffer() { | |||
Some(buffer) => { | |||
if !buffer.has_capacity().await { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second review, I think we should make this method sync, because we should avoid blocking the request handler on disk read / writes under all circumstances. One option to make it sync would be to change GuardedEnvelopeBuffer::has_capacity
to a try_lock
, and cache the resulting bool:
match self.inner.try_lock() {
Ok(guard) => {
let c = guard.backend.has_capacity();
self.cached_capacity.store(c);
c
},
Err(_) => self.cached_capacity.load()
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this could be an idea, given the intrinsic delay of memory and disk usage calculation, adding one by caching it should not be a big deal. My main worry with this is that we can't guarantee updated values in any way if lock contention is arbitrarily high.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds acceptable to me. We can emit a counter metric to track how often we emit cached vs fresh capacity. Once we transform this to a service, the cached capacity will be updated differently, anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I added the metric because I am interested.
pub mod sqlite; | ||
|
||
/// Trait that models a store of [`Envelope`]s. | ||
pub trait EnvelopeStore { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: As discussed, this does not need to be a trait. It might be beneficial to remove it, to avoid reader's expectations like "surely there is also a MemoryEnvelopeStore".
Co-authored-by: Joris Bayer <joris.bayer@sentry.io>
This PR adds the capability for checking the capacity of the envelope buffer. Such capacity is computed differently based on the strategy in use:
MemoryChecker
.Closes: https://github.com/getsentry/team-ingest/issues/517, https://github.com/getsentry/team-ingest/issues/521