ashrimp blog

2021 블로그 리뉴얼 완료

Written in 2021/12/04 11:26:54 UTC, categoried as web

Motivation

블로그를 새로 만들자. 블로그의 성능과 사용성을 점검한 뒤 내린 결정이었다. 당시 블로그 백엔드는 Node.js로 작성해 docker로 배포하고 있었다. 메모리 사용량을 분석해보니 상시 100MiB 정도 먹는다는 충격적인 결과가 나왔다. 뭔가 열심히 돌고 있는 것도 아니었다. 나는 적잖이 놀랐다. 심지어 이게 전부가 아니었다! 블로그 검색 기능을 위해 나는 elasticsearch 노드를 직접 운영하고 있었는데, 이 녀석은 메모리를 최소 2GiB 이상 요구한다. 거기에 데이터베이스까지. 다달이 결제되는 AWS 비용이 9만원씩이나 하는 이유가 있었다. 그래서 블로그의 점진적 개선을 위한 작업을 계획했다. 먼저 백엔드를 Go로 다시 작성하기로 했다. Go가 요즘 대세니까.

Go

Go를 처음 접한 내 감상은, 괜찮은 언어지 않나? 일단 C와 비슷해서 문법도 쉽고 간단하고, C++RTTI랑 비슷한 리플렉션도 지원한다. 무엇보다 유저 레벨 스레드인 goroutine이 인상적이었다. 언어 레벨에서 제공하는 channel을 이용하면 동기화도 어렵지 않다. 네트워크 관련 프로그램을 작성하는데는 최적이다. generic이 없는 것은 매우 아쉽다. 그래도 나름 장단점이 확실한 좋은 언어라 생각한다.

Too verbose

그런데 뭔가 좀 여러모로 장황하다는 느낌을 받았다. 에러 처리가 그 중 하나였다. 아래는 Go에서 자주 볼 수 있는 에러 처리 루틴이다. Rust와 비교하면 다음과 같다.

// Go
foo, err := some_function()

if err != nil {
  return nil, err;
}
// Rust
let foo = some_function()?;

웹 애플리케이션에서 위와 같은 처리는 매우 흔하다. 결국 비슷한 코드가 군데군데 있는 꼴이 된다. 개인적으로 이게 많이 불편했다. 비교 대상인 Rust가 훨씬 더 간결하고 우아하다. 또 다른 문제는 generic이 없어서 그런가 map이나 filter같은 유틸리티가 없다는 점이다. 함수를 만들어 재사용하면 되니 커다란 문제는 아니었다.

ORM problem

나는 ORM을 매우 선호한다. raw SQL보다 이식성이 뛰어나고 직관적이기 때문이다. 그래서 entgo를 사용했다. entgo는 정의된 schema에 따라 코드를 자동 생성한다. 덕분에 타입 안정성과 직관성에서 큰 장점이 있었다. 하지만 불어나는 코드와 더러워지는 git 히스토리를 보자니 마음이 심란해졌다. schema 관리도 어려웠다. schema를 수정하려면 코드가 자동 생성 되는 곳 내부에 위치한 폴더를 열어야 한다. 그러면 무수히 많은 파일이 나를 반겨준다. 다른 ORM을 찾아보았지만 딱히 더 쓸만한 것은 없어보였다.

schema 폴더를 열었을 때

SSG

몇 개월 전이었다. 다크 테마로 디자인을 바꿀까 생각했다. 하지만 마음에 들만한 디자인을 만들어낼 능력이 내겐 없었다. 그래서 디자인을 참고할 다른 블로그를 돌아다니던 도중, 터미널스러운 디자인의 블로그를 발견했다. 매우 마음에 들었다. 전통적인 MPA 방식에 Javascript도 없다. 어드민용 페이지도 없고, 블로그 작성기도 당연히 없을 것이다. 완전한 정적 페이지인 그 블로그들을 보고 무언가 열리는 느낌을 받았다. 그 동안 왜 이리 SPA이니 SSR이니 하는 것에게 집착했을까. 하지만 그렇다고 여러 이점이 있는 최신 웹 기술을 완전히 포기할 수는 없었다. SPA(Single Page Application)는 클라이언트에게 부담을 안겨준다. SSR(Server Side Rendering)은 그 부담을 서버가 대신 받는다. 더 좋은 방법은 없을까? 답은 SSG(Static Site Generator)다. SSR을 페이지 빌드 시에 돌린다. 그 결과물을 정적 페이지로 만드는 방식이 바로 SSG다. SSGSPA와 관련이 없기 때문에, 여기에 SPA를 끼얹을 수도 있다. 하지만 Javascript의 완전한 제거를 위해 그렇게 하지 않았다. 이러면 자연스레 블로그 백엔드도 존재 이유가 사라진다. 하지만 여전히 포스트를 관리할 방법은 필요하다. 파일시스템 어딘가에 포스트를 저장해 놓고 git같은 걸로 관리하면 되지 않을까?

SvelteKit

