Vue.js 참고 자료

가이드

사례

디버깅

사용법

JavaScript 적응기 01 - Vue.js

최근 새 팀에 합류했다.

합류한 팀에서 풀 스택 개발 추구하고 있었고, 그 과정에서 웹 프론트엔드 개발에 vue.js를 사용하고 있었다.

Vue.js

자연스레 자바 스크립트를 사용해야 했는데, 2009~2012년경 윈도우 배치 스크립트 짜기 괴로워, Jscript (윈도우 내장 자바 스크립트 엔진을 이용한 스크립팅)을 했던 뒤로 오랜만에 실무에 사용하게 됐다.

종종 Node.js를 이용한 REST API 서버를 가볍게 써오긴 했으나, 나의 경우엔 업무 외적인 습작에 써왔고, 백엔드 서버로써 사용한 거라 굉장히 다르다고 할 수 있다.

주로 백엔드 내지는 서버 개발자 포지션에 있었으나, 클라이언트 개발에 관심도 많고 종종 해왔던지라 거부감이 없다는 점은 매우 다행이지 않나 싶다. 팀의 방향성과 나의 가치관에 충돌이 있을 땐 스트레스 요소가 될 수 있는데, 나는 아주 좋은 기회라고 생각이 들었고, 재미도 있었다.


대략 한달 간 가량 진행한 Vue.js의 감흥은 생각보다 쉽다 였다. 웹 프론트엔드 문외한이나 다름없었지만, 동료들이 작성한 코드를 보고 구조적인 이해나, 인프런 vue.js 강좌와 동일한 강사분이 써두신 입문서를 보고 금새 무언가를 만들 수 있을 만큼 직관적이고, 동작하는 무언가를 만드는 시간이 적게 소요됐다.

즉 학습도, 실습도 빠른 시간내에 가능했다는 의미다.

가장 도움이 된 것은 vue.js의 컴포넌트 라이프 사이클 이해하기 였는데, 유니티때와 마찬가지로 컴포넌트간 상호 작용이 크게 중요한 만큼, 이 그림은 계속 참고하면서 코딩하게 되는 유용한 자료 였다.

Vue.js Lifecycle

참고: https://medium.com/witinweb/vue-js-%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-7780cdd97dd4


반면 힘들었던 점은 역시나 언어의 태생적인 단점이었다. 나는 강타입 언어를 선호하는데, java script 자체는 typeless 언어라서, 이에 대한 아쉬움이 있었다.

Vue.js에서도 TypeScript를 지원 Vue.js TypeScript 지원 하고 있으나, 현재 팀에선 사용하고 있지 않은 상태였고, 리액트에 비해 상대적으로 미약하다고 한다. 이 부분은 아무래도 좀 이해도가 높아진 후에야 명확히 말할 수 있을 것 같다.

Vue.js 자체의 규칙은 간결하고 직관적이라 쉬운편이라고 할 수 있는 반면, 내가 HTML5, css, bootstrap등에 대한 이해도나 경험이 부족해서 이 부분에 대해서 막히는 부분이 많았다.

html을 직접 작성해야 하고, vue와 결합도가 높게 동작하고 연관하여 분석, 작성 해야 하다 보니 부족한 이해도가 아쉬웠다.

그런 부분을 감안했을 때에도 생각보다 허들이 낮은 편에 속했고, 프레임워크 자체에 대한 룰이나 제약이 단순하고, 해결 방법도 제시되어 있어 큰 어려움이 없이 작업 할 수 있었다.

Vue.js에서 주장하는 쉽고 빠른 간결한 프레임워크라는 주장을 신뢰할 수 있는 경험이었다고 할 수 있겠다.


애초에 Web Frontend라 할 수 있는 작업도 처음이지만, SPA로 작업한 것도 처음이고, 컴포넌트 단위로 코딩한 것도 처음이지만, 게임 UI랑 비교 했을 때에 아주 색다른 개념들은 크진 않았다.

컴포넌트간 통신 및 계층에 따른 의미나 제약을 집중해서 살펴보면서 작업을 진행했다.

