Skip to content

fix: 채팅방 마지막 메시지 메타데이터 동기화#559

Merged
dh2906 merged 5 commits intodevelopfrom
fix/chat-room-last-message-metadata
Apr 21, 2026
Merged

fix: 채팅방 마지막 메시지 메타데이터 동기화#559
dh2906 merged 5 commits intodevelopfrom
fix/chat-room-last-message-metadata

Conversation

@dh2906
Copy link
Copy Markdown
Contributor

@dh2906 dh2906 commented Apr 21, 2026

🔍 개요

  • 메시지는 chat_message에 저장되지만 chat_room.last_message_content, last_message_sent_at가 null로 남아 채팅방 목록 요약 정보가 비는 문제를 수정했습니다.
  • 신규 메시지 저장 경로와 기존 운영 데이터를 함께 보정해, 현재도 맞고 과거 데이터도 복구되도록 정리했습니다.

🚀 주요 변경 내용

  • ChatRoomRepository에 마지막 메시지 메타데이터를 명시적으로 동기화하는 update 쿼리를 추가했습니다.
  • direct, club, group 메시지 전송 경로에서 공통 syncLastMessage를 사용하도록 바꿨습니다.
  • 회원가입 환영 메시지 경로도 동일한 메타데이터 동기화 규칙을 따르도록 맞췄습니다.
  • Flyway 마이그레이션을 추가해 기존 chat_room의 null 메타데이터를 최신 chat_message 기준으로 백필했습니다.
  • 메시지 전송 후 chat_room 메타데이터가 실제 DB에 반영되는지 검증하는 통합 테스트를 추가했습니다.

💬 참고 사항

  • 검증: ./gradlew test --tests "gg.agit.konect.integration.domain.chat.ChatApiTest$SendMessage" --tests "gg.agit.konect.integration.domain.user.UserSignupApiTest$Signup.signupSuccess"
  • pre-push 훅에서 IntelliJ 포맷과 checkstyle 검사를 수행했고, 포맷 변경으로 chore: 코드 포맷팅 커밋 1개가 추가되었습니다.

✅ Checklist (완료 조건)

  • 코드 스타일 가이드 준수
  • 테스트 코드 포함됨
  • Reviewers / Assignees / Labels 지정 완료
  • 보안 및 민감 정보 검증 (API 키, 환경 변수, 개인정보 등)

dh2906 added 2 commits April 21, 2026 14:21
- chat_room.last_message_*가 null로 남아 채팅방 목록과 실제 메시지 상태가 어긋나는 문제를 막는다
- 메시지 저장 직후 chat_room 메타데이터를 명시적으로 업데이트해 영속성 컨텍스트 clear 경로에 의존하지 않도록 정리한다
- 회원가입 환영 메시지처럼 같은 패턴을 쓰는 경로도 함께 맞춰 신규 방의 초기 메타데이터 누락을 방지한다
- 기존 운영 데이터는 Flyway 백필 마이그레이션과 회귀 테스트로 함께 보강해 재발을 막는다
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 21, 2026

Warning

Rate limit exceeded

@dh2906 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 26 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 11 minutes and 26 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e20a4519-8830-4314-8bba-481ba0c12eac

📥 Commits

Reviewing files that changed from the base of the PR and between dbb4243 and 67a2f72.

📒 Files selected for processing (5)
  • src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java
  • src/main/java/gg/agit/konect/domain/chat/service/ChatService.java
  • src/main/java/gg/agit/konect/domain/user/service/UserService.java
  • src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java
  • src/test/java/gg/agit/konect/unit/domain/chat/service/ChatServiceTest.java
📝 Walkthrough

워크스루

채팅 메시지 송신 시 마지막 메시지 메타데이터(lastMessageContent, lastMessageSentAt)를 엔티티 메모리 업데이트와 데이터베이스 퍼시스턴스 모두에 동기화하도록 하는 새로운 저장소 메서드 및 헬퍼 메서드를 추가했습니다. 기존 데이터를 마이그레이션하는 SQL 스크립트와 동기화 로직을 검증하는 테스트를 포함합니다.

변경 사항

Cohort / File(s) Summary
저장소 계층
src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java
JPA 벌크 UPDATE 쿼리를 실행하여 ChatRoomlastMessageContentlastMessageSentAt을 업데이트하는 updateLastMessage() 메서드 추가
서비스 계층
src/main/java/gg/agit/konect/domain/chat/service/ChatService.java, src/main/java/gg/agit/konect/domain/user/service/UserService.java
엔티티 메모리 업데이트와 저장소 기반 데이터베이스 퍼시스턴스를 모두 수행하는 syncLastMessage() 헬퍼 메서드 추가 및 적용
데이터베이스 마이그레이션
src/main/resources/db/migration/V70__backfill_chat_room_last_message_metadata.sql
최신 메시지 기준으로 모든 채팅 방의 last_message_* 필드를 역채우는 마이그레이션 스크립트
통합 테스트
src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java
메시지 송신 후 ChatRoom의 메타데이터 컬럼이 정상 업데이트되는지 검증하는 테스트 케이스 추가

