examples icon indicating copy to clipboard operation
examples copied to clipboard

Redis example with pooled connection

Open rudolfolah opened this issue 3 years ago • 3 comments

The example here: https://github.com/actix/examples/blob/master/databases/redis/src/main.rs

could be updated with the r2d2 example of pooled Redis client connections from here: https://github.com/redis-rs/redis-rs/pull/388/files

The following code worked for me:

use actix_web::{get, web, App, HttpServer, Responder, Result};
use redis::Commands;

#[get("/simple/url/{id}")]
pub async fn example(
    id: web::Path<String>,
    redis_pool: web::Data<r2d2::Pool<redis::Client>>
) -> Result<impl Responder> {
    let mut conn: r2d2::PooledConnection<redis::Client> = redis_pool.get().unwrap();

    // use the connection as usual
    let _: () = conn.set("KEY", "VALUE").unwrap();
    let val: String = conn.get("KEY").unwrap();
    println!("{}", val);

    Ok(web::Json(val))
}

#[actix_web::main] // or #[tokio::main]
async fn main() -> std::io::Result<()> {
    let client = redis::Client::open("redis://127.0.0.1/").unwrap();
    let pool: r2d2::Pool<redis::Client> = r2d2::Pool::builder()
        .max_size(15)
        .build(client)
        .unwrap_or_else(|e| panic!("Error building redis pool: {}", e));
    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(pool.clone()))
            .route("/hello", web::get().to(|| async { "Hello World!" }))
            .service(example)
    })
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

rudolfolah avatar Aug 11 '22 05:08 rudolfolah

no this code is not ok.

let mut conn: r2d2::PooledConnection<redis::Client> = redis_pool.get().unwrap();

This would block thread and can lead to pontential dead lock. You can check out this example for how to improve the usage of r2d2 inside actix-web. Or even better use an async connection poolor redis-rs multiplexiable async connections for more efficient client side query.

fakeshadow avatar Aug 13 '22 17:08 fakeshadow

Thanks for pointing out that the server thread would be blocked.

The code you mentioned makes it clearer that I need to use web::block:

https://github.com/actix/examples/blob/d259177eabf75a7bdd3a84b48d3467c4680c8110/databases/diesel/src/main.rs#L29-L34

The caution about blocking threads appears to be part of the documentation already:

Since each worker thread processes its requests sequentially, handlers which block the current thread will cause the current worker to stop processing new requests:

It may be worth adding a section heading for that part of the docs, something like "Async Long-Running Operations" or "Async and Blocking Threads".

There's some discussion about using MultiplexedConnection over in the redis-rs issue, I can give that a try: https://github.com/redis-rs/redis-rs/pull/388#issuecomment-1003639343

rudolfolah avatar Aug 15 '22 09:08 rudolfolah

Example using web::block

The following code seemed to work:

use actix_web::{get, web, App, HttpServer, Responder, Result};
use redis::Commands;

#[get("/simple/url/{id}")]
pub async fn example(
    id: web::Path<String>,
    redis_pool: web::Data<r2d2::Pool<redis::Client>>
) -> Result<impl Responder> {
    let val = web::block(move || {
        let mut conn = redis_pool.get().unwrap();
        conn.set::<&str, &str, ()>("KEY", "VALUE123")?;
        conn.get::<&str, String>("KEY")
    }).await?.map_err(actix_web::error::ErrorInternalServerError)?;
    Ok(web::Json(val))
}

#[actix_web::main] // or #[tokio::main]
async fn main() -> std::io::Result<()> {
    let client = redis::Client::open("redis://127.0.0.1/").unwrap();
    let pool: r2d2::Pool<redis::Client> = r2d2::Pool::builder()
        .max_size(15)
        .build(client)
        .unwrap_or_else(|e| panic!("Error building redis pool: {}", e));
    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(pool.clone()))
            .route("/hello", web::get().to(|| async { "Hello World!" }))
            .service(example)
    })
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

Multiplexed connections issue

Ran into an issue trying to get the multiplexed connection working.

This was the code:

// Cargo.toml line:
// redis = { version = "0.21.5", features = ["r2d2", "aio", "tokio-comp", "connection-manager"] }

let client = redis::Client::open("redis://127.0.0.1/").unwrap();
    let mut manager = client.get_tokio_connection_manager().await?;
    let pool: r2d2::Pool<redis::aio::ConnectionManager> = r2d2::Pool::builder()
        .max_size(15)
        .build(&manager)
        .unwrap_or_else(|e| panic!("Error building redis pool: {}", e));

And this is the error:

error[E0277]: the trait bound `&ConnectionManager: ManageConnection` is not satisfied
   --> src/main.rs:15:15
    |
15  |     let pool: r2d2::Pool<&redis::aio::ConnectionManager> = r2d2::Pool::builder()
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `ManageConnection` is not implemented for `&ConnectionManager`
    |
    = help: the trait `ManageConnection` is implemented for `Client`
note: required by a bound in `Pool`
   --> /Users/rudolfo/.cargo/registry/src/github.com-1ecc6299db9ec823/r2d2-0.8.10/src/lib.rs:319:8
    |
319 |     M: ManageConnection;
    |        ^^^^^^^^^^^^^^^^ required by this bound in `Pool`

Not sure if I'm misunderstanding the docs or the examples for the multiplexed connection or if the ConnectionManager code in redis-rs is missing the trait and I should open a new issue over there.

rudolfolah avatar Aug 16 '22 02:08 rudolfolah