본문 바로가기메뉴 바로가기


실시간 댓글 개발기(part.1) – DAU 60만 Alex 댓글의 실시간 댓글을 위한 이벤트 기반 아키텍처

안녕하세요! 콘텐츠플랫폼개발팀 briony입니다.
제가 속한 팀 내 인터랙션플랫폼셀에서는 작년 한 해동안 ‘실시간 댓글’이라는 신규 기능을 위해 조직 구성원 모두 그 누구보다도 뜨거운 시간을 보냈는데요. 이 자리를 통해 기존 시스템에 독립적인 아키텍처를 설계하고, 서비스 적용을 위해 성능을 테스트하고 튜닝한 경험을 공유하려 합니다.
오늘은 프로젝트에 대한 소개와 아키텍처를 설계하고 기술을 결정한 내용을 소개하겠습니다.

Alex 댓글은 카카오의 서비스들이 댓글 기능을 좀 더 편하고 안정성 있게 사용자에게 제공할 수 있도록 지원하는 플랫폼입니다. Alex 댓글 플랫폼은 댓글과 이를 위한 정책 관리, 트래픽 대응 및 모니터링을 담당하면서 각 서비스가 서비스 개발에 집중할 수 있는 환경을 제공하고 있는데요, 2015년 다음 뉴스를 시작으로 1boon, #탭, 카카오 TV 등 카카오의 다양한 서비스에서 Alex 댓글을 사용하고 있습니다.

댓글을 역동적으로 만들어보자!

이러한 Alex 댓글에 변화가 필요했습니다! 단순히 댓글을 읽고 쓰는 것 이외의 사용자 인터랙션을 좀 더 끌어내고 싶었습니다. 또한 지속적으로 댓글 UI의 새로고침 버튼을 누르는 사용자 지표를 통해 새로운 댓글을 확인하려는 니즈가 있음을 유추할 수 있었는데요.

사용자의 니즈에 맞춘 Alex 댓글의 새로운 변화를 위해 ‘실시간 댓글’ 프로젝트를 시작하게 되었고, 사용자가 보고 있는 화면에 새로 작성된 댓글을 보여주고 계속해서 변경되는 찬성/반대 수를 갱신해주는 기능을 추가하는 것으로 프로젝트의 첫 발을 내딛게 되었습니다!

신규 기능을 추가하면서 가장 중요하게 생각한 점은 새로운 기능으로 인해 댓글 읽기, 쓰기, 찬성/반대 시도와 같은 댓글 플랫폼이 제공하는 기본적인 기능에 문제가 생기면 절대 안된다는 것 이였습니다. 또한 매일 약 60만 유저가 만들어내는 트래픽이 발생하며, 속보나 이벤트가 발생하는 경우 그 트래픽이 순식간에 몇 배 이상으로 치솟기 때문에 이러한 트래픽을 감당할 수 있어야 한다는 점도 중요했습니다. 따라서 기존의 댓글 시스템과는 독립적으로 동작하는 아키텍처를 설계하게 되었습니다.

실시간 댓글 아키텍처 고민하기

댓글 작성, 찬성, 반대와 같은 사용자의 액션을 하나의 메시지로 생각하고, 실시간으로 이 메시지를 보여주기 위해 필요한 역할을 나눠보면 다음과 같습니다.

  • 새로운 메시지를 사용자에게 보여주고, 사용자가 메시지를 보낼 수 있는 UI를 제공한다.
  • 사용자의 메시지를 받고, 새로운 메시지를 연결된 사용자에게 전달한다.
  • 다양한 메시지를 통일된 포맷으로 가공한다.
  • 필요하다면 메시지를 저장한다.
  • 하나의 서버가 받은 메시지를 다른 서버로 브로드캐스팅한다.

이러한 역할을 모듈화하여 도식으로 그려보면 아래와 같은 메시지의 흐름이 나타나게 됩니다.

물론 하나의 모듈이 여러 역할을 할 수도 있지만, 가능한 최소한의 역할을 하는 것이 안정적이고 트러블 슈팅에서도 그 원인을 더 쉽게 찾을 수 있을 것이라 예상하였습니다. 위와 같이 역할에 따른 모듈을 정하고 난 후 모듈에 적합한 기술을 선택하였습니다.

아키텍처에 기술 입히기

웹에서 실시간 이벤트를 처리해야 할 때 폴링(Polling) 방식과 웹소켓(Websocket) 방식을 고민하게 됩니다. 폴링은 브라우저가 일정한 주기로 서버에 요청을 보내 새로운 데이터가 있으면 UI를 갱신하는 단순한 방식으로 HTTP 프로토콜을 그대로 사용한다는 장점이 있지만 실시간성을 제대로 보장하지 못한다는 점과 불필요한 오버헤드가 많다는 단점이 있었습니다. 반면 웹소켓은 클라이언트-서버 간 커넥션을 유지하기 때문에 이벤트를 주고받을 때 실시간성을 보장할 수 있습니다. 하지만 대용량 트래픽에서는 서버가 클라이언트 수만큼의 연결을 유지해야 한다는 부담이 있습니다.

