Skip to content

Commit 4552934

Browse files
authored
feat(spooler): Add CachingEnvelopeStack (#4242)
1 parent 3678120 commit 4552934

File tree

5 files changed

+112
-5
lines changed

5 files changed

+112
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
- Add additional fields to the `Event` `Getter`. ([#4238](https://github.com/getsentry/relay/pull/4238))
3131
- Replace u64 with `OrganizationId` new-type struct for organization id. ([#4159](https://github.com/getsentry/relay/pull/4159))
3232
- Add computed contexts for `os`, `browser` and `runtime`. ([#4239](https://github.com/getsentry/relay/pull/4239))
33+
- Add `CachingEnvelopeStack` strategy to the buffer. ([#4242](https://github.com/getsentry/relay/pull/4242))
3334

3435
## 24.10.0
3536

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use chrono::{DateTime, Utc};
2+
3+
use super::EnvelopeStack;
4+
use crate::envelope::Envelope;
5+
6+
/// An envelope stack implementation that caches one element in memory and delegates
7+
/// to another envelope stack for additional storage.
8+
#[derive(Debug)]
9+
pub struct CachingEnvelopeStack<S> {
10+
/// The underlying envelope stack
11+
inner: S,
12+
/// The cached envelope (if any)
13+
cached: Option<Box<Envelope>>,
14+
}
15+
16+
impl<S> CachingEnvelopeStack<S>
17+
where
18+
S: EnvelopeStack,
19+
{
20+
/// Creates a new [`CachingEnvelopeStack`] wrapping the provided envelope stack
21+
pub fn new(inner: S) -> Self {
22+
Self {
23+
inner,
24+
cached: None,
25+
}
26+
}
27+
}
28+
29+
impl<S> EnvelopeStack for CachingEnvelopeStack<S>
30+
where
31+
S: EnvelopeStack,
32+
{
33+
type Error = S::Error;
34+
35+
async fn push(&mut self, envelope: Box<Envelope>) -> Result<(), Self::Error> {
36+
if let Some(cached) = self.cached.take() {
37+
self.inner.push(cached).await?;
38+
}
39+
self.cached = Some(envelope);
40+
41+
Ok(())
42+
}
43+
44+
async fn peek(&mut self) -> Result<Option<DateTime<Utc>>, Self::Error> {
45+
if let Some(ref envelope) = self.cached {
46+
Ok(Some(envelope.received_at()))
47+
} else {
48+
self.inner.peek().await
49+
}
50+
}
51+
52+
async fn pop(&mut self) -> Result<Option<Box<Envelope>>, Self::Error> {
53+
if let Some(envelope) = self.cached.take() {
54+
Ok(Some(envelope))
55+
} else {
56+
self.inner.pop().await
57+
}
58+
}
59+
60+
async fn flush(mut self) {
61+
if let Some(envelope) = self.cached {
62+
if self.inner.push(envelope).await.is_err() {
63+
relay_log::error!(
64+
"error while pushing the cached envelope in the inner stack during flushing",
65+
);
66+
}
67+
}
68+
self.inner.flush().await;
69+
}
70+
}
71+
72+
#[cfg(test)]
73+
mod tests {
74+
use super::*;
75+
use crate::services::buffer::envelope_stack::memory::MemoryEnvelopeStack;
76+
use crate::services::buffer::testutils::utils::mock_envelope;
77+
78+
#[tokio::test]
79+
async fn test_caching_stack() {
80+
let inner = MemoryEnvelopeStack::new();
81+
let mut stack = CachingEnvelopeStack::new(inner);
82+
83+
// Create test envelopes with different timestamps
84+
let envelope_1 = mock_envelope(Utc::now());
85+
let envelope_2 = mock_envelope(Utc::now());
86+
87+
// Push 2 envelopes
88+
stack.push(envelope_1).await.unwrap();
89+
stack.push(envelope_2).await.unwrap();
90+
91+
// We pop the cached element.
92+
assert!(stack.pop().await.unwrap().is_some());
93+
94+
// We peek the stack expecting it peeks the inner one.
95+
assert!(stack.peek().await.unwrap().is_some());
96+
97+
// We pop the element and then check if the stack is empty.
98+
assert!(stack.pop().await.unwrap().is_some());
99+
assert!(stack.peek().await.unwrap().is_none());
100+
assert!(stack.pop().await.unwrap().is_none());
101+
}
102+
}

relay-server/src/services/buffer/envelope_stack/memory.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ impl EnvelopeStack for MemoryEnvelopeStack {
2424
}
2525

2626
async fn peek(&mut self) -> Result<Option<DateTime<Utc>>, Self::Error> {
27-
Ok(self.0.last().map(|e| e.meta().received_at()))
27+
Ok(self.0.last().map(|e| e.received_at()))
2828
}
2929

3030
async fn pop(&mut self) -> Result<Option<Box<Envelope>>, Self::Error> {

relay-server/src/services/buffer/envelope_stack/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ use chrono::{DateTime, Utc};
44

55
use crate::envelope::Envelope;
66

7+
pub mod caching;
78
pub mod memory;
89
pub mod sqlite;
910

1011
/// A stack-like data structure that holds [`Envelope`]s.
1112
pub trait EnvelopeStack: Send + std::fmt::Debug {
1213
/// The error type that is returned when an error is encountered during reading or writing the
1314
/// [`EnvelopeStack`].
14-
type Error: std::fmt::Debug;
15+
type Error: std::fmt::Debug + std::error::Error;
1516

1617
/// Pushes an [`Envelope`] on top of the stack.
1718
fn push(&mut self, envelope: Box<Envelope>) -> impl Future<Output = Result<(), Self::Error>>;

relay-server/src/services/buffer/stack_provider/sqlite.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::error::Error;
33
use relay_config::Config;
44

55
use crate::services::buffer::common::ProjectKeyPair;
6+
use crate::services::buffer::envelope_stack::caching::CachingEnvelopeStack;
67
use crate::services::buffer::envelope_store::sqlite::{
78
SqliteEnvelopeStore, SqliteEnvelopeStoreError,
89
};
@@ -38,7 +39,7 @@ impl SqliteStackProvider {
3839
}
3940

4041
impl StackProvider for SqliteStackProvider {
41-
type Stack = SqliteEnvelopeStack;
42+
type Stack = CachingEnvelopeStack<SqliteEnvelopeStack>;
4243

4344
async fn initialize(&self) -> InitializationState {
4445
match self.envelope_store.project_key_pairs().await {
@@ -58,7 +59,7 @@ impl StackProvider for SqliteStackProvider {
5859
stack_creation_type: StackCreationType,
5960
project_key_pair: ProjectKeyPair,
6061
) -> Self::Stack {
61-
SqliteEnvelopeStack::new(
62+
let inner = SqliteEnvelopeStack::new(
6263
self.envelope_store.clone(),
6364
self.batch_size_bytes,
6465
project_key_pair.own_key,
@@ -69,7 +70,9 @@ impl StackProvider for SqliteStackProvider {
6970
// it was empty, or we never had data on disk for that stack, so we assume by default
7071
// that there is no need to check disk until some data is spooled.
7172
Self::assume_data_on_disk(stack_creation_type),
72-
)
73+
);
74+
75+
CachingEnvelopeStack::new(inner)
7376
}
7477

7578
fn has_store_capacity(&self) -> bool {

0 commit comments

Comments
 (0)