2017년에 Go 마이크로서비스 프레임워크를 만들었다. 8년 운영을 통해 얻은 교훈.

발행: (2026년 6월 6일 AM 10:46 GMT+9)
13 분 소요
원문: Dev.to

출처: Dev.to

2017년, 나는 Node.js 기반 전자상거래 서버를 유지보수하고 있었는데, 새로운 프로젝트가 내 책상에 놓였다 — 차량 추적 장치를 위한 IoT 플랫폼이었다. 수천 대의 장치가 지속적으로 데이터를 전송하고, 클라이언트는 장치가 보고하는 내용을 실시간으로 확인하길 기대했다.
Node.js 서버는 거의 즉시 한계를 드러냈다.
장치가 너무 많고, 실시간 데이터가 너무 방대해 처리량이 부족했다. 클라이언트는 지연을 겪었고, 데이터는 늦게 도착했다. 저장소 레이어는 너무 힘들어 매달 30일마다 이전 달 데이터를 삭제해가며 겨우 운영을 유지했다. 그리고 그 모든 것 위에 단일 모놀리식 서버가 있었는데, 문제가 되는 부분만 따로 확장할 방법이 없었다.

우리는 마이크로서비스가 필요했다. 속도가 필요했다. 이를 위해 설계된 무언가가 필요했다.

나는 C++에서 Go로 전향했다 — Python, Ruby, JavaScript에서 온 것이 아니다. 구조화된 컴파일 언어에서 수년을 보내며 타입, 메모리, 동시성에 대해 신중히 고민하고 코드를 작성했다. Node.js는 우회로였고, Java는 1년 정도 사용했다. Go는 마치 집으로 돌아온 느낌이었다.

Go는 2017년엔 아직 어린 언어였다. 커뮤니티는 작았고, 생태계도 얇았다. 하지만 Google이 뒤에서 지원했고, 커뮤니티는 빠르게 성장했으며, 성능 특성은 IoT 플랫폼에 딱 맞았다 — 컴파일되고, 동시성을 지원하며, 빠르다.

결정은 명확했다. 더 어려운 질문은 “처음부터 Go 마이크로서비스 시스템을 어떻게 설계하느냐”였다.

당연한 답은 당시 가장 완전한 Go 마이크로서비스 프레임워크였던 go-micro였다. 나는 시도해봤고, 동작했다. 하지만 뭔가 과했다.

go-micro는 서비스 디스커버리, 플러그인, 완전한 RPC 시스템, 그리고 모든 것에 대한 추상화를 제공한다. 실시간 IoT 시스템을 빠르게 구축해야 하는 팀에게는 새로운 언어 위에 또 다른 새로운 언어를 배우는 느낌이었다. 인지적 부담이 실제로 존재했다.

내가 실제로 필요했던 것은 더 단순했다.

  • 일부 서비스는 큐에서 메시지를 처리해야 했다.
  • 일부는 큐 없이 순수하게 “fire‑and‑forget” 방식으로 응답해야 했다.
  • 일부는 HTTP가 필요했고, 일부는 장치를 위한 원시 소켓 연결이 필요했다.
    시스템은 모듈화돼야 하지만 복잡하지 않아야 했다.

go-micro도 이 모든 것을 할 수 있었지만, 나는 프레임워크와 싸우고 있다는 느낌을 떨칠 수 없었다.

그래서 싸움을 멈추고 직접 만들기 시작했다.

Keel의 첫 번째 버전은 거의 부끄러울 정도로 단순했다.

  • 하나의 메인 파일
  • 하나의 설정 파일
  • 여러 서비스를 정의하고 단일 Docker 컨테이너 안에서 실행할 수 있는 방법

핵심 아이디어는 다음과 같다: 각 서비스가 자신을 등록하고, 프레임워크는 플래그에 따라 올바른 서비스를 부팅한다. 각 서비스는 HTTP, 소켓, 데이터베이스, 메시징 등 필요한 모든 것을 공유 설정과 라이프사이클 시스템을 통해 접근한다.

one binary
one config
multiple services
boot the one you need

그게 전부다. 마법도, 서비스 디스커버리도, 플러그인도 없었다. 단지 Go 마이크로서비스 프로젝트를 구조화하는 깔끔한 방법만 있었을 뿐, 서비스를 추가할 때마다 기반을 다시 만들 필요가 없었다.

첫 번째로 만든 서비스들은 IoT 장치용 소켓 연결을 담당했다. 원시 TCP와 Socket.IO를 동일한 라이프사이클 시스템으로 관리했고, 같은 설정과 데이터베이스 연결을 공유했다.

동작했다. 그래서 계속해서 위에 기능을 쌓아갔다.

Keel에 처음 연결한 메시징 시스템은 Kafka였다. 당시에는 당연한 선택이었다 — Kafka는 대용량 이벤트 스트리밍의 표준이었고, IoT 플랫폼은 방대한 데이터량을 생성하고 있었다.

하지만 Kafka는 무겁고 운영 부담이 컸다. 그때 나는 NATS를 발견했다 — Go에 최적화된 경량 메시징 시스템으로, 빠르고 채택이 놀라울 정도로 간단했다.

