기본 명령어
명령어: 정보를 저장하기 위해 데이터베이스에 전송하는 문자열
문자열 저장 (’Hi there!’)
- SET message ‘Hi there!‘
- Redis는 message를 확인해 키 또는 변수 이름으로 사용한다. Redis에 message라는 변수를 생성하는 것이다. 이후 ‘Hi there!’ 값을 할당한다.
문자열 가져오기
- GET message
- GET 명령어 다음의 단어를 확인해 변수 또는 키의 이름으로 사용한다. Redis에 저장된 키 중 message라는 키를 찾고, message를 찾아서 해당하는 값을 반환한다.
GET과 SET 명령어는 문자열과 숫자만을 저장하는데 사용되는 간단한 명령어이다. Redis에 저장되는 데이터 타입은 리스트, 해시, 세트, 정렬 세트가 있다. 그리고 각 데이터 타입에 사용되는 명령어가 있다.
요소의 리스트나 해시를 사용하려면 다른 명령어를 사용해야 한다. 예를 들어 리스트에 요소를 추가하려면 LINSERT를 사용하고, 해시에 요소를 추가하려면 HSET을 사용해야 한다. 즉, GET과 SET은 리스트에는 사용할 수 없고 문자열에만 사용할 수 있다.
이제 하나의 데이터 타입을 집중적으로 알아보며 관련된 명령어와 각 명령어를 언제 사용하는지 알아보자.
명령어 관련 문서
- Getters
- GET, GETDEL, GETRANGE, GETEX, MGET, LCS, SUBSTR
- Setters
- MSETNX, GETSET, MSET, SET, APPEND, SETEX, SETNX, SETRANGE, SRTLEN, DEL
Reids의 많은 명령어들은 서로 중복된 기능을 가지고 있다. 예를 들어, GET과 GETEX는 거의 유사하게 작동한다. GETEX를 사용하면 GET과 거의 유사한 작업을 하지만, 옵션이 추가될 뿐이다.
명령어 그룹화
- Getters: 데이터를 가져오는 데 사용하는 명령어
- GET, GETDEL, GETEX는 유사한 작업을 한다.
- 따라서 세 명령어가 어떻게 작동하는지 외울 필요 없이 GET에 대해서만 알면 된다.
- Setters: 데이터를 저장하는 데 사용하는 명령어
- SET, SETNX, SETEX가 유사한 작업을 한다.
앞으로 명령어를 검색하기 위해 공식 문서를 자주 사용하게 될 것이다. Redis 코드에 라이브러리를 사용할 때도 자주 사용하게 된다. 문서에 나와 있는 SET 명령어를 파헤쳐 보자.
대문자는 키워드이고, 소문자는 우리가 원하는 값으로 입력하면 된다. 대괄호 안에 있는 단어는 선택적 인수이다. 기호 |는 OR를 의미한다.
SET 변형 명령어
- EX | PX | EXAT | PXAT | KEEPTTL
- 값의 만료 시간 결정
- XX
- Redis에 키가 이미 존재하는 경우에만 SET 명령어 실행됨
- NX
- 키가 존재하지 않는 경우에만 SET 명령어 실행됨
- GET
- 키를 설정했을 때 이전에 저장되어 있던 값 반환
EX | PX | EXAT | PXAT | KEEPTTL
위의 셀을 실행하면 즉시 GET color를 실행하므로 red를 응답하고, 2초 기다린 후 아래 셀을 실행하면 Redis에서 color의 값이 삭제되어 있어 null을 응답한다.
위와 같이 이 선택적 인수 그룹으로 키-값 쌍의 데이터를 언제 자동으로 삭제할지 설정할 수 있다.
- EX
- 삭제할 때까지 대기할 시간을 밀리초 단위로 설정
- EXAT, PXAT
- 날짜와 시간을 명시해 삭제할 미래의 시점 설정
- KEEPTTL
- 키에 적용되어 있는 만료 시간 유지
여기서 자주 사용되는 옵션은 EX와 PX이다.
그런데 왜 만료 시간을 사용하는 것일까? 답은 Redis의 근본적인 용도에 있다.
Redis는 본래 캐싱 서버로 설계되었다. 특정 데이터를 일정 기간 동안 저장한 후 필요하지 않게 되면 삭제한다는 것이다.
만료 시간 필드의 사용 사례를 보자. 뉴스 API를 만든다고 가정해보자.
전 세계 뉴스 헤드라인에 대한 요청을 받은 API 서버이다.
API의 속도를 높이기 위해 애플리케이션에 두 가지 데이터베이스를 사용할 수 있다. 하나는 PostgreSQL이나 MySQL 같은 고전적인 데이터베이스이고, 다른 하나는 Redis 데이터베이스이다.
Redis는 기본적으로 매우 빠르다. API의 속도를 위해 Redis를 캐싱 서버로 사용할 수 있다.
뉴스 헤드라인에 대한 요청을 받아 최대한 빠르게 요청해야 하므로, API가 먼저 Redis에 최신 헤드라인이 저장되어 있는지 확인하고, 없으면 API가 기존 데이터베이스에서 데이터를 가져오려고 한다. (일반적으로 Redis보다 느리다.)
기존 데이터베이스에 최신 헤드라인이 있으므로 API가 최신 헤드라인의 사본을 가져와 요청에 응답한다.
동시에 최신 헤드라인의 사본을 Redis에도 저장할 수 있다.
그런데 여기서 잠시 후 다른 요청을 받았다고 생각해 보자.
뉴스 헤드라인을 요청하면 API가 다시 Redis에 데이터가 있는지 확인한다.
이제는 데이터가 있으므로 API가 기존 데이터베이스를 액세스할 필요 없이 빠르게 데이터를 가져와 응답할 수 있다. 기존 데이터베이스에 접근할 필요가 없으니 애플리케이션이 빨라지는 것이다.
여기서 문제는 메모리 용량이 부족하면 애플리케이션이 자동으로 모든 헤드라인을 Redis에 업데이트하지 않을 수 있다는 것이다.
이 문제를 해결하기 위해 특정 헤드라인이 마지막으로 액세스 된 후, 지정된 초 또는 밀리초 단위의 시간이 지나면 헤드라인을 삭제하는 것이다.
Redis 데이터베이스에 캐싱한 값이므로 공간을 확보하기 위해 자동으로 삭제하는 것이다.
따라서 다음에 요청을 받으면 API가 다시 Redis를 확인하고, 데이터가 없으니 처음에 했던 것처럼 기존 데이터베이스에서 데이터를 가져와 응답한 다음 일시적으로 Redis에 캐싱한다.
이처럼 자동 만료 시간을 설정하는 이유는 Redis의 근본적인 용도가 캐싱 서버이기 때문이다.
오래된 데이터를 캐싱해 유지하는 것을 막고, 메모리가 부족하지 않도록 데이터를 삭제하는 것이다.
Redis의 가장 흔한 사용 사례는 캐싱을 위해서이다.
여러 키 동시에 설정하기
- SETNX
- SET + NX
- 키-값 쌍이 존재하지 않는 경우에만 업데이트 하거나 설정
- MSET
- M: Multiple
- 여러 키-값 쌍을 한 번에 설정할 수 있다.
- SET을 반복해 호출하는 것과 동일
- MSETNX: 설정하려는 키 중 이미 존재하는 키가 하나라도 있으면 명령어 실행X
- MGET
- 하나의 명령어에서 여러 키의 값 가져오기
문자열 범위
- DEL
- 데이터베이스에 존재하는 키와 저장된 데이터 삭제
- 모든 자료형에 사용 가능
- GETRANGE
- 데이터베이스 내 존재하는 문자열에서 특정 범위의 문자열만 가져옴
- SETRANGE
- 기존 문자열의 일부 업데이트 후 생성된 문자열 길이 반환
이 기능들이 유용할까?
Redis를 사용하는 이유는 성능이 좋고 아주 빠른 데이터베이스이기 때문이라는 점을 기억하자.
액면 그대로만 보면 유용하지 않은 명령어처럼 보이지만 명령어를 잘 활용하면 아주 유용하다. 또한 명령어를 잘 활용하면 시스템이 아주 빠르게 동작한다.
예를 들어 대형 제품 회사의 직원이 되었다고 생각해보자. 회사에는 큰 데이터베이스가 있는데, 고전적인 SQL 기반 데이터베이스에 다양한 가구 제품의 목록이 저장되어 있다.
여기서 문제는 데이터베이스에 제품이 매우 많다는 것이다.
회사의 직원이 전 세계에서 동시에 데이터베이스에 액세스해 정보를 쓰려고 한다.
제품이 너무 많고 데이터에 접근하려는 사람이 너무 많으니 애플리케이션이 매우 느릴 수 밖에 없다.
여기서 해야 할 일은 Redis를 사용해 데이터를 최대한 빠르게 액세스해 업데이트할 수 있도록 만드는 것이다.
그냥 보면 쓸데 없어 보이는 명령어를 사용해 어떤 솔루션을 만들 수 있는지 생각해보자. 이 문제를 해결하기 위해 사람들이 어떻게 데이터에 액세스하고 있는지 파악해야 한다.
조사해 보니 사람들이 4가지 방법으로 데이터와 상호작용하고 있었다.
- 제품의 속성을 1~3개만 가져온다.
- 사용자가 한 제품의 속성을 1~3개 업데이트 할 수 있다.
- 여러 아이템의 속성을 한 번에 가져올 수 있다.
- 한 번에 여러 아이템을 생성할 수 있다.
조사해 보니 가구의 타입, 색상 및 재료가 몇 가지 되지 않는다.
따라서 이 표의 데이터에는 반복적인 게 많을 수 밖에 없다.
핵심은 데이터가 반복적이므로 red라는 문자열을 그대로 데이터베이스에 저장하는 대신, 값을 인코딩 해 저장하는 것이다.
Redis 데이터베이스에 문자열 red, green, blue 전체를 저장하는 대신 값을 인코딩한다.
예를 들어 문자 ‘a’를 저장하는 것이다.
가구 제품의 목록을 인코딩된 데이터셋으로 변환하는 것이다.
긴 문자열을 저장하는 대신 각각 특정 속성을 나타내는 간단한 문자로 변환된다.
값을 인코딩 했으니 Redis에 일반적인 문자열로 저장하면 된다.
그리고 여러 가지 속성을 인코딩한 문자를 합쳐 문자열로 저장한다.
이제 Redis의 다양한 명령어를 어떻게 활용해 4가지 작업을 구현할 수 있는지 알아보자.
- 1번 아이템의 타입을 가져옴
- 1번 아이템의 정보를 bcd로 업데이트
- 아이템 1, 2, 3번의 정보를 모두 가져옴
- 4번 아이템을 trq, 5번 아이템을 nzq로 생성
Redis에 모든 데이터를 저장했는데, 가능한 정보를 압축해 단일 문자로 값을 인코딩하였다.
그리고 Redis에 내장된 명령어로 아이템을 가져오고, 업데이트하고, 한 번에 여러 개를 업데이트하고 수정할 수 있다.
위의 이 작업은 매우 빠르게 실행된다.
특히 기존의 데이터베이스에 저장된 아이템의 개수가 많다면 기존 데이터베이스에 비해 훨씬 빠르다.
숫자 다루기
이제 Redis에서 숫자를 저장하고 처리하는 방법을 알아보자.
문자열을 처리하던 것과 유사하게 숫자를 처리할 수 있다.
사실 문자열에 사용하던 것과 거의 동일한 명령어를 사용해서 숫자를 설정하고 가져오는 데 사용할 수 있다.
(SET, GET, MGET, MSET 등)
하지만 숫자를 다룰 때 숫자만을 처리하는 데 사용되는 추가 명령어가 있다.
- DECR, DECRBY, INCRBYT, INCR
여기서 중요한 점은 숫자에만 사용되는 명령어를 문자열 키에 사용하는 실수를 하지 않는 것이다.
예를 들어 문자열에 값을 추가하는 명령어를 실행하면 Redis가 오류 메세지를 반환할 것이다.
그리고 주의할 것이 하나 더 있는데, SET age 20 명령어를 사용하면 Redis가 이 숫자를 문자열 20으로 저장한다.
따라서 앱 서버에서 문자열을 받아 숫자로 변환해야 한다.
- INCR, DECR
- 기존 키에 숫자 1을 더하거나 뺀다.
- INCRBY, DECRBY
- 더하고 뺄 숫자를 저장할 수 있다.
- INCRBYFLOAT, DECRBYFLOAT
- 소수점이 있는 FLOAT 값 처리
INCR 명령어는 데이터베이스에 저장된 숫자를 확인해 1 증가시키는 명령어이다.
여기서 중요한 점은 명령어가 실행되며 두 가지 작업을 동시에 한다는 점이다.
생각해 보면 GET, SET을 이용해 INCR 명령어를 동일하게 구현할 수 있다는 것을 알 수 있다.
(값을 확인하고 수정한 후 다시 저장하니까)
INCR을 사용하는 대신 2개의 작업으로 나눠보자. 데이터베이스에는 upvotes의 키-값 쌍에 20이 저장되어 있다.
GET을 사용해 값을 가져오고(upvotes), upvotes의 현재 값을 API 서버에 문자열로 가져와 숫자로 변환한 다음 1을 더한다. 그리고 데이터베이스에 값을 저장하기 위해 SET 명령어를 사용해 upvotes에 21을 저장한다. 이렇게 해서 INCR과 동일한 작업을 할 수 있는데, 왜 INCR 명령어가 존재하는 것일까?
이 방법이 안 좋은 이유가 2가지 있다.
- Redis 데이터베이스에 2번 접근해야 한다. → API 서버에서 Redis에 2번 요청해야 한다.
- INCR 명령어를 사용하는 것보다 2배 오래 걸린다.
- 동시성 이슈가 발생할 수 있다.
- 예를 들어 인기 있는 게시물에 2개의 추천이 동시에 들어왔다고 가정하자.
- API 서버를 2개 실행하고 있고 둘 다 하나의 Redis 인스턴스에 연결되어 있다고 가정하자.
- 두 API 서버가 현재 upvotes의 값을 가져오려고 각각 GET 명령을 요청한다.
- Redis가 두 서버에 각각 값 20을 전달한다.
- 두 API 서버가 동시에 받은 값을 숫자로 변환 후 1을 더해 21을 만들고, 동시에 Redis에 upvotes 값을 업데이트하도록 요청한다. (SET upvotes 21)
- Redis가 값을 21로 업데이트 한다. → 원하는 결과는 22였는데, 두 서버가 동시에 업데이트하니 21이 되었다.
엄청난 양의 요청을 받다 보면 이러한 상황이 꼭 발생한다.
더 복잡한 비즈니스 로직을 구동하고 있다면 이러한 버그가 발생했을 때 훨씬 처리하기 어려울 것이다.
이를 어떻게 해결해야 할까? 방법은 3가지가 있다.
- 특수한 명령어인 WATCH를 통한 Redis 트랜잭션 사용
- lock 사용
- INCR 명령어 사용
1, 2번째 방법은 복잡하니 나중에 알아보도록 하고, INCR 명령어를 사용하면 다음과 같은 과정을 거친다.
- 동시에 2개의 요청을 받는다.
- API 서버가 Redis에 INCR upvotes를 요청한다.
- 여기서 Redis가 기본적으로 동기화된 단일 스레드로 작동한다.
- Redis가 수신한 순서에 따라 명령어를 하나씩 처리한다.
- 정확히 동시에 INCR upvotes 명령어를 받아도, Redis는 두 INCR 명령어를 순차적으로 처리해
각각 API 서버에 21, 22를 반환한다.
Redis가 기본적으로 동기식으로 작동하며 다양한 요청을 동시에 처리할 때 동시성을 고려해야 한다는 것을 알 수 있다.
'log.info' 카테고리의 다른 글
[Docker] Docker Compose (0) | 2024.06.18 |
---|---|
[Docker] 볼륨 vs 바인드 마운트 (1) | 2024.06.17 |
[Redis] Redis에 대해 알아보자 ! (0) | 2024.06.16 |
[Docker] 데이터 관리 및 볼륨 (0) | 2024.06.14 |
[Docker] 이미지, 컨테이너 요약 (0) | 2024.06.13 |