← 기술 지식

기술 지식 / 프로그래밍 언어 / Go

Go Slice의 기반 배열과 보관 수명

헤더 복사·append 용량·full slice expression·중첩 참조·작은 view의 큰 배열 보유와 비동기 버퍼 소유권을 설명합니다.

언어·런타임 · 원리·구현·운영
기술 지식 트리

이 노트는 Go slice를 원소 배열 자체가 아니라 기반 배열을 가리키는 헤더 값으로 이해하게 합니다. 헤더 복사, append에 의한 저장소 재사용, 하위 slice의 보유 수명, 비동기 버퍼 소유권을 차례로 분리하면 값 복사와 데이터 독립을 혼동하는 오류를 재현하고 진단할 수 있습니다.

변수 복사와 원소 저장소 공유

슬라이스는 기반 배열을 가리키는 정보와 len·cap을 가진 값으로 이해할 수 있습니다. b := a는 이 헤더 정보를 복사하므로 길이를 각각 갖지만 원소 저장소는 공유합니다. Go가 값 전달이라는 사실과 원소까지 독립이라는 주장은 다릅니다.

a := []int{10, 20, 30}
b := a
b[0] = 99
fmt.Println(a) // [99 20 30]

원소가 정수라면 새 배열을 할당해 copy하면 독립적으로 수정할 수 있습니다. 원소가 포인터·map·slice라면 바깥 원소를 복사해도 그 안의 참조 대상은 공유합니다. 깊은 복사의 범위는 도메인 소유 모델로 정합니다.

기반 배열을 가리키는 포인터·len·cap을 하나의 헤더로 생각하면 b := a의 결과를 단계별로 추적할 수 있습니다. 헤더의 len을 바꾸는 연산은 다른 헤더에 보이지 않지만, 공유 배열의 원소를 바꾸는 연산은 둘 다 관찰합니다. 이 기본 모델은 append가 새 배열을 만들었는지 확인하기 전에도 직접 인덱스 대입과 길이 변경을 구분하게 합니다.

Append와 잔여 용량·기반 배열 재사용

연산 기반 배열 헤더
b := a 공유 복사
b[0] = x 공유 원소 수정 길이 유지
여유 cap 안 append 기존 배열 재사용 반환된 slice의 len 증가
cap 초과 append 새 배열에 원소 이전 새 배열·len·cap 반환
make 후 copy 새 바깥 배열 독립 헤더
a := []int{10, 20, 30}
b := a[:1]
b = append(b, 99)
fmt.Println(a, b) // [10 99 30] [10 99]

b := a[:1]는 첫 원소만 보이게 하지만 a의 cap이 3이므로 b의 cap도 3입니다. 그래서 append(b, 99)는 새 배열을 만들지 않고 기존 배열의 index 1에 99를 쓰며, 결과 b는 len 2가 되고 a의 세 번째 원소는 그대로라 출력은 [10 99 30] [10 99]입니다.

a의 len 자체는 3으로 그대로이고, 함수 안의 append가 반환한 새 헤더를 호출자에게 돌려주지 않으면 원소만 바뀐 채 길이는 그대로일 수 있습니다. cap을 넘어 새 배열이 만들어지면 추가 원소가 호출자 slice에 보이지 않을 수도 있으므로, 확장을 지원하는 API는 반환 slice를 사용하게 해야 합니다.

b := a[:1] 뒤 append를 추적할 때는 먼저 old len=1, cap=3을 적고, append가 index 1에 쓰는지 cap 초과로 새 배열을 만드는지 판정합니다. cap 안 재사용이면 a의 보이지 않던 원소 영역도 바뀔 수 있고, cap 초과면 반환된 b만 새 저장소를 가리킵니다. API가 append 결과를 반환하지 않으면 호출자가 새 헤더를 잃는다는 점을 함께 검사합니다.

Cap 제한과 기존 원소 공유

Full slice도 기존 원소는 같은 배열을 봅니다a · len 3 cap 3. b · len 1 cap 1. 기반 배열 [10,20,30]전체 view첫 원소 viewa · len 3 cap 3b · len 1 cap 1기반 배열 [10,20,30]
Full slice도 기존 원소는 같은 배열을 봅니다 화살표는 공유 범위입니다. b=a[:1:1]은 cap을 1로 제한해 추가 append에 새 배열을 요구하지만 b[0]의 직접 쓰기는 여전히 a[0]을 바꿉니다.
그림을 글로 읽기
  • a · len 3 cap 3
  • b · len 1 cap 1
  • 기반 배열 [10,20,30]
  1. a · len 3 cap 3 → 기반 배열 [10,20,30]: 전체 view
  2. b · len 1 cap 1 → 기반 배열 [10,20,30]: 첫 원소 view

