Learn이라기에는 부족하고 그저 인지했을 뿐인 오늘의 TIR(Today I Read)
넘쳐나는 공부거리로 밀리는 블로그 포스트 대체방안... 일단 메모하고... 주말에 틈틈히 제대로 공부한다!
✏️ Go서버 메모리 프로파일링
로컬 환경에서 pprof 돌려본게 전부인 사람 여깄어요... 공부가 많이 필요해요.... 공부 1순위..
팀에서 공인된(?) 리팩토링 담당자라 지금 빨리 공부해서 써먹어야함
0. 즉시 조치: 메모리 증설(매우매우 임시방편)
1. 프로파일링 & 모니터링 메트릭 수집
- Grafana/Prometheus+pprof 조합
## Grafana로 확인
- [ ] 메모리 사용 패턴 (계단식? 선형? 급증?)
- [ ] 문제 발생 시간대 (특정 시간? 랜덤?)
- [ ] CPU와 상관관계 (CPU 높을 때 메모리도?)
- [ ] 네트워크 I/O와 상관관계 (API 호출?)
- [ ] Go 메트릭 있는지 (go_memstats_*, go_goroutines)
## pprof 수집 타이밍
- [ ] Grafana에서 메모리 상승 시작 시점
- [ ] 피크 시점
- [ ] OOM 직전
## 비교 분석
- [ ] 정상 시점 vs 문제 시점 pprof diff
- [ ] Grafana 패턴과 pprof 결과 매칭
2. gdb로 메모리 덤프 & 분석(ㅁㅊ.. gdb 시스템 해킹 이후로 오랜만)
3. docker로 서버 띄워서 컨테이너 내 메모리 사용량 모니터링 + 메모리 덤프 수집(pprof) -> 덤프 분석
4. 테스트 환경에서 포트포워딩 접근해서 pprof 모니터링 & 분석
5. pyroscope 같은 continous profiling 도구 (현실적으로 바로 도입하기에는 무리)
[패턴1: 계단식 증가]
메모리 그래프:
│ ┌─────
│ ┌──┘
│┌─┘
─┘
10시 11시 12시
의미: 특정 이벤트마다 메모리 증가
원인: 캐시, 맵, 슬라이스 무한 증가
[패턴2: 선형 증가]
메모리 그래프:
│ /
│ /
│ /
─┘/
10시 11시 12시
의미: 지속적으로 누수
원인: 고루틴 누수, 커넥션 미해제
[패턴3: 급증 후 유지]
메모리 그래프:
│ ────
│ │
│───┘
의미: 대량 데이터 로드 후 미해제
원인: 대용량 쿼리 결과 캐싱
하다가 안고쳐지면 GC 튜닝까지 고려...흠.. 근데 이럴 경우까지 있나..
✏️ 고루틴 생명주기 규칙
1. main 함수가 종료되면, 모든 고루틴은 강제로 즉시 종료
- main 함수는 모든 고루틴의 '최상위 부모', 프로그램의 생명주기 자체 --> main이 끝나면 프로그램이 끝나므로 다른 모든 고루틴도 함께 사라짐
2. main 함수를 제외하고, 어떤 고루틴도 다른 고루틴을 직접적으로 종료시킬 수 없음
- 고루틴 A가 고루틴 B를 go 키워드로 실행시켰다고 해서, A가 B의 '부모'가 되는 특별한 관계가 생기는 것이 아님. A와 B는 그냥 독립적으로 실행되는 두 개의 동시 작업 단위일 뿐
- 따라서 고루틴 A가 먼저 끝나더라도, 고루틴 B는 자신의 작업이 끝날 때까지 계속 실행됨
예제)
package main
import (
"fmt"
"time"
)
func parent() {
fmt.Println("Parent: Starting child goroutine...")
// 자식 고루틴 시작
go child()
time.Sleep(2 * time.Second)
fmt.Println("Parent: My job is done. I'm exiting.")
// 여기서 parent() 함수는 종료되고, parent 고루틴은 사라집니다.
}
func child() {
fmt.Println(" Child: I'm starting my eternal loop.")
// 이 자식 고루틴은 1초마다 메시지를 출력하는 무한 루프를 돕니다.
for i := 0; ; i++ {
fmt.Printf(" Child: ...working... (%d)...\n", i)
time.Sleep(1 * time.Second)
}
}
func main() {
parent()
fmt.Println("Main: Parent has exited. Waiting for a bit to see what child does...")
time.Sleep(5 * time.Second)
fmt.Println("Main: Exiting.")
}
결과)
Parent: Starting child goroutine...
Child: I'm starting my eternal loop.
Child: ...working... (0)...
Child: ...working... (1)...
Parent: My job is done. I'm exiting.
Main: Parent has exited. Waiting for a bit to see what child does...
Child: ...working... (2)...
Child: ...working... (3)...
Child: ...working... (4)...
Child: ...working... (5)...
Child: ...working... (6)...
Main: Exiting.
결과를 보면, "Parent: My job is done." 이라는 메시지가 출력된 후에도 child 고루틴은 전혀 멈추지 않고 계속해서 작업을 수행하는 것을 볼 수 있다. parent 고루틴은 이미 사라졌지만 child는 그 사실을 전혀 알지 못하는데, 이 child가 바로 '좀비 고루틴'
--> 그래서 Context가 필요한 것! Context는 부모와 자식 고루틴 사이에 '작업 종료'라는 신호를 전달할 수 있는 유일한 표준적인 방법
만약 parent가 Context를 만들어서 child에게 전달하고, parent가 종료되기 직전에 그 Context를 cancel()했다면, child는 루프 안에서 ctx.Done() 채널을 확인하고 스스로 루프를 멈추고 안전하게 종료할 수 있었을 것!
✏️ Graceful Shutdown 중요성
main의 강제 종료에 의존하면 안 되는 이유
- 데이터 정합성 파괴
- 만약 어떤 고루틴이 데이터베이스 트랜잭션을 막 시작해서 A 계좌에서 돈을 출금했는데, B 계좌에 입금하기 바로 그 찰나에 main이 종료된다면 어떻게 될까? 돈은 공중으로 사라지고 데이터는 영원히 깨진 상태로 남는다.
- 파일을 쓰는 도중이었다면, 파일은 반만 쓰인 채로 손상됨.
- 진행 중인 작업 유실
- 만약 고루틴이 메시지 큐에서 중요한 작업을 꺼내와 처리하는 중이었다면? 프로그램이 강제 종료되면 그 작업은 처리되지 않은 상태로 유실될 수 있음. 다음 실행 때 이 작업을 다시 처리할 수 있다는 보장이 없음.
- 외부 리소스 정리 불가
- 작업 중에 임시 파일을 만들었거나, 외부 시스템에 잠금(lock)을 걸었다면 어떻게 될까? 강제 종료 시에는 이 리소스들을 정리하는 defer 구문이나 후처리 코드가 실행될 기회조차 없다. 임시 파일은 서버에 쓰레기로 남고, 외부 시스템의 잠금은 풀리지 않아 다른 프로세스에 장애를 일으킬 수 있다.
- 운영의 어려움
- "서버를 정상적으로 종료합니다" 와 같은 마지막 로그를 남길 수 없음. 서버가 정상적으로 종료된 것인지, 아니면 알 수 없는 에러로 죽은(crash) 것인지 구분하기가 매우 어려워짐.
Graceful Shutdown의 역할
우리가 Context, WaitGroup, os.Signal을 이용해 구현하는 Graceful Shutdown은 바로 이 "시스템 종료" 버튼의 역할을 한다.
- signal.Notify: 운영체제가 "이제 그만 종료해줘 (SIGTERM)" 라고 보내는 신호를 정중하게 받는다.
- context.CancelFunc: 이 신호를 받으면, 모든 고루틴에게 "하던 일 마저 정리하고 퇴근 준비해" 라고 Context를 통해 알려준다.
- 고루틴의 정리 작업: 각 고루틴은 이 신호를 받고, DB 트랜잭션을 커밋하거나 롤백하고, 열었던 파일을 닫고, 마지막 로그를 남기는 등 뒷정리
- sync.WaitGroup: 모든 고루틴이 "저 퇴근 준비 다 됐습니다" 라고 보고할 때까지 기다림
- main 종료: 모든 것이 안전하게 정리된 것을 확인한 후에야 비로소 main 함수가 종료
✏️ 스테이블 코인
사실 카이아 스테이블 코인 해커톤에 나간다. 이와 관련해서 스테이블 코인에 관한 리서치들을 모조리 탐독하려고 노력중
텔레그램 글을 더 많이 읽고 있긴 한데... 일단 좋은 포필러스의 리서치 글들을 남겨본다.
https://4pillars.io/ko/articles/are-stablecoin-payments-a-threat-to-banks-and-card-networks
스테이블코인 결제: 은행과 카드 네트워크에게 위협인가?
스테이블코인은 전통 결제 시스템을 어떻게 바꿀까요?
4pillars.io
https://4pillars.io/ko/issues/what-are-the-advantages-of-krw-stablecoins-compared-to-prepaid-funds
원화 스테이블코인, 선불충전금과 비교하여 어떤 이점이 있을까? (ASA 오피니언 #8)
원화 스테이블코인, 과연 한국에서 선불충전금과 어떤 차이가 있을까요?
4pillars.io
https://4pillars.io/ko/issues/stablecoin-future-of-money
Stablecoin: Future of Money
스테이블코인은 트럼프의 친암호화폐 정책 속에서 잠재적인 미래 기축통화로 부상하고 있습니다. 한국에서 또한 달러 기반 스테이블코인 거래량이 지속적으로 증가하고 있으며, 금융 생태계
4pillars.io
'Own > TIR(Today I Read)' 카테고리의 다른 글
[TIR] 2025.08.22(금) (0) | 2025.08.22 |
---|---|
[TIR] 2025.08.19(화) (0) | 2025.08.19 |