Alex 댓글은 하루 사용자가 60만이 넘고 API 서버가 받는 요청이 초당 수천 건이 넘는 서비스입니다. 웹소켓을 선택한다면 안정성에 대한 걱정을 가지고 가야 했습니다. 저희가 당장의 요구사항인 기본 액션에 대한 실시간 표현만 생각했다면 프로토콜을 간단히 구상하여 폴링을 사용했을 겁니다. 하지만 추후에 다른 사용자 액션이 추가되어 다양한 UI를 도입할 것을 예상했을 때, 더 확장성 있는 기술을 선택하는 것이 맞아 보였습니다. 그래서 저희는 웹소켓 챌린지를 시작하기로 했습니다. 사실 저희 조직에서는 댓글 외에도 Alex 채팅이라는 플랫폼을 함께 관리했기에 조직 내에서 웹소켓 기술에 좀 더 익숙한 것도 결정에 한 몫을 하였습니다.

메시지 브로커는 카프카로 결정했습니다. 현재 댓글 시스템에서 댓글 작성, 찬성, 반대에 대한 이벤트를 카프카에 쌓고 있었기에 이후에 실시간 이벤트에 새로 추가될 사용자 액션도 카프카에 쌓는 것이 이후 메시지를 가공하는 프로세서의 동작에 통일성을 줄 수 있을 것이라 예상하였습니다.

메시지를 가공하는 프로세서는 Apache NiFi를 사용하였습니다. NiFi는 분산 데이터 처리를 위한 오픈소스로 데이터 플로우를 직관적으로 설계할 수 있고, 대용량 데이터 처리에 적합하다는 장점을 가지고 있습니다. 또한 웹 UI를 통해 실시간으로 플로우 제어가 가능하며 현재 처리되고 있는 메시지를 직접 확인할 수도 있어서 모니터링에도 유용할 것으로 판단하였습니다. (다만 커스터마이징에 제약이 있고 유지 보수의 어려움을 느껴 현재는 프로세싱을 하는 어플리케이션을 따로 띄워 사용하고 있습니다.)

Storage와 브로드 캐스팅을 위한 메시지 브로커로는 Redis를 사용하였습니다. Redis를 사용해 기존 DB에 저장되지 않는 새로운 값들을 임시로 저장할 수 있고, key event listener를 통해 저장된 값이 갱신되었을 때 이를 알 수 있습니다. 또한 pub/sub 기능을 사용하여 메시지 브로커로도 사용할 수 있기에 가공이 완료된 메시지를 각 서버로 전송하는 용도로 활용할 수 있었습니다.

완성된 아키텍처에서의 메시지 흐름

댓글 웹 클라이언트는 댓글 UI를 로딩하고 웹소켓 서버와 연결을 맺은 후 해당 페이지의 새로운 댓글 이벤트에 대한 구독을 합니다. 웹소켓 서버는 클라이언트와 연결을 맺은 후 페이지에 대한 레디스 채널을 구독합니다. 사용자의 댓글 작성, 찬성, 반대 액션은 기존의 댓글 시스템을 통해 수행되고, 기존 댓글 시스템은 이 이벤트를 카프카에 쌓습니다. 메시지 프로세서는 이 카프카를 구독하고 있다가 새로운 이벤트가 발생 했을 때, 메시지를 가공하고 메시지에 맞는 레디스 채널로 발행합니다. 웹소켓 서버는 이 발행한 메시지를 다시 웹 클라이언트로 전달하고 웹 클라이언트는 사용자에게 변경된 내용을 보여줍니다.

이러한 일련의 과정을 통해 사용자는 별도의 조작 없이 현재 보고 있는 댓글의 변경사항을 아래처럼 실시간으로 확인할 수 있게 되었습니다.


지금까지 Alex 댓글 플랫폼에 실시간 기능을 적용하기 위한 아키텍처를 설계하고 기술을 선정하는 과정을 소개드렸습니다. 이벤트 기반의 아키텍처는 기존 모듈과의 의존도를 낮추면서 대용량 트래픽이 예상되는 새로운 기능을 적용하는데 적합하였습니다. 모든 내용은 설계한 대로 쉽게 진행할 수 있었지만, 웹소켓의 적용은 기술이 지닌 특수한 성격 때문에 테스트와 성능개선을 위해 많은 시행착오를 겪어야 했습니다.

