Jekyll Build Speed Up!

종종 제 Github page는 빌드가 실패합니다. 물론 대략적인 이유는 알고 있었습니다. Github page는 약 최대 13분 전후 정도의 build time을 가질 수 있는데, 이를 넘어가게 되면 pending 되거나 실패합니다. 제 Jekyll의 빌드 시간이 15분 정도 걸리던 상태로 당연히 실패하는 경우가 발생했었죠.

이는 Github Page에서 Jekyll 사용 시 자동 빌드 기능을 제공하는데, 해당 기능에서만 제한이 있습니다. 별도로 Github action으로 구성하시만 빌드 Limit이 길기 때문에 크게 걱정은 없을 것 같습니다.

그래서 오늘은 제가 Jekyll 빌드 속도를 올릴 수 있는 방법들에 대해 이야기하려고 합니다.

Analysis #

우선 빌드에 어떤 부분이 가장 많이 소요되고 있는지 찾아야 합니다. Jekyll은 build 시 --profile 이란 옵션을 통해 각 파일에 대한 빌드 소요시간을 알 수 있습니다.

bundle exec jekyll serve --profile
Filename                        | Count |      Bytes |    Time
--------------------------------+-------+------------+--------
_layouts/default.html           |   790 | 131322.55K | 405.412
_includes/navigation.html       |   790 |  61779.70K | 266.733
_includes/header.html           |   790 |  42560.48K | 179.197
_includes/head.html             |   790 |  52723.54K | 136.753
_includes/footer.html           |   790 |  22425.51K |  88.645
_layouts/post.html              |   649 |   8993.10K |   1.292
_includes/article-content.html  |   113 |   2383.00K |   0.637
sitemap.xml                     |     1 |     95.25K |   0.518
_layouts/tag_page.html          |    40 |   1630.02K |   0.453
_includes/social-links.html     |   790 |    729.82K |   0.187
_includes/javascripts.html      |   790 |    369.82K |   0.168
_layouts/redirect.html          |   633 |    380.09K |   0.080
_includes/google-analytics.html |   790 |    318.62K |   0.074
_pages/archive.html             |     1 |    133.15K |   0.066
search.json                     |     1 |    178.49K |   0.031
_includes/main.scss             |   790 |   2453.32K |   0.03
....

그러면 어떤 부분에서 시간을 많이 잡아먹는지 알 수 있습니다.

My case #

제 경우에는 의외로 navigation 쪽에서 큰 오버헤드가 발생했었는데, 이는 Liquid의 무분별한 사용으로 인해 발생한 문제였습니다. Liquid 문법이 페이지를 만들기 쉽고 편리하기 때문에 자주 사용되지만, 만약 빌드 되어야하는 파일이 많다면 이는 반대로 속도에 큰 영향을 줄 수 있습니다.

기존 코드를 보면 네비게이션(메뉴)을 위해서 모든 Post에 모든 Page의 이름을 읽어와서 넣어주는 로직이 있었느데, 이는 다시 말해 1,000개의 포스트가 있다면 1,000 x page의 갯수 만큼의 루프를 발생시켰었습니다. 굉장하죠 😮

<ul class="nav__list list-reset">
  {% for page in site.pages %}
    {% unless page.name == 'tags.html' or page.name == '404.html' %}
      {% if page.show != 'none' %}
      {% if page.title %}
      <li class="nav__item">
        <a href="{{ page.url | prepend: site.baseurl }}" class="nav__link">{{ page.title }}</a>
      </li>
      {% endif %}
      {% endif %}
    {% endunless %}
  {% endfor %}
</ul>

그래서 아래와 같이 고정 데이터로 수정하엿습니다.

<ul class="nav__list list-reset">
<li class="nav__item"><a href="/about/" class="nav__link">About</a></li>
<li class="nav__item"><a href="/archive/" class="nav__link">Archive</a></li>
<li class="nav__item"><a href="/cullinan/" class="nav__link">Cullinan</a></li>
<li class="nav__item"><a href="/phoenix/" class="nav__link">Phoenix</a></li>
<li class="nav__item"><a href="/resources/" class="nav__link">Resources</a></li>
</ul>

