Golang context 이해하기

Context란?

Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.

위는 공식 문서에 나와있는 표현으로, 이에 따르면 context는 API와 프로세스 경계에서 기한, 취소 신호, 요청 범위의 값들을 전달한다. 우리가 어떤 API를 호출하거나, 새로운 goroutine을 생성하여 작업을 수행할 때 context를 통해서 호출이 완료되어야 하는 기한을 명시할 수도 있고, 요청했던 작업을 취소할 수도 있으며, 특정 값을 전달할 수도 있다.

Context 인터페이스

Context는 인터페이스이고, 다음과 같은 네 가지 메서드로 구성되어 있다.

type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}

각 메서드는 다음과 같은 기능을 한다.

메서드 기능
Deadline() 해당 컨텍스트에서 수행되는 작업이 취소되어야 하는 시각(time.Time)을 반환한다. 기한이 없는 경우에는 ok==false를 반환한다.
Done() 해당 컨텍스트가 취소될 때 닫히는 채널을 반환한다. 취소되지 않는 컨텍스트의 경우에는 nil이 반환된다.
Err() Done() 채널이 아직 닫히지 않았으면, nil을 반환하고, 닫히지 않았다면 왜 닫혔는지를 설명하는 error가 반환된다. 에러 메시지는 "Canceled if the context was canceled" 또는 "DeadlineExceeded if the context's deadline passed." 둘 중에 하나이다.
Value() 해당 컨텍스트에서 키와 연관된 값을 반환한다. 연관된 값이 없는 경우 nil을 반환한다.

Context 구현

context 패키지는 Context 인터페이스 뿐 아니라, 다음과 같은 Context 구현 메서드들을 제공한다.

context.Background()

빈 Context를 반환한다. 이 함수로 생성된 Context 객체는 기한도, 값도 없고 취소되지도 않는다. 이 메서드는 탑레벨의 Context를 생성하는데 사용된다. 뒤에 가서 보게 되겠지만 context.TODO()를 제외한 다른 Context 생성 함수들은 부모 Context에 파생되어 생성된다. context.Background()는 부모가 없는, 즉 루트 Context를 생성하기 때문에 탑레벨 Context라고 하는 것이다.

Background() --- WithValue() --- WithCancel
|
`- WithTimeout()

context.TODO()

context.Background()와 동일하게 기한, 값이 없고 취소되지 않는 빈 Context를 반환한다. 소스 코드를 들어가서 보면 실제로 context.Background()와 동일하게 emptyContext 객체를 반환하는 것을 볼 수 있다. 따라서 context.Background()를 써야할 곳에 context.TODO()를 써도 동일하게 동작할 것이다. 하지만 이름에서 알 수 있듯 이 함수는 Context를 쓰긴 해야 하지만 어떻게 써야할지 확실치 않은 곳에 써야한다.

context.WithValue(Context, key, val interface{}) (ctx Context)

부모 Context에서 파생되어, {key, val} 쌍을 저장하는 자식 Context를 반환한다. 저장된 값은 Value(key interface{}) 메서드로 읽어올 수 있다.

ctx := context.WithValue(context.Background(), "foo", "bar")
fmt.Println(ctx.Value("foo")) // "bar"

쓰임새는 맵과 비슷하지만, 값을 읽어오는 과정은 맵과는 다르다. Value(key interface{}) 메서드는 현재 Context에 찾는 key에 해당하는 값이 없으면 부모 Context를 하나씩 거슬러 올라가면서 값을 찾는다. 끝내 값을 찾지 못하면 nil이 반환되고, 값을 찾으면 해당 값이 반환된다.

// ctx1{1:a} --- ctx2{2:b}
// |
// `- ctx3{3:c}
ctx1 := context.WithValue(context.Background(), 1, "a")
ctx2 := context.WithValue(ctx1, 2, "b")
ctx3 := context.WithValue(ctx1, 3, "c")
fmt.Println(ctx2.Value(1)) // "a"
fmt.Println(ctx2.Value(2)) // "b"
fmt.Println(ctx3.Value(1)) // "a"
fmt.Println(ctx3.Value(2)) // <nil>
fmt.Println(ctx3.Value(3)) // "c"