b := a[:1:1]은 b의 len과 cap을 모두 1로 제한하지만, b가 가리키는 기반 배열은 여전히 a가 가리키는 배열과 같습니다. 따라서 b[0]을 바꾸면 a[0]도 바뀌고, b에 한 원소를 append하면 cap 1을 넘어 새 저장소가 생깁니다. cap 제한은 기존 원소를 복제하거나 원래 큰 할당을 작은 할당으로 바꾸는 기능이 아니라, 이후 append가 기존 배열을 재사용할 수 있는 범위만 제한합니다.

작은 Slice와 큰 기반 배열의 보유 수명

100 MiB 수신 버퍼에서 필요한 20바이트만 잘라 장기 캐시에 보관하면 작은 slice가 큰 기반 배열의 수명을 유지할 수 있습니다. 보관 경계에서 필요한 부분을 작은 새 slice로 copy하면 수명을 분리할 수 있습니다. 짧게 빌리는 경로는 복사를 아끼고 오래 남기는 경로는 적은 복사 비용으로 큰 보유량을 줄일 수 있습니다.

포인터 원소가 있는 slice의 논리 삭제 후 뒤쪽 슬롯에 불필요한 참조가 남는지도 확인합니다. 그 배열이 계속 살아 있으면 지운 것처럼 보이는 객체를 붙잡을 수 있습니다. 더 이상 필요 없는 슬롯을 clear하는 방식은 다른 별칭이 그 슬롯을 필요로 하지 않는 소유 조건 아래 적용합니다. 표준 라이브러리의 삭제 함수나 관용구가 tail 슬롯을 정리하는지는 사용 중인 Go 버전의 문서와 소스에서 확인합니다.

heap profile은 주로 할당 위치와 표본 보유량을 보여 주며 모든 객체의 root 경로를 자동으로 제공하는 도구는 아닙니다. 큰 버퍼를 만드는 호출 위치와 장기 보관 코드·수명 추적을 함께 조사하고, 필요하면 더 구체적인 덤프·계측을 사용합니다. in-use 감소와 RSS 반환 시점도 다릅니다.

선택 기준은 slice가 빌리는 기간과 데이터 소유권입니다. 함수 내부에서 곧 소비하는 view는 복사를 피할 수 있지만, cache·queue·goroutine·pool을 넘어가는 데이터는 필요한 범위의 새 배열로 경계를 명시하는 편이 안전합니다. 포인터 원소의 tail clear는 모든 별칭이 그 슬롯을 더 이상 사용하지 않는다는 소유 조건을 확인한 뒤 적용합니다.

비동기 전달과 Pool 반환 시점

수신 버퍼의 slice를 worker에 보내고 곧바로 pool에 반환하면, worker가 읽기 전에 다른 요청이 같은 기반 배열을 덮어쓸 수 있습니다. 채널로 slice 헤더를 보냈다는 것은 len·cap·포인터 정보만 전달한 것이지 데이터 저장소를 복제한 것이 아니므로, 완료될 때까지 원본 버퍼를 유지하거나 필요한 영역을 복사하거나 명시적으로 소유권을 이전해야 합니다.

copy는 겹치는 slice 사이 복사를 지원하지만 다른 goroutine이 동시에 수정하는 배열에 동기화 없이 써도 된다는 뜻은 아닙니다. nil slice와 길이 0의 non-nil slice도 len·range는 비슷해도 nil 검사·일부 JSON 표현에서 다를 수 있으므로 외부 계약을 정합니다. 용량 성장 배율은 런타임 구현 세부로 취급합니다.

헤더·원소·보유량의 개별 확인

대입, cap 안 append, cap 초과 append, full slice 직접 수정, make+copy, 중첩 포인터를 작은 예제로 검사합니다. 큰 배열 보유는 동일 부하와 보관 기간에서 필요한 부분 복사 전후 in-use·할당률·RSS를 따로 봅니다. 버퍼 반환과 worker 읽기 경쟁은 race detector와 순서를 제어한 테스트로 확인합니다. 이 노트는 기대 동작과 측정 절차이며 실제 서버 메모리 프로파일을 측정한 결과는 아닙니다.

작은 테스트의 기대 결과는 대입에서 원본 원소가 함께 바뀌고, cap 제한 append에서는 새 backing array가 생기며, make+copy에서는 직접 수정이 분리되는 것입니다. 비동기 pool 시험에서는 worker가 읽기 전 반환했을 때 race 또는 데이터 변경이 관찰되는지, 복사·소유권 이전 후에는 사라지는지 확인합니다. in-use heap 감소와 RSS 감소는 별도 시각에 관찰합니다.

후속·관련 개념

함께 읽을 문서

전체 기술 지식
선택 연습 · 관련 질문 3개

질문은 이해를 확인하는 보조 자료입니다. 학습 경로와 문서의 범위는 질문 수와 독립적입니다.