Dev

go 언어에서 deep copy 사용

DevOps Engineer 2025. 3. 24. 08:02
728x90

Go 언어는 대부분 값 타입(value semantics)이 기본이지만, 슬라이스, 맵, 포인터, 구조체 포인터 등을 다루다 보면 얕은 복사(shallow copy)로 인한 예기치 않은 공유 문제가 발생할 수 있어요.
이럴 때 deep copy(깊은 복사)가 필요해집니다.

 

✅ 언제 Deep Copy가 필요한가?

🔹 1. 공유된 메모리로 인한 부작용을 피하고 싶을 때

  • 슬라이스나 맵은 참조 타입이기 때문에 복사해도 같은 메모리 영역을 가리킵니다.
  • 이를 수정하면 원본 데이터도 함께 변경되죠.
original := []int{1, 2, 3}
copy := original
copy[0] = 100
fmt.Println(original) // [100 2 3] ← 원본이 변경됨!

이런 부작용을 막기 위해서는 deep copy실제 데이터를 새롭게 복사해야 해요.

 

 

🔹 2. 다른 고루틴과 안전하게 데이터 분리하고 싶을 때

  • 병행성 상황에서 공유된 맵/슬라이스를 동시에 쓰면 Race Condition이 발생할 수 있어요.
  • 복사해서 쓰면 고루틴 간 충돌을 방지할 수 있습니다.

 

🔹 3. 입력 데이터 원본을 보존해야 하는 상황

  • 함수나 API에서 입력 데이터를 수정하되, 원본 데이터를 나중에 다시 사용해야 할 때.
  • 구조체가 중첩되거나 포인터 필드가 포함되어 있을 경우, 얕은 복사로는 부족합니다.

✅ Go에서 Deep Copy 구현 방식

Go에는 deepcopy() 같은 내장 함수는 없지만, 아래 방식으로 직접 구현할 수 있어요:

🔸 1. 슬라이스 복사

src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)

 

🔸 2. 맵 복사

original := map[string]int{"a": 1}
clone := make(map[string]int)
for k, v := range original {
    clone[k] = v
}

 

 

🔸 3. 구조체 Deep Copy (수동)

type User struct {
    Name string
    Tags []string
}

func DeepCopyUser(u User) User {
    tagsCopy := make([]string, len(u.Tags))
    copy(tagsCopy, u.Tags)
    return User{
        Name: u.Name,
        Tags: tagsCopy,
    }
}

 

🔸 4. 구조체 복사 (범용 - JSON 활용)

import "encoding/json"

func DeepCopy[T any](src T) (T, error) {
    var dst T
    bytes, err := json.Marshal(src)
    if err != nil {
        return dst, err
    }
    err = json.Unmarshal(bytes, &dst)
    return dst, err
}

※ 단점: 성능 느림, omitempty 영향, 인터페이스 필드는 제대로 복사 안 될 수 있음

 

🔍 참고: 실무에서 Deep Copy가 필요한 예시

  • Redis 캐시에서 가져온 데이터를 수정할 때 원본을 보존해야 할 경우
  • 상태(state)를 보존한 채 변경 이력을 관리하는 시스템 (예: Undo/Redo 기능)
  • 백업 또는 스냅샷 기능을 구현할 때

✅ 요약

Go에서는 슬라이스, 맵, 포인터를 사용할 때 얕은 복사로 인해 공유 문제가 발생할 수 있고, 이를 방지하기 위해 deep copy가 필요합니다.
구현은 직접 해야 하며, 구조에 따라 copy(), 루프, JSON 활용 등의 방식으로 처리합니다.

728x90