2019년 5월 17일 금요일

[Webpack 공부 #2] Webpack with HTTP2?


...그리하여 Webpack의 가장 큰 사용목적인 모듈 패키징이 무엇인지 알기 위해, HTTP1.1에 대해서, 또 동시에 HTTP2에 대해서 알게되었습니다.

더 깊게 웹팩을 파기 전에, 저는 지금즘 한가지 생각이 들었는데
"그렇다면, HTTP2 방식을 사용하면 웹팩의 존재가 필요없지 않는가?"
였습니다.

결론적으로 웹팩과 HTTP2를 병행하게된다면, 더 높은 성능향상을 꽤할 수 있다 입니다.
어찌보면 당연한 말입니다.

특정 페이지를 로딩할때 필요한 리소스들이 아래와 같다고 한다면
index.html
default.css
default.js
favicon.co
title.png
총 5개의 리소스가 필요로 하며,
Http2를 이용한 서버에서 클라이언트가 처음으로 index.html을 요청하며 connection을 만들면,
서버는 5개의 스트림을 만들어 전송을 하게 됩니다.

하지만 웹팩을 통해 번들링을(예를들어) 통해 단 하나의 번들로 묶는다면,
스트림은 단 한번만 전송하면 되기 때문입니다.

또 이렇다고 마냥 모든 파일들을 번들링하기엔 석연찮은 구석도 있습니다.



번들링의 수준
번들링을 하게된다는것은 여러 파일들을 합쳐서 제공한다는 말입니다.
그렇다면 모든 js파일들을 번들링하는것은 옳은걸까요? 분명 connection/stream은 단 한번이니 좋을것 입니다. 하지만 다른쪽 성능을 생각해 본다면 어떨까요?

모던 js를 이용해 모듈화를 한 경우의 윂페이지라면, 페이지나 작업마다 사용하는 js파일들이 다를것 입니다. 이런 경우라면 모든 js파일들을 다운로드받고 싶지 않을것 입니다. 모듈화 한 이유가 없으니깐요.

모든 js파일들을 번들링 하게된다면, 모든 페이지마다 하나의 큰 파일을 받아야 할것입니다. 설상 그 js파일들이 쓸모없을지라도 말이죠.

그럼 특정 js파일들만 번들링을 하고, 필요에따라 제공하는것은 어떨까 하는 생각이 들것입니다. 번들링 된 파일로 stream 횟수는 적당히 줄어들며,불필요한 js를 로드하느라 성능 낭비를 할 필요도 없을거구요.

하지만 이 하이브리드 구조와, 번들링을 하지 않은 js파일들의 벤치마크를 했을때 결과는 놀랍게도 의도와 많이 달랐다고 주장합니다.

위의 글에 따르자면, 번들러를 사용할떄 쓰는 압축 기술은 대용량 파일의 압축에 더 유리하기 때문에, js 모듈따위의 작은 용량의 파일은 압축 비용이 원본비용보다 높은 문제를 지적합니다.

이러한 데이터들과 벤치마크를 통해 지금까지 중론처럼 나온 결론은
번들링은 HTTP1.1 과 HTTP2 를 막론하고 성능향상을 유도할 수 있으나,
과도한 번들링은 서버의 캐싱문제로 다른 퍼포먼스 이슈를 초래할 수 있습니다.
결과적으로 상황과 기능 등에 맞게 세분화 한 번들링 / 하지만 너무나도 세세한 경우라면 stream이 증가할 것이므로 너무 세세하지는 않게 적당한 번들링을 사용해야 한다는 것 입니다.




이제 기본적인 웹팩의 용도를 넘어 좀더 향상된 사용방법을 간단하게 훑어보았으며, 이제는 실제로 코드를 짜 봐야 할 시간인거 같군요.

[Webpack 공부 #1] Http1.1 and Http2

새로 취직하게 된 직장에서는 어렴풋이 모든것을 처음부터 시작해야하는것이 아닌가 하는 판단이 들기 시작하고 있습니다.
코드관리, 서버, 배포, 모든 환경을 스스로 정의해야할지도 모른다는 생각이 점차 들기 시작하여, 저는 개발환경 구축부터 서비스를 위한 환경구축에 대한 정보 탐색을 시작했습니다.

당장 머릿속에 떠돌던 몇가지 자동화 혹은 개발환경의 힌트들이 면접중에 떠올랐습니다.

GitLab Jira DevOps Docker Jenkins..

왜냐하면 저에게 '환경 구축'이라는 단어들에 대해서 매우 생소하고 무지렁이인 상태였기 때문입니다.

무지렁이가 할수있는것은 리서칭 이지요!

우선 요청도 있고 해서 먼저 리서칭 한것은


Webpack 입니다.

웹팩이란 js 모듈이나, 기타 리소스들을 하나로 묶는 모듈 번들러 입니다. 그럼 왜 js나 리소스들을 하나로 묶는가에 대해 알아보아야 합니다. 그것을 알기 위해서는 HTTP1.1에 대해서 알아봐야 합니다.

HTTP 1.1 vs HTTP 2

기본적인 웹 프로토콜에서는 1요청당 1응답만을 돌려줍니다. 이 말은 하나의 웹 페이지에 필요한 리소스 파일들이 아래와 같다고 하면 [ index.html | default.css | foo.js | favicon.jpg | index.png ] 우리는 HTTP 요청과 응답 handshake 를 5번 해야 모든 윂페이지 로딩이 정상적으로 끝나게 됩니다. 5번의 지연시간이 소요되는것 입니다. HTTP 1.1의 전송방식에 문제점은 몇가지가 있는데,
1. HOL Blocking ( Head of Line Blocking)
동일한 네트워크 큐에 있는 패킷들이 Head부분의 패킷에 의해 지연되는 형상을 야기하는데,
웹 환경에서 HOL Blocking은 두가지가 있습니다.
HTTP HOL Blocking
TCP HOL Blocking
HTTP1.1 에서 앞서 언급된 여러개의 리소스를 모두 받기 위해서는 하나의 파일당 하나의 요청을 주고받는 행위를 없에기 위해 pipelining이 나왔습니다. 이 기술은 단 한번의 커넥션을 통해 TCP socket을 연결하는 방식입니다. 소켓이 연결이 되면, 해당 소캣을 통해 리소스들을 하나하나 순차적으로 받는 방식입니다. 이 말은 HTTP1.1에서는 전송방식이 반드시 동기화 형식으로 차례로 받을수 밖에 없다는 문제점을 보여줍니다. 하지만 이방식 역시 socket 통신을 통해 이루어지며, 그러는 와중에 리소스 다운로드에 지연시간이 생길 수 있습니다. 이런 경우, 다음 리소스들은 현재 받고있는 리소스의 다운로드가 완료 될 때 까지 팬딩상태에 머무르게 되며, 이를 우리는 HTTP HOL Blocking 이라고 부릅니다. TCP의 특성상 보낸 데이터가 올바르게 전송되었는지 확인한 후, 실패시 다시 보낼 수 있게 설계되어있는데( 3way hand shacking) 이런 경우가 발생할시, TCP는 전송받은 패킷의 순서를 관리하기 위해, 재전송을 받을때 까지 다음 순서의 패킷을 받지 않게 되며, 이런경우 TCP HOL Blocking이 걸린 상태라고 부를 수 있습니다.

2. 높아지는 RTT(Round Trip Time) 위에서 언급되다싶이, HTTP1.1은 동시에 하나의 connection(물론 세팅을 통해 늘릴수 있다고도 한다)을, 동기적으로 처리하기 때문에, TCP구조인 HTTP의 특성상 3way-handshacking을 하나의 리소스를 받을때 마다 이루어지기 때문에 RTT가 증가됩니다.

3. Header의 비대화 HTTP 1.1 이 되어가며 Header에 입력되는 메타데이터들의 양이 늘어나게 됩니다.그리고 모든 connection을 통한 통신에서는 Header가 반드시 포함됩니다. 특정 경우에 따라 완벽히 동일한 Header가 몇번이고 반복적으로 전송되기도 할것 입니다( 하나의 동일한 높은 용량의 데이터인 경우 페킷을 나누어 전송할 것 이며, 이때 헤더는 페킷 순서와 끝 유무 정도를 재외하면 동일할 것 입니다)


이러한 여러가지 문제점들이 있었고, 솔루션 또한 여러가지 존재하게 되었었습니다.
하지만 근본적인 솔루션들은 아니었으며, HTTP2 가 이러한 문제들을 해결하기 위해 나타났습니다...
그리고 HTTP2는 1.1과 동일한 형태를 유지하되 속도적인 측면을 개선하기 위해 개발되었으므로, 기존 명세와는 큰 차이는 없습니다. 추가는 있겠지만..

그리고 HTTP2에서 소개하는 기술은 아래와 같습니다.
Multiplexed Streams
클라이언트는 하나의 connection에서 이제 여러개의 메세지들을 받을 수 있습니다. 그리고 이제 순서를 따지지 않으며, stream으로 받게 됩니다.
Stream Prioritization
순서를 따지지 않고 데이터들을 받을수 있다는것은, 보여줄 내용을 순서에 맞게 보낼 수 없다는 의미이기도 합니다. 예를들어 js파일을 먼저 받고싶지만, 순서를 따지지 않게되기 때문에, 어느 타이밍에 js파일을 받는지 알 수 없게 됩니다. 그러한 이유로 각각의 리소스파일에 우선순위인 가중치 를 부과 할 수 있으며, 이를통해 무엇을 우선적으로 받을 수 있습니다.
Server Push
이제 클라이언트의 요청이 있지 않아도 서버는 임의로 데이터를 전달 할 수 있습니다. 기존의 1.1 에서는 클라이언트는 html을 먼저 받은뒤, 이후에 추가적으로 무슨 리소스가 필요한지 파악을 할 수 있었고, 요청을 보낼 수 있었습니다. 하지만 Server Push를 이용하여, 클라이언트가 첫번째 요청을 하게되면, 서버측에서 추가적으로 필요한 리소스들을 찾아내어 클라이언트의 추가 요청없이 임의로 전송을 하는 방식입니다. 이렇게 함으로써 connection 추가 요청 수를 줄이는 방법으로 성능향상을 꽤합니다.
Header Compression
무겁고 중복적으로 전송되는 Header 다이어트를 목적으로, Header Table과 Huffman Encoding 기법을 사용하게 됩니다. Header Table는 처음 전송받은 Header를 특별한 Table에 저장 해 두며 또다시 헤더를 요청받게 되면, 기존에 저장된 헤더 table과 diff를 때려서, 다른 부분만 명세하여 전송됩니다. 이렇게 함으로써 중복되는 header 데이터 전송 낭비를 해결했으며, 상당한 수준의 로드타임 개선을 이루었습니다.


2019년 5월 7일 화요일

방어 코딩?

첫번째 회사에 근무할때 완전히 다른 파트를 담당하느라 바쁘긴 해도, 사수분이 내 코드의 리뷰를 이따금 해 주곤 했었다. 또 아이러니하게도 그런 리뷰를 받는 날에는 항상 시간에 좇겨 어쩔수없이 짜거나, 진퇴양난으로 인해 만들어진 자부심이라곤 눈꼽만큼도 남아있지 않은 슬픈 코드들이 항상 비류 대상이었고, 항상 부끄러운 배움의 시간이었다.

어쩌면 내가 울상이거나 한숨을 쉬거나 '이런 코드를 정말로 라이브시켜도 되나' 하는 표정이었을때 리뷰를 해주셨던게 아닐까 글을 쓰다보니그런 생각이 흘러간다.

아무튼 이러한 리뷰시간을 갖으면서, 나는 짧은 개발자 인생에서 처음듣는 단어가 있었는데 바로 방어코딩 이라고 그는 불렀다.

그당시 내 보수중인 코드는 spring java였었으며, expected value가 다를수 도 있었기 때문에, 또 내 개발 커리어의 경험에 따라 - 특히 그당시 javascript의 undef value 로 크게 혼이 난적이 있었기 때문에 - 나는 가지고오는 value나 params 를 검사부터 하는것을 지향했었다.

예컨데 이런 함수의 형태를 만들어야 한다면 :

private foo getFooValueFromBar ( Bar bar ) {
 return FooMaker(bar);
}

난 대게 이러한 방식으로 함수 안을 보강했었다.
foo myFoo = null;
if ( bar == null || bar == '' ) {
 throw new Error("givin param is null");
} else {
 myFoo = FooMaker(bar);
 if ( myFoo != null ) return myFoo;
 else throw new Error("returned foo is null");
}

난 아직도 이게 그렇게 틀린 코드라고 생각하지는 않는다.
사수 역시 동의했다. 예외를 모두 under control 하는점은 좋으나, 굳이 이런게 필요하지 않은 경우까지 모두 한다는것이 내 사수분의 의견이었다.

그의 주장은 이해할 수 있었다. 왜냐하면 그 당시 메인 프로젝트는 이미 수많은 코드들의 스파게티로 엉망인 상태였으며, 우리는 이 스파게티코드를 대대적으로 변경하는 v2.0 작업을 진행하기 전까지는 '최대한 덜 지저분하게 판을 벌려가며 새 기능을 넣을것' 이라는 둘만의 목표의식을 지나고 있었다. (하지만 잘 지켜지지는 못했다 적어도 난)


최근 내 여자친구의 사수가 방어코딩이라는 언급을 하며 코드를 소개한 적이 있었다.
그녀는 spring java를 담당하던 이전직장의 나와는 달리, javascript를 담당하는 포지션이었으며, 그녀의 사수는 최적화와 숏 코딩에 대한 상당한 지지와 자부심을 가진 자신감 넘치는 사수였다.

그는 반대로 방어코딩에 대하여 매우 긍정적이며, 오히려 더 세밀하게 해야한다는 취지의 주장을 했다( 적어도 그렇게 이해했다. 내가 들은 이야기는 아니니깐 말이다 ) 그 역시 외주를 통해 만든 말라비틀어진 스파게티 수준의 코드들로 surrounded되어, 매일을 히스테리성 발작을 보일정도로 괴로워하는 사람이었으며, 내 사수분과 같은 생각인 2.0까지만 이 코드들을 최대한 아름답게 보강하며, 이후에는 새 버전을 올리며 버리자는 주장을 했었다.

이러한 글을 쓰기전에 나는 어떻게 두명의 3년차 4년차 개발자 선배들이 각기 다른 의견을 제시하는지에 대해 생각을 해 봤었고, 무엇이 이들을 상반된 주장을 하게 되는가 하는 호기심에 정리를 해 보기 위해 쓴 단순한 글이다.

java개발자로 5년차가 되어가고 있던 그는, 한 스타트업의 모든 프로덕트를 혼자서 2년넘게 관리해 오며, 언제나 스스로를 낮추며 공부를 멈추지 않은 훌륭한 사수였으며,
javascript 개발자로 3년차를 보내고 있는 그는, 상당히 유능한 실력으로 인정받아, 그 역시 또 다른 스타트업의 프론트앤드를 총괄하고있는 팀장이며, 항상 자신감 넘치고, 배움에 대해서 즐거움을 느끼는 타입이다.

둘다 이전에 만들어진 '완성하기 급급한' 프로덕트들로 괴로워하며, 여유만 생긴다면 새 프로덕트로 갈아탈 기회만을 노리고 있는 사람들이었다.

그렇기에 난 개발환경과 언어에 따라 두 가지 상반된 의견이 나왔으리라 라는 추측을 하고있다.

개인적으로 느끼는 java와 javascript의 차이점중 하나로 이것을 드는데,
java는 예외발생에 대해서 매우 민감하게 반응을 하고 오류를 뱉고 죽어버리는 반면,
javascript는 예외가 발생해도 은근슬쩍 계속 작동이 되어버리기 떄문에, 실제 오류발생 부분에서 상당히 지난 뒤에, 완전히 코드가 꼬인뒤에에 모든게 폭발한 뒤에에 오류가 나타나버리는것이다.

그런이유로 java는 발생하는 예외에 대해 민감하기 때문에, 추가적으로 개발자가 공들여야 하는 예외사항의 범위가 작으나, javascript는 모든 예외사항을 다 고려해야하기 때문에, 후자가 더 방어코딩이 필요한것이 아닐까 한다.

그래서 방어코딩, 해야하는가 말아야 하는가의 결론은

언제나 그렇듯 '지금 내 상황에 맞게'  ...

CSS border 요약

Border

border 프로퍼티로 굵기, 스타일, 컬러 를 정의할 수 있습니다.

  • 굵기 는 border-width 로써 경계선의 굵기를 정의하며
border-top-width | border-right-width | border-bottom-width | border-left-width
로 각 위치별로 따로 정의할 수 있습니다.

  • border-style에서 경계선의 스타일을 정의할 수 있습니다.
style은
border-top-style | border-right-style| border-bottom-style| border-left-style
로 각 위치별로 따로 정의할 수 있습니다.

  • 마지막으로 border-color로 경계선의 색을 정의할 수 있습니다.


border-width : 1px;
border-style : dotted;
border-color : #000;
위 인자들을 이용해 만든 border는

border : 1px dotted #000;
로 줄일수 있습니다.



border-radius 를 사용하여 둥근 모서리를 만들수 있습니다.
이 값은 border에 둥근 모서리를 만들수 있게 해 주며, border가 설정되어있어야만 보입니다.

border-radius : 1px;
border : 1px solid #000;

위와같이 border값 설정이 필요합니다.

  • border-radius값은 아래의 네가지 프로퍼티들의 조합입니다.
border-top-left-radius | border-top-right-radius | border-bottom-right-radius | border-bottom-left-radius

그러므로 특정 위치만 둥근 모서리를 만들고 싶을때
border-top-left-radius : 1px; border-top-right-radius : 0px;
border-bottom-right-radius : 1px; border-bottom-left-radius : 0px;
혹은
border-radius : 1px 0 1px 0;
으로 표현 할 수 있습니다.

border-radius 의 인자값이 두가지 혹은 세가지로도 표현이 가능하며,
border-radius : param1, param2, param3, param4
param3의 default value는 param1과 동일하며,
param4의 default value는 param4와 동일합니다.


border-image 로 경계선에 이미지 띄우기
해당 프로퍼티는 세가지의 값을 받는데,

이미지 경로, 이미지를 자르는 시작점, 모서리가 아닌 영역에서의 이미지 처리방법 을 정의합니다.

  • 이미지 경로는 말 그대로 background에 쓰일 이미지 경로를 정의하며
  • 이미지를 자르는 시작점의 경우,
우선 border-image에 입력된 이미지는 3x3 으로 총 9개의 이미지가 slice됩니다.
이 slice된 영역들은 border의 각 꼭지점과 면을 담당하게 됩니다.
하지만 필요에따라 이 slice하는 영역을 변경해야하는 경우가 생기는데 이때 이곳에서 slice할 영역을 정의하는것 입니다. 30% 로 자른경우, 
이미지 총 길이에서 30%씩 start, end영역을 자르며, 나머지 영역인 40%는 면에 그리게됩니다.
만일 이 자리는 영역이 50% 가 넘어가게 되면, start와 end영역에만 그려지며, 면에는 그려지지 않습니다(남은 영역이 없기 때문에)
  • 이미지 처리방법의 경우에는 stretch와 repeat 가 있습니다. 위의 자르는 영역에서 나온 면 부분의 이미지를 늘리거나 반복시킵니다.