자취생 커뮤니티 개발을 진행하며 1:1 채팅을 지원해야 하는 요구사항이 있었다. 채팅과 같은 실시간 데이터 송/수신이 필요한 기능은 데이터베이스를 어떻게 구축해야할지 몰라 고민도 많이 하고 검색도 많이 해봤다. 검색결과 많은 사람들이 채팅과 같은 기능은 채팅방 정보 채팅방 이름, 참여자들 정도만 RDBMS에 저장하고 메시지와 같은 데이터는 모두 redis나 mongodb와 같은 nosql을 사용하라 했다. 이유는 RDBMS에 경우 실시간 데이터 저장에 있어 성능이 좀 떨어진다는 말이 많았다.
하지만 RDBMS 설계도 완벽하지 않은 내가 Nosql 설계를 하려니 데이터를 어떻게 저장해야될지 감이 잘 잡히지 않았다. 많은 고민끝에 아직 RDBMS로 채팅을 구현했을 때 성능의 저하를 경험해보지 못 했기 때문에 일단 기존의 RDBMS를 이용해 구현을 하고 테스트 등을 통해 부하를 줘 실제로 성능이 많이 떨어진다고 느껴지면 Nosql로 변경하자라고 결심했다. RDBMS의 성능이슈를 직접 경험을 해봐야 왜 Nosql을 사용하는지 정확히 깨달을 수 있을거 같아 결심한 이유도 있었다.
RDBMS를 이용해 채팅을 구현하기로 결심했지만 막상 테이블을 설계하려니 생각보다 쉽게 설계되지 않았다. 검색을 통해 여러 사람들이 RDBMS를 이용해 어떻게 채팅을 구축했는지 보았지만 채팅방, 참여자, 채팅, 사용자 와 같은 테이블들이 얽혀있어 이해가 쉽지 않았다. 또한 다들 많은 사람들이 참여하는 채팅방을 기준으로 DB를 설계한 터라 나의 프로젝트와는 잘 맞지 않았다.
난 천천히 나의 프로젝트의 요구사항을 보며 어떤 데이터들이 필요할지 생각해보았다. 나의 프로젝트는 1:1 채팅만 지원하면 되므로 메시지, 읽음여부, 발신자, 수신자, 발신날짜 등이 주요 데이터였다. 처음엔 앞의 데이터를 모두 한 테이블에 넣으면 발신자와 수신자의 중복이 너무 많을 것 같아 채팅방 테이블을 만들어 발신자와 수신자를 저장하고 채팅방을 구분하기 위해 발신자와 수신자의 이름을 조합하여 구분했다. 하지만 테스트를 하다보니 채팅내역을 조회할 때 발신자와 수신자와 제대로 구분되지 않고 한 사람이 메시지를 보낸것 처럼 조회됐다.
채팅 저장 로직의 개선이 필요했고 DB 설계가 이게 맞는걸까 라는 고민을 다시하기 시작했다. 채팅방 테이블에 발신자와 수신자를 저장하니 1:1 채팅을 진행한다 해도 내가 발신자이고 상대방이 수신자인 데이터와 내가 수신자이고 상대방이 발신자인 데이터 총 2개의 데이터가 저장돼야 했다. 채팅 메시지와 같은 데이터를 저장해도 앞의 2개의 데이터 모두를 이용해 join해 데이터를 조회해야 했다. 이게 정말 좋은 방법인가?에 대해 생각하며 발신자와 수신자를 채팅방이 아닌 채팅 테이블에 저장해보았다. 발신자와 수신자가 중복돼며 저장된다는 단점은 있지만 채팅 내역을 조회할 때도 발신자와 수신자의 PK만 이용해 조회할 수 있었으며 채팅방이라는 테이블은 굳이 필요하지 않아 보였다.
채팅방 테이블을 지우려했지만 난 Spring Stomp를 이용해 채팅을 구현했는데 1:1 채팅을 지원하기 위해선 나와 상대방을 묶어서 구분할 수 있는 값이 필요했다. 이를 위해 채팅방 테이블을 만들어 채팅 테이블고 1 : N 관계를 만들었고 향후 발신자, 수신자 만을 이용해 채팅방을 조회할 수 있도록 발신자와 수신자의 PK를 정렬해 '_'로 구분하여 저장했다. 아래의 코드는 JPA를 이용해 구성한 채팅, 채팅방 엔티티이다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ChatRoom extends BaseEntity {
@Id @GeneratedValue
@Column(name = "chat_room_id")
private Long id;
private String senderAndReceiver;
@Builder
public ChatRoom(String senderAndReceiver) {
this.senderAndReceiver = senderAndReceiver;
}
}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Chat extends BaseEntity {
@Id
@GeneratedValue
@Column(name = "chat_id")
private Long id;
private String message; // 메시지
private Boolean isRead;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "sender_id")
private Member sender; // 발신자
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "receiver_id")
private Member receiver; // 수신자
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "chat_room_id")
private ChatRoom chatRoom;
@Builder
public Chat(String message, Boolean isRead, Member sender, Member receiver, ChatRoom chatRoom) {
this.message = message;
this.isRead = isRead;
this.sender = sender;
this.receiver = receiver;
this.chatRoom = chatRoom;
}
public void updateIsReadTrue() {
this.isRead = true;
}
}
채팅 테이블에 메시지, 읽음, 발신자, 수신자 등을 저장하고 채팅방과 1 : N 관계를 만들었다. 채팅방의 senderAndReceiver 는 발신자와 수신자의 PK가 각각 1, 2일 때 "1_2" 로 낮은순으로 정렬해 저장했다. 만약 발신자와 수신자의 PK가 각각 2, 1이여도 "1_2"로 저장돼 향후 채팅방을 조회할 때 사용할 수 있도록 구현했다.
@MessageMapping("/private-message")
public Message receivePrivateMessage(@Payload Message message, @Header(name = "Authorization") String header){
jwtTokenVerifier.verifyToken(header);
Member loginMember = ((MemberContext) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getMember();
message.setSenderNickname(loginMember.getNickname());
message.setCreatedDate(LocalDateTime.now());
simpMessagingTemplate.convertAndSendToUser(message.getChatRoomId().toString(),"/private",message);
Long savedChat = chatService.saveChat(message.getMessage(), loginMember.getId(), message.getReceiverId(), message.getChatRoomId());
emitterService.notify(message.getReceiverId(), CHAT_MESSAGE);
alarmService.saveAlarm(CHAT_MESSAGE, false, AlarmType.TALK, message.getReceiverId());
return message;
}
해당 코드는 Spring Stomp를 이용해 메시지를 보내는 코드인데
simpMessagingTemplate.convertAndSendToUser(message.getChatRoomId().toString(),"/private",message);
해당 구문에서 발신자와 수신자를 묶어서 구분해줄 값으로 채팅방의 PK를 이용하기 위해 채팅방 테이블을 만들었다.
유튜브나 구글링을 통해 채팅 구현에 대해 검색하면 다들 쉽게 구현할 수 있다고 소개하지만 직접 데이터베이스를 구축하며 실시간으로 데이터를 저장해야한다고 생각하니 쉽지 않았다..
'Project > DB' 카테고리의 다른 글
| Redis / RedisTemplate 실험 (0) | 2024.01.20 |
|---|---|
| [Independe] 추천, 신고, 즐겨찾기 DB 설계에 대한 고민 (0) | 2023.09.15 |
| [Independe] 대댓글 설계에 대한 고민 (1) | 2023.09.14 |