나는 나의 선택권이 있는 개인 프로젝트에선 프론트엔드 프레임워크로 항상 Svelte를 사용한다. 마침 직접 구현한 SSR도 있고, 가져다 개조해서 쓰면 되겠지. 완전히 오산이었다. Webpack으로 빌드한 SSR 컴포넌트인데 SSR시 필수적인 render 함수가 일부 컴포넌트에는 존재하지 않았다. 도무지 원인을 찾을 수 없어서 커뮤니티에 도움을 청해보아도 깜깜무소식이었다. (지금 생각해보면 Webpack이 아니라 rollup.js를 쓰는게 맞지 않았나 싶다.) 그러다 SvelteKit을 써보라는 조언을 받았다. 다행히 사용법은 어렵지 않았다. 기대하던대로 모든게 잘 동작했다. 딱 하나, Javascript가 생긴다는 것만 빼면.

Elder.js

Elder.jsSvelteSSG 프레임워크다. SEO를 굉장히 강조하고 있다. 커뮤니티에 조언을 구했더니 이걸 쓰라는 말을 들었다. SvelteKit으로 작성했던 웹 페이지들을 전부 Elder.js로 포팅했다. 도중에 ES ModuleCommonJS의 환장할 콜라보레이션 덕분에 매우매우 해멨다. 다행히 이삼일 정도 날려서 해결했다. Tailwind CSS 관련 문제도 있었다. 역시 시간을 갈아서 해결했다.

삽질하고 있는 나 1 삽질하고 있는 나 2

결과물이 내가 딱 원하던 그것이라 매우 만족했다. 다만 한 가지 아쉬운 점이 있었다. Elder.js에서는 모든 permalink/로 끝난다. 아무 문제가 없는 것처럼 보인다. 하지만 일부 환경에서는 (특히 나같은 AWS S3 + AWS Cloudfront를 구성한 입장에서) explicit하게 index.html까지 붙인 완전한 경로가 필요하다. 하지만 그런 기능은 없다. 편법으로 해결했다. 빌드할 때마다 경고가 줄줄 나지만.

Subfont

터미널스러운 블로그의 핵심 디자인 요소는 무엇일까? 바로 고정폭 서체다. 나 역시 고정폭 서체를 포기할 수 없었다. 그래서 고심한 끝에 NotoSans Mono CJK를 사용하기로 했다. CJK인 이상 폰트 용량이 큰 것은 어쩔 수 없지. 그런데 커도 너무 컸다. RegularBold 딱 두 개인데, 개당 10MiB 정도 한다. 총합 20MiB라는 말도 안 되는 크기를 서빙해야 한다(폰트가 전체 용량의 99%가 넘는다). 시험해보니 역시 전송 시간이 너무 오래 걸렸다. 그래서 Subfont를 쓰기로 했다. Subfont는 페이지를 렌더링해본 다음 실제로 사용하는 글리프만 모아서 서브셋을 만들어준다. 사용법이 쉽고 강력하다. (역시 상당한 삽질이 있었지만) 적용하고 나니 개당 10MiB 짜리가 거의 20KiB 정도로 줄어버렸다. 경이로웠다.

Syntax Tree

이제 프론트가 완전히 준비됬다. 포스트만 잘 컴파일해서 html로 생성해주기만 하면 된다. 기존의 marked를 쓸까 잠깐 고민했다. 하지만 누군가 추천해준 remark가 생각나 별 생각 없이 이걸 써보기로 했다. 그런데 사용해보니 매우 잘 설계됬다는 걸 느낄 수 있었다.remarkunified 프로젝트의 일부로써 모든 것을 ast 형태로 표현한다. 이 ast를 다른 용도의 ast로 손쉽게 가공할 수 있다. syntax-treeast와 그 유틸리티들이 모여있는데, 규모가 대단하다. 내게 필요한 astmdasthast 둘 뿐이다. 전자는 markdownast고 후자는 htmlast다. 계획은 이렇다.

  1. 포스트를 읽고 mdast로 변환한다.
  2. mdasthast로 바꾼다.
  3. hast를 가공해 이미지와 비디오를 넣는다.
  4. hastplain texthtml text로 serialize한다.

약간 헤메는 감이 없잖아 있었지만, 그래도 잘 해결했다. 이후에 GFMKaTeX 지원을 추가했다.

Deployment

이렇게 만들어진 static 사이트를 배포해야 한다. 어떻게 할까. AWS S3AWS Cloudfront를 쓰기로 했다. 별 이유는 없었다. 내가 자주 쓰던 스택이라 그런가. 세팅과 자동 배포 작업은 두 시간도 걸리지 않았다. 그마저도 대부분은 ACM에서 승인 받기를 기다리는 시간이었다. 작업이 모두 끝났다. 새로운 블로그를 확인해 본다. 이쁘고 쾌적하다. 그런데 모바일에서 보면 깨져보인다. 아직 모바일 대응을 안 했으니까. 시간날 때 할 예정이다.

요금 상황

기존의 AWS에서 호스팅하던 블로그 백엔드 서버는 날려버렸다. 이제 다달이 9만원씩 내지 않아도 된다!

Conclusion

기존 블로그 시스템을 SSG를 통해 그냥 정적 페이지로 만들어봤다. 귀찮게 백엔드를 작성/유지보수하지 않아도 된다. 포스트도 기존 IDE를 활용해 훨씬 더 편리하게 작성할 수 있다. 디자인도 맘에 들고 이쁘다(남들 눈에도 그런지는 모르겠다). 간만의 성공적인 마무리다.