시퀀스 다이어그램

sequenceDiagram
    participant ChatSvc as ChatService
    participant Entity as ChatRoom Entity
    participant Repo as ChatRoomRepository
    participant DB as Database

    rect rgba(100, 200, 150, 0.5)
    Note over ChatSvc,DB: 메시지 송신 시 마지막 메시지 동기화
    ChatSvc->>Entity: room.updateLastMessage(content, sentAt)
    activate Entity
    Entity->>Entity: 메모리 필드 업데이트
    deactivate Entity
    
    ChatSvc->>Repo: updateLastMessage(roomId, content, sentAt)
    activate Repo
    Repo->>DB: UPDATE chat_room SET<br/>lastMessageContent = ?,<br/>lastMessageSentAt = ?
    DB-->>Repo: 업데이트된 행 수
    deactivate Repo
    Repo-->>ChatSvc: 결과 반환
    end
Loading

예상 코드 리뷰 노력

🎯 3 (Moderate) | ⏱️ ~25분

관련 PR

추천 라벨

버그, 테스트, DB

🐰 메시지가 날아와 채팅방에 내려앉고
메모리도 들쑥날쑥, 데이터베이스도 함께
동기화라는 춤을 추며
마지막 말씀은 영원히 기억되네
테스트로 검증한 우리의 정성! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 변경 내용의 핵심을 명확하게 반영하고 있습니다: 채팅방의 마지막 메시지 메타데이터 동기화 문제를 수정한다는 주요 변경 사항이 잘 요약되어 있습니다.
Description check ✅ Passed PR 설명은 문제 상황, 목표, 주요 변경 내용, 검증 방법 등을 상세하게 다루고 있으며 변경사항과 직접적으로 관련이 있습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/chat-room-last-message-metadata

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dh2906 dh2906 self-assigned this Apr 21, 2026
@dh2906 dh2906 added the 버그 정상적으로 동작하지 않는 문제 상황 관련 이슈입니다. label Apr 21, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 21, 2026

🧪 JaCoCo Coverage Report (Changed Files)

Summary

  • Overall Coverage: 83.5% ✅
  • Covered Lines: 839 / 1005
  • Changed Files: 3

Coverage by File

Class Coverage Lines Status
ChatRoomRepository
gg.agit.konect.domain.chat.repository
0.0% 0/2
UserService
gg.agit.konect.domain.user.service
64.8% 70/108 ⚠️
ChatService
gg.agit.konect.domain.chat.service
85.9% 769/895