컴포넌트 간의 순환 참조, 생성 순서 문제, 상호 통신, 순환 참조 문제 등 다양한 이슈가 얽혀 있었다. 선행 조건들이 늘어 날 수록 컴포넌트 간의 관계도 복잡해지고, 결합도가 높아지는 걸 느낄 수 있었는데, 이에 대한 몇가지 우회 방법들이나 팁이 존재하더라.

또 다른 측면의 걱정은 느리지 않을까?

처음 맞닥뜨린 vue.js는 각 컴포넌트를 감시하게 동작 할 것 같다는 생각이 강하게 들었었다.

앵귤러의 특징이면서도 단점으로 여겨지는 양방향 바인딩도 존재하고, v-bind나 v-if 등의 키워드로써 걸어놓은 트리거들이 동작하는 것들이 많고, 쉽게 사용할 수 있게 직관성을 강조하다보니 그만큼 느리지 않을까 하는 우려가 있었다. (물론 내부적으로 최대한 폴링보다는 이벤트 드리븐으로 잘 짰겠지만, 미지의 영역인 프론트 엔드 프레임워크에 대한 막연한 의구심 같은 거라고 할 수 있겠다.)

아주 의외인 것은 리액트보다 빠르다는 점이었다. 측정한 곳이 vue.js 공식 페이지다보니 수치나 정말 복잡하게 사용하는 상황에서 마저 우위에 있을지를 100% 믿기엔 어렵겠지만 말이다.

막연한 의구심을 해소하기 위해 이런 저런 글들과, 지인들에게 자문을 구했는데, 결론은 프론트엔드 프레임워크 대부분이 사용성과의 밸런스를 맞추기 위한 코스트를 감안하고 구현되어 있고, 돔을 직접 다루는 번거로움과 어지간히 잘 관리하지 못하면 돔을 찾고 수정하는 과정에 오버헤드가 걸리곤 하는데, 이를 프레임워크가 관리하면서 적정 수치 이상으로 유지해주기에 신경 쓸 거리가 적다고 봐야 한다는 것이었다.

이런 고민들이나 의구심들 대다수가 게임 UI 에서도 공통되게 고민되는 것 들이고, 비슷하게 접근하고 나면 납득 할 수 있는 것들이 많았다.

다만 현재까지의 생각과 감상이 든 과정 모두 내가 적응기로써 짧은 기간 학습+적응을 목표로 작업한 admin web에 한정된 이야기였고, 브라우저 호환성 및 메모리 이슈, 훨씬 더 복잡한 UI간 상호 작용을 요구하는 작업에선 훨씬 더 많은 감상이 있지 않을까 싶다.

Mongodb 서버 구축 및 아키텍쳐

Mongodb를 실 운용해본 후기 및 상황에 따른 권장 구성에 대해서 설명해보고자 한다.


최종 데이터는 1.3TB였고, read & write node 역할을 함께 하게끔 구성해서 3대로 운용하다가, read node, write node 각각 3개씩으로 나누어 운용하며 겪었던 경험에 대한 이야기다.

1.3TB데이터는 모두 단일 콜렉션에 담았으며, 인덱스만으로 aggregation을 시도했고, 이 과정에서의 성능 차이를 주는 요소를 확인했으며, 일일 API 호출 통계(정상 API 호출까지 모두 집계)를 집계했으며, 일 데이터는 400MB~2GB 사이였다.

분산 콜렉션에 map reduce하는 방법도 고려해봄직 했으나, 이 역시 연산 코스트는 작지 않으며, 큰 단일 콜렉션에서의 성능과 이슈를 확인해보고 싶었다. (인덱스 효율, 인덱스를 타는 케이스, 타지 않는 케이스가 방대해진 인덱스에서도 동일하게 동작하는지 확인 등)

대다수의 상황에서는 hot data개념이라거나, read node, write node 분리 정도만 염두에 두어도 충분히 효과를 볼 수 있다.


mongodb는 기본적으로 샤딩과 레플리카를 모두 기본 옵션으로 지원한다. 샤딩은 오토 샤딩을 지원하며, 이는 리밸런싱을 배제한 옵션이다.

mongodb의 구성에서는 몇가지 이해하고 넘어가야 할 조건들이 존재한다. 아무리 RDB보다 빠르다지만 1억건 이상의 데이터에서는 결국 성능 문제를 맞닥뜨린다. 이 중 가장 큰 이슈가 되는 것은 당연하게도 agreegate 동작이다.

