Railway로 배포하기, 동시성 이슈 고려하여 좋아요 기능 추가하기

1. 스프링 부트 애플리케이션의 배포와 데이터베이스 연결 오류 해결

프로젝트의 초기 단계에서 배포 편의성을 고려하여 Railway 플랫폼을 선택하였습니다. Dockerfile을 기반으로 한 자동 배포 시스템을 구축하였으나, 실행 단계에서 JDBCConnectionException이 발생하는 문제에 직면하였습니다. 해당 오류의 주된 원인은 데이터베이스 연결 문자열의 형식 불일치였습니다.

Railway에서 제공하는 DATABASE_URL은 표준 PostgreSQL 형식을 따르지만, 스프링 부트의 JDBC 드라이버는 명시적인 드라이버 식별자를 포함한 주소를 요구합니다. 이를 해결하기 위해 단일 URL 변수에 의존하기보다는 Railway가 제공하는 개별 환경 변수인 PGHOST, PGPORT, PGUSER, PGPASSWORD, PGDATABASE를 조합하여 application.yml에 설정하였습니다. 이러한 방식은 설정의 명확성을 높일 뿐만 아니라, 환경 변화에 더욱 유연하게 대응할 수 있게 합니다.

또한, 서버의 물리적 위치가 응답 속도에 미치는 영향을 확인하였습니다. 한국 사용자를 주 대상으로 하는 서비스의 경우, 미국보다는 싱가포르 지역에 서버와 데이터베이스를 배치하는 것이 네트워크 지연 시간을 유의미하게 단축시킬 수 있음을 확인하였습니다. 이때 서버와 데이터베이스를 반드시 동일한 지역에 배치하여 내부망 통신을 유도하는 것이 성능 최적화의 핵심입니다.


2. 효율적인 ‘좋아요’ 기능 구현을 위한 데이터베이스 설계

게시물 좋아요 기능은 단순한 카운트 이상의 데이터 무결성을 요구합니다. 단순히 게시물 테이블에 카운트 컬럼을 두는 방식은 누가 좋아요를 눌렀는지 식별할 수 없으며, 중복 요청을 제어하기 어렵습니다. 따라서 ‘유저’와 ‘게시물’ 간의 다대다 관계를 해소하기 위한 별도의 PostLike 엔티티를 도입하였습니다.

엔티티 설계 시 클래스 명명에 주의를 기울였습니다. SQL 예약어인 LIKE와의 충돌을 방지하기 위해 PostLike라는 명칭을 사용하였으며, userIdpostId의 조합에 유니크 제약 조건을 부여하여 데이터 중복을 원천적으로 차단하였습니다. 또한, 특정 유저가 누른 좋아요 목록을 조회하는 성능을 높이기 위해 userId 컬럼에 인덱스를 추가하였습니다.

식별자 전략으로는 복합키 대신 자동 생성되는 기본키(Long ID)를 사용하는 대리키 전략을 선택하였습니다. 이는 JPA 구현의 복잡성을 낮추고 향후 요구사항 변경에 유연하게 대처하기 위함입니다. 엔티티 간 연관 관계 설정 시에는 성능 최적화를 위해 지연 로딩(FetchType.LAZY)을 기본으로 적용하였습니다.


3. 동시성 이슈 제어와 JPA 영속성 관리

서비스 규모가 확장됨에 따라 여러 사용자가 동시에 좋아요를 누를 때 발생하는 동시성 이슈, 즉 ‘갱신 손실(Lost Update)’ 문제를 고려해야 했습니다. 자바 애플리케이션 레벨에서 단순히 값을 읽어와 증가시킨 후 다시 저장하는 방식은 데이터 정합성을 보장하지 못합니다.

이를 해결하기 위해 데이터베이스의 원자적 연산(Atomic Update)을 활용하였습니다. JPA의 @Modifying 어노테이션과 함께 UPDATE Post p SET p.likeCount = p.likeCount + 1과 같은 JPQL을 작성하여 데이터베이스가 직접 값을 연산하도록 유도하였습니다. 이 방식은 행 단위 잠금을 통해 여러 요청이 동시에 들어오더라도 정확한 카운트를 보장합니다.

여기서 주의할 점은 JPA의 영속성 컨텍스트 관리입니다. 벌크 수정 쿼리는 영속성 컨텍스트를 거치지 않고 데이터베이스에 직접 반영되므로, 메모리상의 엔티티 상태와 데이터베이스의 실제 상태 간에 불일치가 발생할 수 있습니다. 이를 방지하기 위해 @Modifying(clearAutomatically = true) 옵션을 사용하여 쿼리 실행 직후 영속성 컨텍스트를 초기화함으로써, 이후 조회 시 최신 데이터를 보장받도록 설계하였습니다.


4. 인프라 운영 비용 분석 및 최적화 전략

개발이 진행됨에 따라 운영 및 개발 환경을 분리하면서 인프라 비용에 대한 검토가 필요해졌습니다. Railway의 하비 플랜은 월 5달러의 크레딧을 제공하지만, 자원을 많이 점유하는 스프링 부트 서버 2개와 PostgreSQL 데이터베이스를 24시간 가동할 경우 월 예상 비용이 약 18달러에서 23달러에 달한다는 결과를 얻었습니다.

비용 효율화를 위해 우선 하나의 PostgreSQL 인스턴스 내에 운영용(prod)과 개발용(dev) 데이터베이스를 각각 생성하여 공유하는 방식을 도입하였습니다. 이는 데이터베이스 서비스를 추가로 생성함으로써 발생하는 고정 비용을 절감하는 효과가 있습니다.

더 나아가, 고정 비용 모델인 AWS Lightsail로의 이전을 검토하였습니다. 월 5달러의 Lightsail 플랜은 1GB의 RAM을 제공하며, 이는 도커를 활용하여 운영 서버, 개발 서버, 데이터베이스를 동시에 가동하기에 충분한 사양입니다. 비록 초기 설정 및 GitHub Actions를 통한 CI/CD 구축 과정이 Railway에 비해 복잡할 수 있으나, 장기적인 운영 비용 측면과 CI/CD 구축 경험이라는 학습적 가치를 고려할 때 Lightsail이 더 합리적인 선택지임을 확인하였습니다.


5. 결론 및 향후 계획

본 프로젝트를 통해 단순한 기능 구현을 넘어, 배포 환경에서의 네트워크 지연 이슈, 데이터베이스의 논리적·물리적 설계, 트랜잭션과 동시성 제어, 그리고 인프라 비용 관리까지 서비스 전반의 메커니즘을 심도 있게 이해할 수 있었습니다. 특히 추상화된 프레임워크 뒤에서 일어나는 데이터베이스와 영속성 컨텍스트의 상호작용을 파악한 것은 큰 수확이었습니다.

향후에는 AWS Lightsail 환경으로 인프라를 이전하고, GitHub Actions와 Docker Compose를 결합하여 안정적인 배포 자동화 파이프라인을 구축할 예정입니다. 또한, 좋아요 기능 외에도 트래픽이 몰릴 수 있는 핵심 기능들에 대해 캐싱 전략을 도입하여 성능을 더욱 고도화해 나갈 계획입니다.

링크:
링크: » 일본어로 보기 (日本語で見る)
링크: » 영어로 보기 (Switch to English)
공유: