[Redis] ๋ถ„์‚ฐ ๋ฝ (Distributed Lock)

[์›๋ณธ ๋งํฌ]

๋ฝ์€ ๋™์‹œ์„ฑ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„ ์‹œ์Šคํ…œ์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ ์ค‘ ํ•˜๋‚˜๋‹ค.

๋ณดํ†ต์€ ๊ด€๊ณ„ํ˜•๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋ฝ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•ด์„œ ๋ฝ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ณค ํ•˜๋Š”๋ฐ, ๊ทธ๊ฒƒ๋งŒ์œผ๋กœ๋Š” ํ™•์žฅ์„ฑ์ด๋‚˜ ์„ฑ๋Šฅ ๋ฉด์—์„œ ๋ถ€์กฑํ•  ๋•Œ๊ฐ€ ์žˆ๋‹ค.
์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ๋ฅผ ์ด์šฉํ•œ Lock ์ฒ˜๋ฆฌ๋„ ์‹ฑ๊ธ€ ์•ฑ์—์„œ๋‚˜ ๊ฐ€๋Šฅํ•˜์ง€, ์Šค์ผ€์ผ์•„์›ƒ๋œ ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ๋Š” ์œ ํšจํ•˜์ง€ ์•Š๋‹ค.

๊ทธ๋Ÿด๋•Œ๋Š” Lock์„ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณค ํ•˜๋Š”๋ฐ, ๋ณดํ†ต Redis๋ฅผ ์ค‘์•™ ๋ฉ”๋ชจ๋ฆฌ๋กœ ๋‘๊ณ  Lock ๊ธฐ๋Šฅ์„ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.




ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ๊ตฌ์„ฑ

์šฐ์„  redis๋ฅผ docker๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋„์› ๋‹ค.

docker run -p 12345:6379 redis

๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜๋Š” Rust๋กœ ์ž‘์„ฑํ•œ ์˜ˆ์ œ์ฝ”๋“œ๋‹ค.
๊ฐœ๋… ์„ค๋ช…์„ ์œ„ํ•œ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ผญ ์ฝ”๋“œ ์ „์ฒด๋ฅผ ๋‹ค ์ดํ•ดํ•  ํ•„์š”๋Š” ์—†๋‹ค.

use futures::future::join_all;
use redis::Commands;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send>> {
    let redis_client = redis::Client::open("redis://127.0.0.1:12345/").unwrap();

    let mut redis_connection = redis_client.get_connection().unwrap();

    let _: () = redis_connection.set("counter", 0).unwrap();

    // 10000๋ฒˆ ๋ฐ˜๋ณตํ•ฉ๋‹ˆ๋‹ค.
    let _ = join_all((0..10000).into_iter().map(|_| {
        let redis_client = redis_client.clone();

        return tokio::spawn(async move {
            let mut redis_connection = redis_client.get_connection().unwrap();

            let old: i32 = redis_connection.get("counter").unwrap();

            let new = old + 1;

            println!("new: {}", new);

            let _: () = redis_connection.set("counter", new).unwrap();
        });
    }))
    .await;

    let counter: i32 = redis_connection.get("counter").unwrap();

    println!("counter: {}", counter);

    Ok(())
}

์ฝ”๋“œ์˜ ๋กœ์ง ์ž์ฒด๋Š” ๋งค์šฐ ๋‹จ์ˆœํ•˜๋‹ค.
redis์—์„œ "counter"๋ผ๋Š” ํ‚ค์— ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ๋กœ 10000๋ฒˆ ๋™์‹œ์— ๋ฐ˜๋ณตํ•˜๋ฉด์„œ ์ฆ๊ฐ€๋ฅผ ์‹œํ‚ค๊ณ  ์žˆ๋‹ค.

์›๋ž˜ ๊ธฐ๋Œ€ํ•œ๋Œ€๋กœ๋ผ๋ฉด 10000๋ฒˆ ๋ฐ˜๋ณตํ–ˆ์œผ๋‹ˆ 10000์ด ๋˜์–ด์•ผ ํ•˜์ง€๋งŒ,