전환은 간단했다. NATS는 내가 필요로 하는 모든 것을 제공했다 — fire‑and‑forget 이벤트 배포를 위한 Pub/Sub, 서비스 간 요청/응답을 위한 RPC — Kafka의 무거운 운영 부담 없이. 이후 모든 프로젝트에 NATS를 사용했다.

Keel의 첫 번째 버전은 하나의 문제를 해결했다. 그 뒤의 모든 프로젝트는 새로운 레이어를 추가했다.

  • 전자상거래 프로젝트는 페이지네이션이 필요했다 — 결과적으로 세 가지 종류가 등장했다: 일반 페이지네이션, SQL 페이지네이션, 복잡한 MongoDB 쿼리를 위한 집계 페이지네이션. 그래서 Keel에 세 가지 모두를 구현했다.
  • 헬스케어 프로젝트는 FHIR 호환성과 소프트 삭제가 필요했다 — 논리적으로는 삭제되지만 물리적으로는 데이터베이스에 남는 레코드. 그래서 데이터베이스 레이어에 소프트 삭제를 구현했고, deletedAtdeletedBy 필드를 자동으로 포함하는 _deleted 컬렉션으로 레코드를 라우팅했다.
  • 소셜 플랫폼은 실시간 기능이 필요했다 — Socket.IO 방, 사용자에게 방송, 방에 방송, 커스텀 이벤트. 그래서 HTTP와 함께 Socket.IO 서버를 Keel의 라이프사이클 시스템에 포함시켰다.
  • 핀테크 프로젝트는 신뢰성 보장이 필요했다 — 서킷 브레이커, 헬스 체크, 레디니스 프로브. 그래서 이 기능들도 추가했다.

한 프로젝트에서 나타난 모든 운영 문제는 다음 프로젝트에서 해결된 문제로 전환되었다. 8년 동안 여러 기업과 산업을 거치며 Keel은 더 이상 “프레임워크”가 아니라 Go 서비스를 구축하는 방식이 되었다.

서비스 등록, 두 메서드 구현, 부팅

func init() {
    servicehandler.Register("myservice", func() servicehandler.ServiceBase {
        return &MyService{}
    })
}

func (s *MyService) Run() error {
    // your service logic here
    return nil
}

func (s *MyService) Stop() {
    // graceful shutdown
}

이렇게 세 메서드만 있으면 된다. 나머지는 모두 설정이다.

그 시작점에서 서비스는 다음을 이용할 수 있다:

  • Gin 기반 HTTP 라우팅과 미들웨어 계층. 자동 생성 OpenAPI/Swagger 문서. 내장 /health 및 버전 엔드포인트. 인간 친화적인 오류 메시지를 제공하는 API 검증.
  • 세 가지 DB 드라이버 — MongoDB, MySQL, PostgreSQL — 소프트 삭제, 인덱스 관리, 마이그레이션, 세 가지 형태의 페이지네이션을 포함한 풍부한 연산 집합. Redis 캐시와 분산 락. Meilisearch 전체 텍스트 검색.
  • NATS 메시징 — Pub/Sub, RPC, 토픽 레지스트리, 배치 이벤트 퍼블리시. Socket.IO 방, 브로드캐스트, 커스텀 이벤트.
  • 서킷 브레이커. 레디니스/리브니스/스타트업 프로브를 갖춘 헬스 체크. OpenTelemetry 분산 트레이싱. zap을 이용한 구조화 로그. 트레이스 ID가 포함된 구조화 오류.
  • FCM 푸시 알림. gomail을 통한 이메일. WhatsApp 알림. 모두 공통 인터페이스와 동시성 제한 아래 제공.
  • 모든 goroutine에 대한 패닉 복구. SIGINT/SIGTERM 시 Graceful Shutdown. 전역 상태를 공유하지 않는 다중 서비스 단일 바이너리.

새 서비스를 제로부터 5분 이내에 만들 수 있다. Claude Sonnet in Cursor 로 독립 검증됨.

Keel은 8년 동안 사내 레포지토리에서 살아왔다. 내가 일한 모든 회사가 사용했으며, 내가 만든 모든 시스템이 Keel 위에서 운영되었다. IoT 플랫폼, 핀테크 시스템, 헬스케어 인프라, 소셜 플랫폼 등에서 매일 수백만 건의 요청을 처리했다.

비밀이 아니라 관성 때문에 사내 도구로만 유지했다. 내부 도구는 실제 문제를 해결하니 공개하면 더 유용하다. Go 마이크로서비스를 만들면서 매번 같은 15가지를 처음부터 연결하는 데 지쳤다면 — Keel은 바로 그 상황을 위해 존재한다.

👉 https://github.com/glodb/keel
👉 https://github.com/glodb/keel-code — 완전한 작동 예제

Aafaq Zahid는 17년 동안 분산 시스템과 프로덕션 인프라를 구축해 온 소프트웨어 아키텍트이자 창립 엔지니어다. Keel과 DBFusion의 창시자이며, 이전에 18,000개의 IoT 장치를 위한 맞춤형 UDP 프로토콜 설계에 대해 글을 썼다.

LinkedIn: https://linkedin.com/in/aafaqzahid

0 조회
Back to Blog

관련 글

더 보기 »

모바일 한여름 열풍

!Cover image for Mobile Midsommer Madnesshttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploa...