이 영역을 누르면 첫 페이지로 이동
천천히 꾸준히 조용히 블로그의 첫 페이지로 이동

천천히 꾸준히 조용히

페이지 맨 위로 올라가기

천천히 꾸준히 조용히

천천히 꾸준히 조용히.. i3months 블로그

[Redis] 동시성 제어

  • 2024.05.25 23:25
  • Database/Redis
반응형

 

 

 

Redis는 캐시 저장소로 사용되지만.. 일단 "데이터베이스" 이니 일관성, 동시성, 신뢰성을 보장하기 위해 트랜잭션을 지원한다. 

 

await isolatedClient.watch(itemsKey(attrs.itemId));

return isolatedClient
    .multi()
    .rPush(bidHistoryKey(attrs.itemId), serialized)
    .hSet(itemsKey(item.id), {
        bids:item.bids + 1,
        price: attrs.amount,
        highestBidUserId: attrs.userId
    })
    .zAdd(itemsByPriceKey(), {
        value: item.id,
        score: attrs.amount
    })
    .exec()

 

 

Redis는 MULTI 명령어로 트랜잭션을 시작한다.

이후 입력되는 명령어들은 실행 큐에 저장되고 EXEC 명령어가 실행될 때 큐에 있는 모든 명령어가 순차적으로 실행된다. 

 

트랜잭션으로 묶은 명령어 중 하나라도 실패한다면 트랜잭션은 종료된다.

 

WATCH 명령어는 트랜잭션에서 특정 키를 모니터링해 트랜잭션 실행 전 키가 변경되는 경우 트랜잭션이 실패하도록 하는 Optimistic Lock 매커니즘을 제공한다. 

 

예시에서는 node-redis 라이브러리로 itemsKey(attrs.itemId) 를 모니터링한다.

WATCH 이후 다른 클라이언트에 의해 해당 키 값이 변경된다면 EXEC 명령어는 실패하고 NULL 을 반환한다. 

 

 

const client = createClient({
	socket: {
		host: process.env.REDIS_HOST,
		port: parseInt(process.env.REDIS_PORT)
	},
	password: process.env.REDIS_PW,
	scripts: {
		unlock: defineScript({
			NUMBER_OF_KEYS: 1,
			transformArguments(key: string, token: string) {
				return [key, token]
			},
			transformReply(reply: any) {
				return reply;
			},
			SCRIPT: `
				if redis.call('GET', KEY[1]) == ARGV[1] then
					return redis.call('DEL', KEYS[1])
				end
			`
		}),
		addOneAndStore: defineScript({
			NUMBER_OF_KEYS: 1,
			SCRIPT : `
				local keyToAssignIncrementedNumberTo = KEYS[1]
				
				return redis.call('SET', KEYS[1], 1 + tonumber(ARGV[1]))
			`,
			transformArguments(key: string, value: number) {
				return [key, value.toString()]
				// evalsha id 1 books:count 5 
			},
			transformReply(reply: any) {
				return reply
			}
		}),
		incrementView: defineScript({
			NUMBER_OF_KEYS: 3,
			SCRIPT: `
				local itemsViewsKey = KEYS[1]
				local itemsKey = KEYS[2]
				local itemsByViewsKey = KEYS[3]

				local itemId = ARGV[1]
				local userId = ARGV[2]

				local inserted = redis.call('PFADD', itemsViewsKey, userId)

				if inserted == 1 then
					redis.call('HINCRRBY', itemsKey, 'views', 1)
					redis.call('ZINCRBY', itemsByViewsKey, 1, itemId)
				end
			`,
			transformArguments(itemId: string, userId: string) {
				return [
					itemsViewsKey(itemId),
					itemsKey(itemId),
					itemsByViewsKey(),
					itemId,
					userId
				];
			},
			transformReply() {}
		})
	}
});

 

 

node-redis 라이브러리를 사용해서 기본적인 Redis 명령어를 실행할 수 있지만, 몇 가지 이유로 Redis와 통신할 때 Lua Script를 사용하기도 한다. 

 

Lua Script를 Redis 서버에 업로드하고 실행할 때는 EVAL 이나 EVALSHA 명령어를 사용한다.

 

redis.call('set', 'key', 'value')
return redis.call('get', 'key')

EVAL "redis.call('set', 'key', 'value'); return redis.call('get', 'key')" 0

 

EVALSHA 명령어는 Lua Script의 SHA1 해시를 사용해서 스크립트를 실행해 한 번 로드된 스크립트를 여러 번 사용할 때 효과적이다.

 

예시는 node-redis 라이브러리에서 defineScript를 사용해 Lua Script를 정의하고 실행한다.