📊 View Workflow Run

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java`:
- Line 23: The `@Modifying` annotation currently uses clearAutomatically=true
which clears the persistence context and detaches the members entity loaded
earlier, so after syncLastMessage() the subsequent
restoreDirectRoomFromIncomingMessage() changes (leftAt, visibleMessageFrom,
lastReadAt) on the detached members entity are not persisted; remove the
clearAutomatically=true (or set it to false) on the `@Modifying` annotation in
ChatRoomRepository (the method annotated there that triggers
sendDirectMessage()/syncLastMessage()/restoreDirectRoomFromIncomingMessage()) so
the persistence context is not cleared and the later mutations to members remain
managed and are flushed at commit, leaving flushAutomatically as-is if you still
need immediate flushing.

In `@src/main/java/gg/agit/konect/domain/chat/service/ChatService.java`:
- Around line 1213-1217: syncLastMessage currently unconditionally writes
message metadata and can be overwritten by concurrent commits; change it to
perform a conditional DB update that only sets chat_room.last_message_* when the
stored latest is older than this message (use message.getCreatedAt() plus
message.getId() for tie-breaker) e.g. add/replace
chatRoomRepository.updateLastMessage(...) with a new repository method
(updateLastMessageIfLatest) that updates only when NOT EXISTS a message with
(createdAt > :sentAt OR (createdAt = :sentAt AND id > :messageId));
alternatively, acquire a write/row lock on the ChatRoom record before calling
room.updateLastMessage(...) and only call room.updateLastMessage(...) after the
DB update succeeds so in-memory state stays consistent with the persisted
conditional update.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 04889cdb-fae7-4f1a-88a6-21077cd77b6b

📥 Commits

Reviewing files that changed from the base of the PR and between 83ff829 and dbb4243.

📒 Files selected for processing (5)
  • src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java
  • src/main/java/gg/agit/konect/domain/chat/service/ChatService.java
  • src/main/java/gg/agit/konect/domain/user/service/UserService.java
  • src/main/resources/db/migration/V70__backfill_chat_room_last_message_metadata.sql
  • src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: coverage
  • GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (3)
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.

  • 코멘트는 반드시 한국어로 작성한다.
  • 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
  • 각 코멘트 첫 줄에 심각도를 [LEVEL: high|medium|low] 형식으로 반드시 표기한다.
  • 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
  • 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
  • 가능하면 재현 조건 및 실패 시나리오도 포함한다.
  • 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
  • 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
  • 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
  • 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
  • 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.

Files:

  • src/main/java/gg/agit/konect/domain/user/service/UserService.java
  • src/main/java/gg/agit/konect/domain/chat/service/ChatService.java
  • src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java
**/*

⚙️ CodeRabbit configuration file

**/*: 공통 리뷰 톤 가이드:

  • 모든 코멘트는 첫 줄에 [LEVEL: ...] 태그를 포함한다.
  • 과장된 표현 없이 사실 기반으로 작성한다.
  • 한 코멘트에는 하나의 이슈만 다룬다.
  • 코드 예시가 필요하면 최소 수정 예시를 제시한다.
  • 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.

Files:

  • src/main/java/gg/agit/konect/domain/user/service/UserService.java
  • src/main/resources/db/migration/V70__backfill_chat_room_last_message_metadata.sql
  • src/test/java/gg/agit/konect/integration/domain/chat/ChatApiTest.java
  • src/main/java/gg/agit/konect/domain/chat/service/ChatService.java
  • src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java
src/main/resources/db/migration/**/*.sql

⚙️ CodeRabbit configuration file

src/main/resources/db/migration/**/*.sql: Flyway 마이그레이션 리뷰 규칙:

  • 버전 파일명 규칙(V{number}__{description}.sql) 위반 여부를 우선 확인한다.
  • 이미 배포된 마이그레이션 수정/재번호 부여 위험이 있으면 반드시 차단 코멘트를 남긴다.
  • 파괴적 변경(drop, rename 등)은 롤백 가능성과 운영 영향 관점에서 검토한다.

Files:

  • src/main/resources/db/migration/V70__backfill_chat_room_last_message_metadata.sql
🧠 Learnings (1)
📚 Learning: 2026-04-13T00:26:23.225Z
Learnt from: dh2906
Repo: BCSDLab/KONECT_BACK_END PR: 533
File: src/main/java/gg/agit/konect/domain/chat/service/ChatService.java:1511-1516
Timestamp: 2026-04-13T00:26:23.225Z
Learning: In ChatService.java (Spring Boot + JPA, MySQL InnoDB), within a `Transactional(readOnly = true)` method, retrying a repository count query (e.g., `countNewerMessagesByChatRoomId`) to handle concurrent inserts is ineffective under REPEATABLE READ isolation: the same DB snapshot is used throughout the transaction, so the retry always returns the same result. A new transaction (`Propagation.REQUIRES_NEW`) would be required for a true retry, but accepting a 1-page offset as a UX tradeoff is preferred for search navigation in this codebase.

Applied to files:

  • src/main/java/gg/agit/konect/domain/chat/service/ChatService.java
  • src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java

Comment thread src/main/java/gg/agit/konect/domain/chat/repository/ChatRoomRepository.java Outdated
Comment thread src/main/java/gg/agit/konect/domain/chat/service/ChatService.java
dh2906 added 2 commits April 21, 2026 14:30
- sendMessage의 명시적 readOnly=false 표기를 기본 @transactional로 단순화해 클래스 레벨 설정과의 관계만 남긴다
- ChatRoomRepository updateLastMessage 시그니처 타입 표기를 정리해 읽는 부담을 줄인다
- UserService의 환영 메시지 경로도 syncLastMessage 헬퍼를 사용하도록 맞춰 채팅 메타데이터 갱신 책임을 일관되게 유지한다
- last_message 메타데이터 갱신 쿼리에서 clearAutomatically를 제거해 direct room 복구 흐름의 엔티티 변경이 detach로 유실되지 않게 한다
- 최신 메시지보다 오래된 트랜잭션이 chat_room 마지막 메시지 요약을 덮어쓰지 못하도록 messageId tie-breaker 조건을 추가한다
- direct room 재노출과 조건부 메타데이터 갱신을 테스트로 고정해 리뷰에서 지적된 회귀를 막는다
- 마지막 메시지 갱신 조건에서 현재 저장한 메시지 자신은 비교 대상에서 제외해 DB timestamp 정밀도 차이로 인한 오판을 막는다
- direct 채팅방 목록과 재입장 시나리오가 chat_room.last_message_* 컬럼에 의존하므로 간헐적으로 이전 메시지가 남는 상태를 방지한다
- CI에서 흔들리던 채팅방 나가기/마지막 메시지 메타데이터 테스트와 jacoco 경로를 로컬에서 다시 검증했다
@dh2906 dh2906 merged commit 64df724 into develop Apr 21, 2026
5 checks passed
@dh2906 dh2906 deleted the fix/chat-room-last-message-metadata branch April 21, 2026 06:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

버그 정상적으로 동작하지 않는 문제 상황 관련 이슈입니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant