웹호스팅환경에서 git push 때 자동으로 pull 하기

ssh, bare repository, post-receive hook 을 사용해 로컬저장소에서 push 하면 호스팅 서버에서 자동으로 pull 받을 수 있는 환경을 꾸며봅니다.

Bitbucket 에서 private 저장소를 만들어 버전관리를 하고 있는 프로젝트가 있다. 웹 프로젝트이고 이 프로젝트의 결과물은 Cafe24 에 월 500원짜리 웹호스팅에서 서비스된다.

처음엔 remote origin 을 Bitbucket 으로 두고, 이 저장소를 통해 로컬과 호스팅 서버간의 통신을 했다. 소스를 고치고 push 하고, 호스팅서버의 커맨드라인에서 pull 을 해야했으니 매우 불편했다. ftp 때는 저장만 하면 자동으로 ftp 로 올려주는 프로그램도 있었는데, 더 좋은 도구를 쓰는데 해야할 일은 더 많아졌다.

물론 이럴 때 쓰기 위해 hook 이 존재하는 것이다. 호스팅서버에서 커맨드라인으로 git을 지원하므로 호스팅 서버에 베어 저장소를 만들어 hook 을 걸었다.

아래는 그 과정이다.

호스팅서버에서 git bare 저장소를 만든다.

elco@hosting $ git init --bare ~/myproject.git

git bare 저장소의 .git/hooks/post-receive.sample 파일을 편집해 post-receive 로 다시 저장한다. 나의 경우는 아래 처럼 했다. 아래 내용은 getting “fatal: not a git repository: ‘.’” when using post-update hook to execute ‘git pull’ on another repo 에서 참고했다.

cd ~/myproject || exit
unset GIT_DIR
git pull origin master

호스팅 서버에 서비스 디렉토리를 만들고 bare 저장소를 clone 해둔다.

elco@hosting $ git clone ~/myproject.git ./myproject

이미 존재하고 있는 로컬 저장소에서 새로운 git bare 저장소를 origin 으로 설정하고, push 한다. 기존의 Bitbucket 프로젝트는 bb 라는 이름으로 다시 저장했다. (이미 origin 이 존재한 경우만 아래와 같이 한다. origin 이 없었던 경우에는 clone 으로 한다.)

elco@mymac $ git remote rename origin bb
elco@mymac $ git remote add origin elco@hosting:~/myproject.git
elco@mymac $ git push origin master

로컬저장소에서 내용을 변경해 push 해 테스트한다.

elco@mymac $ git commit -am "hook test"
elco@mymac $ git push origin master

처음부터 hook 을 사용할 것을 생각하지 않아 remote origin을 지우고 다시 넣고 하긴 했지만, 작업을 진행하면서 계속 머리속을 맴도는 질문이 있었다.’이런 작업을 svn 에서는 어떻게 처리했지?’

서버를 떼어다 옮기거나 리모트 스크립트를 호출하지 않으면 중앙집중식에서는 이와 같은 방법으로는 처리할 수가 없다. 이것이 바로 중앙집중저장소와 분산저장소의 차이가 된다. 여러 서버 저장소를 두고 선택적으로 push 할 수 있다는 점. 이것이 분산 버전관리의 장점이라고 생각한다.

p.s

  • cafe24 의 git 을 활용하면 월 500원짜리 private git 저장소를 만들어 사용하는 것도 가능하다.
  • 호스팅 서버에 Bitbucket 저장소도 remote 로 추가하고 hook 에서 git push bitbucket 이라는 라인도 추가해놓으면 push 한번에 bitbucket 과 호스팅 서버에 동시 저장도 가능하겠다.

jQuery 요소 선택의 성능문제, 셀렉터와 탐색 중 어떤 것이 더 빠를까?

스택오버플로를 둘러보다 아래와 같은 질문을 찾았다.

Is jQuery traversal preferred over selectors?

jQuery 에서 요소 선택의 성능문제를 묻는 질문인데,

$("#vacations").find("li").last();

$("#vacations li:last");

이 두 가지중 어떤 것이 더 빠를까? 하는 질문이다.

질문자도 그렇고, 개인적인 생각으로도 처음엔 후자가 더 빠르지 않을까했는데, 베스트 답변은 생각과는 조금 달랐다.