mongodb는 기본적으로 memory mapped file을 기반으로 한다.

이는 메모리 + 가상 메모리를 이용한다는 의미로써, 물리 메모리가 적재할 데이터 보다 작다면 디스크 IO가 크게 발생하고 이는 곧 성능 저하로 이어진다는 의미다.

이 부분이 긴가 민가 했는데, 실제 운용해보니 인덱스에 있는 데이터는 메모리에 올리고 인덱스에 없는 데이터까지 연관 조회 혹은 aggregate시에 인덱스에 없는 데이터를 조건에 포함시켰을 때, 메모리에 없는 데이터를 읽기 위해 계속 메모리에 적재를 시도한다.

이 과정에서 가상 메모리를 결국 사용하게 되고 성능이 급격히 저하되는 것을 확인할 수 있었다.

또한 메모리에 올라와있는 데이터를 hot data라 부르는데, 이를 적절한 크기로 유지해주면 성능상 큰 이득을 볼 수 있다. hot data에 대한 자세한 이야기는 밑에서 좀 더 설명하겠다.


샤딩과 레플리카 구성 으로 넘어오자면, mongodb의 경우 여타 db들과 비교했을 때 샤딩, 레플리카 구성 모두 쉽다.

mongodb에는 3가지 종류의 서버가 존재한다.

MongoDB Sharding 구조

하나의 config 서버와 여러개의 MongoDB 서버로 구성되어 있다.

  • Config 서버
    • 중개자 계층, 샤딩을 위한 메타 데이터를 저장한다. (데이터들의 위치 정보를 저장)
  • Mongos 서버
    • MongoDB의 중개자 역할, Config 서버의 메타 데이터를 이용해 각 MongoDB에 데이터 접근을 도와준다.(라우터와 같은 역할)
  • Mongod 서버
    • MongoDB의 데이터 서버
    • 서버 장애에 대비해 MongoDB 서버 안에 여러 개의 리플리카 셋 구조로 구성되어 있다.

Client(응용 계층) → Mongos(중계 계층) → Config(중계 계층) → Mongod(데이터 계층)

mongodb sharding internals

필요한 구성요소는 이렇고, 배치와 연관 관계 설정에 따라 여러가지 모델이 있을 수 있는데, 핵심은 read node와 write node(+read지만 write만 하는 것을 권장한다)를 잘 선정 해야 하는 데 있다.

read node와 write node를 같이 두지 않는 것은 RDB에서도 중요하나 mongodb에서는 더욱 더 크게 중요하다.

위에서 언급한 hot data를 node마다 따로 보유하기 때문이다.


document를 참고해서 시스템을 구축하고 운용하면서 몇가지 궁금증이 생겼었다.

  1. config 서버는 왜 3대를 구축해야 하는가?
    • config 서버는 registry+router라고 봐야 함.
    • 결국 데이터를 쌓을 곳 혹은 읽을 곳을 전달해주는 서버가 되므로, 서버 수에 따라 안정성 및 반응 속도, 데이터 유실율, Read/Write 정합성을 맞추는 데에 중요한 역할을 한다.
    • 3개의 서버중 하나가 죽어도 나머지 서버가 설정 정보를 보유,전달 하면서 지탱해주는 3중화라고 봐야 한다고 한다.
  2. 자동으로 해주는게 별로 없는데 auto sharding이라 부르는 이유는 무엇인가?

둘다 mongodb 공식 페이지 및 각종 글을 읽어보고 내린 결론인데, 이견이나 보강하실 부분이 있으시다면 좀 더 자세히 설명해주시면 감사하겠다.


또 다른 팁은 무엇이 있을까?

