Show GN: 오픈소스 커뮤니티 빌더 TSBOARD의 새로운 백엔드 : GOAPI
(github.com/sirini)약 7개월 전에 처음으로 TSBOARD 프로젝트를 소개드렸습니다.
그 때는 프론트엔드와 백엔드 모두 타입스크립트로 작성되었고, 백엔드 구동을 위해 Bun 런타임을 사용했었습니다.
그러나 여러 사정으로 백엔드를 새로 다시 만들게 되었고, 기존 TSBOARD 프로젝트와 분리된 GitHub 저장소에 올려서 공개합니다. 이 새로운 백엔드는 Go 언어로 작성되었고, TSBOARD 정식 버전에서 바이너리 형태로 함께 제공됩니다.
왜 백엔드를 새로 만들었나?
- Bun 런타임은 정말 훌륭한 성능을 보여줍니다. 하지만 올인원 툴킷이라는 또 다른 소개말이 무색하게 패키지 관리는 아직 npm을 따라가지 못하고 있습니다.
- 이 때문에 TSBOARD를 사용하려면 Node.js 와 Bun 이렇게 2개의 런타임이 필요했습니다. 번잡하기도 하고, 사용자 입장에서도 불편하구요.
- 지금은 고쳐졌지만 초창기에 가상 CPU에서 동작하지 않는 문제로 인해 정작 개발자인 저도 다른 서버에 올리지 못하는 상황이 치명적이었습니다.
- (다른 분들의 지적처럼) 오케스트레이션을 하면 된다고는 하지만, 싱글 스레드의 제약이 어쨌든 태생적으로 있는 JS 런타임의 한계를 넘어서서, 여러 쓰레드를 활용하고 싶었습니다.
- 더 많은 타입이 필요했습니다. 타입스크립트만으로는 이 갈증을 채울 수가 없었어요.
왜 Go 언어를 선택했나?
- 새로운 백엔드는 1) 컴파일이 되어야 하고 2) 메모리 관리는 알아서 해주면 좋겠고 3) 별도의 런타임 같은 걸 설치할 필요가 없어야 했습니다.
- Rust, Kotlin, Python, PHP 그리고 Go 언어를 두고 고민끝에, 위 3가지 조건을 모두 만족하지만 저로서는 처음인 Go 언어를 선택하게 되었습니다. (미안해 PHP)
- Go 언어의 단순함이 가장 마음에 들었고, 타입스크립트와의 유사점들도 선택에 주요 포인트였습니다. 무엇보다 동시성 관리나 메모리 관리 측면에서 다른 대안 대비 좋은 선택이었다고 생각합니다.
Go 언어, 써보니 어땠나?
- 세상에 장점만 있는 언어는 없다는 사실을 Go 언어도 증명하고 있구나, 하는 점을 깨달았습니다.
if err != nil { }
은 반드시 필요하긴 하지만 정말 너무 귀찮습니다. try catch finally 가 그리울 때가 종종 있습니다. -
go-mysql-driver
의 문제로 추정되지만, DB I/O라는 병목이 있는 실제 개발 환경에서는 그렇게 빠르게 동작하진 않았습니다. (여기 긱뉴스에 올린 글 참조: https://news.hada.io/topic?id=18048) - 암묵적인 인터페이스 적용은 아직도 좀 어색합니다. implements 나 extends 같은 키워드를 쓰기 싫었던 걸까요?
- 포인터는 반가웠습니다. 특히 메모리 해제 시점을 고민하지 않아도 되는 점이!
- 여러 타입들, 단순하지만 강력한 구조체, 슬라이스는 완소입니다. 키워드가 적어서 금방 배워서 써먹을 수 있는 점이 가장 좋습니다.
-
go
키워드로 마법처럼 경량 쓰레드를 쓸 수 있다니...! 행복합니다!
백엔드를 JS 런타임 기반에서 Go로 바꾼 후기...?
- 이런 짓은 한 번으로 족합니다.
- DB I/O 부분을 벤치마킹 하면서 여러 테스트들을 해봤는데, 성능 관점에서는 사실 JS 런타임이나 Go 바이너리나 큰 차이를 느끼기 어렵습니다. 예를 들어 JS 에서 많이 사용되는 이미지 라이브러리
sharp
같은 경우도 마찬가지로libvips
라이브러리를 사용하고, DB I/O 없는 웹 애플리케이션은 없을테니까요. - 그럼에도, 저는 고생은 진탕 했지만 잘 바꿨다고 생각하고, 앞으로 백엔드는 Go 언어만 계속 사용할 생각입니다.
- 메모리 사용량이 정말 의미 있는 수준으로 떨어집니다. 물론 Rust로 개발하면 더 최적화를 할 수 있겠지만, GC를 사용할 수 있는 대가로 이정도 수준이면 충분히 만족할 수 있습니다.
- 언어 자체의 심플함이 정말 미쳤습니다. 외워야 할 키워드도 적고, 사용 패턴도 대부분 정해져 있으니 배우기도 쉽구요. (물론 잘 쓰는 건 다른 얘기입니다만) 원시 타입이 다양하게 준비되어 있는 점이 정말 마음에 들었습니다.
- 무엇보다 만족스러운 점, 컴파일 이후 바이너리만 있으면 일단 실행이 된다는 점입니다. 백엔드 구동을 위해 추가로 무언가를 설치하고 그 걸로 코드를 다시 실행해야 하는 건 이제 더 이상 사양하고 싶습니다.
어떻게 사용하나?
- Windows 환경은 아쉽게도 지원하지 않습니다. Linux/Mac 환경에서 바이너리 실행을 하면 됩니다.
- 사용하시는 서버에
libvips
라이브러리가 사전에 설치되어 있어야 합니다. 바이너리가 해당 라이브러리의 기능들을 이용해서 이미지 처리를 하기 때문입니다. - 자세한 내용은
README.md
파일에 설명해 두었습니다.
아직 많이 부족하고, 여전히 다른 개발자분들의 고견을 기다리고 있습니다. 긱나이트 행사 때 우물쭈물한 제 자신이 원망스러울 정도입니다. 버그 제보나 개선 제안, 혹은 색다른 의견 모두 환영합니다.
올해는 12월이 유난히 버거운데, 그래도 새해에는 좀 더 나은 미래가 있기를 소망해 봅니다. 긴 글 읽어주셔서 감사드립니다. 마지막으로 TSBOARD v1.0.0 출시 노트 링크를 아래에 공유드립니다.
암묵적인 인터페이스 적용은 아직도 좀 어색합니다. implements 나 extends 같은 키워드를 쓰기 싫었던 걸까요?
장단점이 있는데, 장점으로는 표준/외부 라이브러리 코드 수정 없이, 다른 사람이 만든 구현체를 사용하면서, 그 일부를 내가 만든 interface로 취급할 수 있다는 점이 장점으로 종종 느껴지더라고요. Java의 FunctionalInterface 처럼, 또는 duck typing을 컴파일 언어에 적용한 것 처럼요. 반대로 implements/extends를 반드시 선언하는 방식이면, 내가 만든 interface에 붙이려면 중간에 Adapter를 하나 구현해야 하는 것에 반해서요.
단점으론, interface에 메서드를 추가/삭제/변경할 때에, 다른 정적 타이핑 언어 대비 에러 표시되는 위치가 달라서 좀 불편하죠.
앗 그렇군요! 생각도 못한 장점이 있었네요. 에러 메시지는 다행히 gopls 였나? vscode의 go언어 확장이 잘 잡아줘서 빠트린 것이나 잘못 구현된 건 금방 찾을 수 있었습니다. 좀 더 익숙해지면 저도 언젠가 더 잘 쓰게 될 것 같습니다. ㅎㅎ 댓글로 설명해 주셔서 감사드립니다! 새해 복 많이 받으세요~!