본문 바로가기

전체 글

(12)
[Redis] 트랜잭션 도중 연결이 끊기면? 와이어샤크와 소스코드로 파헤친 원자성 1. 문제의 발단: 피드 삭제 스케줄러와 원자성서비스 운영 중, 일정 기간 활동하지 않은 유저의 피드를 정리하는 '피드 삭제 스케줄러'를 개발하고 있었습니다. 로직은 간단했습니다.ZSET에서 오래된 유저 조회해당 유저의 피드 데이터 삭제 (DEL)ZSET에서 유저 정보 삭제하지만 한 가지 의문이 들었습니다. "만약 2번(피드 삭제) 명령어를 레디스로 보냈는데, 3번을 수행하기 전에 어플리케이션 서버가 죽어버리면 어떻게 되지?" 데이터의 정합성을 위해 이 작업들은 '전부 실행되거나, 아예 실행되지 않아야(All or Nothing)' 합니다. 그래서 레디스의 트랜잭션 명령어인 MULTI와 EXEC을 사용하기로 했습니다.2. 가설 검증: 장애 상황 시뮬레이션레디스의 트랜잭션은 관계형 DB와 달리 롤백을 지원..
[Spring Boot] "서버가 터졌는데 왜 내부 경로가 다 보여?" : Global Exception으로 막는 정보 유출 [Spring Boot] "500 에러에 서버 경로가 다 보인다고?" : Global Exception Handler로 막아낸 정보 유출왜 만들게 되었나jooshop 프로젝트에서 상품 조회 API를 구현하던 중, 존재하지 않는 상품 ID를 요청했을 때 NullPointerException이 발생했습니다. 이때 반환되는 응답을 확인하고 문제를 인식했습니다.{ "timestamp": "2024-02-05T10:15:30.123+00:00", "status": 500, "error": "Internal Server Error", "trace": "java.lang.NullPointerException: Cannot invoke Product.getName() at com.exampl..
SNS 서비스 | 10. 좋아요 병합 스케줄러 앞서 좋아요 요청의 잠금 경합으로 인한 병목 지점을 개선하고자 레디스에 좋아요 데이터를 캐싱하는 방법을 사용하였습니다. 이 무한히 늘어날 수 있는 문제가 있는 좋아요 데이터는 일정 주기로 데이터베이스에 병합되야 합니다. 병합은 다음과 같은 순서로 이루어집니다. 1. 락 획득2. 좋아요 키 조회3. 좋아요 데이터베이스 저장4. 좋아요 키 삭제5. 락 해제 분산 락 기법을 통한 분산 서버 환경에서의 동시성 제어는 앞서 피드 삭제 스케줄러를 구현할 때 해결할 수 있었습니다.좋아요 키 조회좋아요 데이터를 저장하는 키는 post:*:like 키 패턴으로 조회가 가능합니다. KEYS 커맨드와 SCAN 커맨드의 차이를 알아보겠습니다. KEYS 커맨드는 주어진 패턴과 일치하는 모든 키를 반환합니다. 이 작업의 시간 복..
SNS 서비스 | 9. 피드 삭제 스케줄러 피드 삭제 스케줄러사용자는 피드를 조회할 때 마다, Sorted Set 자료구조인 user_activity를 key로 갖는 집합의 value에 userId를, score로 현재 시각인 유닉스 타임스탬프 10자리를 저장합니다.  일정 주기로 동작하는 스케줄러는, ZRANGEBYSCORE 커맨드를 이용해 활동을 오래 하지 않은 사용자를 검색할 수 있습니다. 예를 들어 한달 간 활동하지 않은 사용자를 검색해야할 경우 0을 min으로, 한달 전 타임스탬프 값을 max로 설정할 수 있습니다. 검색한 userId 집합은 피드 key 형태로 변환된 컬렉션으로 저장 후, DEL 커맨드를 이용해 해당하는 유저의 피드를 삭제할 수 있습니다. 피드를 삭제한 후 ZREMRANGEBYSCORE를 이용해 user_activity..
SNS 서비스 | 8. Redis를 활용한 피드 조회 시스템 기존 MySQL을 이용해 피드를 임의로 추출하는 쿼리 방식은 필연적인 디스크 I/O를 유발했습니다. 뿐만 아니라 사용자의 게시글 조회 여부와 관계 없이 조건에 부합하는 모든 게시글을 조회하기 위해 데이터베이스에 부하를 주게 됩니다. 이 과정에서 사용자의 팔로잉 사용자를 찾는 과정, 그 사용자의 게시글을 찾는 과정, 게시글이 반환될 피드의 조건에 부합하는지 필터링하는 과정은 복잡한 조인 비용 뿐만이 아닌, 임시 테이블과 파일 소트로 인한 메모리 압박이라는 문제가 있습니다. 앞서 레디스를 활용한 피드 발행 시스템을 구현했습니다. 사용자가 게시글을 작성하면, 해당 게시글이 팔로워들의 피드에 자동으로 전파됩니다. 이로 인해 사용자는 피드를 조회할 때 팔로잉 정보를 별도로 찾는 연산을 수행할 필요가 없습니다. 또..
SNS 서비스 | 7. Redis를 활용한 피드 발행 시스템 피드 저장 자료구조 먼저 피드를 저장할 자료구조를 선택해야 합니다. 사용자 ID를 피드의 key 값으로 가지며, value로서 item을 여러 개 저장할 수 있는 자료구조가 적합할 것입니다. 또한 최신 게시글이 피드 상단에 노출되야 하므로 최신 순 정렬이 필요합니다. Sorted set 자료구조를 먼저 살펴보겠습니다. 피드는 feed:{userId}의 key와 postId의 집합을 value로 가질 수 있습니다. score로 10자리 숫자의 초 단위 TimeStamp 또는 Auto Increment로 생성되는 postId를 가질 수 있습니다. 이 자료구조의 장점은 socre 기반으로 value가 정렬되므로 매우 정확한 정렬 기준을 가진다는 것입니다. 피드를 조회할 떄 ZREVRANGE를 통해 페이징 기반..
SNS 서비스 | 6. Local Cache vs Global Cache In-Memory Database의 방식 중에서도 Local Cache를 이용할 것인지, 중앙 서버를 통한 Global Cache를 이용할 것인지에 따라 나뉩니다. 각자 장단점이 존재합니다.Local Cache각 애플리케이션 서버가 자신만의 메모리 영역을 가지고 데이터를 관리하는 방식입니다. 이 방식은 네트워크 호출이 없어 매우 빠른 응답 속도를 보장하며, 외부 의존성이 없어 안정적인 서비스 운영이 가능합니다. 그러나 서버 간 데이터 정합성을 맞추기 어렵고, 메모리 사용량이 각 서버마다 중복되어 전체적인 비용이 증가할 수 있다는 단점이 있습니다. 모든 사용자들에게 동일하게 보이는 Universal Data를 저장할 경우 서버 간 데이터 동기화를 위해 Redis Pub/Sub과 같은 이벤트 기반 통신이 필..
SNS 서비스 | 5. In-Memory Database SNS 서비스에서 피드는 중요한 기능입니다. 사용자들의 활발한 상호작용을 지원하고 실시간성을 보장해야 하는 피드 시스템에서 피드 조회 시 응답 시간은 중요한 성능 요소입니다. 하지만 기존 MySQL을 통해 피드를 추출하는 방식은 필연적으로 디스크 I/O를 유발합니다. 이에 On-Disk Database와 In-Memory Database의 차이점을 알아보겠습니다.On-Disk DatabaseOn-Disk Database 에서 데이터는 영구적으로 디스크에 저장됩니다. 자주 사용되는 데이터는 캐싱되어 메모리에 올라와 있는 경우도 있지만, 그렇지 않은 데이터를 찾고자 할 때는 직접 디스크에서 데이터를 검색하는 과정을 거쳐야 합니다. 디스크에 저장된 데이터는 바로 찾을 수 없으며, 디스크의 데이터를 페이지 단위..