GopherCon Korea 2023

 

개요

GopherCon Korea 2023 화면

2023년 8월 5일 GopherCon 2023의 1일차 행사에 참석한 후 세션 내용을 메모한 기록.

 

이번 GopherCon Korea 2023 행사는 세종대학교 대양AI센터 12F에서 열렸습니다.

GopherCon Korea 2023의 모바일 티켓

행사장 안은 시원했지만 당시 날씨는 ‘전국 폭염경보’ 상태였습니다.

 

다음날(8월 6일)에 열리는 GopherCon 2일차 행사는 개인 일정이 있어서 아쉽게도 참석하지 못했습니다.

 

오피스 아워

저는 행사 참석 전에 미리 당근마켓 부스에 오피스 아워를 신청했습니다.

당시 저는 EKS의 스팟 노드 운영과 파드의 스케일 인 과정에서 발생하는 Graceful Shutdown 이슈를 엔터프라이즈 환경에서 경험하고 있었고, 이 때 발생하는 시스템 불안정성과 503 Service Unavailable 에러 발생에 대한 해결 노하우를 얻고 싶어서 오피스 아워를 신청했습니다.

 

제가 궁금했던 사항

10시부터 Keynote 발표가 시작되고, 하나의 세션을 듣다가 11:15 ~ 11:40 동안에는 오피스 아워에 참석했습니다.
당근마켓 백엔드 개발자분에게 많은 노하우를 듣고, 이런저런 기술적인 이야기를 나눴습니다.

 

당근마켓의 문제해결 사례

인프라 구성

직접 설명받은 간략한 당근마켓 인프라 구성. 사실 이 부분은 당근마켓 SRE 밋업에서 이미 여러번 소개된 아키텍처입니다.

당근마켓의 EKS 아키텍처

 

Graceful Shutdown 기법

 

HTTPS 통신 시 외부로 나가는 문제

Istio 환경에서 내부 HTTP 통신은 정상적이나, HTTPS 통신을 붙이면 잘 안되는 문제가 있었습니다.

해결방법은 Istio의 VirtualService 설정에 HTTPS 트래픽에 대한 Mesh 처리가 빠져 있었던 걸로 확인되었다고 합니다.

문제해결 전 트래픽 흐름

문제해결 후 개선된 트래픽 흐름

한줄 요약하자면 당근마켓의 엔지니어와 개발자분들은 경이로운 문제 해결 능력을 갖추고 있는 실력자들 모임 같아 보인다.

 

세션 내용

키노트

 

Open Source In Go

스피커: 박남규, LitmusChaos

Go Proverbs
Golang과 관련된 속담
go-proverbs.github.io

 

CNCF 160개 프로젝트 중, Go 언어로 개발된 프로젝트가 100개, 컨트리뷰터 20만명

 

CNCF의 각 카테고리별 오픈소스를 잘 활용한다면 클라우드 네이티브 인프라를 쉽게 구축할 수 있습니다.

CNCF Landscape

CNCF Landscape 보기

 

CI/CD

CI/CD 파이프라인 표준 아키텍처

+------------+     +----------------+     +--------+
| Developers | --> | Github Actions | --> | Harbor |
+------------+     +----------------+     +--------+
                            |      |                  
                            v      | Triggered
                         +------+  |      +--------+
                         | Helm |  +----> | ArgoCD |
                         +------+ <------ +--------+

CI/CD 파이프라인에서 Harbor는 컨테이너 이미지 저장소이며 이미지 취약점 스캔 기능을 활용합니다.

헬름 차트에서 값이 변하는 부분들은 values.yaml 부분에 정의를 합니다.
일반적인 경우, 헬름 차트의 형태는 그대로 유지되고 이미지 태그 값 정도만 바뀌므로 values.yaml 파일에 업데이트를 수행하면 됩니다.

 

OpenTelemetry

 

오픈소스 기여

오픈소스 기여Contribution하는 방법에 대해 더 알고싶다면 테라폼 창립자인 Mitchell Hashimoto가 쓴 Contributing to Complex Projects를 보면 좋습니다.

오픈소스 기여에 도움을 주는 행사와 도구들

 

