-
Notifications
You must be signed in to change notification settings - Fork 16
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: Add redis backend #813
base: master
Are you sure you want to change the base?
Conversation
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.
Gotta admit, Redis is not really the DB that I would have picked for this, but sure, provided that the durable storage element is set, it should be able to handle light loads.
FWIW, I had done a version that used Postgres a long time ago. (If you go through the Changelog, you might find a reference to it.) Sadly, I burned that branch a while ago.
Still, thank you for this. There are a few things you'll need to do to fix it up.
The requirements are:
- Please don't use
.unwrap()
(or if you do, please explain why.) - We actually do need to support topic messages.
- (Make sure that all unit and integration tests pass)
@@ -1,5 +1,5 @@ | |||
pub fn main() { | |||
if !cfg!(feature = "bigtable") { | |||
panic!("No database defined! Please compile with `features=bigtable`"); | |||
if !cfg!(feature = "bigtable") && !cfg!(feature = "redis") { |
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.
bigtable
and redis
are conflicting feature flags, no?
If you like, we do a conditional check in syncstorage-rs to prevent folk from accidentally specifying both flags.
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.
They should not be conflicting, the server is selected with the dsn url
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.
Right, the actual database selection is non-conflicting, but the compilation is not. I don't know of a situation where someone might want to have both Bigtable and Redis support enabled on the back end at the same time, considering that there's only one database you can specify or use.
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 agree there is no use case when both are used, but I can see some examples for compiling both. For instance if a generic container image is published/used, or one do a build that can be used in different environments, (dev/tests with redis, integration/prod with bigtable)
Thank you for the review, I've addressed most of the comments. I went to redis, because this is also what I used for Uppush, which also work as a webpush server. I've seen in the issue comments you had a version with Postgres. Regarding the topics, this implementation supports topic, but in another way. See the tests: we store 2 messages with the same topic and fetch a single one (if I set topic to None, it fails) // We store 2 messages, with a single topic
let test_notification_0 = crate::db::Notification {
channel_id: topic_chid,
version: "version0".to_owned(),
ttl: 300,
topic: Some("topic".to_owned()),
timestamp,
data: Some(test_data.clone()),
sortkey_timestamp: Some(sort_key),
..Default::default()
};
assert!(client
.save_message(&uaid, test_notification_0.clone())
.await
.is_ok());
let test_notification = crate::db::Notification {
timestamp: now(),
version: "version1".to_owned(),
sortkey_timestamp: Some(sort_key + 10),
..test_notification_0
};
assert!(client
.save_message(&uaid, test_notification.clone())
.await
.is_ok());
let mut fetched = client.fetch_timestamp_messages(&uaid, None, 999).await?;
assert_eq!(fetched.messages.len(), 1); By the way, using the current Regarding the tests, they all pass |
Sigh, just like the other PR, I'm afraid we can't accept this PR with unsigned commits. Please feel free to resubmit this with signed commits. (This is a policy put in place by our Security Operations team.) |
If this is a topic message: zadd(msg_list_key) and zadd(exp_list_key) will replace their old entry in the sorted sets if one already exists and set(msg_key, message) will override it too
Exclude lower band from redis range
All the commits are signed |
@@ -1,5 +1,5 @@ | |||
pub fn main() { | |||
if !cfg!(feature = "bigtable") { | |||
panic!("No database defined! Please compile with `features=bigtable`"); | |||
if !cfg!(feature = "bigtable") && !cfg!(feature = "redis") { |
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.
Right, the actual database selection is non-conflicting, but the compilation is not. I don't know of a situation where someone might want to have both Bigtable and Redis support enabled on the back end at the same time, considering that there's only one database you can specify or use.
|
||
mod error; | ||
|
||
use super::RedisDbSettings; |
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: (feel free to ignore) we don't tend to use super
a lot because it can make refactoring surprisingly weird. Instead we usually root off of crate
like above.
so this would be:
use crate::db::redis::RedisDbSettings;
Ok(channels) | ||
} | ||
|
||
/// Delete the channel. Does not delete its associated pending messages. |
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.
As I note above you might be the exception here and actually want to delete data when the channel is dropped.
conn: Arc::new(Mutex::new(None)), | ||
settings: db_settings, | ||
metrics, | ||
redis_opts: SetOptions::default().with_expiration(SetExpiry::EX(MAX_ROUTER_TTL)), |
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.
So, fun fact: Bigtable and dynamodb can make writes kind of expensive, so we instead rely on TTLs for data to get cleaned up. Redis doesn't really have that limit, so while having an expiration definitely makes sense, feel free to drop values as they're no longer required (e.g. once you get an ACK, when the ChannelID or UserID is dropped, etc.)
Honestly, this is probably a lot more important in the context of Redis, since memory is more a problem than disk space.
/// | ||
/// If [`limit`] = 0, we fetch all messages after [`timestamp`]. | ||
/// | ||
/// This can return expired messages, following bigtables behavior |
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, but there's no real need to do that, and in fact, it's probably an anti-pattern we need to fix in bigtable. (There's a potential abuse vector that should be cut off.)
Ideally, messages should be dropped once they've been decisively handled (e.g. if they're ACK'd or if the NACK response specifies that the message is undeliverable. see #795 )
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.
Expired messages are removed from redis, so it doesn't use memory for expired messages.
But the message id is not removed from the message lists when it expires.
We can't just remove expired messages from the result, because if the messages vec is empty (so when there are 10 expired messages), check_storage_advance
stops.
So there are 3 main solutions:
- Change
check_storage_advance
to check ifmessages.is_empty() && timestamp_has_changed() // to write
, but I didn't want to change logic on other components - Writing a loop in
fetch_timestamp_messages
but this is counter productive to do yet another loop - Returning dummy expired events, that
check_sotrage_advance
will remove (this is what it does now)
Ideally, messages should be dropped once they've been decisively handled (e.g. if they're ACK'd or if the NACK response specifies that the message is undeliverable. see #795 )
remove_message
do remove the message, so this is already the case :)
Currently, autopush requires using Bigtable, there is no particular issue for a big, public server, but some may want to host their own server, or to do some tests locally. So this PR adds another kind of backend using Redis. It should work with redis forks too.
It mostly follow the behavior of bigtable with a few differences:
run_gauntlet
) is a bit different for this: it adds 2 messages for the same topic and check only one is fetched.fetch_timestamp_messages
are the date of the record in ms, with bigtables it uses the message'ssortkey_timestamp
, in seconds.I've also added a docker-compose file to run everything.
For the moment RedisClientImpl doesn't use connection pooling, but this should be easily implemented, it also provide the Command trait, returned by
RedisClientImpl.connection
Fixes #774