본문 바로가기
golang

[Golang] Cache 사용하기

by weq155 2023. 12. 8.
반응형

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 언어에서 캐시를 사용하는 이유와 그 방법에 대해 간략하게 설명합니다. 구체적인 구현 사례나 코드 예시가 필요하다면 추가적인 정보를 제공할 수 있습니

반응형