위 예제에서 ctx1ctx2, ctx3의 부모이기 때문에 ctx2, ctx3 모두 1에 연관된 값 "a"를 가져올 수 있다. 하지만 ctx3에서부터 거슬러 올라갈 때는 2 키에 해당하는 값이 없기 때문에 nil이 반환된다.

context.WithCancel(Context) (Context, CancelFunc)

부모 Context에서 파생된 취소 가능한 Context 객체와 취소 함수를 반환한다. 취소가 가능한 Context이기 때문에 Done() 함수는 nil 이 아닌 채널을 반환한다. 이 Done 채널은 취소 함수가 호출되거나 부모 Context의 Done 채널이 닫히면 닫힌다. 연관된 자원을 해제하기 위해서, 작업을 해제하는 즉시 CancelFunc를 호출해야한다. 공식 문서의 이 예제는 취소 함수를 호출하여 goroutine 누수를 막는 것을 보여준다.

context.WithDeadline(context.Context, time.Time) (Context, CancelFunc)

부모 Context에서 파생된 취소 가능하며, 지정된 시간에 자동으로 취소되는 Context 객체를 반환한다. WithCancel()과 마찬가지로 취소 함수도 함께 반환된다. 기한이 지나면 자동으로 취소되기는 하지만, 자원을 빠르게 해제하기 위해서 작업 종료 후에 취소 함수를 호출해야 한다.

context.WithTimeout(context.Context, time.Duration) (Context, CancelFunc)