aggregate처럼 집계연산은 실시간이면 좋긴하지만 또 일정 시간의 소요를 감안하고 운용되곤 한다. 실제로 hadoop 과 같은 빅데이터 솔루션들 대다수는 실시간이라기보다는 배치 잡으로 동작하여 긴 시간이 소요되는 걸 감내하고 운용되는 것을 감안하면 감내할 수 있는 부분이다.

  1. aggregate와 같은 메모리 사용량이 큰 동작을 처리하는 node를 분리하고, 응답성이 중요한 데이터를 다루는 read node를 분리하는 것도 하나의 전략이 될 수 있다.
    • 메모리 사용량이 큰 동작은 hot data가 잦은 갱신이 이뤄지는 원흉이지만 또 한편으로는 통계나 집계의 핵심 역할이다. 이를 여타 솔루션으로 보내기보단, 직접 처리할 수 있으면 빅데이터의 중간 처리자나, 직접적인 통계 처리자의 역할을 충분히 수행 할 수 있기에, 여타 read node에 영향을 주지 않게끔 분리하는 것은 충분히 합리적인 선택지 중 하나가 될 것이다.
  2. document 단위로 최대한 read node를 분리해서, document 단위의 hot data를 의도하고 최대한 hot data 연산이 이루어 질 수 있게끔 구성하는 것도 방법이다.
    • document가 유사하면, 유사한 쿼리를 요청할 확률이 높고 이는 같은 인덱스를 사용할 확률이 높다.
    • 그럼에도 인덱스들이 메모리 사용량을 상회할 것이라고 생각된다면, 이 마저 감안해서 분산하는 것이 옳다.
    • 특히 main db로써 사용중이라면 응답성의 핵심이 mongodb의 응답 속도 일 것이므로, 인덱스를 얼마나 잘 활용할 수 있는지에 맞게끔 read node를 분산해야 한다.
  3. 여타 DB도 마찬가지이지만, mongo db도 항상 read & write node를 분리해야만 하는 것은 아니다.
    • 내가 주로 사용한 agreegation 자체가 최근에 사용된 데이터 기반이 아닌,  전날 데이터 전체를 가져와야 했으므로, read node 자체가 write node가 바라보는 working set이 무의미한 구성이기도 했고, 서로 영향을 주고 받지 않게 하기 위해서였다.
    • 대다수 상황에서는 read & write node는 일치 시키고, write node와 replica set 구축하는 것이 더 일반적이며, 효율적인 구성이 된다는 점을 염두에 두자.

너무나 중요해서 다시 한번 강조하자면, mongodb를 구축한 서버의 메모리와 주로 다루게 되는 인덱스의 크기를 안넘어서게 하면 최대한 메모리 내에서의 연산을 이끌어내서 성능 저하를 피할 수 있다는 점이다.

node 분배의 최적화는 hot data를 RAM을 넘어서지 않게끔 관리해주고 hit rate를 높이는 데에 있으나, 이렇게 강조했음에도 현실적으론 용도에 맞게끔 node를 매번 구성하기 어렵다. auto-sharding이 된다고해도 리밸런싱이 지원되는 것은 아니며, 직접적으로 hot data (=working set) 를 관리하는 기능은 없기 때문.

node의 hot data(=working set)을 완벽히 최적화하긴 어렵기 때문에, 현실적인 가이드 라인은 active한 데이터가 mongodb node의 물리 메모리 크기를 넘어서지 않게끔 어플리케이션을 구성하는 것 정도가 아닐까 싶다.

mongodb도 rdb와 유사하게 쿼리 실행 계획을 볼 수 있다.

explain 명령을 통해 볼 수 있는데, 여기선 winningPlan, rejectPlan등의 성공과 실패시 어떻게 동작하냐만 보여줄 뿐 실제 어떤 쿼리가 인덱스를 잘 이용해서 Full Scan을 안할 것인지, hot data로 인해 가상 메모리를 크게 쓸 건지에 대한 설명까진 이어지지 않는다.

실은 나 역시 구축하고 두번의 구성 변경을 해야 했다. 이유는 예측한 데이터보다 훨씬 컸고, 용량이 크면 클수록 인덱스를 타지 않은 aggregate 동작은 시스템 부하가 컸으며, 물리 램 사용량이 매우 중요했고, 메모리에 존재하지 않는 데이터 억세스 코스트가 컸다.

node마다의 부하 분산도 첫 계획대로 되지 않았기 때문이다.

그럼에도 몇가지 팁은 최초 설계가 실제 운용시의 갭을 줄이는 데에 용이하지 않을까 생각한다.

내 시행 착오와 경험담이 도움이 되길 바란다.


참고

Mongodb 장단점, 활용시 고민할 사항들

