2022년 회고

12월 초에 일년간 한 일을 정리하는 이력서 형식의 글을 쓰다보니까 점점 회고록이 되어가서 그냥 본격적으로 회고록이나 적기로 했다. 2022년에도 개발에 엄청난 시간을 쏟아부었고, 나름 많이 놀러다녔는데 회고록에 적을만한 건 별로 없어서 내가 기록해놓고 싶은 것들이 주된 내용이 될 것이다.

한 작업들

우선 한 일부터 정리해보려고 한다. 1월에 성과 평가가 있대서 무슨 일을 했나 깃허브 이력을 뒤져봤는데, 1년간 정말 많은 일을 했더라.

swc minifier 안정화

제일 큰 건 swc minifier를 stable로 배포하고 next@13에서 기본값으로 켠 것이다. 이걸 위해서 엄청나게 많은 작업을 했다. 일단 엄청나게 많은 최적화 규칙을 구현했다. Gzip 관련 최적화도 했고... 엄청나게 많다고 해도 느낌이 잘 안 올텐데, terser압축률에서 이길 정도로 많은 로직을 구현했다. 아래는 내 노력을 단적으로 보여주는 트윗이다.

{{< tweet user="hugoccampos" id="1594443064319303683" >}}

물론 버그도 엄청나게 많이 만들었다. 이젠 거의 다 고쳤지만 말이다.

minifier 버그는 그 특성상 디버깅이 굉장히 힘들다. 변수 이름부터 알아보기 힘들게 바뀌고, 디버거를 붙여도 콜 스택의 어느 지점에서 minification이 잘못됐는지 파악하는 것도 힘들고, swc minifier 코드 중 어느 파트가 잘못된 건지 테스트하는 것도 복잡하다. 사실 마지막 건 러스트라서 그런 건데, 그런 제약들 덕분에 러스트가 빠른 거라 그냥 이제 받아들이고 있다. 조금만 더 설명하자면, 압축기가 도는 동안 입력 파일에 대한 &mut을 들고 있어서 특정 라인까지 실행된 뒤에 입력 파일이 어떻게 변했는지 확인하는 게 매우 힘들다.

하도 디버깅이 어렵다보니까 디버깅을 위해서 별 방법을 다 썼다. creduce를 이용해서 자동으로 버그랑 관련있는 코드만 남긴다던가... cargo의 패치 기능을 이용해서 한 함수씩 비활성화하면서 문제가 되는 함수를 찾는다던가...

===============================================================================
 Language            Files        Lines         Code     Comments       Blanks
===============================================================================
 JavaScript           9353      1723803      1462554       156706       104543
 JSON                 2483        19539        19539            0            0
 Markdown                3           24            0           15            9
 Shell                  21          444          234          114           96
 Plain Text              3         2167            0         2167            0
 TOML                    2          105           93            2           10
-------------------------------------------------------------------------------
 Rust                   84        42585        36132          845         5608
 |- Markdown            65          713            0          589          124
 (Total)                          43298        36132         1434         5732
===============================================================================
 Total               11949      1788667      1518552       159849       110266
===============================================================================

minifier의 분석기를 별도 모듈로 분리하기 전의 minifier 코드 라인 수다. 하도 코드가 많다보니 문제가 되는 코드를 찾는 게 쉽지 않았다.

swc 성능 개선

또한 swc의 전반적인 성능을 엄청나게 올렸다. 내가 직접 최적화한 건 ECMAScript 처리용 모듈들과 CSS 처리용 모듈들이다. 당연하지만 매번 프로파일링을 해본 뒤 핫 패스인 경우만 최적화했는데, 러스트 릴리즈 빌드가 워낙 느리다 보니까 기다리는 것도 고생이었다. swc는 배포할 때 LTO를 켜고 배포하지만, 컴파일 시간이 너무 긴 관계로 대부분 LTO를 끄고 프로파일링했다.

이와 관련해서 가장 기억에 남는 건 DoD이다. 이건 워낙 신선한 접근방식이라 블로그 글로 몇번 썼다.

  1. 성능에 대한 깨달음

  2. DoD에 대한 기록

  3. DoD식 사고방식

난 어릴 때부터 내용보단 그걸 떠올리기 위한 사고방식을 배우는 걸 좋아했다. 예를 들어 상대성 이론을 배울 때도 어디에 착안했길래 저런 생각을 했을까를 고민했다. 내가 내린 결론은 광속이 변하지 않는다는 관측 결과에 모든 걸 맞추다가 나온 이론이라는 것이다. 지금 생각해보면 수험생 시절에 할 고민은 아닌 것 같지만...

