Redis 주니어 코딩 면접
새 면접Q. 블로그 서비스에서 각 게시글에 달린 태그들을 관리하려고 합니다. 하나의 게시글에는 여러 태그가 붙을 수 있고, 태그 추가/삭제/존재 여부 확인이 빈번하게 발생합니다. 또한 두 게시글의 공통 태그를 찾는 기능도 필요합니다. 어떤 Redis 데이터 타입을 사용하고 어떤 명령어들로 구현할 수 있는지 설명해주세요.
Set 자료구조를 사용하는 것이 적합합니다. 각 게시글의 태그를 'post:{postId}:tags' 키로 Set에 저장하고, SADD로 태그 추가, SREM으로 삭제, SISMEMBER로 존재 여부를 확인할 수 있습니다. 두 게시글의 공통 태그는 SINTER 명령어로 교집합을 구할 수 있으며, SUNION으로 합집합, SDIFF로 차집합도 구할 수 있습니다. Set은 중복을 자동으로 제거하고 집합 연산을 지원하므로 태그 관리에 최적화되어 있습니다.
- • Set 자료구조 사용
- • SADD/SREM/SISMEMBER로 기본 CRUD
- • SINTER/SUNION/SDIFF로 집합 연산
- • 중복 자동 제거 특성 활용
만약 태그별로 게시글 목록을 역으로 조회해야 한다면 어떻게 인덱스를 설계하시겠습니까?
Q. 사용자 프로필 정보(이름, 이메일, 나이, 주소 등 10개 필드)를 Redis에 저장하고 있습니다. 단일 필드만 업데이트하거나 조회하는 경우가 많은데, 전체 데이터를 JSON 문자열로 저장하는 방식과 Hash 자료구조를 사용하는 방식 중 어느 것이 더 효율적인지 비교하고, 각각의 장단점을 설명해주세요.
Hash 자료구조를 사용하는 것이 더 효율적입니다. Hash는 HSET으로 개별 필드만 업데이트하고 HGET으로 특정 필드만 조회할 수 있어 네트워크 트래픽과 파싱 비용을 줄일 수 있습니다. JSON 문자열 방식은 단일 필드 변경 시에도 전체 데이터를 읽고 파싱하고 다시 직렬화해야 하므로 비효율적입니다. 다만 JSON 방식은 복잡한 중첩 구조를 표현하기 쉽고 애플리케이션 코드가 단순해지는 장점이 있습니다. Hash는 HINCRBY로 숫자 필드의 원자적 증감도 가능하며, HGETALL로 전체 조회, HMGET으로 다중 필드 조회도 지원합니다.
- • Hash는 개별 필드 접근 가능
- • JSON은 전체 읽기/쓰기 필요로 비효율적
- • Hash는 HINCRBY 등 원자적 연산 지원
- • JSON은 중첩 구조 표현에 유리
Hash 자료구조에서 필드가 매우 많아지면(수천 개 이상) 성능에 어떤 영향이 있을까요?
Q. 포인트 전송 기능을 구현하는데, A 사용자의 포인트를 차감하고 B 사용자의 포인트를 증가시켜야 합니다. Redis에서 MULTI/EXEC를 사용하여 이 작업을 원자적으로 처리하려고 할 때, A 사용자의 포인트가 부족한 경우를 어떻게 처리해야 하는지 설명하고, WATCH 명령어의 역할을 함께 설명해주세요.
MULTI/EXEC만으로는 조건부 로직을 처리할 수 없으므로 WATCH를 함께 사용해야 합니다. 먼저 WATCH로 A 사용자의 포인트 키를 감시하고, GET으로 현재 포인트를 확인한 후 잔액이 충분한지 검증합니다. 충분하면 MULTI로 트랜잭션을 시작하고 DECRBY와 INCRBY로 포인트를 이동시킨 후 EXEC로 실행합니다. 만약 WATCH한 키가 다른 클라이언트에 의해 변경되었다면 EXEC는 nil을 반환하고 트랜잭션이 취소되므로, 이 경우 재시도 로직을 구현해야 합니다. 더 간단한 방법은 Lua 스크립트를 사용하여 조건 검사와 업데이트를 원자적으로 수행하는 것입니다.
- • WATCH로 낙관적 락 구현
- • 조건 검사 후 MULTI/EXEC 실행
- • EXEC가 nil 반환 시 재시도 필요
- • Lua 스크립트가 더 간단한 대안
이러한 낙관적 락 방식이 비관적 락(분산 락) 방식과 비교했을 때 어떤 상황에서 더 적합한가요?
Q. 실시간 채팅 애플리케이션에서 사용자가 읽지 않은 메시지 수를 표시해야 합니다. 각 채팅방마다 사용자별로 읽지 않은 메시지 수를 관리하고, 메시지를 읽으면 카운트를 0으로 초기화해야 합니다. 100개의 채팅방에 속한 사용자의 전체 읽지 않은 메시지 총합을 빠르게 계산하려면 어떤 방식으로 데이터를 설계하고 구현해야 하는지 설명해주세요.
각 채팅방마다 'room:{roomId}:unread' Hash를 생성하여 userId를 필드로, 읽지 않은 메시지 수를 값으로 저장합니다. 새 메시지가 오면 HINCRBY로 해당 사용자의 카운트를 증가시키고, 메시지를 읽으면 HDEL로 필드를 삭제하거나 HSET으로 0을 설정합니다. 특정 사용자의 전체 읽지 않은 메시지 수를 계산하려면 Lua 스크립트를 사용하여 모든 채팅방의 Hash에서 해당 userId 필드 값을 조회하고 합산합니다. 또는 'user:{userId}:total_unread' 키에 총합을 별도로 관리하고 메시지 증감 시 함께 업데이트하는 방식도 가능하지만, 일관성 유지가 복잡해질 수 있습니다.
- • Hash로 채팅방별 사용자 카운트 관리
- • HINCRBY로 카운트 증가
- • Lua 스크립트로 여러 Hash 집계
- • 별도 총합 키 관리는 일관성 주의
채팅방이 수만 개로 늘어나면 Lua 스크립트 실행 시간이 길어질 텐데, 어떻게 최적화할 수 있을까요?
Q. 대량의 사용자 데이터(100만 건)를 Redis에 초기 로딩해야 합니다. 단순히 반복문으로 SET 명령어를 100만 번 실행하는 것보다 성능을 개선할 수 있는 방법들을 설명하고, 각 방법의 장단점과 주의사항을 말씀해주세요.
첫째, Pipeline을 사용하여 여러 명령어를 배치로 전송하면 네트워크 �왕복 비용(RTT)을 크게 줄일 수 있습니다. 수천 개씩 묶어서 전송하되, 너무 크면 메모리 버퍼가 부족할 수 있으므로 적절한 크기로 분할합니다. 둘째, MSET 명령어를 사용하여 한 번에 여러 키-값 쌍을 설정할 수 있지만, 단일 명령어가 너무 크면 블로킹 시간이 길어집니다. 셋째, Redis의 RESTORE 명령어와 함께 RDB 파일을 직접 로드하는 방법도 있습니다. 넷째, 여러 클라이언트를 병렬로 실행하여 동시에 데이터를 입력하면 처리량을 높일 수 있지만, Redis는 싱글 스레드이므로 CPU 코어 수만큼만 효과적입니다. Pipeline이 가장 일반적이고 효과적인 방법입니다.
- • Pipeline으로 RTT 감소
- • MSET으로 배치 처리
- • 적절한 배치 크기 조절 필요
- • 병렬 클라이언트로 처리량 증가
Pipeline 사용 시 일부 명령어가 실패하면 어떻게 처리해야 하나요? 트랜잭션과 어떤 차이가 있나요?
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!