당근마켓의 Go 도입기

스피커: 당근마켓 변규현

 

당근마켓의 Go 사용 현황

대부분의 당근마켓 프로젝트는 Golang 기반으로 작성되어 있습니다.

운영하는 서비스에서 얼마나 많은 요청(트래픽)을 처리하는가?

스피커인 변규현님은 당근마켓 전부터 이미 개인적으로 Golang을 시작했었다고 합니다.

 

회사에서 Go를 시작한 계기

  1. 현재 기술로는 해결하기 어려운 상황이 발견됨
    1. Ruby on Rails 서버로 감당하기 트래픽이 당근마켓에서 발생하고 있었습니다.
    2. 이때 RDS의 약 60% 부하가 채팅 트래픽에서만 발생하고 있었습니다.

 

Go 성능 이슈

 

CPU 스로틀링 해결

GOMAXPROCS는 CPU Quota와 별개로 동작하는 문제가 있었습니다. 관련 이슈는 Automatically set GOMAXPROCS according to linux container cpu quota를 참고합니다.

당근마켓은 CPU Throttling Optimization을 해결하기 위한 목적으로 uber-go/automaxprocs 패키지를 활용했습니다.
uber-go/automaxprocs 패키지는 Linux 컨테이너에 설정된 CPU Quota에 따라 자동적으로 GOMAXPROCS를 설정해주는 역할을 합니다.

트래픽이 많이 발생하는 대부분의 당근마켓 프로젝트에는 uber-go/automaxprocs가 적용되어 있다고 생각하면 됩니다.

 

불필요한 메모리 할당을 최소화

slice & map : 메모리 할당시 더블링을 사용 doubling을 피해 필요한 메모리만큼 사용

 

메모리 사용 개선하기

GC 튜닝하기 GC Frequencey가 높은 현상을 발견.
분당 400회씩 GC가 도는 걸 발견. 400개의 stop-the-world가 발생, 이는 레이턴시에 영향을 줄 수 있고

stop-the-world
Go 언어는 자동으로 가비지 컬렉션을 수행하여 개발자가 직접 메모리 관리를 신경 쓰지 않도록 도와줍니다. 그러나 가비지 컬렉션을 실행하려면 프로그램의 실행을 잠시 멈춰야 할 때가 있습니다. 이때 발생하는 멈춤 현상을 “stop-the-world"라고 합니다.
“stop-the-world"는 프로그램의 실행이 가비지 컬렉션을 위해 잠시 중단되는 상황을 의미합니다.

GC 작업이 CPU Time의 30%까지 소모. 서비스 운영에 굉장히 낭비라고 판단되었음.

GC Cycle의 횟수를 줄여야 할까? GC Cycle로 인해 레이턴시 영향도 최소화, 인프라 비용 절감도 필요했고 개선이 필요하다고 판단했었음.

 

Go v1.20 기준으로 GC Cycle의 튜닝 파라미터는 2개 제공하고 있습니다.

같이 보면 좋은 자료
GOMEMLIMIT is a game changer for high-memory applications

 

환경변수 이름As wasNow
GOGC100 (Default)-1 (비활성화)
GOMEMLIMITno soft limit (Default)container의 메모리 스펙의 60~80%

Now에서 GOGC 값의 경우는 memory limit을 초과할 때에만 GOGC가 동작하도록 설정했습니다.

 

적용 후 개선된 Application 성능 결과는 다음과 같습니다.

 

당근마켓은 이 외에도 다양한 오픈소스들을 공개하고 생태계에 기여하고 있습니다.

 

시간 관계상 이번 발표에서 공유하지 못한 내용들

달리는 자동차의 바퀴 갈아끼우는 짤방

당근마켓 발표 마지막 쯤에 위 ‘달리는 자동차의 바퀴 갈아끼우기’ 짤방을 보니 신기했습니다. 저 사진을 보니까 합성은 아니고 진짜로 가능한 것처럼 보이네요. 매우 위험하다는 게 문제겠지만.

“운영되고 있는 서비스를 고객에게 임팩트를 주지 않고, 어떻게 성능을 최적하고 빠르게 기능을 전달할 수 있는지를 고민하고 있습니다.”

 