아무튼 내가 진짜 중요시하는 건 사고방식이다. 그리고 DoD는 나한테 새로운 사고방식을 가르쳐줬다. 그래서 올해에 나한테 코딩 관련해서 제일 큰 영향을 끼친 건 이것이다.

성능 개선 폭도 컸다. 하나만 가져와보자면,

es/minify/libraries/antd
                        time:   [620.86 ms 624.46 ms 628.66 ms]
                        change: [-22.841% -22.308% -21.758%] (p = 0.00 < 0.05)
es/minify/libraries/d3  time:   [129.75 ms 130.21 ms 130.61 ms]
                        change: [-15.798% -15.371% -14.986%] (p = 0.00 < 0.05)
es/minify/libraries/echarts
                        time:   [485.80 ms 487.35 ms 488.88 ms]
                        change: [-23.405% -22.989% -22.583%] (p = 0.00 < 0.05)
es/minify/libraries/jquery
                        time:   [39.674 ms 39.790 ms 39.888 ms]
                        change: [-7.4199% -6.9714% -6.5607%] (p = 0.00 < 0.05)
es/minify/libraries/lodash
                        time:   [54.658 ms 54.983 ms 55.276 ms]
                        change: [-6.0030% -5.0606% -4.3268%] (p = 0.00 < 0.05)
es/minify/libraries/moment
                        time:   [24.989 ms 25.056 ms 25.132 ms]
                        change: [-6.9642% -6.4017% -5.7664%] (p = 0.00 < 0.05)
es/minify/libraries/react
                        time:   [11.304 ms 11.333 ms 11.348 ms]
                        change: [-4.0229% -3.4375% -2.8645%] (p = 0.00 < 0.05)
es/minify/libraries/terser
                        time:   [113.05 ms 113.25 ms 113.53 ms]
                        change: [-14.455% -13.868% -13.346%] (p = 0.00 < 0.05)
es/minify/libraries/three
                        time:   [168.85 ms 169.15 ms 169.52 ms]
                        change: [-19.905% -19.348% -18.833%] (p = 0.00 < 0.05)
es/minify/libraries/typescript
                        time:   [1.3660 s 1.3703 s 1.3747 s]
                        change: [-20.914% -20.551% -20.250%] (p = 0.00 < 0.05)
es/minify/libraries/victory
                        time:   [290.98 ms 293.52 ms 296.23 ms]
                        change: [-16.272% -15.458% -14.495%] (p = 0.00 < 0.05)
Benchmarking es/minify/libraries/vue
es/minify/libraries/vue time:   [59.382 ms 59.627 ms 59.912 ms]
                        change: [-7.4172% -6.7056% -5.9969%] (p = 0.00 < 0.05)

10% ~ 20% 개선은 엄청나게 큰 것이다. 그리고 지금은 더 빨라졌다.

(엄청난 양의) swc 버그 수정

워낙 기능이 많다보니 버그가 나는 원인도 엄청나게 다양하더라. 여기에 적기엔 양이 지나치게 많은 관계로 기록해놓고 싶은 것만 몇가지 적겠다.

버그들 중에 어려운 버그들은 진짜 디버깅하겠다고 별의 별 방법을 다 썼다. dbg!()로 출력하는 방법을 제일 많이 썼고, 테스트 실패에 관련된 정보를 웹으로 볼 수 있게 링크로 출력하는 테스트 시스템을 만들기도 했다.

여담으로, 소스맵 버그나 minifier 버그들은 Vercel 팀원중 @jridgewell님이 엄청나게 도와주셨다. 내가 이론적 기반이 엄청나게 약하다보니 그쪽 관련해서 특히나 도움이 되었다. 저분에게 매우 고마운 점이, 저분은 자기 업무가 아니다. 어느 날 내가 팀 슬랙 채널에 업데이트 게시함변서 minifier PR 리뷰를 어떻게하면 더 잘할 수 있을지 모르겠다고 적었는데, 자기가 도와주겠다고 하신 것이다. 저분이 내 실수도 많이 잡아주셨다. swc의 경우, 기본적으로 실수를 잡아낼 프로세스가 없다. 내가 거의 모든 PR을 리뷰하다보니 내 PR을 리뷰할 사람이 딱히 없어서 그렇다.

CSS 처리용 모듈들 개선/구현

또한 CSS 관련 작업도 많이 했다. 코어 멤버인 @alexander-akait님이 많이 기여해주셨지만, 나도 관련 작업을 많이 했다.

  • AST 타입 정의

  • 파서

  • 코드 생성기

