프롤로그에서 썼듯이, 나는 커뮤니티를 만들고 싶어서 이 프로젝트를 시작했고,
그 커뮤니티를 위한 유입 도구로 코인 모의투자를 붙였다.
그래서 “유저가 만든 데이터”가 쌓이고,
그걸 서로 보는 재미가 있는 구조가 필요했다.
그 결과물이 지금 투자킹에 들어간
피드 / 댓글 / 좋아요 / 팔로우 / 랭킹 / 코인 채팅방 같은 소셜 기능이다.


피드(Feed)
피드는 다음 기준으로 단순하게 가져갔다.
- 제목 + 본문
- 짧은 글 위주 (길이는 제한)
- 이미지 업로드는 없음
- 정렬은 최신순 / 인기순(Hot) 두 개만
그리고 티어, 레벨을 표시해주면서 자신의 자산과 활동량을 사람들한테 보여줄 수 있도록 했다.
인기순 정렬 – Reddit hot 점수 간이 버전
투자킹의 인기순 정렬은 Reddit hot 알고리즘을 단순화한 느낌이다.
대략 이런 개념이다:
- 기본 점수(score) = (좋아요 수 × 가중치) – (싫어요 수 × 가중치) + 댓글 수
- 시간 보정 = “얼마나 오래됐는지”를 초 단위로 나눈 값
- 최종 hot 점수 = log( |score| + 1 ) + 시간 보정
실제 구현은 훨씬 더 단순한 형태지만,
중요하게 본 포인트는 딱 두 가지였다.
- 좋아요/싫어요/댓글이 영향력을 가진다
- 시간이 지나면 자연스럽게 내려간다
즉, 예전 글이 무조건 상단에 고정되지 않고,
새 글이 일정 수준의 반응만 받아도 상단에 올라올 수 있게 했다.
무한 스크롤 – Offset 대신 Cursor
피드는 무한 스크롤 구조로 만들었다.
여기서 고민한 건 페이지네이션 방식이다.
- Offset 기반: LIMIT 20 OFFSET 40
- Cursor 기반: “마지막 글의 created_at + id를 기준으로 그 이후만 가져오기”
피드처럼 계속 글이 추가되는 구조에서 Offset은 문제가 많다.
- 새 글이 중간에 끼어들면
- 1페이지, 2페이지, 3페이지 사이에 중복/빠짐이 생김
- 유저 입장에서는 “아까 봤던 글이 또 보이거나, 아까 본 글이 사라지는” 느낌이 든다
그래서 투자킹에서는 Cursor 기반 페이지네이션을 썼다.
- 프론트에서는 React Query의 useInfiniteQuery 사용
- 백엔드에서는 “마지막 글의 (정렬 기준 key, id)”를 커서로 넘김
- DB 쿼리는 WHERE (created_at, id) < (마지막 커서) 형태로 처리
이렇게 하면:
- 새 글이 중간에 끼어도
- 이미 내려받은 페이지의 내용은 안 흔들리고
- 뒤로 스크롤할수록 안정적인 타임라인을 유지할 수 있다
댓글 / 대댓글 – 깊이는 최소한, 대화는 유지
댓글은 피드를 살리는 기본 기능이라 반드시 넣었다.
설계 기준은 이 정도였다.
- 댓글 최대 500자
- 대댓글은 1단계까지만 (댓글 → 대댓글까지만, 그 이상 중첩 없음)
- 좋아요/싫어요 가능
- 정렬은 최신순
무한 중첩 댓글은 기술적으로 구현이 어렵진 않다.
하지만 실제로 운영해보면:
- UI가 지저분해지고
- 어디까지가 누구에게 하는 말인지 헷갈리고
- 악성 답글이 꼬리를 물기 쉽다
그래서 딱 한 단계까지만 열어두고 막았다.
팔로우 / 팔로워 – 나중에는 개인화의 베이스가 되는 기능
팔로우 기능은 초반에는 크게 체감이 안 되는 기능일 수 있다.
하지만 유저가 쌓이면, 이쪽이 훨씬 중요한 축이 된다.
팔로우 구조는 단순하다.
- 팔로우 / 언팔로우
- 팔로워 / 팔로잉 목록
기술적으로는 별 건 아니다.
그냥 many-to-many 관계 하나 만들고, 인덱스 잘 잡고,
조회 쿼리를 적당히 튜닝하는 정도다.
랭킹(리더보드)
랭킹기능은 이 사이트에 필수적이라고 생각했다.
기본적으로 티어 순서대로 랭킹을 산정하고, 티어가 같다면 레벨을 기준으로 정렬하도록 했다.
실시간 총 자산 기준으로 하지 않은 이유는 실시간 총 자산은 db에 저장되는 데이터가 아니며 실시간으로 변하고 계산하는데 큰 리소스가 필요한 작업이기 때문이다.
기술적으로는:
- 랭킹 데이터를 Redis Sorted Set에 올려두고
- 주기적으로 DB 기준 값과 동기화한다
이렇게 하면:
- 조회는 Redis에서 빠르게 하고
- 영속 데이터는 DB에 맡기는 구조가 된다
코인별 실시간 채팅 – WebSocket + Redis Pub/Sub
소셜 기능 중에서 가장 “실시간”을 많이 타는 부분이 코인별 채팅방이다.
- BTC 방, ETH 방, SOL 방…
- 코인별로 룸을 만들고
- 갑자기 가격이 튀는 순간 채팅이 한 번에 몰린다
여기서는 WebSocket을 선택했다. 이유는 명확하다.
- 채팅은 양방향 통신이 기본이다
- SSE는 서버 → 클라이언트 단방향이라 채팅에는 안 맞는다
- 폴링은 트래픽/지연/리소스 측면에서 비효율적이다
구조는 단순하게 이렇게 잡았다.
- 클라이언트 ↔ FastAPI WebSocket 서버
- WebSocket 서버 ↔ Redis Pub/Sub
메시지 흐름은 이런 식이다.
- 클라이언트가 BTC 채팅방에 입장
- 서버에서 Redis의 btc_chat 채널에 구독
- 누군가 메시지를 보내면
- 서버 → Redis PUBLISH
- 같은 코인 방에 붙어 있는 다른 워커/서버들도 메시지 수신
- 최종적으로 같은 코인 방에 연결된 모든 WebSocket 세션에 브로드캐스트
로그는 Redis Sorted Set에 최근 N개만 쌓는다.
- 너무 오래된 채팅까지 다 들고 있을 필요는 없고
- 들어왔을 때 최근 흐름만 빠르게 보여주면 충분하다고 봤다

'모의투자 개발기' 카테고리의 다른 글
| 1인 개발 모의투자 커뮤니티 "투자킹" 소개 (1) | 2025.11.25 |
|---|---|
| 모의투자 커뮤니티 1인 개발기 (5) – 무중단 배포 구현하기 (0) | 2025.11.25 |
| 모의투자 커뮤니티 1인 개발기 (3) – 게임적 요소 한스푼 (0) | 2025.11.25 |
| 모의투자 커뮤니티 1인 개발기 (2) – 실시간 가격 업데이트 구현 (0) | 2025.11.25 |
| 모의투자 커뮤니티 1인 개발기 (1) – 서비스 구조 설계 (0) | 2025.11.25 |