덕분에 결과는 엄청났죠 :D

_layouts/default.html           |   791 | 70615.56K | 149.066
_includes/head.html             |   791 | 52790.21K | 147.258
_layouts/post.html              |   650 |  9000.80K |   1.366
_includes/article-content.html  |   113 |  2386.38K |   0.640
_includes/footer.html           |   791 |  2164.44K |   0.602
_layouts/tag_page.html          |    40 |  1632.33K |   0.452
sitemap.xml                     |     1 |    95.39K |   0.443
_includes/header.html           |   791 |  2035.43K |   0.389
_includes/javascripts.html      |   791 |   370.28K |   0.183

기존: 405+266+179+137+88 = 1075

현재: 149+147 = 296

결국 1075 - 296 으로 779 초의 시간이 절약됬습니다. 대략 10분이 넘는 시간이네요. 페이지 정상 노출되는지 테스트하고, github page에 반영해봤는데, 기존에 커트라인에 걸쳐있던 빌드타임이 5분 내로 끝나는 안정적인 모습이 되었습니다.

Speed-Up Tips #

Optimize Liquid #

제 사례를 보면 아시겠지만, 잘못된 로직의 Liquid는 빌드 타임을 굉장히 늘릴 수 있습니다. 그래서 이를 분석하고 줄여나가는 과정을 통해 좀 더 빠르게 만들 수 있습니다. 굳이 불필요한 반복문 등은 줄이고 Static하게 직접 찍어주는 형태로 전환하면 사이트의 구성에 따라 큰 효과를 얻을 수도 있습니다

추가로 liquid-c Gem도 있습니다. Liquid 문법의 처리를 C로 처리하면서 조금 더 속도를 올릴 수 있다고 하네요. 적용은 Gemfile에 liquid-c를 명시해준 후 패키지 설치(bundle install) 이후 빌드하시면 됩니다.

gem 'liquid-c'

Using Github Action #

Github Page 배포의 기본 빌드는 Jekyll의 기본 옵션을 사용하여 빌드합니다. 그래서 모든 페이지를 빌드하기 때문에 속도가 느릴 수도 있습니다. 만약 속도가 문제가 되었다면 기본 Github page 기능으로 빌드하지 않고 jekyll-action 등의 Github action을 통해 빌드하는 것이 좋습니다.

Limit이 매우 큰 것도 좋지만, Action 자체에서도 캐싱을 사용하여 빌드 속도를 줄여주기 때문에 기본 빌드보다는 확실히 빠릅니다.

name: Deploy Job

on:
  push

jobs:
  jekyll:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2

    - uses: actions/cache@v2
      with:
        path: vendor/bundle
        key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile') }}
        restore-keys: |
          ${{ runner.os }}-gems-

    - uses:  helaili/jekyll-action@v2
      with:
        token: ${{ secrets.GITHUB_TOKEN }}

Include Cached #

jekyll-include-cache란 Gem을 통해 재 빌드가 불필요한 파일을 캐시하도록 명시할 수 있습니다.

gem `jekyll-include-cache`

위와 같이 Gemfile에 명시한 후 include 시 캐시가 필요한 부분(수정되지 않을 구간들)은 아래와 같이 include_cached 로 include 하면 좀 캐시하여 매번 새로 빌드하지 않게 됩니다.

{% include_cached header.html %}

Flags #

limit_posts #

jekyll 빌드 또는 serve 시 --limit_posts 플래그를 사용하면 posts의 갯수 제한을 걸어 빌드할 수 있습니다. 지정한 수 만큼의 최신 글만 볼 수 있기 때문에 로컬에서 글을 작성하고 실시간으로 결과를 확인할 때 유용합니다. (굳이 불필요한 파일까지 빌드할 필요가 없으니깐요)

bundle exec jekyll serve --limit_posts=10

incremental #

jekyll 빌드 또는 serve 시 --incremental 플래그를 사용하면 변화가 있는 페이지만 리빌드하게 됩니다.

bundle exec jekyll serve --incremental

References #