를 엄청나게 개선했고, CSS Modules 처리기와 최신 버전의 CSS를 오래된 브라우저에서도 인식되는 CSS로 바꿔주기 위한 컴파일러 패스들을 구현했다.

CSS Modules의 경우, 내가 트랜스폼을 설계하고 초기 버전을 구현했고, @alexander-akait님이 개선해주셨다. 내가 구현한 건 AST를 변경하는 대신 토큰을 트랜스폼 패스에서 인식해서 다시 파싱하는 형태였는데, 이러면 성능상 안 좋다고 AST를 변경하는 게 맞다고 하셨다.

CSS Nesting의 경우엔 내가 작업했고, 구조가 잘 나와서 손볼게 많지 않았다. 이것도 재밌게 작업했던 기억이 있다. 컨퍼런스 때문에 데드라인이 있어서 시간 압박이 있었지만 걱정은 딱히 되지 않았다. 시간 내에 여유롭게 끝낼 수 있다는 자신감이 있었기 때문이다.

(엄청난 양의) 코드 리뷰

코드 리뷰에도 많많치 않은 시간을 썼다. 코드 리뷰의 비율이 25% 이상이니까 적어도 코드 리뷰를 2천번 넘게 했다는 소리다. 그리고 PR 중에 큰 건 리뷰할 파일이 1000개 넘어가고 그랬다. 제일 힘들었던 건 모듈 트랜스폼들 갈아엎는 PR이었는데, 고생한 기억이 아직도 생생하다.


그리고 이건 @alexander-akait님의 작품인데, 아직 JavaScript 쪽에서 사용하긴 힘들지만, swc에는 xml, html 처리용 시스템이 존재한다. 리뷰나 프로파일링/분석은 대부분 내가 했는데, 이 모듈들도 유용하게 쓰일 것 같다. 아직 밝힐 순 없지만 기대중인 게 조금 있다.

@swc/core/next-swc 바이너리 크기 개선

cargo-bloat을 이용해서 프로파일링을 해보니까 ECMAScript 파서가 수차례 복사된 상태로 최종 바이너리에 들어가고 있었다. 알고보니까 러스트의 경우 제너릭을 사용하면 사용한 crate 별로 제너릭을 한 벌씩 복사해서 들고 있더라. 이게 성능 측면에선 일반적으로 더 좋지만, 파서는 용량도 크고 그렇게 해도 성능 이득이 없다. 오히려 용량을 너무 크게 만들어서 코드 캐시가 비효율적이 된다.

해결책에 대해 고민하다가 좋은 생각이 났다. 다른 crate에서 제너릭을 안 쓰면 해결되는 문제니까, parse_file_as_module이라는 제너릭이 아닌 일반 함수를 만들었다. 물론 이거 하나만으론 안 되고, parse_file_as_expr, parse_file_as_script 등등 여러 개를 만들었다. 개인적으로 이건 러스트 제너릭의 문제점이라고 생각한다. 알아서 용량 보고 공유할지 말지 결정해주면 좋을 것 같은데 말이 쉽지 구현은 엄청나게 어려울 것 같아서 그냥저냥 쓰고 있다. 사실 저렇게 처리된다는 걸 알고만 있으면 큰 문제는 아니다. 살짝 귀찮은정도? 그래도 전반적으로 러스트에 만족하기 때문에 계속 쓸 것이다.

@swc/core/next-swc 컴파일 시간 개선

라이브러리의 컴파일 시간을 최적화하고 있다는 건, 라이브러리가 충분히 성숙했다는 뜻이다. 그래서 작업 자체는 재미 없었지만 이 작업은 할만했다.

내가 취미가 생산적인거지 열심히 사는 스타일인게 아니라서 작업 자체가 재미없으면 하기가 매우 힘들다. 근데 예외적인 케이스였어서 한 항목으로 적어놓기로 했다.

컴파일 타임 quasi-quoter 구현

swc를 쓰는 러스트 프로젝트가 꽤 되다보니까 API를 깔끔하게 유지하는 것도 일이었다. 근데 제일 깔끔한 API는 언제나 문자열을 그대로 사용하는 것이다. 그래서 컴파일 타임 quasi-quotter를 구현했다. 이걸 구현한 건 Turbopack 팀의 요청 때문이었는데, 잘 쓰고 있어서 뿌듯하다.

quote_expr!(
  "'could not resolve \"' + $arg + '\" into a module'",
  arg: Expr = *expr.clone(),
)