장점

  1. 비동기 드라이버를 사용할 수 있다.
    • 현재 JDBC의 경우 동기 드라이버만 존재해 블러킹 포인트가 된다.
  2. RDB와 개념이 유사해, 쿼리 변환기가 있을 만큼 개념적으로 어색하지 않다.
    • 사용법도 마찬가지로 이질감이 없다.
  3. RDB에 비해 성능이 100배 이상 빠르다.
    • 별도의 캐시 솔루션이 필요하지 않을 만큼 성능 문제에서 우월하다.
  4. 스키마 관리가 필요 없다.
  5. 이미 성숙기에 접어들어 운용, 개발, 유틸리티에 부족함이 없다.
    • Cassandra, Couchbase, Mongodb의 경우 대규모 트래픽, 데이터 저장, fail-over, fault-tolerance에 다양한 대안이 마련 되어있는 상황이다.
    • redis과 비교해봐도 충분히 안정권에 들어온 상태다.
  6. 샤드 추가가 간편하다.
    • 다른 NOSQL처럼 리밸런싱은 불가능하지만, 그럼에도 적정 수치때 샤드를 추가해준다면 장점은 충분히 누릴 수 있다.

단점

  1. 복잡한 쿼리를 사용할 수 없다.
    • join을 사용할 수 없다.
  2. 메모리 사용량이 큰 편이다.
    • 메모리 부족 시 퍼포먼스가 급락한다.
  3. 데이터 일관성이 보장되지 않는다.

적절한 사용 사례

  1. 로그성 데이터나 빅데이터 처리의 중간 저장소
    • RDB보다는 성능이 우월하고, 파일보다는 다양한 유틸리티성 기능과 검색에 유연하다.
  2. 설정 데이터의 보관소
    • 이는 Redis같은 Key-Value DB가 유용하다고 여겨질 수 있으나, 검색 조건의 다양화가 필요할 경우 MongoDB가 훨씬 유용하다.
  3. null 필드가 많이 존재할 때
    • 데이터에 null 필드가 가변으로 다양하게 존재할 경우, rdb보다 스토리지 사용량, 처리 속도등에서 효율이 좋다.
  4. 압도적인 퍼포먼스가 필요할 떄
    • RDB 대비 100배 이상의 차이를 내는 압도적인 퍼포먼스 차이.
    • memory mapped file 기반 구조에서의 장점
  5. nosql 계열에서 압도적인 index 활용도
    • Single Field Indexes : 기본적인 인덱스 타입
    • Compound Indexes : RDBMS의 복합인덱스 같은 거
    • Multikey Indexes : Array에 미챙되는 값이 하나라도 있으면 인덱스에 추가하는 멀티키 인덱스
    • Geospatial Indexes and Queries : 위치기반 인덱스와 쿼리
    • Text Indexes : String에도 인덱싱이 가능
    • Hashed Index : Btree 인덱스가 아닌 Hash 타입의 인덱스도 사용 가능
  6. 집계 연산, paging, 복잡한 쿼리 (단일 document 한정)가 필요할 때
    • key-value db (redis, aerospike 등)와 달리 집계 연산, paging이 가능함.
      • RDB와 동일한 접근의 데이터 스토어로서 사용 가능함.
        • 실제로 쿼리 변환기가 존재함.
  7. 스키마 관리가 불필요함.
    • json 기반 저장 구조로, 유연한 동적 데이터 저장이 가능.
    • 정규화할 데이터보다는, 단일 스키마 기반의 참조 데이터 저장에 장점이 많음.
    • 로그성 데이터도 매우 적합 함.