๊ฒฐ๊ณผ๋Š” ๊ทธ๋ ‡์ง€ ์•Š๋‹ค.

get์œผ๋กœ ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ณ , set์œผ๋กœ ๋‹ค์‹œ ์„ค์ •ํ•˜๋Š” ๋ฏธ๋ฌ˜ํ•œ ๊ฐ„๊ทน์—์„œ ๋™์‹œ์„ฑ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
์ด๋ฅผํ…Œ๋ฉด counter๊ฐ€ 2์ธ ์ƒํƒœ์—์„œ ๋™์‹œ์— 2๊ฐœ์˜ ์Šค๋ ˆ๋“œ๊ฐ€ "2" ๊ฐ’์„ ๋ฐ›์•„์˜ค๋ฉด, 2->3, 3->4๋กœ ๊ฐ’์ด ๋Š˜์–ด๋‚˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ 2->3, 2->3 ์™€ ๊ฐ™์ด ์ค‘๋ณตํ• ๋‹น์„ ํ•ด๋ฒ„๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ด๋Ÿฌํ•œ ์ƒํ™ฉ์„ ๋ง‰๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด Lock์ด๋‹ค.
์‚ฌ์‹ค ๊ทธ๋ƒฅ ๋™์‹œ์— ์ ‘๊ทผ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋„๋ก ๋ง‰์•„๋ฒ„๋ฆฌ๋Š” ๊ฒƒ์ด๋‹ค.




setnx ๊ธฐ๋ฐ˜์˜ Spin Lock

Spin Lock์€ Lock์ด ๋Œ€๊ธฐ์ค‘์ผ๋•Œ ๋บ‘๊ธ€๋บ‘๊ธ€ ๋ˆ๋‹ค๊ณ  ํ•ด์„œ ๋ถ€๋ฅด๋Š” ๋ช…์นญ์ธ๋ฐ, ๊ตฌํ˜„์€ ์–ด๋ ต์ง€ ์•Š๋‹ค.

๋งค ํ”„๋กœ์„ธ์Šค๋งˆ๋‹ค redis์˜ setnx ๋ช…๋ น์œผ๋กœ lock ๊ฐ’์„ ์ €์žฅํ•˜๊ณ , ์ด๋ฏธ lock์ด ์žˆ๋‹ค๋ฉด ๋Œ€๊ธฐํ•˜๊ณ , ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋๋‚˜๋ฉด lock ๊ฐ’์„ ์ œ๊ฑฐํ•˜๊ฒŒ ํ•˜๋ฉด ๋œ๋‹ค.

setnx๋Š” ๊ธฐ์กด์— ๊ฐ’์ด ์กด์žฌํ•˜๋ฉด 0์„ ๋ฆฌํ„ดํ•˜๋Š” atomicํ•œ ๋ช…๋ น์ด๋ผ, 1์„ ๋ฐ˜ํ™˜ํ• ๋•Œ๊นŒ์ง€ ๋ฌดํ•œ๋ฃจํ”„๋ฅผ ๋Œ๋ ค์„œ ๋Œ€๊ธฐ๋ฅผ ํƒ€๊ฒŒ ํ•˜๋ฉด ๋œ๋‹ค. ์ด๋ ‡๊ฒŒ ๋ง์ด๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋‚˜๊ฐˆ๋•Œ๋Š” del๋กœ ์ œ๊ฑฐํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

์ด๋Ÿฌ๋ฉด ์•„์ฃผ ๊ฐ„๋‹จํ•œ Spin Lock์˜ ์™„์„ฑ์ด๋‹ค.

์ž. ์ด์ œ ๊ธฐ๋Œ€ํ•œ๋Œ€๋กœ ๋™์ž‘ํ•  ๊ฒƒ์ด๋‹ค.

์ž˜ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