처럼 사용하면 컴파일 시점

Box::new(Expr::Bin(BinExpr {
  span: DUMMY_SP,
  op: op!(bin, "+"),
  left: Box::new(Expr::Bin(BinExpr {
    span: DUMMY_SP,
    op: op!(bin, "+"),
    left: Box::new(Expr::Lit(Lit::Str(Str {
      span: DUMMY_SP,
      value: "'could not resolve \"".into(),

    }))),
    right: expr.clone(),
  })),
  right: Box::new(Expr::Lit(Lit::Str(Str {
    span: DUMMY_SP,
    value: "\" into a module'".into(),
  })))
}))

같은 코드로 바뀐다. 저걸 quote! 없이 적으면 상당히 길기 때문에 귀찮은데, quote! 매크로를 쓰면 되게 간편해진다.

stc 공개 및 룰 구현

stc를 오픈소스화할지 고민 많이 했는데, 오랜 고민 끝에 오픈소스로 공개했다. 내 친구들 중 개발쪽인 사람들은 대부분 내가 오픈소스 때문에 스트레스를 많이 받은 걸 안다. 그래서 내가 이걸 공개했다고 했을 때 다들 놀라더라.

근데 지금은 이게 매우 잘한 결정이라고 생각한다.

기여자나 기부자가 많아서 동기 부여가 많이 되었다.

기타 작업들

정리하다보니 뿌듯한 일, 혹은 기록해두고 싶은 일이 많아서 항목이 너무 많아졌는데, 이외에도 이것저것 많은 작업을 했다.


우선 깃허브, 슬랙, 디스코드, vscode등 엄청나게 유명한 회사들이나 프로젝트들에서 swc를 쓰고싶어했고, 그들을 도와줬다. 이슈들을 우선 처리해주기도 했고, 채팅으로 도와주기도 했다.


swc용 Wasm 플러그인도 하나 만들었다.

https://github.com/swc-project/plugins/tree/main/packages/loadable-components

next.js 에 필요했던 건 아니고, 그냥 주말에 심심해서 작업해봤다. 저 정도 플러그인은 이틀 정도면 구현이 가능하더라.


swc에는 swc_ecma_lints라는 꽤 큰 패키지가 있다.

===============================================================================
 Language            Files        Lines         Code     Comments       Blanks
===============================================================================
 JavaScript              7           66           55            0           11
 TOML                    1           39           34            1            4
 TypeScript              6           38           32            2            4
-------------------------------------------------------------------------------
 Rust                   42         6623         5445           56         1122
 |- Markdown             3           11            0            9            2
 (Total)                           6634         5445           65         1124
===============================================================================
 Total                  56         6766         5566           59         1141
===============================================================================

이 모듈을 설계하고, 컴파일 타임에 잡을 수 있는 에러들을 잡아주기 위한 룰들을 구현했다. const에 다시 할당하는 걸 잡아준다던가 하는 그런 룰들이다. 이걸 구현한 건 babel이 해당 에러들을 잡아주기 때문이다. 이게 진짜 필요한가 싶었지만 next.js에서 babel 대신 swc를 쓰게 되면서 생긴 regression인 셈이라 그냥 작업했다.


그리고 DevMap라는 프로젝트를 만들었다. 개발하기 싫을 때 다른 분들한테 도움되는 글이라도 쓰려고 만든 프로젝트이다. 그런데 코딩이 하기 싫을 땐 글 쓰는 것도 싫더라.


Wasm 플러그인 이슈도 많이 해결했다. Wasm도 정말 문제가 많더라. 그리고 rkyv의 버그로 추정되는 이슈 때문에 @kwonoj님이랑 디버깅을 한참 했다.


또한, swc 유지 관리 프로세스를 많이 개선했다. swc를 쓰는 러스트 프로젝트가 하도 많다보니 swc는 매우 독특한 배포 전략을 사용한다. 한 PR이 머지될 때마다 영향 받는 모듈들을 바로 배포하는 게 그것이다. 물론 이따위 정책을 쓰는 대규모 프로젝트는 지구상에 swc 하나뿐이겠지만, 난 버그 픽스 보내고 다음 버전 배포될 때까지 기다려야하는 것도 오픈소스의 문제점 중 하나로 본다.