결론부터 말하자면 jQuery 셀렉터가 아니라 표준 CSS 셀렉터를 사용하면 빠르다. 그렇지 않으면 탐색이 더 낫다

답변이 좀 길지만, 읽어볼 만한 가치가 있어보여 아래 번역한다.

There’s no cut and dry answer to this, but with respect to the :last selector you’re using, it’s a proprietary extension to the Selectors API standard.Because of this, it isn’t valid to use with the native .querySelectorAll method. What Sizzle does is basically try to use your selector with .querySelectorAll, and if it throws an Exception due to an invalid selector, it’ll default to a purely JavaScript based DOM selection/filtering. This means including selectors like :last will cause you to not get the speed boost of DOM selection with native code.

이 문제에 대한 명백한 해답은 없지만 질문에 나와있는 :last 셀렉터에 집중해 살펴보도록 하겠다. :last 셀렉터는 Selector API 표준을 jQuery 전용으로 확장한 것이다. 따라서 이 셀렉터는 네이티브 .querySelectorAll 메서드로는 사용할 수 없다.

Sizzle 은 기본적으로 요청받은 셀렉터를 .querySelectorAll 로 동작시켜본 후, 올바르지 않은 셀렉터로 인한 예외가 발생하면 순수 JavaScript 기반으로 DOM 선택, 필터링 한다.

즉 :last 등이 포함되면 네이티브 DOM 셀렉션의 빠른 속도를 얻을 수 없게 된다.

Furthermore, there are optimizations included so that when your selector is very simple, like just an ID or an element name, the native getElementById and getElementsByTagName will be used, which are extremely fast; usually even faster than querySelectorAll. And since the .last() method just grabs the last item in the collection instead of filtering all the items, which is what Sizzle filters normally do (at least they used to), that also will give a boost.

게다가 셀렉터가 ID나 태그이름만 사용된 경우처럼 아주 간단하다면 정말 빠른 네이티브 getElementById 와 getElementsByTagName 을 사용하도록 최적화 로직이 내장되어있다. . 이들은 일반적으로 querySelectorAll 보다 훨씬 빠르다.

그리고 .last() 메서드는 일반적인 Sizzle 의 필터처럼 모든 아이템 중에서 필터링하는 것이 아니라 그저 컬렉션에서 마지막 아이템을 가져오는 형태로 동작하므로 여기서도 이점을 찾을 수 있다.

