1. 프로젝트 개요
Monew는 사용자가 관심사를 등록하고, 관심사와 관련된 뉴스 기사를 모아볼 수 있는 뉴스 피드 서비스입니다. 사용자는 기사에 댓글을 작성하거나 좋아요를 누를 수 있으며, 관심사에 새 기사가 등록되거나 본인이 작성한 댓글에 좋아요가 눌렸을 때 알림을 받을 수 있습니다.
2. 담당한 작업
이 프로젝트에서 댓글 도메인과 알림 도메인을 담당했습니다. 댓글 작성/조회/수정/삭제/좋아요 기능과, 댓글 좋아요 및 관심사 기사 등록과 연결되는 알림 생성/조회/확인/삭제 기능을 개발했습니다.
2-1) 댓글 도메인
댓글 등록, 수정, 삭제, 목록 조회, 좋아요 등록 및 취소 API 구현
기사별 댓글 목록 QueryDSL 커서 기반 페이지네이션 구현
중복 좋아요 방지 및 좋아요 수 증가/감소 처리
2-2) 알림 도메인
이벤트 기반 비동기 알림 시스템 구축
알림 목록 조회, 단건 확인, 전체 확인 API 구현
미확인 알림 목록 QueryDSL 커서 기반 페이지네이션 구현
JPA 벌크 연산(@Modifying)을 활용한 다건 알림 읽음 처리 및 삭제 성능 최적화
Spring Scheduler를 활용한 주기적인 알림 물리 삭제 배치 작업 구현
3. 기술적 성과
3-1) 이벤트 기반 아키텍처 및 비동기 처리 도입
Spring Application Event를 도입하여 핵심 도메인(댓글, 기사)과 서브 도메인(알림) 간의 결합도를 최소화하였습니다.
@TransactionalEventListener(phase = AFTER_COMMIT)와 @Async를 활용하여 메인 비즈니스의 DB 트랜잭션이 안전하게 커밋된 이후에만 알림이 비동기로 생성되도록 보장하여 데이터 정합성을 확보했습니다.
3-2) 영속성 계층 최적화 및 객체 지향 설계
Spring Data JPA와 QueryDSL을 활용하여 데이터 조회 시 성능 저하를 방지하는 동적 커서(Cursor) 기반 페이지네이션을 구현했습니다.
다형성을 활용한 단일 테이블 상속 전략으로 알림 엔티티 구조를 설계하여 새로운 알림 타입 확장에 유연한 OCP(개방-폐쇄 원칙) 구조를 확립했습니다.
JPA의 @SQLDelete 및 @SQLRestriction 어노테이션을 적용하여 논리적 삭제 메커니즘을 구현했습니다.
3-3) RESTful API 설계 및 동시성 제어
Spring MVC 기반으로 계층이 명확하게 분리된 REST API를 설계하고 구현했습니다.
다중 사용자가 동시에 댓글 좋아요를 요청할 때 발생하는 갱신 손실을 막기 위해 JPA @Version을 활용한 낙관적 락 기반의 동시성 방어 구조를 적용했습니다.
4. 문제점 및 해결 과정
4-1) 댓글 좋아요 경쟁 조건(TOCTOU) 및 데이터 불일치
Situation: 좋아요 취소 시 '존재 확인 -> 카운트 감소 -> 삭제'의 3단계 로직에서 동시 요청 시 좋아요 수가 음수(-1)가 될 위험이 있었습니다.
Task: 삭제 성공 여부를 알 수 없는 void 반환 타입을 수정하고, 원자적인 삭제 로직을 구현해야 했습니다.
Action: CommentLikeRepository의 삭제 쿼리 반환 타입을 int로 변경했습니다. 영향받은 행 수(deletedCount)를 반환받아 1일 때만 카운트를 감소시키도록 로직을 개선했습니다. 또한, 등록 시 발생하는 DB 유니크 제약 위반 예외를 409 Conflict로 변환 처리했습니다.
Result: 동시 요청 상황에서도 좋아요 카운트의 무결성을 보장했으며, 클라이언트에게 명확한 에러 응답을 제공할 수 있게 되었습니다.
4-2) 댓글 목록 커서 페이지네이션의 데이터 중복/누락 문제
Situation: 댓글 정렬 시 정렬 키가 중복될 경우, 페이지를 넘길 때 동일한 데이터가 다시 나타나거나 누락되는 문제가 생길 수 있었습니다.
Task: 대량의 댓글 조회 시에도 사용자에게 끊김 없고 정확한 목록을 제공해야 했습니다.
Action: 정렬 기준의 마지막 단계에 고유 ID를 추가하는 'Tie-breaker' 전략을 도입했습니다. 기준값과 UUID를 결합한 복합 커서를 활용하도록 CommentService를 수정했습니다.
Result: 작성일순/좋아요순 모두에서 안정적인 페이지 이동이 가능해졌고, 같은 좋아요 수나 같은 생성 시간을 가진 댓글도 id 기준으로 일관되게 정렬할 수 있었습니다.
4-3) 운영 환경에서의 알림 생성 오류 수정
Situation: 운영 환경에서 알림 생성 시 데이터베이스 관련 예외가 발생하며 제대로 동작하지 않는 문제가 나타났습니다. 또한 예외 발생 시 내부 쿼리 정보가 노출되어 보안 취약점이 드러나는 상황이였습니다.
Task: 배포 담당자분께 운영 DB 수정을 요청드려야 했고, 누락된 댓글 좋아요 이벤트 발행 로직을 추가해야 했습니다. 또한 데이터베이스 예외 발생 시 전역 예외 처리 로직을 개선해야 했습니다.
Action: 알림 엔티티에 단일 테이블 전략을 적용하면서 추가된 자식 엔티티용 컬럼(interest_id, comment_id)을 운영 DB에 반영하였습니다. 이후 기존 DB 스키마에 남아있는 resource_id 컬럼에는 임의의 UUID를 주입하는 필드(resourceId)를 추가하여 NOT NULL 제약 조건을 방어하였습니다. CommentLikeService 내 댓글 좋아요 등록 시 누락된 이벤트 발행 로직을 추가하여 이벤트가 생성되도록 하고, 내부 쿼리 정보가 노출되지 않도록 전역 예외 처리 로직을 개선하여 문제를 해결하였습니다.
Result: 배포 전 운영 환경 스키마와 코드 간의 정합성을 꼼꼼히 체크하는 프로세스의 중요성을 깨달았습니다.
5. 협업 및 피드백
6. 코드 품질 및 최적화