[Redis] 요구 사항 파악, Redis 구조, 실제 활용 사례

2020. 10. 7. 12:10Database/Redis

1. 요구사항 파악

대부분의 서비스에서는 Redis를 단순 캐시 용도로 사용한다. 하지만 Redis를 어떻게 배치하는지에 따라 시스템의 성능에 큰 영향을 미칠 수 있다. 캐싱 전략은 캐싱되는 데이터의 유형과 해당 데이터에 대한 액세스 패턴에 따라 달라진다.

 

서비스에서 Redis의 도입을 고민할 때, 캐시용으로 사용할 것인지, 저장소용으로 사용할 것인지를 분명히 해야한다. 즉, Redis에 저장되었던 데이터가 없어져도 같은 데이터가 RDBMS에 남아 있기 때문에 문제가 없거나, 혹은 일부 값이 유실되어도 괜찮은 경우인지 확인해야 한다. 데이터운영팀에서는 특별한 경우가 아니면 Redis를 영구 저장소용으로 사용하는 것을 권장하지 않는다. Redis의 데이터를 파일로 보관하기 위한 Persistence 기능(RDB, AOF) 으로 인한 장애발생 가능성이 굉장히 크기 때문이다.

 

fork()로 인한 COW(Copy-On-Write)로 전체 성능 저하가 발생하거나, 파일이 생성되지 않으면 레디스에 write를 할 수 없는 등 다양한 장애 사례가 존재한다. 따라서, 꼭 필요한 경우에만 데이터를 저장하는 것이 좋다. 

요구사항 파악

 

 

 

2. Redis 구조

1) Look Aside (Lazy Loading)

이 구조는 캐시를 옆에 두고 필요할 때만 데이터를 캐시에 로드하는 캐싱 전략이다. 캐시는 데이터베이스와 어플리케이션 사이에 위치하여 단순 키-값 형태를 저장한다. 애플리케이션에서 데이터를 가져올 때 Redis에 항상 먼저 요청하고, 데이터가 캐시에 있을 때에는 Redis에서 데이터를 반환한다. 데이터가 캐시에 없을 경우 애플리케이션에서 데이터베이스에 데이터를 요청하고, 애플리케이션은 이 데이터를 다시 Redis에 저장한다.

 

이 구조를 사용하면 실제로 사용되는 데이터만 캐시할 수 있고, Redis의 장애가 애플리케이션에 치명적인 영향을 주지 않는다는 장점을 가지고 있다. 하지만 캐시에 없는 데이터를 쿼리할 때 더 오랜 시간이 걸린다는 단점과 캐시가 최신 데이터를 가지고 있다는 것을 보장하지 못하는 단점이 있다. 캐시에 해당 키값이 존재하지 않을 때만 캐시에 대한 업데이트가 일어나기 때문에 데이터베이스에서 데이터가 변경될 때에는 해당 값을 캐시가 알지 못하기 때문이다.

Look Aside

2) Write-Through

이 구조는 데이터베이스에 데이터를 작성할 때마다 캐시에 데이터를 추가하거나 업데이트한다. 이로 인해 캐시의 데이터는 항상 최신 상태로 유지할 수 있지만, 데이터 입력 시 두 번의 과정을 거쳐야 하므로 지연 시간이 증가한다는 단점이 존재한다. 또한 사용되지 않을 수도 있는 데이터도 일단 캐시에 저장하기 때문에 리소스 낭비가 발생한다. 이를 해결하기 위해 데이터 입력 시 TTL을 꼭 사용하여 사용되지 않는 데이터를 삭제하는 것을 권장한다.

Write-Through

 

 

 

3. 실제 활용 사례

1) '좋아요' 처리

게시물에 달린 댓글에 '좋아요' 기능의 가장 중요한 것은 한 사용자가 하나의 댓글에 한 번만 '좋아요'를 할 수 있도록 제한하는 것이다. RDBMS에서는 UNIQUE 조건을 생성하여 처리할 수 있지만, 만약 많은 입력이 발생하는 환경에서 RDBMS을 이용한다면 INSERT와 UPDATE에 의한 성능 저하가 필연적으로 발생하게 된다.


Redis의 Set을 이용하면 이 기능을 간단하게 구현할 수 있으며, 빠른 시간 안에 처리할 수 있다. Set은 순서가 없고, 중복을 허용하지 않는 집합이다. 댓글의 번호를 사용해서 키를 생성하고, 해당 댓글에 '좋아요'를 누른 사용자의 ID를 아이템으로 추가하면 동일한 ID값을 저장할 수 없으므로 한 명의 사용자는 하나의 댓글에 한번 만 '좋아요'를 누를 수 있게 된다.

'좋아요' 처리

2) 게임 서비스의 일일 방문자 수

순 방문자수(UV)는 서비스에 사용자가 하루에 여러번 방문했다 하더라도 한 번만 카운팅 되는 값이다. 즉, 중복 방문을 제거한 방문자의 지표라고 생각할 수 있다. 많은 서비스에서 이 수치를 이용해 사용자의 동향을 파악하고, 마케팅을 위한 자료로 활용한다.

 

