: 대규모 핀테크 컨퍼런스에서 발생하는 비효율적인 운영 및 참가자의 불편을 해결하는 모바일 웹 기반 사일런트 컨퍼런스 솔루션
참가자가 온·오프라인에서 능동적으로 참여할 수 있도록 지원하며, 컨퍼런스 운영의 효율성과 만족도를 높이는 올인원 솔루션을 제공합니다.
혼잡한 강연장, 강연 선택의 어려움, 비효율적 질의응답, 세션 간 이동 어려움, 높은 운영 비용 등 기존 컨퍼런스의 문제점을 개선하고자 기존 스피커 시스템 대신 무선 헤드셋을 활용하여 한 공간에서 여러 개의 세션을 동시에 진행할 수 있는 혁신적이 포럼 방식의 사일런트 컨퍼런스를 선택하였습니다. 또한 시장의 성장 가능성을 고려하여 핀테크 컨퍼런스를 선정하였습니다.
시작일: 2025년 2월 26일
종료일: 2025년 4월 2일
김지민(본인)
시스템 아키텍처
플로우 차트
// 기존 방식
// 메시지 1000개 유지
if (redisTemplate.opsForList().size(CHAT_LIST_KEY) > MAX_CHAT_SIZE) {
➡️ (CHAT_LIST_KEY + sessionId)로 처리하도록 변경 피드백 받음
private static final String CHAT_MESSAGE_KEY_PREFIX = "chat-messages:"; // 메시지 저장용
private static final int MAX_CHAT_SIZE = 1000;
String chatKey = CHAT_MESSAGE_KEY_PREFIX + message.getSessionId();
chatKey에 세션아이디를 포함시키고
redisTemplate.opsForList().trim(chatKey, -MAX_CHAT_SIZE, -1);
리스트 크기 1000개로 유지
// 기존 방식
List<Object> rawMessages = chatRepository.getRecentMessages(String.valueOf(sessionId));
Redis에서 전체 메시지 조회 후 QUESTION 카테고리만 필터링 하여 좋아요 수 기준으로 정렬 후, Pageable 기준으로 자바에서 페이징하는 방식으로 구현
⬇️
그러나, 페이징 처리를 통해 일정 부분씩 끊어서 조회하여 데이터로딩에 대한 부하를 줄이는 장점이 있는데, 레디스에서 전체 조회 후 자바에서 페이징처리해도 부하를 줄일 수 있을지에 대한 의문 발생
⬇️
Redis List를 사용하고 있어서 전체 조회 후 페이징 처리를 하는 방법을 선택했었는데, 메시지 개수가 많아지면 부하를 줄이기 어려울 것 같다고 생각
⬇️
찾아본 결과 List는 전체를 가져와서 잘라야 하고,
ZSet은 정렬된 상태에서 원하는 범위만 잘라서 가져올 수 있다고 함.
레디스는 읽기 속도가 빨라서 1000개정도까진 무리없어서 전체조회 후 자바에서 페이징처리해도 괜찮을 것 같다는 의견도 있지만, 실시간 좋아요 수에 따른 실시간 질문 정렬이 가능하도록 하기 위해서 ZSet으로 리팩토링 하는 방향으로 결정하여 해결했음
"우리가 유저 정보를 조회할 때 사용하는 두 API:
/api/v1/users/account
/api/v1/users/profile
이 API들의 응답(Response)에는 userId가 안 들어있는데,
그럼 우리가 ChatMessage를 생성할 때 필요한 userId는 어디서 가져와야 해?"
지금 프론트엔드는 서버에 메시지를 보낼 때 userId를 포함해야 하는 상황인데,
현재 제공된 API 중 어디에도 userId가 포함돼 있지 않은 상황
1. DTO에 userId를 추가
2. ChatMessage에서 userId 대신 email 사용
➡️ 두가지 방법을 논의했으나, Spring Security가 로그인한 유저의 정보를 담아주는 객체 Principal을 사용하는 것으로 결정
💡 프론트는 단순하게 요청만 보내면 되고, 백엔드는 Principal로 유저 식별 가능
String messageKey = "chat-messages:" + message.getMessageId();
채팅 메시지 저장 시, 메시지 아이디를 키 값으로 저장하여 메시지를 제대로 불러올 수 있도록 하여 에러 해결
Text '2025-03-29T22:16:19.342572147' could not be parsed, unparsed text found at index 19
"2025-03-29T22:16:19.342572147"는 나노초 단위까지 포함된 ISO-8601 포맷,
근데 기본적으로 Jackson + JavaTimeModule 조합은 나노초까지는 처리 못 함. 최대 마이크로초 (6자리)까지만 지원
즉, Jackson이 timestamp를 LocalDateTime으로 파싱할 때 ".342572147"에서 뒷자리까지 파싱 못 해서 실패
// 💡 LocalDateTime 직렬화 시 나노초 제거 (기존 오류 해결용)
objectMapper.configOverride(LocalDateTime.class)
.setFormat(JsonFormat.Value.forPattern("yyyy-MM-dd'T'HH:mm:ss"));
➡️ 나노초를 제거하여 에러 해결
기능 구현 결과
처음으로 PM, PD, 프론트엔드 개발자와 협업하며 프로젝트를 진행했습니다. 이전에는 백엔드 개발자끼리만 팀을 이뤄 작업했기 때문에 기술적인 부분에 대해 깊이 설명하지 않아도 소통이 원활했지만, 이번에는 다른 분야의 팀원들과 함께하다 보니 PM과 PD에게 기술적인 내용을 쉽게 풀어 설명하는 데 어려움을 느꼈고, 프론트엔드 개발자와의 협업 방식도 새롭게 배울 수 있는 계기가 되었습니다.
특히 프로젝트 막바지에는 소통이 원활히 이루어지지 않아 시간 부족으로 인해 완성도가 다소 아쉬웠던 점도 있었지만, 이러한 경험을 통해 개발 일정 공유와 커뮤니케이션의 중요성을 깊이 깨닫게 되었습니다. 또한, 그동안 백엔드 입장에서만 작성하던 API 명세서를 프론트엔드 개발자에게도 도움이 될 수 있도록 구성하는 법을 고민해보며 협업 역량을 키울 수 있었습니다.
이번 프로젝트에서는 처음으로 채팅 기능을 직접 설계하고 구현해보았는데, WebSocket, STOMP, Redis Pub/Sub 등 익숙하지 않던 기술들을 다뤄보며 부족한 점이 많았음을 느꼈습니다. 특히, 혼자 개발할 때는 잘 느끼지 못했던 로그의 중요성과 에러 추적의 필요성을 프론트와의 협업 과정에서 실감할 수 있었습니다. 이번 경험을 바탕으로, 다음에는 보다 구조적이고 안정적인 채팅 시스템을 구현할 수 있을 것이라는 자신감도 생겼습니다.
이번 사일런트 컨퍼런스 프로젝트는 단순한 기능 구현을 넘어 실제 사용자 환경을 고려한 문제 해결 중심의 개발 경험이었습니다. 핀테크라는 주제를 통해 실시간 데이터 처리, 사용자 참여 유도, 시스템 아키텍처 구성 등 현업과 유사한 문제 상황을 경험해볼 수 있었고, 그 과정에서 기술적 역량뿐 아니라 팀원과의 원활한 커뮤니케이션 역량 역시 중요하다는 것을 몸소 느꼈습니다.
특히 실시간 채팅 기능을 처음부터 끝까지 직접 설계하고 구현하면서, Redis, WebSocket, STOMP, ZSet 등 새로운 기술 스택을 익히고 적용해볼 수 있었으며, 실시간성과 효율성, 확장성을 동시에 고려한 백엔드 개발이 얼마나 중요한지를 깨닫는 계기가 되었습니다.
이 프로젝트는 단발성으로 끝나는 서비스가 아닌, 실제 현장에 적용 가능하고 확장 가능한 플랫폼으로의 발전 가능성을 지니고 있습니다. 향후 IoT 기반 출입 체크, AI 기반 Q&A 정리 및 추천 시스템 등과 연계된다면, 단순한 행사 보조 시스템을 넘어 미래형 스마트 컨퍼런스 플랫폼으로 성장할 수 있을 것이라 기대됩니다.
이번 경험을 통해 얻은 지식과 교훈을 바탕으로, 다음 프로젝트에서는 더욱 완성도 높은 시스템을 설계하고 구현할 수 있도록 노력하겠습니다.