부적절한 사용 사례

  1. 데이터 무결성이 가장 중요한 가치 일 때
    • 단일 document 무결성은 유지되지만, 멀티 document 무결성은 유지 되지 않음.
      • 애초에 join이 되지 않는지라, 일말이 오차는 존재할 수 있음.
  2. 데이터 처리량보다 일관성 있는 데이터 구조가 중요할 때
    • 데이터 처리량이 빠른 이유는 ACID를 수행하지 않기 때문이다.
    • 특히 샤딩+레플리카를 조합해서 사용 할 때 무결성이 깨진 상태의 데이터가 조회 될 수 있다. (document 자체가 corrupt 된다는 의미는 아니고, 조회한 시점 이전의 데이터가 조회 될 수 있거나, 이미 삭제된 데이터가 조회될 수 있다는 의미)
  3. 운용 이슈에 대한 우려 사항들이 해소가 덜 됐을 때
    • NoSQL 계열도 여타 DB와 마찬가지로 운용 이슈가 중요하고, 이에 대한 경험과 노하우가 부족하다면, 이 부분이 리스크가 될 수 있다.
    • 임계치가 높을 뿐 RDB와 마찬가지로 데이터가 많아지면 성능이 저하되는 부분은 마찬가지고, 이를 해결하기 위한 방안과, 풀 스캔이나, 메모리 사용량이 커질 수 있는 작업을 production 레벨에선 사용할 수 없게 만들어야 한다.
  4. 데이터 무결성이 무엇보다 중요할 때
    • 결제, 아이템 등을 담기엔 여전히 RDB보단 불안한 면이 존재한다.
  5. 엄격한 데이터 타입 검사나 연관 관계가 중요할 때
    • 데이터 타입이 동적 결정되는 스키마리스 DB이고, 컬럼도 동적 컬럼이다보니 마이그레이션이 어려운 편이고, 제약 검사를 추가할 만큼 스키마가 중요하다면 선택해선 안된다.
      • 코드 레벨에서 제어할 방법도 있지만, 접근 권한을 준 다른 서버 혹은 툴 등에서 쿼리를 날렸을 때 이를 막을 수 없다.
    • 또한 테이블 간 연관 관계가 중요하다면, 이 또한 선택지에서 배제해야 한다.
    • 위에서 언급한 대로 여러 document (테이블)을 동시에 조회할 수 없기에, 동일한 시점에 무결성이 깨지지 않은 데이터를 조회할 수 없다.

참고 자료

JWT 사용기

JWT란 Json Web Token으로써, JSON으로 이루어진 데이터를 token화 한 것을 말한다.

JWT의 특징은 여럿이 있는데, 정보를 token 내에 보유하고 있는 (self contained) 독특한 토큰이다.

만료 시간은 부여할 수 있으나 강제 만료는 불가능하다. JWT를 이용한 강제 만료를 구현하기 위해선 이를 검증하는 저장소를 한번 거쳐야 되는 이슈가 있는데, 매번 키 검사를 해야 하는 구조라면 신경 쓰일 수 밖에 없는 요소라고 할 수 있다. (결합도도 증가 하는 단점도 덤이다.)

즉 탈취되도 문제가 적은 AccessToken으로써 기간 만큼만 유효성을 유지시키고, 서명을 통한 발급 정보 크로스 체크, 해쉬 검사에 쓸 값도 같이 담아서 받아 재확인 함으로써 유효하다는 것을 검증하는 구조로 사용 할 만 하다.

부가정보를 다양하게 담을 수 있으므로, 확실히 검증 된 결과를 바탕으로 여러가지 정보 값을 체크 하는 용도로 쓸 수 있으므로, DB 조회량을 감소 시키는 효과를 볼 수 있다.

또한 토큰 발급과, 정보 값을 검증하는 로직을 서버에만 둔다면 (secret 키를 사용자에게 감췄다면), 결합도를 낮추고 서버의 접근 권한을 검사/허용하는 용도로 쓸 수도 있다.

json_web_tokens

참고 : https://auth0.com/learn/json-web-tokens/

너무 중요해서 다시 한번 강조하자면, 보안적 핵심은 secret은 Server만 안다는 점이다.

아래와 같이 Authorization Server에서 발급한 정보를 바탕으로 특정 Resource Server로 질의할 수 있다는 점인데, 이 과정에서의 결합도도 낮아지며, 보안 취약점도 없다는 점이 매우 중요하다.

jwt_client-credentials-grant

참고 : http://jwt.io

JWT가 아니었다면 아래와 같았을 것이다. JWT이기에 위와 같은 과정만으로 접근 허가를 확인 받을 수 있다.

jwt_before

많이 강조되는 점은 아니지만, 성능상으로도 충분히 합리적이다. 생성 자체는 가볍다지만, 정보를 다시 조회하기 위해서 DB 종속성을 갖게 되는 일반 토큰과 달리 정해진 규칙대로 풀고 확인하면 되기 때문이다.


참고 링크

Java-JWT

Angular-JWT

JWT