Skip to content
Jaime Alonso Fernández edited this page Mar 9, 2026 · 2 revisions

What is Redis?

Redis (Remote Dictionary Server) is an open-source, in-memory data structure store. It is commonly used as:

  • A cache
  • A session store
  • A message broker
  • A lightweight NoSQL database

Unlike traditional databases that read and write primarily to disk, Redis stores data in memory, which makes it extremely fast (sub-millisecond response times).


Why We Use Redis in This Project

In our application, Redis is used primarily for caching Running Games State, ensuring quick access. Games in Redis expire automatically after a defined TTL (Time To Live).


How Redis Is Configured

Redis runs as a separate service on the server. It is installed and started independently from any dependency, though the gameManager Docker container uses it as a dependency.


redis.rs: Redis Connection & State Management

In redis.rs we define all logic related to connecting to Redis, managing distributed locks, and storing/retrieving game state.

Connection Pool — create_pool

We use bb8 as an async connection pool manager together with bb8_redis. This ensures we reuse connections efficiently across concurrent requests rather than opening a new connection on every operation.

`redis.rs`

pub async fn create_pool(redis_url: &str) -> RedisPool {
    let manager = RedisConnectionManager::new(redis_url)
        .expect("Error creating Redis manager");
    bb8::Pool::builder()
        .build(manager)
        .await
        .expect("Redis pool cannot be created")
}

The RedisPool type alias is defined as:

`redis.rs`

pub type RedisPool = bb8::Pool<RedisConnectionManager>;

Distributed Locking — acquire_lock / release_lock

Since multiple requests could attempt to write to the same game state concurrently, we use a distributed lock pattern via Redis atomic commands.

Acquiring the lock

`redis.rs`

let result: Option<String> = redis::cmd("SET")
    .arg(&lock_key)
    .arg("locked")
    .arg("NX")   // Only set if Not eXists
    .arg("EX")
    .arg(ttl_secs)
    .query_async(&mut *conn)
    .await
    .map_err(MatchError::Redis)?;

The NX flag makes this operation atomic: only one caller will receive Some(...) back, meaning they acquired the lock. All others receive None.

Lock keys follow the pattern lock:match:{match_id} and expire automatically via EX to prevent deadlocks if a process crashes mid-operation.

Releasing the lock

`redis.rs`

pub async fn release_lock(pool: &RedisPool, match_id: &str) -> Result<(), MatchError> {
    let mut conn = pool.get().await.map_err(|_| MatchError::Pool)?;
    let lock_key = format!("lock:match:{}", match_id);
    let _: () = conn.del(lock_key).await.map_err(MatchError::Redis)?;
    Ok(())
}

The lock is always explicitly released after the write operation completes.


Saving and Reading Game State — save_match_state / get_match_state

Game state is serialized to JSON and stored under keys of the form match:{match_id}, with a TTL of 3600 seconds (1 hour).

The save_match_state function wraps the write in a retry loop that attempts to acquire the lock up to 10 times, sleeping 50ms between attempts:

`redis.rs`

for _ in 0..10 {
    if acquire_lock(pool, match_id, 5).await? {
        // ... perform write ...
        release_lock(pool, match_id).await?;
        return result;
    }
    tokio::time::sleep(std::time::Duration::from_millis(50)).await;
}
Err(MatchError::LockTimeout)

If the lock cannot be acquired after all retries, a MatchError::LockTimeout is returned.


Match Initialization — create_match

create_match bootstraps a new game by:

  1. Building the initial board layout in YEN notation (the game engine's format).
  2. Serializing it to JSON.
  3. Persisting both the player list (match:{id}:players) and the initial game state (match:{id}) to Redis.
`redis.rs`

let layout: String = (1u32..=*size)
    .map(|row| ".".repeat(row as usize))
    .collect::<Vec<_>>()
    .join("/");

let initial_state = YEN::new(*size, 0, vec!['B', 'R'], layout);
let state_json = serde_json::to_string(&initial_state)?;

Error Handling — MatchError

All Redis operations return a Result<_, MatchError>, a custom error type defined with thiserror:

Variant Cause
Redis Underlying redis::RedisError
Serialization serde_json failure during state encoding
Pool Could not obtain a connection from bb8 pool
LockTimeout Lock not acquired after 10 retries

Clone this wiki locally