실제 서비스에서는 이를 구하기 위해서 대표적으로 세 가지 방법을 사용합니다. 첫 번째로 액세스 로그(access log)를 분석하는 방법, 두 번째로 외부 서비스(Google Analytics)의 도움을 받는 방법, 세 번째로는 접속 정보를 로그 파일로 작성하여 배치 프로그램으로 돌리는 방법이다. 이 세 가지 방법 중 GA를 제외하고는 정보를 실시간으로 조회할 수 없다.


게임의 유저는 천 만명이라 가정하고, 일일 방문자 횟수를 집계하며 이 값은 0시를 기준으로 초기화된다고 가정한다. 사용자 ID는 0부터 순차적으로 증가된다고 가정하고, String의 각 bit를 하나의 사용자로 생각할 수 있다. 사용자가 서비스에 방문할 때 사용자 ID에 해당하는 bit를 1로 설정한다. 1개의 bit가 1명을 의미하므로, 천 만명의 유저는 천 만개의 bit로 표현할 수 있고, 이는 곧 1.2MB정도의 크기이다. Redis String의 최대 길이는 512MB이므로 천만 명의 사용자를 나타내는 건 충분하다.

 

예를 들어, 2020년 1월 29일에 ID가 7인 사용자가 방문했다면 일곱 번째 인덱스를 1로 설정한다. 이날에 서비스에 방문한 총방문자 수를 조회하기 위해서는 이 문자열에서 1로 설정된 bit의 개수를 구하는 BITCOUNT 연산을 사용하여 간단히 구할 수 있다.

게임 서비스의 일일 방문자 수

만약 출석 이벤트 등을 진행하기 위해 정해진 기간 동안 매일 방문한 사용자를 구하고 싶을 경우, Redis의 BITOP 명령어를 사용하면 간단하다. Redis 서버에서 바로 AND, OR, XOR, NOT 연산을 할 수 있으므로, Redis에서 개별 비트를 가져와서 서버에서 처리하는 번거로움을 줄여준다.

 

예를 들어, 2020년 1월 29일부터 31일까지 매일 접속한 사용자는 ID가 7인 사용자와 11인 사용자라는 것을 BITOP을 이용한 AND 연산을 통해 쉽게 구할 수 있다.

3) 최근 검색 목록

일반적으로 최근 검색 목록 기능을 관계형 데이터베이스를 이용해 구현하려면, 사용자가 최근에 검색했던 테이블에서 최근 5개의 데이터를 조회해야한다. 하지만 이렇게 RDBMS의 테이블을 이용해서 데이터를 저장한다면 중복 제거, 멤버별로 저장된 데이터의 개수를 확인, 오래된 검색어는 삭제하는 작업이 모두 이루어져야 한다.

SELECT * FROM KEYWORD WHERE ID = 123 ORDER BY reg_date DESC LIMIT 5;

이러한 번거로운 작업들은 중복을 허용하지 않고, 정렬되어 저장되는 Redis의 Sorted Set을 사용하면 간단하게 구현할 수 있다. Sorted Set은 가중치를 기준으로 오름차순으로 정렬되기 때문에, 가중치로 시간을 사용한다면 이 값이 가장 큰, 나중에 입력된 아이템이 맨 마지막 인덱스에 저장된다.

 

예를 들어, 멤버 ID가 123인 사람이 최근 검색한 사람은 정렬되어 저장된다. 이때 가중치는 입력 순간의 나노세컨드이고, 가장 처음 검색한 사람의 ID는 46, 가장 마지막 검색한 사람은 50이다. 이때 ID가 51인 사람을 검색하면 마지막에 데이터가 추가된다.

항상 다섯 명만 저장하기 위해서는 인덱스가 0인 아이템을 지우면 된다. 하지만 아이템 개수가 6보다 작을 때에는 0번째 인덱스를 삭제하면 안 되기 때문에 매번 아이템의 수를 먼저 확인해야 하는 번거로움이 있다. 이때 Sorted Set의 음수 인덱스를 사용한다면 더 간단해진다. 음수 인덱스는 인덱스의 마지막부터 큰 값부터 작은 값으로 매겨진다.

데이터에 멤버를 추가한 뒤, 항상 -6번째 아이템을 지운다면 특정 개수 이상의 데이터가 저장되는 것을 방지 할 수 있게 된다. 인덱스로 아이템을 지우려면 ZREMRANGEBYRANK 커맨드를 사용하면 간단하다. 즉, Redis의 Sorted Set을 이용하면 많은 공수를 들이지 않고도 최근 검색한 내용을 보여줄 수 있는 기능을 구현할 수 있게 된다.


[참고] medium.com/garimoo/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%A0%88%EB%94%94%EC%8A%A4-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-02-f1029893e263

[참고] medium.com/garimoo/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%A0%88%EB%94%94%EC%8A%A4-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-04-17256c55493d

728x90

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

[Redis] Redis 요약 및 정리  (0) 2020.10.08
[Redis] 데이터 유형별 명령어  (0) 2020.10.06
[Redis] 서버, 연결 명령어  (0) 2020.10.06
[Redis] 키 명령어  (0) 2020.10.06