Making parallel requests to Redis with Fred

Viewed 34

I'm a complete Rust newbie and just tried to convert a simple microservice to Rust. It needs to do many parallel Redis requests for HTTP requests it gets and I'm a bit puzzled with the language syntax. I'm trying to do multiple Redis queries in parallel in an actix-web handler. I have the following type for the Redis GET function in Fred:

fn get<R, K>(&self, key: K) -> AsyncResult<R>
where
    R: FromRedis + Unpin + Send,
    K: Into<RedisKey>, 

docs here: https://docs.rs/fred/5.2.0/fred/interfaces/trait.KeysInterface.html#method.get

In my own code I would then have a for-loop like this:

let resp_futures = vec!{};
for key in keys.iter() {
    resp_futures.push(state.redis.get(key));
}
let resps = join_all(resp_futures).await;

Each Redis query should basically return an Option. However, this doesn't work due to some issues with type inference. Any ideas what's the correct way to send parallel Redis requests with the Fred Redis library? The complete server with some unnecessary stuff removed is the following:

use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use actix_web::http::header::ContentType;
use serde::Deserialize;

use std::env;
use std::sync::Arc;
use fred::prelude::*;
use fred::pool::RedisPool;
use futures::future::join_all;

//
// Server configuration
//

fn get_redis_url() -> String {
    match env::var("REDIS_URL") {
        Err(_) => panic!("REDIS_URL environment variable not set."),
        Ok(s) => s
    }
}

struct AppState {
    redis: Arc<RedisPool>
}

//
// Request parsing
//

fn get_redis_keys_from_accounts(accounts: Option<&String>) -> Vec<String> {
    match accounts {
        None => vec!{},
        Some(s) => s.split(",").map(|s| "sid:".to_owned() + s).collect()
    }
}

fn get_redis_keys_from_app_accounts(accounts: Option<&String>) -> Vec<String> {
    match accounts {
        None => vec!{},
        Some(s) => s.split(",").map(|s| "aid:".to_owned() + s).collect()
    }
}

fn get_redis_keys(req: web::Form<RouteRequest>) -> Vec<String> {
    let ids = get_redis_keys_from_accounts(req.ids.as_ref());
    let accounts = get_redis_keys_from_app_accounts(req.accounts.as_ref());
    let aliases = get_redis_keys_from_app_accounts(req.aliases.as_ref());

    ids.into_iter().chain(accounts.into_iter()).chain(aliases.into_iter()).collect()
}

//
// Request handling
//

#[derive(Debug, Deserialize)]
struct RouteRequest {
    ids: Option<String>,
    accounts: Option<String>,
    aliases: Option<String>
}

#[post("/v1/route")]
async fn route(state: web::Data<AppState>, req: web::Form<RouteRequest>) -> impl Responder {
    let keys = get_redis_keys(req);

    // TODO: Fix this!
    let resp_futures = vec!{};
    for key in keys.iter() {
        resp_futures.push(state.redis.get(key));
    }
    let resps = join_all(resp_futures).await;

    HttpResponse::Ok().content_type(ContentType::json()).body(r#"{"status": "ok"}"#)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    println!("Connecting to Redis backend");

    let url = get_redis_url();
    let config = match RedisConfig::from_url(&url) {
        Ok(x) => x,
        Err(_) => panic!("Invalid redis URL")
    };
    let policy = ReconnectPolicy::default();
    let redis = Arc::new(match RedisPool::new(config, 5) {
        Ok(x) => x,
        Err(_) => panic!("Unable to create Redis connection pool")
    });
    let _ = redis.connect(Some(policy));

    println!("Starting HTTP server");
    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(AppState {redis: redis.clone()}))
            .service(route)
    })
    .bind(("0.0.0.0", 8080))?
    .run()
    .await
}

Output of cargo check is :

error[E0698]: type inside `async fn` body must be known in this context
  --> src/main.rs:76:39
   |
76 |         resp_futures.push(state.redis.get(key));
   |                                       ^^^ cannot infer type for type parameter `R` declared on the associated function `get`
   |
note: the type is part of the `async fn` body because of this `await`
  --> src/main.rs:78:39
   |
78 |     let resps = join_all(resp_futures).await;
   |                                       ^^^^^^
1 Answers

At line 70 you have to give a hint about the type which should be used. For a String the following line should be used:

        resp_futures.push(state.redis.get::<String, _>(key));
Related