내부적으로는 Redis의 EVALSHA 명령어를 사용해 스크립트를 실행하고, transformArguments와 transformReply로 스크립트 실행에 필요한 인자와 결과를 변환한다. 

 

node-redis로 Redis 서버에 명령어를 실행하는 경우 기본적으로 각 명령어가 개별적으로 실행되지만, Lua Script로 Redis 서버와 통신하는 경우 스크립트를 모두 원자적으로 실행해 동시성 문제를 해결할 수 있다. 

 

이 외에도 Lua Script는 Redis 서버에서 바로 실행되니.. 네트워크 통신 오버헤드를 줄일 수 있고, 복잡한 로직이나 조건부 처리를 script로 처리할 수 있다.

 

굳이 비교하자면.. node-redis는 JPA를 사용해 추상화 정도가 좀 더 높고, Lua Script는 Native SQL을 사용해 추상화 정도가 좀 더 낮다는 맥락으로 이해하면 된다. 

 

 

일반적인 RDBMS에서는 Lock 메커니즘을 제공해 데이터 일관성과 트랜잭션을 관리하는데, Redis에서는 따로 Lock 메커니즘을 제공하지 않아 필요 시 사용자가 직접 패턴을 설계해야 한다. 

 

 

반응형
저작자표시 (새창열림)

'Database > Redis' 카테고리의 다른 글

[Redis] Stream  (1) 2024.10.10
[Redis] Module - RediSearch  (0) 2024.10.06
[Redis] 파이프라인과 자료구조  (0) 2024.05.15
[Redis] 캐시 서버와 명령어  (0) 2024.04.27

댓글

이 글 공유하기

  • 구독하기

    구독하기

  • 카카오톡

    카카오톡

  • 라인

    라인

  • 트위터

    트위터

  • Facebook

    Facebook

  • 카카오스토리

    카카오스토리

  • 밴드

    밴드

  • 네이버 블로그

    네이버 블로그

  • Pocket

    Pocket

  • Evernote

    Evernote

다른 글

  • [Redis] Stream

    [Redis] Stream

    2024.10.10
  • [Redis] Module - RediSearch

    [Redis] Module - RediSearch

    2024.10.06
  • [Redis] 파이프라인과 자료구조

    [Redis] 파이프라인과 자료구조

    2024.05.15
  • [Redis] 캐시 서버와 명령어

    [Redis] 캐시 서버와 명령어

    2024.04.27
다른 글 더 둘러보기

정보

천천히 꾸준히 조용히 블로그의 첫 페이지로 이동

천천히 꾸준히 조용히

  • 천천히 꾸준히 조용히의 첫 페이지로 이동

검색

방문자

  • 전체 방문자
  • 오늘
  • 어제

카테고리

  • 분류 전체보기 (677)
    • Algorithm (205)
      • Data Structure (5)
      • Theory && Tip (33)
      • Baekjoon (166)
      • ALGOSPOT (1)
    • Spring (123)
      • Spring (28)
      • Spring Web MVC (20)
      • Spring Database (14)
      • Spring Boot (6)
      • Spring 3.1 (11)
      • Spring Batch (6)
      • Spring Security (16)
      • JPA (12)
      • Spring Data JPA (5)
      • QueryDSL (4)
      • eGovFramework (1)
    • Programming Language (74)
      • C (25)
      • C++ (12)
      • Java (19)
      • JavaScript (15)
      • Python (1)
      • PHP (2)
    • Computer Science (142)
      • Machine Learning (38)
      • Operating System (18)
      • Computer Network (28)
      • System Programming (22)
      • Universial Programming Lang.. (8)
      • Computer Architecture (4)
      • Compiler Design (11)
      • Computer Security (13)
    • Database (21)
      • Database (7)
      • MySQL (3)
      • Oracle (3)
      • Redis (5)
      • Elasticsearch (3)
    • DevOps (20)
      • Docker && Kubernetes (8)
      • Jenkins (4)
      • Amazon Web Service (8)
    • Mobile (28)
      • Android (21)
      • Flutter (7)
    • 💡 솔루션 (17)
    • 👥 모각코 (9)
    • 💬 기록 (7)
    • 📚 공부 (6)
    • -------------- (25)

최근 글

나의 외부 링크

메뉴

  • 홈
반응형

정보

i3months의 천천히 꾸준히 조용히

천천히 꾸준히 조용히

i3months

블로그 구독하기

  • 구독하기
  • RSS 피드

티스토리

  • 티스토리 홈
  • 이 블로그 관리하기
  • 글쓰기
Powered by Tistory / Kakao. Copyright © i3months.

티스토리툴바