IMO, keep away from the proprietary stuff. Now that .querySelectorAll is pretty much ubiquitous, there are real advantages to only using standards-compliant selectors. Do any further filtering post DOM selection. In the case of $(“#vacations”).find(“li”), don’t worry about the interim results. This will use getElementById followed by getElementsByTagName, and will be extremely fast. If you’re really super concerned about speed, reduce your usage of jQuery, and use the DOM directly.

개인적으로는 jQuery 전용 셀렉터를 배제하는 것이 좋겠다고 생각한다. .querySelectorAll 의 쓰임새가 훨씬 많은데, 표준 셀렉터를 사용해야만 그 이점을 누릴 수 있다. 그 이후의 필터링들은 DOM 셀렉팅 이후에 진행하도록 하자.

$(“#vacations”).find(“li”) 의 경우는 성능에 대해 크게 고민하지 않아도 된다. 이들은 getElementById 후에 getElementsByTagName 을 사용할 것이고, 이들은 정말 빠르기 때문이다.

진정으로 속도에 대해 고민한다면 jQuery 를 사용하지 말고 DOM 에 직접 접근하는 것이 좋다.

You’ll currently find notes in the docs for selectors like :last, that warn you about the performance loss:

Because :last is a jQuery extension and not part of the CSS specification, queries using :last cannot take advantage of the performance boost provided by the native DOM querySelectorAll() method. To achieve the best performance when using :last to select elements, first select the elements using a pure CSS selector, then use .filter(“:last”).

But I’d disagree that .filter(“:last”) would be a good substitute. Much better would be methods like .last() that will target the element directly instead of filtering the set. I have a feeling that they just want people to keep using their non-standards-compliant selectors. IMO, you’re better of just forgetting about them.

jQuery 문서에도 :last 와 같은 셀렉터들의 성능문제에 대한 내용이 추가되었다.

:last 는 CSS 명세에 포함되지 않은 jQuery 의 확장이므로, :last 쿼리를 사용하는 것은 네이티브 DOM querySelectorAll() 의 성능향상을 기대할 수 없다. :last 를 사용해 요소를 선택할 때 최고의 성능을 얻으려면 먼저 순수 CSS 셀렉터로 요소를 선택하고, .filter(“:last”) 를 사용하라.

하지만 .filter(“:last”) 가 좋은 대체물이라고 생각하지는 않는다. .last() 와 같은 메서드들이 컬렉션을 필터링하기보다는 곧바로 요소를 잡아낼 수 있어서 훨씬 낫다. 내 느낌엔 이들이 사람들이 계속 비표준 셀렉터를 사용하길 원하고 있는 것 같다. 그러니 이건 그냥 잊는게 좋다고 생각한다.

그러니 jQuery 셀렉터보다는 표준 CSS 셀렉터를 숙지해 사용하도록 하자 🙂

자바스크립트 파일이 세미콜론으로 시작하는 이유

어떤 코드를 살펴보면 자바스크립트 파일이 세미콜론으로 시작하는 때가 있다.

;(function () {})();

왜일까 궁금했었는데 스택 오버플로를 살펴보니 대략 아래와 같은 답변이다.

이 세미콜론은 자바스크립트 파일을 합칠 때(concatenating) 안정성에 도움이 된다. HTTP Request 개수를 줄이기 위한 목적으로 파일을 합쳐서 전송할 파일의 개수를 줄이는 경우가 있는데, 이때 순서상 해당 파일의 직전 파일이 세미콜론으로 끝나지 않으면 파일을 합쳤을 때 제대로 동작하지 않기 때문이다.

다시말해, a.js 와 b.js 를 합쳐 ab.js를 만들 때

a.js

(function () {alert('a');})()

b.js

(function () {alert('b');})();

ab.js

(function () {alert('a');})()(function () {alert('b');})();

이렇게 되면 문제가 될 수 있다는 것이다.

closure compiler 나 uglify 같은 프로그램을 통해 작업을 수행한다면 문제가 없겠지만, 수동으로 합치는 경우는 문제가 발생할 수도 있겠다.

물론 이런 문제를 인식하고 있는 개발자라면 자기가 개발한 파일의 끝에 세미콜론은 꼭 붙여주겠지.

Semicolon on the beginning of the JavaScript Files

Facebook Comment 를 Disqus 로 내보내는 FB2Disqus

페이스북의 Social comment 를 잘 사용하고 있었지만, Disqus 로 옮기고 싶었다.

페이스북 사용자가 아니면 댓글을 달기 어려운 면이 있기 때문에 좀 더 확장하고 싶었다. 그리고 더 큰 이유는 디자인 이었다.

블로그를 새로 꾸미면서 블로그 글 영역을 가변폭으로 만들었는데 Facebook 의 social comment 는 고정폭 만 지원하고 있다. 그리고 페이스북의 댓글창보다는 Disqus 의 디자인이 더 아름다워 보이고 내 블로그에 더 잘 어울린다는 생각이 들었다.

Exporter 쯤은 있겠지 생각만 하고 있다 검색해보니 나오질 않아서 하나 만들게 되었다.

우선은 요즘 주로 사용하고 있는 Coffeescript 로 만들려고 했다. 마이그레이션은 필요한 데이터를 모으고 재구성해서 다시 API를 호출해야 하는 과정인데, 이 과정을 비동기식으로 작성하려니 괜히 헷갈리고 코드 모양은 안나니 기분이 좀 상했다. 그 덕에 Promise 공부만 더 하게 됐다.

그래서 오랫만에 PHP로 작업했다. 두 서비스 모두 PHP SDK 를 지원하고 있어서 어렵지 않게 구현할 수 있었다.

Disqus 에서 작성자의 프로필 사진을 강제로 넣을 수가 없어 프로필 사진은 이전하지 못했지만 우선은 만족. Github 에 올렸다. FB2Disqus on Github

테스트가 없는 것과 FQL 을 쓰면 좀 더 나을 뻔 했는데, 뭐 그건 문제가 생기면 작성하기로 하고..

그나저나 댓글창도 바뀌었는데 댓글 하나 달아주지 않으시렵니까? 🙂

Facebook social comment export to Disqus

FB2Disqus on Github

SSI 를 활용한 프론트엔드 리소스 버저닝

개요

리소스 버저닝은 리소스 캐시를 목적으로 한다. 리소스 캐시는 해당 파일이 변경된 경우 컨텐츠를 새로 전송(Cache Miss)하고, 변경되지 않았다면 컨텐츠를 전송하지 않는(Cache Hit)다.

많이 사용되는 방법으로 버전이름을 쿼리스트링(Query String, GET 파라미터라고도 함)에 명시하는 방법이 있다.

버전이름을 쿼리스트링에 명시하는 방법은

<script src="vendors.js?v0.9.5"></script>
<script src="app.js?v0.9.5"></script>   

이와 같이 버전이름(v0.9.5)을 파라미터로 적어주고, 이후 버전이 올라가면 버전이름을 변경시켜주는 방법이다.

<script src="vendors.js?v0.9.6"></script>
<script src="app.js?v0.9.6"></script>

이 방법은 파일 이름을 변경하지는 않지만 파일의 내용이 변경될 때마다 주소를 변경시키므로 브라우저나 웹서버 간에 혹시 발생할 수 있는 캐시가 먹어 파일이 갱신되지 않는 문제를 방지할 수 있다.

일괄 버전 업데이트? 새로 업데이트된 파일만 버전 업데이트?

하지만 위 방식에도 맹점이 있다. 만약 0.9.6 업데이트가 app.js 에만 관련되어 있다면 vendors.js 는 굳이 새로 전송할 필요가 없는데도 다시 전송하게 되므로, 버전을 모두 변경하는 방식은 약간의 손해를 감수해야 한다.

현재 Baas.io 프로젝트에서는 1. 빌드 프로세스에 버전명을 명시하기 까다롭고, 2. 잘 변경되지 않는 라이브러리파일과 자주 변경되는 어플리케이션 파일을 구분하고 있어서 새로 업데이트된 파일만 버전을 올려주는 방법을 적용했다.

자동 버저닝

SSI 코드

<!--#config timefmt="%s" --> <!-- 시간을 읽는 방법을 Unix Timestamp 로 설정 -->
<link href="/css/styles.css?<!--#flastmod virtual="/css/styles.css" -->" rel="stylesheet" type="text/css" /> <!-- last modified 타임을 GET 파라미터로 넘김 -->
<script type="text/javascript" src="/js/vendor/vendors.min.js?<!--#flastmod virtual="/js/vendor/vendors.min.js" -->"></script>
<script type="text/javascript" src="/js/app/common.min.js?<!--#flastmod virtual="/js/app/common.min.js" -->"></script>

결과

<link href="/css/styles.css?1355283501" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="/js/vendor/vendors.min.js?1359420532"></script>
<script type="text/javascript" src="/js/app/common.min.js?1360469267"></script>

새로 업데이트된 파일의 버전을 올려주는 방법을 자동화하기 위해 파일의 각 마지막 변경시간(Last Modified Time) 을 Unix 타임스탬프형식으로 쿼리스트링으로 넘겨주도록 했다.

이렇게 구성하면 각 파일의 마지막 변경시간(서버에 탑재된 시간)이 이 파일의 버전이 되므로 선택적으로 버저닝이 가능하다. 그리고 마지막 변경시간을 SSI 를 통해 구하도록 되어 있어서 수동으로 버전을 올려줄 필요가 없다.

장점

  • 버저닝 최적화/자동화
    • 릴리즈 주기에 맞춰 버전을 일괄변경 한 것이 아니라 각 파일마다 버저닝했으므로 업데이트 되지않은 파일들은 이전 캐시를 그대로 사용할 수 있다.
  • 간단한 적용
    • 빌드 과정에 손을 쓰지 않고 SSI 코드를 삽입한 것이라 간단하게 적용할 수 있었다.

단점

  • HTTP Request당 오버헤드 증가
    • 파일의 마지막 변경시간을 구해야 하므로 Disk IO 발생이 늘어나게 된다.

P.S.

squid 등 일반적으로 많이 사용되는 프록시 서버에서는 쿼리스트링이 달려있는 리소스들은 캐시를 타지 않고 모두 새로 받아온다고 한다. 이런 이유로 쿼리스트링으로 버저닝하는 것보다 파일명 자체를 변경시키는 것이 더 낫다고 한다. Revving Filenames: don’t use querystring