Skip to content
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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

p1gp1g
Copy link

@p1gp1g p1gp1g commented Dec 19, 2024

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:

  • when saving a message with a topic, it simply removes the previous message for this topic. So topic messages and timestamp messages are processed the exact same way. Therefore the test (run_gauntlet) is a bit different for this: it adds 2 messages for the same topic and check only one is fetched.
  • Timestamps used and returned by fetch_timestamp_messages are the date of the record in ms, with bigtables it uses the message's sortkey_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

Copy link
Member

@jrconlin jrconlin left a 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") {
Copy link
Member

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.

Copy link
Author

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

Copy link
Member

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.

Copy link
Author

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)

@p1gp1g
Copy link
Author

p1gp1g commented Dec 21, 2024

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 chidmessageid allows to do this automatically, the topic isn't necessary: ddc8a46

Regarding the tests, they all pass cargo test --no-default-features --features redis

@jrconlin
Copy link
Member

jrconlin commented Jan 7, 2025

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.)

@p1gp1g
Copy link
Author

p1gp1g commented Jan 7, 2025

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") {
Copy link
Member

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;
Copy link
Member

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.
Copy link
Member

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)),
Copy link
Member

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.

@wrenix
Copy link

wrenix commented Jan 16, 2025

Is there any healthycheck? It looks like, that autoconnect does not work if the redis is not ready on startup (and afterwards no reconnect happens and my query of /health think everything is fine)

@p1gp1g
Copy link
Author

p1gp1g commented Jan 16, 2025

Is there any healthycheck? It looks like, that autoconnect does not work if the redis is not ready on startup (and afterwards no reconnect happens and my query of /health think everything is fine)

I thought getting the connection was enough. I've added a ping to the healthcheck (which was added last week to the redis crate 👍 )

@p1gp1g
Copy link
Author

p1gp1g commented Feb 10, 2025

I have rebased this branch too

@jrconlin
Copy link
Member

Sigh. Just noticed that these commits aren't signed. We require all commits to be signed before we can accept them. (I've tried to make that very clear in the docs and PR templates, but I guess there's still work to do)

Sadly, that means we can't accept this PR as is (even one unsigned commit corrupts things.)

You can either close this PR and resubmit it or I can submit it on your behalf, but I'll need one of the other core folk to sign off on it.

@p1gp1g
Copy link
Author

p1gp1g commented Feb 11, 2025

I have squashed the branch and signed the commit, let me know if you prefer keep the different commits for the review

@jrconlin jrconlin self-requested a review February 11, 2025 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Using autopush with local database (like sqlite)
3 participants