ํ•˜์ง€๋งŒ ์—ฌ๊ธฐ์—๋Š” ๋‹จ์ ์ด ์ข€ ์žˆ๋Š”๋ฐ...
์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋‹จ์—์„œ ๋ฌดํ•œ๋ฃจํ”„ ๋Œ๋ฉฐ ๊ธฐ๋‹ค๋ฆฌ๋Š”๊ฑด ๊ทธ๋ ‡๋‹ค ์น˜๊ณ , setnx๋ฅผ ์™€์žฅ์ฐฝ ๋‚ ๋ ค๋Œ€๊ธฐ ๋•Œ๋ฌธ์— Redis์— ๊ฐ€ํ•ด์ง€๋Š” ๋ถ€๋‹ด์ด ์—„์ฒญ๋‚˜๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

์ด๊ฑธ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‹จ์—์„œ ๋ฌดํ•œ๋ฃจํ”„๋ฅผ sleep๋„ ๊ฑธ์–ด๊ฐ€๋ฉด์„œ ์ฒœ์ฒœํžˆ ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๊ธด ํ•œ๋ฐ, best practice๋ผ๊ณ  ๋งํ•˜๊ธฐ๋Š” ์–ด๋ ต๋‹ค.




Redis pub/sub ๊ธฐ๋ฐ˜์˜ Lock ๊ตฌํ˜„

Redis๋Š” ๋‹คํ–‰์Šค๋Ÿฝ๊ฒŒ๋„ pub/sub์ด๋ผ๋Š” ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜์˜ ์‹œ์Šคํ…œ์„ ์ œ๊ณตํ•œ๋‹ค.

๊ทธ๋Ÿฌ๋‹ˆ๊นŒ, ์ด๊ฑธ๋กœ lock์„ ํ’€๋•Œ๋งˆ๋‹ค ์•Œ๋ฆผ์„ ๋ณด๋‚ด์„œ, ๊ทธ๋•Œ ๋‹ค์‹œ setnx๋ฅผ ์‹œ๋„ํ•˜๋„๋ก ํ•˜๋ฉด, ๋ฌด์ž‘์ • ์Ÿ์•„์ง€๋Š” setnx์˜ ํšŸ์ˆ˜๋ฅผ ์ค„์—ฌ์„œ Redis์˜ ๋ถ€๋‹ด์„ ์ค„์ด๋Š” ๊ฒƒ์ด๋‹ค.

Lock ํ•ด์ œ๋ฅผ ํ• ๋•Œ Lock์ด ํ’€๋ ธ๋‹ค๊ณ  ์•Œ๋ ค์ฃผ๊ณ 

Lock ์ง„์ž…์„ ํ• ๋•Œ๋Š” ๋ฃจํ”„๋ฅผ ๋Œ๋ ค๊ณ  ํ• ๋•Œ๋งˆ๋‹ค ์ € pub/sub์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ธ”๋Ÿญ์„ ๊ฑธ์–ด์ฃผ๋ฉด ๋œ๋‹ค.

์ด๋Ÿฌ๋ฉด lock์ด ํ•ด์ œ๋ ๋•Œ๋งŒ setnx๋กœ lock ์ง„์ž…์„ ์‹œ๋„ํ•  ๊ฒƒ์ด๋‹ค.

๋™์ž‘์€ ์ด์ „๊ณผ ๊ฑฐ์˜ ๋™์ผํ•˜๊ณ ,

์ผ๋ฐ˜ SpinLock์€ Redis CPU๊ฐ€ ์ด์ •๋„๋กœ ๋จน๋˜๊ฒŒ

๊ฑฐ์˜ ์ ˆ๋ฐ˜ ์ •๋„๋กœ ์ค„์–ด๋“ค์—ˆ๋‹ค.

์ด ๋กœ์ง์€ ๊ผญ ๋‹ค ์ง์ ‘ ๊ตฌํ˜„ํ•  ํ•„์š”๋Š” ์—†๊ณ , ๊ดœ์ฐฎ์€ Redis ํด๋ผ์ด์–ธํŠธ ๊ตฌํ˜„์ฒด๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ทธ๊ฑธ ์“ฐ๋ฉด ๋œ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์—ฌ๊ธฐ์—๋„ ๋ฌธ์ œ๊ฐ€ ์žˆ๊ธด ์žˆ์œผ๋‹ˆ.. Redis๋งˆ์ € ๋ถ„์‚ฐํ™”๋œ ๋ฉ€ํ‹ฐ๋…ธ๋“œ Redis ํด๋Ÿฌ์Šคํ„ฐ์—์„œ๋Š” ๊ธฐ๋Œ€ํ•œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ๋…ธ๋“œ๋ผ๋ฆฌ ๊ฐ’์ด ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์œผ๋‹ˆ๊นŒ!