다음에는 오늘 공유드린 아키텍처를 구현한 어플리케이션의 트래픽 테스트를 위해 웹소켓을 테스트하고 이를 토대로 성능을 개선한 내용을 공유 드리겠습니다. 감사합니다!


인터랙션플랫폼셀에서는 더 좋은 서비스를 위해 고민을 하고 함께 문제를 해결해 나갈 분을 찾고 있습니다. 아래 모집 공고를 참고하여 많은 관심과 지원 부탁드립니다. : )
“인터랙션플랫폼(댓글, 투표, 공감)웹 서비스 개발자 모집” 바로가기!

briony.jo
briony.jo 다양한 사람들과 즐겁게 일하고 함께 성장하는 것을 꿈꾸는 개발자입니다. 대용량 트래픽에도 끄떡없는 견고한 서버 프로그래밍과 클린 코드에 관심이 많습니다.
Top Tag
adaptive-hash-index
adt
ai
Algorithm/ML
Algorithm/Ranking
almighty-data-transmitter
angular
anycast
applicative
Architecture
arena
async
aurora
Backend
bgp
blind-recruitment
block
brian
cahtbot
cd
ceph
cgroup
ci
cite
clojure
close-wait
cloud
cloudera-manager
clustered-block
cmux
cnn
code-festival
code-review
coding
competition
component
conference
consul
container
contest
couchbase
COVID-19
cpp
Data
DB
deep-learning
developer
developers
devops
dns
docker
eslint
Featured
friendstime
front-end
functional-programming
funfunday
fzf
garbage-collection
gawibawibo
GC
github
go
graphdb
graphql
growth
ha
hadoop
hbase
hbase-manager
hbase-region-inspector
hbase-snashot
hbase-table-stat
hbase-tools
hri
ifkakao
infrastructure
innodb
internship
Java
javascript
json
kafka
kakao
kakao-commerce
kakao-games
kakaoarena
kakaok
kakaokrew
kakaomap
KCDC
khaiii
kubernetes
l3dsr
l4
links
load-balancing
machine-learning
marathon
meetup
melon
mesos
Messaging
microservice
mobil
monad
mtre
mysql
mysql-realtime-traffic-emulator
nand-flash
network
new
new-krew
nomad
ocp
opensource
openstack
page
parallel
PBA
programming-contest
pycon
python
quagga
reactive-programming
reactor
recommendation
recruitment
redis
redis-keys
redis-scan
rest
rubics
ruby
rxjs
s2graph
scala
scalaz
server
sharding
shopping
socket
spark
spark-streaming
SpringBoot
ssd
Statistics/Analysis
Stomp
storage
storm
style-guide
support
System
talk
talkchannel
tcp
tech
test
Thread-Debugging
time-wait
tmux
update
vim
vim-github-dashboard
vim-plugin
vue
web-cache
webapp
WebSocket
weekly
All Tag
adaptive-hash-index
adt
ai
Algorithm/ML
Algorithm/Ranking
almighty-data-transmitter
angular
anycast
applicative
Architecture
arena
async
aurora
Backend
bgp
blind-recruitment
block
brian
cahtbot
cd
ceph
cgroup
ci
cite
clojure
close-wait
cloud
cloudera-manager
clustered-block
cmux
cnn
code-festival
code-review
coding
competition
component
conference
consul
container
contest
couchbase
COVID-19
cpp
Data
DB
deep-learning
developer
developers
devops
dns
docker
eslint
Featured
friendstime
front-end
functional-programming
funfunday
fzf
garbage-collection
gawibawibo
GC
github
go
graphdb
graphql
growth
ha
hadoop
hbase
hbase-manager
hbase-region-inspector
hbase-snashot
hbase-table-stat
hbase-tools
hri
ifkakao
infrastructure
innodb
internship
Java
javascript
json
kafka
kakao
kakao-commerce
kakao-games
kakaoarena
kakaok
kakaokrew
kakaomap
KCDC
khaiii
kubernetes
l3dsr
l4
links
load-balancing
machine-learning
marathon
meetup
melon
mesos
Messaging
microservice
mobil
monad
mtre
mysql
mysql-realtime-traffic-emulator
nand-flash
network
new
new-krew
nomad
ocp
opensource
openstack
page
parallel
PBA
programming-contest
pycon
python
quagga
reactive-programming
reactor
recommendation
recruitment
redis
redis-keys
redis-scan
rest
rubics
ruby
rxjs
s2graph
scala
scalaz
server
sharding
shopping
socket
spark
spark-streaming
SpringBoot
ssd
Statistics/Analysis
Stomp
storage
storm
style-guide
support
System
talk
talkchannel
tcp
tech
test
Thread-Debugging
time-wait
tmux
update
vim
vim-github-dashboard
vim-plugin
vue
web-cache
webapp
WebSocket
weekly

위로