WithDeadline()과 동일하지만 기한(e.g. 2021-10-03 00:00:00 을 받는 것이 아닌 기한까지 남은 시간(e.g. 5 minutes)을 받는다.

References

https://pkg.go.dev/context

Java Cipher - 알고리즘, 운용 모드, 패딩의 이해

자바에서는 대칭키 알고리즘을 사용하여 데이터를 암호화/복호화할 때 javax.crypto.Cipher 클래스를 사용한다. 이 클래스의 인스턴스는 정적 메서드인 Cipher.getInstance()를 호출하여 가져올 수 있는데, 호출시 사용할 알고리즘, 운용 모드, 패딩 방식을 인자로 넘겨줘야 한다. 대칭키 암호화에서 알고리즘, 운용 모드, 패딩은 무엇이고 어떤 역할을 하는지 알아보자.

Continue Reading →

Kafka 메시지 압축률 안나와서 고생한 이야기

카프카를 사용할 때 메시지를 압축하면 디스크와 네트워크 자원을 절약할 수 있다. 대신 압축을 하고 푸는 데 CPU를 좀 더 쓰게 되지만 메시지 압축은 대체로 프로듀서와 컨슈머의 처리량을 높여준다. 디스크 성능이 좋지 않거나, 네트워크 대역폭이 낮은 환경에서는 압축의 효과를 크게 볼 수 있다. 하지만 메시지를 압축하도록 설정하더라도 압축률(compression ratio)이 낮다면 디스크 절약이나 처리량 향상같은 효과를 볼 수 없다.

Continue Reading →

Log4j2 Filter의 onMatch, onMismatch 값들의 의미

Log4j2의 Filter를 사용하면 다양한 방법을 로그를 제어할 수 있다. Log4j2 - Filters 문서를 보면 Log4j2가 제공하는 필터의 종류와 설정 방법이 설명되어 있다. 필터 종류마다 설정해야 하는 속성들이 조금씩 다르지만 모든 필터는 공통적으로 onMatch, onMismatch 속성을 갖고있다. 이 속성들은 각각 Filter에서 정의한 값과 매칭될 때 또는 그렇지 않을 때의 동작을 의미한다. 여기에 들어갈 수 있는 값은 ACCEPT, DENY, NEUTRAL 세가지이다. 그런데 잠깐, ACCEPT는 로그를 쓰겠다는 의미일 것이고, DENY는 쓰지 않겠다는 의미일 것이다. 그럼 도대체 NEUTRAL은 뭘까?

Continue Reading →

Node.js에서 exports와 mudule.exports의 차이

요 며칠간 Node.js를 공부하고 있다. 회사에서 주로 자바로 개발하고 있고, 개인적으로도 자바와 같은 객체 지향 + 정적 타입 언어가 익숙해서 좋아하지만 사람 일은 모르는 것 아니겠는가. 어딜 갖다놔도 쓸만한 사람이 되려면 아무쪼록 이것저것 배우는 게 좋을 것 같아서 다른 언어도 배우고 있다. Mozilla에 잘 정리된 Express/Node.js 튜토리얼이 있길래 문서를 차근차근 읽어보는 중이다.(Mozilla 사이트의 문서들은 하나같이 깔끔하고 좋은 것 같다!)

Continue Reading →

개발자가 몬티홀 문제(Monty Hall Problem) 이해하는 이야기

몬티 홀 문제(Monty Hall Problem)몬티 홀은 이 문제를 소개한 TV쇼의 사회자이다. 방송을 계기로 문제가 유명해지면서 문제의 이름이 몬티 홀 문제(Monty Hall Problem)로 굳어졌다. 문제는 아래와 같다. 문이 세 개가 있다. 하나의 문 뒤에는 자동차(상품)가 있고, 나머지 두 개의 문 뒤에는 염소(꽝)가 있다. 참가자는 문 하나를 고른다. 사회자는 참가자가 선택하지 않은 문 중 염소가 있는 문을 하나 열어서 보여준다. 사회자는 참가자에게 문을 바꿀 수 있는 기회를 준다. 이 때 참가자는 문을 바꾸는 게 이익일까? 바꾸지 않는 게 이익일까?

Continue Reading →

Java InterruptedException은 어따 쓰는겨?

Thread.sleep()과 InterruptedException자바 개발자라면 Thread.sleep() 메서드를 한번쯤은 써봤을 것이다. 이 메서드는 제품 단계의 코드에서는 잘 쓰이지 않지만 테스트 코드 등에서 어떤 로직이 실행될 때까지 특정 시간동안 기다려야 할 때 사용된다. 그런데 이 메서드를 사용하려고 할 때마다 컴파일러가 불평을 늘어놓는다. 그저 코드 실행을 몇 초 뒤로 미루고 싶었을 뿐인데…

Continue Reading →

Java Object 클래스의 wait과 notify의 사용법

Object의 wait, notify와 notifyAll자바의 최상위 클래스인 Object에는 몇 가지 메서드가 존재한다. 널리 쓰이는 toString()은 객체를 문자열로 표현할 때, hashCode()는 객체의 해시 값을 계산할 때 사용된다. 거의 사용되지 않고 가끔 IDE의 자동 완성 기능에서나 보게 되는 메서드들도 있으니 그것이 바로 wait(), notify(), notifyAll()이다. 이들의 동작을 간략히 정리하면 다음과 같다.

Continue Reading →

Java의 고유 락(intrinsic lock)에 대해

고유 락과 synchronized 블록자바의 모든 객체는 락(lock)을 갖고 있다. 모든 객체가 갖고 있으니 고유 락(intrinsic lock)이라고도 하고, 모니터처럼 동작한다고 하여 모니터 락(monitor lock) 혹은 그냥 모니터(monitor)라고도 한다. 자바의 synchronized 블록은 동시성 문제를 해결하는 가장 간편한 방법으로, 고유 락을 이용하여 여러 스레드의 접근을 제어한다. 간단한 예제를 통해 synchronized의 사용법을 알아보자.

Continue Reading →