Redlock

Redlock์€ ๊ฐ€์žฅ ๊ถŒ์žฅ๋˜๋Š” ํ˜•ํƒœ์˜ Lock ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด๋‹ค.
pub/sub ๊ธฐ๋ฐ˜์˜ Lock์— ๋ฉ€ํ‹ฐ๋…ธ๋“œ Redis ํ™˜๊ฒฝ๊นŒ์ง€ ๊ณ ๋ คํ•œ ๊ฒƒ์ด๋‹ค.

์ž์„ธํ•œ ๊ฒƒ์€ ๊ณต์‹๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜๋ฉด ์ข‹๋‹ค.
https://redis.io/docs/manual/patterns/distributed-locks/

๋ฉ€ํ‹ฐ๋…ธ๋“œ์—์„œ์˜ ์ถฉ๋Œ์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ์ด๋Ÿฌํ•œ ๋กœ์ง์„ ๋”ฐ๋ฅธ๋‹ค.

  1. ํ˜„์žฌ ์‹œ๊ฐ„์„ ๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„๋กœ ๊ฐ€์ ธ์˜จ๋‹ค.

  2. N๊ฐœ์˜ ๋…ธ๋“œ์—์„œ ๋ชจ๋‘ ๋™์ผํ•œ ํ‚ค ์ด๋ฆ„๊ณผ ๋ฌด์ž‘์œ„ ๊ฐ’์œผ๋กœ ์ˆœ์ฐจ์ ์œผ๋กœ Lock์„ ํš๋“ํ•˜๋ ค๊ณ  ์‹œ๋„ํ•œ๋‹ค.
    ์—ฌ๊ธฐ์„œ ๊ฐ ๋…ธ๋“œ์— ์ž ๊ธˆ์„ ์„ค์ •ํ•  ๋•Œ ํด๋ผ์ด์–ธํŠธ๋Š” ์ด Lock ์ž๋™ ํ•ด์ œ ์‹œ๊ฐ„๊ณผ ๋น„๊ตํ•˜์—ฌ ์ž‘์€ ํƒ€์ž„์•„์›ƒ์„ ์‚ฌ์šฉํ•˜์—ฌ Lock์„ ํš๋“ํ•œ๋‹ค.
    ์˜ˆ๋ฅผ ๋“ค์–ด, ์ž๋™ ํ•ด์ œ ์‹œ๊ฐ„์ด 10์ดˆ์ธ ๊ฒฝ์šฐ ํƒ€์ž„์•„์›ƒ์€ ๋Œ€๋žต 5-50 ๋ฐ€๋ฆฌ์ดˆ ๋ฒ”์œ„์ผ ์ˆ˜ ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•จ์œผ๋กœ์จ ํด๋ผ์ด์–ธํŠธ๋Š” ๋‹ค์šด๋œ Redis ๋…ธ๋“œ์™€ ํ†ต์‹ ์„ ์‹œ๋„ํ•˜๋Š” ๋™์•ˆ ์˜ค๋žœ ์‹œ๊ฐ„ ๋™์•ˆ ๋ธ”๋ก๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•œ๋‹ค.
    ๋…ธ๋“œ๊ฐ€ ์‚ฌ์šฉ ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ ๊ฐ€๋Šฅํ•œ ๋นจ๋ฆฌ ๋‹ค์Œ ๋…ธ๋“œ์™€ ํ†ต์‹ ์„ ์‹œ๋„ํ•ด์•ผ ํ•œ๋‹ค.

  3. ํด๋ผ์ด์–ธํŠธ๋Š” Lock์„ ํš๋“ํ•˜๊ธฐ ์œ„ํ•ด ๋‹จ๊ณ„ 1์—์„œ ์–ป์€ ํƒ€์ž„์Šคํƒฌํ”„๋ฅผ ํ˜„์žฌ ์‹œ๊ฐ„์—์„œ ๋นผ์–ด ๊ฒฝ๊ณผ๋œ ์‹œ๊ฐ„์„ ๊ณ„์‚ฐํ•œ๋‹ค.
    ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋Œ€๋‹ค์ˆ˜์˜ ๋…ธ๋“œ(์ ์–ด๋„ 3๊ฐœ)์—์„œ Lock์„ ํš๋“ํ•˜๊ณ , Lock์„ ํš๋“ํ•˜๋Š” ๋ฐ ๊ฑธ๋ฆฐ ์ด ์‹œ๊ฐ„์ด Lock์˜ validity time๋ณด๋‹ค ์ž‘์€ ๊ฒฝ์šฐ์—๋งŒ Lock์ด ํš๋“๋œ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ๋œ๋‹ค.

  4. Lock์ด ํš๋“๋œ ๊ฒฝ์šฐ, validity time์€ ๋‹จ๊ณ„ 3์—์„œ ๊ณ„์‚ฐ๋œ ๊ฒฝ๊ณผ ์‹œ๊ฐ„์„ ์ดˆ๊ธฐ validity time์—์„œ ๋บ€ ๊ฐ’์œผ๋กœ ๊ฐ„์ฃผ๋œ๋‹ค.

  5. ๋งŒ์•ฝ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์–ด๋–ค ์ด์œ ๋กœ ์ธํ•ด Lock์„ ์–ป์ง€ ๋ชปํ–ˆ๋‹ค๋ฉด (N/2+1 ๊ฐœ์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ž ๊ธ€ ์ˆ˜ ์—†์—ˆ๊ฑฐ๋‚˜ ์œ ํšจ ์‹œ๊ฐ„์ด ์Œ์ˆ˜์ธ ๊ฒฝ์šฐ), ํด๋ผ์ด์–ธํŠธ๋Š” Lock์„ ์–ป์ง€ ๋ชปํ•œ ๊ฒƒ์œผ๋กœ ์—ฌ๊ฒผ๋˜ ๋ชจ๋“  ๋…ธ๋“œ๋ฅผ unlockํ•œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์—ฌ๊ธฐ์—๋Š” ๋ช‡๊ฐ€์ง€ ๊ตฌํ˜„์ฒด๋“ค์ด ์กด์žฌํ•œ๋‹ค.

