Go 언어에서 캐시의 중요성과 그 이점
소개
Go 언어는 강력한 동시성 처리 기능과 빠른 실행 속도로 잘 알려져 있습니다. 하지만, 모든 시스템에서 성능은 핵심적인 요소이며, 이를 위해 캐시의 사용은 필수적입니다. 본 포스트에서는 Go 언어에서 캐시를 사용해야 하는 주요 이유들을 탐색해보겠습니다.
캐시란 무엇인가?
캐시는 자주 사용되는 데이터나 계산 결과를 임시로 저장하는 메모리 영역입니다. 이 데이터는 빠르게 접근할 수 있으며, 시스템의 전반적인 성능을 향상시키는 데 기여합니다.
Go에서 캐시 사용의 이점
1. 성능 향상
캐시를 사용하면 데이터베이스나 외부 서비스에 대한 반복적인 요청을 줄일 수 있습니다. 이는 네트워크 지연을 감소시키고, 전체적인 애플리케이션의 응답 속도를 빠르게 합니다.
2. 시스템 부하 감소
데이터베이스와 같은 외부 시스템에 대한 부담을 줄임으로써 시스템 전체의 부하를 낮출 수 있습니다. 이는 특히 고부하 환경에서 중요한 이점입니다.
3. 비용 절감
외부 데이터 소스에 대한 요청을 줄임으로써, 데이터 전송 비용과 서버 유지 비용을 절감할 수 있습니다. 이는 특히 클라우드 기반 서비스에서 큰 이점으로 작용합니다.
4. 동시성과 확장성
Go의 동시성 기능과 함께 캐시를 사용하면, 여러 사용자의 요청을 효율적으로 처리할 수 있습니다. 이는 확장성 있는 애플리케이션 구축에 필수적입니다.
Go에서 캐시 구현하기
다른 라이브러리를 사용하는 방법도 있지만
아래 예시는 cache.json 파일을 생성하여 캐시를 관리하는 방법입니다.
구조체 정의
# Config를 사용해서 캐시 사용 여부 판단 (추후에 Kubernetes CRD로 사용하기 위함)
type Config struct {
CacheEnabled bool `json:"cacheEnabled"`
CacheData map[string][]string `json:"cacheData"`
}
# CacheItem 구조체 정의 필요한 캐시 필드를 정의합니다.
type CasheItem struct {
Value string
Timestamp time.Time
}
# 캐시 경로 설정
const (
cacheFilePath = "./cache.json"
)
Config 받아오기
아직은 로컬 테스트이기 때문에 Viper를 사용해서 Config를 받아옵니다.
Path에 위치에 config.yaml을 생성해서 config 관리
func initViper() error {
viper.SetConfigName("config")
viper.AddConfigPath("./")
viper.SetDefault("cacheEnabled", true)
return viper.ReadInConfig()
}
---
config.yaml
cacheEnabled: true
cachePath: ./cache
캐시 관리 로직
# 캐시 추가 기존 캐시에 존재하지 않는 항목일 경우 추가
func cacheAdd(cacheData map[string][]string, key, value string) {
if _, exists := cacheData[key]; !exists {
cacheData[key] = []string{}
}
cacheData[key] = append(cacheData[key], value)
}
// 캐시 삭제
func cacheDelete(cacheData map[string][]string, key string) {
delete(cacheData, key)
}
// 캐시 업데이트 (기존 값 대체)
func cacheUpdate(cacheData map[string][]string, key, value string) {
cacheData[key] = []string{value}
}
// 설정 파일에 변경된 캐시 데이터 저장
func loadCacheFromFile(filePath string) (map[string][]string, error) {
fileData, err := os.ReadFile(filePath)
if err != nil {
if os.IsNotExist(err) {
return make(map[string][]string), nil
}
return nil, err
}
cacheData := make(map[string][]string)
if err := json.Unmarshal(fileData, &cacheData); err != nil {
return nil, err
}
return cacheData, nil
}
# 캐시를 파일로 저장
func saveCacheToFile(cacheData map[string][]string, filePath string) error {
dataBytes, err := json.Marshal(cacheData)
if err != nil {
return err
}
return os.WriteFile(filePath, dataBytes, 0644)
}
캐시 테스트
func main() {
println(time.Since(time.Now()))
// Viper 설정 초기화 및 설정 파일 로드
if err := initViper(); err != nil {
fmt.Printf("Viper 초기화 오류: %v\n", err)
return
}
// 설정 구조체 로드
var config Config
if err := viper.Unmarshal(&config); err != nil {
fmt.Printf("설정 구조체 언마샬 오류: %v\n", err)
return
}
// 캐시 데이터 로드
cacheData, err := loadCacheFromFile(cacheFilePath)
if err != nil {
fmt.Printf("캐시 데이터 로드 실패: %v\n", err)
return
}
cacheUpdate(cacheData, "update123", "error123")
if err := saveCacheToFile(cacheData, cacheFilePath); err != nil {
fmt.Printf("캐시 데이터 저장 실패: %v\n", err)
return
}
cacheDelete(cacheData, "1")
if err := saveCacheToFile(cacheData, cacheFilePath); err != nil {
fmt.Printf("캐시 데이터 저장 실패: %v\n", err)
return
}
}
// Viper 설정 초기화 및 설정 파일 로드
func initViper() error {
viper.SetConfigName("config")
viper.AddConfigPath("./")
viper.SetDefault("cacheEnabled", true)
return viper.ReadInConfig()
}
// 캐시 작업 처리
func processCache(config *Config, cacheData map[string][]string) {
names := []string{"pod123", "pod456", "pod789"}
namespaces := []string{"default", "monitoring", "kube-system"}
kinds := []string{"pod", "service", "deployment"}
msgs := []string{"poderr", "serviceerr", "deploymenterr"}
if config.CacheEnabled {
for _, name := range names {
for _, namespace := range namespaces {
for _, kind := range kinds {
// 각 조합에 대한 키 생성
key := name + "_" + namespace + "_" + kind
for _, msg := range msgs {
// 캐시에 추가
cacheAdd(cacheData, key, msg)
}
}
}
}
fmt.Printf("캐시 데이터: %v\n", cacheData)
}
}
생성된 캐시
결론
Go 언어에서 캐시를 사용하는 것은 성능 최적화, 시스템 부하 감소, 비용 절감 등 다양한 이점을 제공합니다. 효율적인 캐시 구현은 Go 언어로 작성된 애플리케이션의 성능을 크게 향상시킬 수 있습니다.
캐시를 이용해서 중복 데이터를 판단하고 불필요한 api 호출을 줄일 수 있습니다.
이 블로그 포스트는 Go 언어에서 캐시를 사용하는 이유와 그 방법에 대해 간략하게 설명합니다. 구체적인 구현 사례나 코드 예시가 필요하다면 추가적인 정보를 제공할 수 있습니
'golang' 카테고리의 다른 글
[Kubebuilder] kubernetes Operator get pod 만들기 (1) | 2023.11.17 |
---|---|
[golang] kubernetes Operator 만들기 Kubebuilder API (0) | 2023.11.16 |
[golang] kubernetes Operator 만들기 Kubebuilder 설치 (0) | 2023.11.16 |
[golang] kubernetes Operator 만들기 Kubebuilder Controller-runtime 6 - Controller (0) | 2023.11.16 |
[golang] kubernetes Operator 만들기 Kubebuilder Controller-runtime 5 - Reconciler (0) | 2023.11.16 |