swc의 현 API로 tailwind를 구현할 수 있다는 개념증명을(Proof of Concept를) 해줬다. Vercel 덕분에 재밌는 경험을 한 것 같다. 테일윈드 팀이 https://github.com/tailwindlabs/minimal-tailwind-postcss-plugin 를 만들어줬고, 나한테 PoC를 부탁했다. 저게 러스트 코드로 표현이 되는지을 알고싶다는 것이었는데, 이 정도가 이상적인 것 같다. 초기 구현은 나를 포함한 swc 팀이 도와주고, 유지 관리는 각 프레임워크 개발자들이... 솔직히 유지 관리 부담은 지금도 지나치다.


swc의 CI 시간도 크게 줄였다. CI 시간이 생산성을 낮추는 수준으로 길어져서 날 잡고 CI 최적화를 했다.

한 활동들

개발 외적으로도 이것저것 많이 했는데 지금 돌아보니까 회고록에 적을만한 건 별로 없는 것 같다. 그래도 몇가지는 적겠다.

GDSC 가입 및 활동

난 현재 성대 휴학생 신분이다. 복학할 생각은 없고 휴학을 더 못하게 되면 자퇴할 건데, 사람 만나기엔 휴학생 신분이 나아서 휴학을 유지하고 있다.

어느 날 구글 이름 달린 코딩 동아리가 생긴다길래 설명 읽어보다가 지원했다. 뭔가 배우길 기대한 건 아니고 단순히 사람 만나려고 들어간 것이다. 동아리가 모집 시기가 늦어서인지 이벤트가 많진 않았는데 가보니까 재밌길래 최대한 참여했다.

이것 말고도 이번 년도엔 사람을 엄청 만나고 다녔다. 이게 작년까지와 올해부터의 가장 큰 차이다.

제주도 여행

{{< tweet user="kdy1dev" id="1595225477064847360" >}}

일주일 휴가 쓰고 동생이랑 같이 제주도에 놀러갔다왔다. 밖에선 기만이 될 게 뻔해서 썰도 못 푸는데 가족이라 다 알고있어서 솔직한 얘기를 할 수 있었다.


내년부턴 여행을 많이 다닐 생각이다. 휴가를 쓸지는 모르겠다. 내가 하고 있는 업무 특성상 대체할 인력이 없다. 근데 애초에 업무 시간이 정해져있지 않아서 휴가를 안 써도 된다. 계약서에도 없고 입사 초기에 매니저 승인도 받았다.

swc 이슈 같은 경우 대부분 내가 봐야하는 이슈고, 난 대부분의 경우 10분 내로 답변해준다. 그래서 내 업무 시간이 자유인 게 서로한테 좋다.

사설인데, 나는 팀원이 나 때문에 자기 작업 못하고 기다려야하는 걸 극도로 싫어한다. swc에서 PR 하나 머지될 때마다 러스트 모듈들을 배포하는 것도 내가 배포할 때까지 다른 사람들이 패치된 코드를 못 쓰는 게 싫어서이다. 말만 들어도 제정신 아닌 방식이지만, 뭐 내 성격이 그런 걸 어쩌겠나.

닌텐도 스위치 구입

이건 써야하나 고민했는데 삶의 질이 크게 올라간 관계로 쓰기로 했다. CI를 기다려야할 때 다른 작업을 하면 CI가 다 돈 다음에도 스위칭 코스트 떄문에 더 중요한 작업을 못하는 경우가 좀 있었다. 그런데 닌텐도 스위치는 언제든지 멈출 수 있다보니 그런 게 없어서 좋았다.

게임 몇개 깔았더니 용량이 모자라서 검색해봤는데 마이크로 SD 카드를 꽂아서 용량을 늘릴 수 있다길래 바로 최대 용량부터 찾아봤다. SDXC의 경우 2TB가 최대 용량이라고 하길래 2TB 카드를 한참 찾았다. 근데 알고보니까 시중에 나온 제품은 최대 용량이 1TB더라. 그래서 그냥 1TB짜리로 샀다.

나중에 생각해보니 닌텐도에도 돈을 꽤 썼더라. 게임이 꽤 비쌌다.

기타

음악

적을까말까 고민했지만 닌텐도도 적었는데 싶어서 적는다.

유튜브 뮤직에서 보니까 한곡을 2천번 넘게 들었다고 하더라. 한 3주 정도동안 저 노래만 들었다.

내년 계획

난 취미가 코딩인지라 내년에도 코딩을 엄청나게 많이 할 예정이다. swc, stc, 그리고 내가 새로 시작한 비밀 프로젝트 이렇게 3개를 작업할 생각이다.

근데 그보다 내년엔 꼭 연애할 거다. 이번엔 진심이다. 느낌이 약간 다르다고 해야하나?