질문답변

Q: 당근마켓에서 사용하는 실제 프로덕션 환경에서 고루틴의 개수 제한이나 균형점이 있는지? A: Worker Pool에 들어갈 때 아토믹한 카운터를 하나 만들고, Go routine에서 카운트를 내려주고 Graceful SHutdown 할때 → Worker Pool을 체크한 후 죽는 방식

60,000개 이상의 고루틴이 에러를 내뿜으면서 한번에 죽은 사례가 발생했었습니다.

개선할 필요가 있는 장애나 이슈 현상이 먼저 관측이 된 이후, 그 다음에 어플리케이션을 개선을 하는 것이 시간 효율적입니다.

“고작 1~2명의 유저가 접속하는 서비스가 있는데, 어플리케이션 문제가 발생하기도 전에 어플리케이션의 성능을 튜닝하려고 하는 건 너무 시간비용 낭비라고 생각합니다.”

차라리 그 시간에 새로운 기능을 개발해서 배포하는 게 더 생산성 있는 일이라는 걸 말씀하시는 듯

 

그 외 스피커 분의 Go 도입시 조언들

 

API 서버 테스트코드 A-Z

케이스별 효과적인 테스트코드 작성 전략

스피커: 김진용
네이버클라우드 → 네이버랩스 → 룩코 (백엔드 엔지니어)

테스트 코드를 잘 짜기 위해서는 어떤 프로젝트 구조를 가지는 것이 유리한지, 3rd Party API와 같은 외부 의존성이 있는 경우에는 테스트를 어떻게 작성해야 하는 지에 대해서 설명하고, 유용하게 사용할 수 있는 테스트 패키지들에 대해 설명하는 세션이었습니다.

 

강의 대상Who should attend?

 

Golang에 대한 테스트는 Table Driven Testing 방식을 주로 사용합니다.

 

func TestIsSubset(t *testing.T) {
  type args struct {
    list1 []string
    list2 []string
  }
  tests := []struct {
    // ...
  }
}

 

테스트 관련 자주 사용하는 go CLI 명령어

# 전체 코드 테스트
go test ./...

go test github.com/gophercon/example/overview

# 특정 패키지나 파일에서 이름이 `TestIsSubset`인 함수를 실행
go test github.com/gophercon/example/overview -run TestIsSubset

# 테스트 cover 파일 저장
go test -coverpkg=./... -coverprofile=coverage.out ./...

# cover 파일을 htlm로 변환
go tool cover -html=coverage.out

참고로 -run 옵션에는 정규표현식도 적용할 수 있습니다.

 

어떤 코드가 테스트를 어렵게 만드는가?

// Go routine case
func callAPI(v string) error {
  time.Sleep(3 * time.Second) // simulate delay
  fmt.Printf("%v Do Something v vlaue: ", v)
  return nil
}
func UpdateData(data []string) error {
  // ...
}
type Config struct {
  // 전역설정
  DebugMode bool
}

var GlobalConfig *Config

type Service struct {}
func (s *Service) DoSomething() {
  // 직접적인 globalConfig 참조
  if GlobalConfig.DebugMode {
    // ...
  }
}

 

Golang Project Layout

Golang에서는 성격에 따라서 프로젝트 레이아웃도 천차만별. Golang은 프로젝트 구조에 대한 명확한 표준이나 강제하는 부분이 없음.

 

결과적으로 Go 프로젝트 구조는 아직 명확한 정답이 없습니다. 여러 사례를 경험해본 결과 적당히, 상황에 맞게, 알아서 작성하는 것이 대부분입니다.
팀 구성원들이 다들 동의할 수 있는, 참조할 수 있는 프로젝트 구조면 괜찮을 것 같습니다.

 

Clean 아키텍처 원칙

자세한 사항은 Go Clean Architecture를 참고

Go Clean Architecture의 구성도

보통 Interface 같은 경우 한 패키지에 몰아져 있으면 장점들이 많습니다.

중요: 상위 레이어는 하위 레이어를 참조할 수 있지만, 하위 레이어는 상위 레이어를 알지도 못하고 참조할 수 없습니다.

 

