Node.js + Websocket 프로젝트 삽질기2016. 03. 11 / 파프리카
| 회사소개
파프리카 . 2013 년 3 월 15 일 시작된 반도의 _ 흔한 _ 웹 _ 개발 _ 회사입니다 .웹서비스 개발을 중심으로 , 모바일 , HTML5 컨텐츠 제작 등을 하고 있습니다 .
SBSCNBC 인터렉티브 주식 정보 웹사이트 및 앱 개발 ,LG 전자 R&D 센터 차량용 반응형 하이브리드앱 개발 ,LG 전자 R&D 센터 프리즘 서비스 웹프론트 개발 ,아동용 HTML5 인터렉티브 컨텐츠 개발 ,각종 중 / 소규모 웹사이트 개발 등
틈새 공지투자자 : 모집중사장여친 : ( 급구 )00 명 상시모집중
슈퍼 . 다이나믹 . 인터렉티브 . 모던 .웹 . 프로젝트( 고객의 눈높이 )
Super dynamic..what?
어쨌든 , 개발은 계속되어야 한다 .쭈우우우우우우우우우우우우욱 ~
| 초기 아키텍트 : SIMPLE IS THE BEST
Goal 12/31
AWS EC2Browse
r
Socket.io
Node.js
NginX
OracleSocket.io
HTML/CSS/JS/Media FTP
| 프론트 설계 : MVP 패턴
Goal 12/31
https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter
다이나믹하고 인터렉티브한 프론트를 제대로 소화하기 위해 상태관리 및 잦은 수정 등에 유리한 MVP 을 적극 활용하기로 함 .프론트 코딩을 보다 구조화 할 수 있다 .
| 프론트 설계 : MVP 패턴 도입배경
Goal 12/31
기획상 모바일웹의 뷰의 레이아웃은 PC 웹과 상이하다 .
PC 신형브라우저 VIEW 모바일웹 VIEW
공통 PRESENTER( 필요시 일부 오버라이드 )
공통 MODEL
공통부 재활용원을 그리려다 .. 수전증
PC 구형브라우저 VIEW
| 웹소켓
Goal 12/31
Ajax Long Polling 방식은 HTTP 오버헤드에 재접속 속도도 느릴테고…별도의 서버 설정도 필요할테고…
무엇보다 ,
슈퍼다이나믹인터렉티브모던웹프로젝트에 어울리지 않잖아 ?
그리고…웹소켓을 어떻게든 써보고 싶은 불타오르는 욕망
| Node.js
Goal 12/31
( 근거없음 )
역시… Node.js 를 어떻게든 써보고 싶은 불타오르는 욕망
Node.js 라는 이름만으로도… .
슈퍼다이나믹인터렉티브모던웹프로젝트에 어울리잖아 ?
| Node.js + Socket.io
Goal 12/31
사실 프로젝트 이전에 초간단 채팅 어플리케이션을 만들어 봤습니다 .Publish/Subscribe 패턴으로 제공되는 간결한 API 에 푹 빠졌고그 달콤함과 Node.js 의 빠른 코드작성 능력에 한번 더 눈이 멀고…
( 복선ㅎ ) 그 달콤함에 취해 앞일을 낙관하게 되는데…
| Redis
Goal 12/31
사이트의 제공데이터를 실시간으로 빠르게 전파하고유저의 데이터입력이 없는 ( 그런데 왜 인터렉티브 ?)스냅샷 형태의 데이터를 제공하는 특성
Pub/Sub Channel 제공
역시… Redis 를 어떻게든 써보고 싶은 불타오르는 욕망
슈퍼다이나믹인터렉티브모던웹프로젝트에 어울리는 메모리 DB
프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프 로 토 타 이 핑 프로토타이핑
성공적 ?
Goal 12/31
“ 왜 이렇게 쉬워 ?”
Goal 12/31
“ 그래 , 그럴리 없지 ...”
Goal 12/31
그럴리 없지 ...
Goal 12/31
AWS EC2
Node.jsSocket.
io
AWSLBS
아마존 LBS 에서 WebSocket 을 지원할 수 있는Sticky Session 을 지원하지 않았다 .
| AMAZON LBS
Browser
Socket.io AWS EC2
Node.jsSocket.
io
Goal 12/31
며칠의 삽질과 기도 (?) 끝에 구글신의 신탁이 있었으니 ..
Goal 12/31
HAProxyHAProxy is free, open source software that provides a high availability load balancer and proxy server for TCP and HTTP-based applications that spreads requests across multiple servers.[2] It is written in C[3] and has a reputation for being fast and efficient (in terms of processor and memory usage).[4]
HAProxy is used by a number of high-profile websites including GitHub, Bitbucket,[5] Stack Overflow,[6] Reddit, Tumblr, Twitter[7][8] and Tuenti[9] and is used in the OpsWorks product from Amazon Web Services.[10]
https://en.wikipedia.org/wiki/HAProxy
무슨 말인지 1 도 모르겠지만 ... 어쨌든
| 들어는 보았나 ! 질풍발란서 ! H.A.P.R.O.X.Y
Goal 12/31
HAProxy
HAProxy 에서 TCP 기반의 웹소켓을 StickySession 방식으로 설정하는데도 많은 삽질이 뒤따랐다 .
소통원활 ! 사이다 ! x100
Browser
Socket.io
AWS EC2
Node.jsSocket.
io
AWS EC2
Node.jsSocket.
io
| 구세주 HAProxy
“ 그런데 , 그럴리 없지 ...”
Goal 12/31
이제 됐네 , 됐어 .
Goal 12/31
우리에게도 이런 일이 일어났습니다 . 어플리케이션이 숨지는데 이유는 모르겠고
| Node.js 의 반란
Goal 12/31
각종 커뮤니티 , 구글신의 신탁을 기대해 봐도 답은 또렷하지 않고Try catch 로 도배를 해보아도 에러의 내용은 알 수 없으니팀원들의 눈동자에는 마치 미지의 세계에서 낯선 존재를 만났을 때의 공포가…
프로세스가 죽으면 다시 살리는 각종 툴을 써봐도 이 상태로 출시할 수는 없고
오랑캐 Node.js 무용론 ,Java 회귀론 등 척화배척사상이 힘을 얻어 갈 무렵
| Node.js 의 반란 _ 시즌 2
Goal 12/31
바다 밖 어딘가에는 Node 기반 상용 서비스가 있다는 전설 같은 이야기를 믿고진시황이 불노초를 찾듯 , 장혁이 추노하듯 버그를 다시 찾아나서게 되는데…
오랑캐 Node.js가 비동기식 Callback 형태의 동작임을 다시금 깨닫게 된다 .
| Node.js 의 반란 _ 시즌 3
Goal 12/31
비동기메커니즘에서의 Callback 이란 무엇인가 !
비동기식 호출의 요체로서 호출자의 스택이 모두 끝나기전에는 호출되지 않는 특성물론 , 싱글스레드에서만 .
AB
Event Loop수전증 2
C
2) B 에서 C호출
1) A 에서 Try Catch
3) C 에서 Exception 발생
D
4) 1 번의 스택이 정리되었기 때문에 C 함수에서 Exception 이 발생해도 잡을 수가 없다 . 물론 Node.js 에서는 Domain 개념이 있기 때문에 방도가 전혀 없는 것은 아니다 .
| Node.js와 asynchronous callback mechanism
Goal 12/31
로깅강화와 예외처리 디버깅 요령 체득 등…다시 한번 상용서비스로서의 가능성을 품게 된다 .
Node.js가 불안정하다는 의견이 종종 보이지만 , 이후로는 단 한번도 원인없이 숨진적이 없기에 개발자의 역량문제로 잠정결론
| Node.js 의 반란을 진압하고…
Goal 12/31
그 이후 ,모든 것이 순조로웠다 .
눈을 뜨기 힘든 가을 보다 높은저 하늘이 기분 좋아
10 월의 어느 멋진 날에 - 김동규
Goal 12/31
오늘은 어디서 무얼 할까 창밖에 앉은 바람 한 점에도
10 월의 어느 멋진 날에 - 김동규
다들 열일했고 ,
Goal 12/31
10 월의 어느 멋진 날에 - 김동규
더 좋은 것은 없을 거야 10 월의 어느 멋진 날에
사무실은 평온했다 .
그러던 어느날 ,뜻밖의 방문
Goal 12/31
Requiem for Gaebalja
Goal 12/31
- 여기 새로운 개발요건이 있소 . 무덤은 미리 파놨으니 걱정마시오 .
그대는 일을 끝내고 눕기만 하면되오 .
“ 올 것이 왔구나”
Goal 12/31
처음에는 간단한 푸시발송 서버를 구현하려고 했으나 ,발송량이 상당할 것으로 예상되어 Amazon SNS 를 사용하기로 협상
결론적으로는 안정적이고 빠른 발송이 보장되었으며(또 )Publish/Subscribe 모델이 지원하여 Topic 기반의 관리가 가능
| 실시간 OOO 알림 : 앱 푸쉬서버 필요
Goal 12/31
기획단계에서 그렇게 필요할거다 필요할거다 랩을 해도 안듣더니이제와서 웬말이냐 !
| 재설계를 고려해야하는 요건 : 로그인
“ 이 정도야 뭐~”
Goal 12/31
| 회원가입 , 로그인 , 아이디찾기 , 비밀번호찾기 ..... 등등
그런데 말입니다 ,
Goal 12/31
“ 웹소켓에는 세션유지 기능이 없지 말입니다 .”
Goal 12/31
Redis
Preflight
Upgrade
| Socket.io, Redis, Node session module 만으로는 웹소켓의 세션 유지가 안된다 ( 눈물 )
Socket.io 에서 Preflight(Ajax) 일 때만 세션을 생성하고 ,Websocket 통신시에는 세션을 만들지 않음 . 위는 일반적인 Node.js 의 세션공유방식 (Redis 를 세션스토어로 ..)
Browser
Socket.io
Node.js
Socket.io Express
Mod.SessionSt
ore
Goal 12/31
웹소켓상의 통신구간에서 세션을 가져올 방도를 찾을 수 없었고
시간은 흘러가는데…
그래서 어쩔 수 없이…
Socket.io 에서 웹소켓 통신 중 세션 획득하신 분 연락 좀요 . 피자 배달해드림 .
| NPM 리파지토리를 탈탈 털어봐도…
Goal 12/31
…
웹소켓용 세션관리 모듈을 직접 만듦
_ 사족 : 세션 모듈명은 FkingSession 이지만 이것은 수제임을 강조하기 위해 은유적으로 햄버거의 이름을 차용한 것으로 , FantastickBurgerKingSession 의 약자임을 밝힙니다 . 이상한거 아님 . 참고로 다른 업체에서 소스 인수인계해 갔는데 별말없는거 보면… 하 .
| Trick or Threat
Redis
onconnection
oncookie
Browser
Socket.io
Node.js
Socket.io Express
수제세션 - 모듈
수제 세션 생성
주기적으로 document.cookie 로
정보 업데이트
Goal 12/31
이 모든 것은 베타런칭 불과 20 일 전이었다 .
웰컴투 헬프리카 !
Goal 12/31
메인시안 변경
소소한 기획변경Goal 12/31
| 베타런칭 10 일 전
Goal 12/31
프론트엔드 애니메이션 문제 발생 : callback, UI state
_ 비록 싱글스레드로 동작하나 비동기통신과 콜백이 난무하는 UI 의 특성_ 각 애니메이션이 끝나는 지점을 확인하여 다음 애니메이션을 진행해야 함_( 일정압박에 따른 ) 개발자의 모럴해저드 발생 , 감으로 .. 종료시간을 추측_애니메이션의 순서가 뒤엉켜 화면낸 요소들이 곂치거나 사라지는 문제발생_ 각 화면 요소 및 렌더링 엔진의 STATE점검 로직 부족
_Async 라이브러리 도입_Callback chain 전면적 리팩토링
_애니메이션 프로세스 재조정
| 모럴 헤저드
| 그리고 12 월 ..
정식오픈 7 일 연기그와중에 기획변경
Goal 12/31
_ 수시로 이뤄져야 하는 코드리뷰와 리팩토링의 부재_ 모델을 제외한 거의 모든 부분을 재작업_ 여기서 MVP 모델의 유지보수의 용이함을 체감
극심한 정신적 육체적 피로 ... 어찌보면 당연한 귀결
| 리팩토링 : 모바일웹 MVP 모델
Goal 12/31
Interlude여기까진 중간보스
Goal 12/31
| 로딩속도가 느리다고 ?
Goal 12/31
_ 웹소켓 접속과 초기 데이터의 사전 로딩의 필요상 로딩화면이 먼저 나타는 구조_ 소켓서버 구조적 결함발견 ( 불필요한 루프제거 ), 로딩속도 개선_ 초기데이터의 양이 생각보다 큼으로 초기데이터의 분할 로딩방식으로 변경_ 눈속임이지만 초기기동속도가 처음 5~6 초에서 1~3 초 대로 축소
| 리펙토링 : 점진적인 로딩방식
Goal 12/31
그리고다가오는 그 날
Goal 12/31
12 월 23 일최종보스의 등장
_ 고객사에서 email DM 발송_1분 단위 동접 4천명_예상보다 터무니없이 저조한 서버 성능
Goal 12/31
재료 1 : Publish/Subscribe 모델재료 2 : Node 의 싱글스레드재료 3 : 고용량의 Subscribe 데이터
다수 사용자
Node.js 의 채널 Subscribe 핸들러에서 서버에 접속한 모든 클라이언트에게고용량의 데이터 (2~5MB) 를 순차적으로 전송하는 과정에서 이벤트 큐 마비 .싱글스레드이므로 핸들러가 종료되기 전까지 다음 이벤트가 순환되지 못한다 .
Goal 12/31
| Too much love will kill you
약 1 시간 정도의 분석 끝에 Payload 문제임을 발견노드 소켓서버의 복제 , 초기 5 개 인스턴스에서 25 개 인스턴스로 확장
서버는 일시적으로 정상화됐으나…
_2vCPU EC2 서버에서 다수 인스턴스를 돌리게 됨으로 생기는 비효율_ 근본적인 Payload문제 미해결_Payload가 해결되지 않으므로 many servers with low connections 아키텍트 유지
Goal 12/31
| 예측하지 못한 Payload 그리고 조치
Payload 를 줄이기 위한 방안 데이터압축 _js 압축스크립트의 성능 문제로 클라이언트 UI freezing 우려_클라이언트에서 web worker 도입 검토
JSON 데이터 Key 필드 축약_ 개발 가독성을 위해 Key 의 이름이 긴 편임 , 축약시 약 20~30% 데이터 감소 가능_ 그러나 , 전면적 리팩토링에 대한 시간적 부담감_ 데이터전송 부에서 Key 필드를 별도의 메타데이터로 한벌 만들고 Value 만 묶어서 보내는 방식_ 역시 전면적 리팩토링 필요
데이터의 양은 둘째치더라도 소켓채널 전송시 , 로그인 등의 액션에서 타임아웃 발생어느정도의 비효율은 감수하더라도 근간 서비스가 동작하도록 리팩토링 결정
Goal 12/31
| 2박 3 일간의 사투
회원가입 / 로그인 / 북마크 / 프로필힛트카운트 증가 등의 로직도 WebSocket 에서 동작
구조적문제점 탈피를 위한 사용자의 전송데이터 채널을 신설하기로 결정
웹소켓 채널은 오직 Server Push 용으로 용도전환
Node.js 에서 오라클 쿼리 작성 생산성이 열악하여 Java-Spring 으로 REST API 화
Goal 12/31
| Rest Server 도입
Goal 12/31
| REMIND : 초기 아키텍트
AWS EC2Browse
r
Socket.io
Node.js
NginX
OracleSocket.io
HTML/CSS/JS/Media FTP
실제로는 더 복잡함
| 최종 아키텍트
Browser
Ajax
Socket.io
AWS EC2
Node.js
NginX
OracleSocket.io
HTML/CSS/JS/Media FTP
HAProxy
AWS EC2Node.jsSocket.
io
Node.js
Rest API
Goal 12/31
처음부터 알았더라면 ...그렇게 2박 3 일간의 전투가 끝나고 ...
만약 , 초기 기획이 지금과 같았고 이러한 문제점을 미리 알았다면 …
1. 오라클 연동은 자바 - 스프링 REST ( 초반에 고민 )2. 최신버전 노드 사용 ( 오라클 Module 때문에 1.11 버전 사용 )3. WebSocket 은 서버 Push 용으로만 사용4. 어쩌면 고전적인 Ajax Long Polling 이 나았을지도
Goal 12/31
종전여러 실수가 있었으나 ,
어찌되었든 우리는 짧은 시간 동안 프로로서 문제해결을 했다 .하지만 , 전투에서는 승리했으나 전쟁에서도 승리했다 말할 수 있을까 ?
상처뿐인 영광 , 아니 상처뿐인 상처로 남았다 .
Goal 12/31
그 후 , ( 프로젝트 종료예정일이 지난 후 )
_미뤄뒀던 관리자페이지 개발_푸쉬기능 개발_ 오직 HAProxy 만이 프로젝트 내내 말썽을 일으키지 않았음
끝
Goal 12/31
감사합니다 .