๋ณด๋ฉด ์ข€ ์ด์ƒํ•œ๊ฒƒ๋„ ๋งŽ๋‹ค. ์Šคํƒ€ ๊ฐœ์ˆ˜ ๋ณด๊ณ  ๊ดœ์ฐฎ์œผ๋ฉด ๊ณจ๋ผ์“ฐ์ž..
๋‹น์žฅ ๋‚ด๊ฐ€ ์จ๋ณธ Rust ๊ตฌํ˜„์ฒด๋„ ์ œ๋Œ€๋กœ ๋œ ๋ฌผ๊ฑด์ด ์•„๋‹ˆ์—ˆ๊ณ , Go๋„ ๊ทธ๋žฌ๋‹ค.
Node.js ๊ตฌํ˜„์ฒด๋Š” ์™œ ์—†์ง€...?

์•„๋ฌดํŠผ ์ €๊ธฐ์„œ Redisson์ด ์ด ๋ฐฉ๋ฉด์—์„œ ์ข€ ์ž˜ ์“ฐ์ด๋Š” ๊ฒƒ ๊ฐ™์•˜๋‹ค.



์ฐธ์กฐ
https://redis.io/docs/manual/patterns/distributed-locks/
https://hyperconnect.github.io/2019/11/15/redis-distributed-lock-1.html
https://hudi.blog/distributed-lock-with-redis/
http://redisgate.kr/redis/command/setnx.php