결론

테스트를 어렵게 만드는 요소

 

레이어별 테스트 UseCase

Repository

Repository 레이어에서는 데이터베이스에 직접 접근하는 계층이기 때문에 의도한대로 데이터가 CRUD가 잘 동작했는지 확인할 필요가 있습니다.

 

외부 의존성 대체하는 테스트 도구는 크게 3가지가 있습니다.

 

UseCase

해당 레이어에서는 비즈니스 로직을 구현하는 계층이고, 테스트 또한 로직을 좀 더 집중해서 작성이 필요함

외부 의존성을 가지는 부분은 Repository에서 처리하고 있기 때문에 테스트를 셋업하는데 들어가는 노력을 최소화할 필요가 있음

Mocking 사용시 참조 라이브러리

 

Delivery

HTTP API 기준으로 어떤 프레임워크(gin, echo, fiber)를 사용했느냐에 따라 테스트를 하는 방식이 달라질 수 있음.

외부에서 올바른 요청이 들어왔는지 확인할 필요가 있습니다.

Usecase 레이어와 올바르게 통신하고 반환된 데이터를 올바르게 처리하고 반환하는지 확인해야 합니다.

func TestCreateUser(t *testing.T) {
  // ...

  mockuserUsecase.on("CreateUser", mock.Anything, ..., nil)
}

 

정리

유닉스 철학Unix Philosophy
한 가지 일을 잘 수행하는 프로그램을 작성하십시오.
Do One Thing And Do It Well.

 

런타임에 정해지는 값들

대표적으로 시간이나 생성 때마다 만들어지는 ID 값이 대표적임.

type Realtime strcut {}
...

대안으로 google의 go-cmp 패키지를 사용하는 것도 좋은 방법입니다.

인터페이스를 별도로 선언하면서 처리하는게 너무 과하다고 생각하면 런타임시에 정해지는 값들에 대해서는 테스트를 무시하고 작성하는 것도 방법이 될 수 있습니다.

Table Driven Testing with go-cmp의 샘플 코드는 다음과 같습니다.

func TestUserUsercase_CreateUser(t *testing.T) {
  tests := []struct {
    name     string
    userName string
    want     User
  }{...}
  for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
      us := UserUsecase{}

      got := us.CreateUser(tt.userName)
      assert.True(t, cmp.Equal(tt.want, got, cmpopts.IgnoreFields...))
    })
  }
}

 

어쩔 수 없이 비대해지는 함수들
회사가 성장하면서 비즈니스 로직은 복잡해지고 하나의 함수가 처리하는 기능의 크기가 커지는 일은 불가피하다. (먼저 심적으로 받아들이는 게 중요하다.)

이 경우 함수 분할(Function Splitting)로 문제를 해결합니다.

하나의 큰 함수를 여러 개의 작은 함수로 분할하여 기능을 분리합니다. 이렇게 하면 각 함수의 역할이 명확해지고 코드가 더 모듈화됩니다. 관련된 작업을 수행하는 작은 함수들을 만들어 가독성을 높일 수 있습니다.

 

고루틴 & 채널
함수내부에 있는 Goroutine이 별도의 동기화 과정을 거치지 않고 종료되는 경우 호출 결과를 담는 채널을 반환해줍니다.

 

내용 정리

 

Go와 K8s로 만드는 Datacenter Autopilot

스피커: 현대자동차, 박도형, Cloud Platform Developer

엔지니어 커리어 전체가 Private Cloud 플랫폼 개발만 하신 엔지니어이셔서 인상적이었습니다.

HMetal

현대의 자체 서비스인 인프라 자동화 솔루션 HMetal에 대한 소개 및 동작방식 세션이었습니다.

HMetal은 현대에서 자체 개발한 Baremetal as a Service 입니다.

Barmetal 서버를 --> Cloud처럼 자동화된 방식으로 --> 사용자에게 제공

 

K8s Operator Pattern 도입 결정

HMetal의 대략적인 아키텍처

 

참고자료

GopherCon Korea 공식 사이트

유닉스 철학

Golang korea 발표 저장소