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