본문 바로가기
WIKI 기술 지식 베이스

가비지 컬렉션

원문 보기 위키 갱신

Tip

lakeFS Enterprise 사용자는 이 Spark 프로그램 대신 스탠드얼론 GC 프로그램을 실행할 수 있어요.

출처: 문서

본문

기본적으로 lakeFS는 모든 객체를 영원히 보관해요. 덕분에 과거 데이터 버전으로 시간 여행을 할 수 있죠. 하지만 때로는 기반 스토리지에서 객체를 완전히 제거하고 싶을 수 있어요. 이유는 비용 절감이나 프라이버시 정책 등 다양해요.

가비지 컬렉션은 lakeFS에서 기반 스토리지의 데이터를 삭제하는 유일한 기능이에요. 객체를 참조하는 모든 브랜치에서 그 객체가 삭제되거나 교체된 이후, 그리고 그 브랜치에 설정된 보존 기간이 지나기 전에는 절대 제거하지 않아요. Object Lifecycle과 Branch Lifecycle이 오래된 데이터와 오래된 브랜치를 은퇴시켜 이 상태로 만들어 주죠. 세 기능이 어떻게 맞물리는지는 Data Retention 문서를 참고하세요.

가비지 컬렉션(GC) 작업은 기반 스토리지에서 다음을 제거하는 Spark 프로그램이에요:

  • lakeFS에서 삭제(또는 교체)되었고 여러분이 정의한 규칙에 따라 만료된 것으로 간주되는 커밋된 객체

  • 더 이상 접근할 수 없는 커밋되지 않은 객체

  • 예를 들어, 커밋되기 전에 삭제된 객체가 그렇죠.

가비지 컬렉션 규칙

Info

이 규칙은 한 번이라도 커밋된 객체에만 적용돼요. 보존 규칙이 없으면 작업은 접근 불가능한 커밋되지 않은 객체만 제거해요.

가비지 컬렉션 규칙은 lakeFS에서 삭제(또는 교체)된 후 객체가 스토리지에 얼마나 오래 유지되는지를 결정해요. GC 작업은 브랜치별로 정의된 일수만큼 삭제된 객체를 보관해요. 브랜치별 규칙이 없으면 리포지토리 기본 규칙을 사용해요. 객체가 여러 브랜치 계보(ancestry)에 존재한다면, 모든 관련 브랜치의 보존 기간이 끝난 후에야 제거돼요.

리포지토리의 GC 규칙 예시:

{
  "default_retention_days": 14,
  "branches": [
    {"branch_id": "main", "retention_days": 21},
    {"branch_id": "dev", "retention_days": 7}
  ]
}

위 예시에서는 기본적으로 삭제 후 14일간 객체가 보관돼요. 하지만 main 브랜치에 존재하는 객체라면 21일간 보관되고요. dev 브랜치에 만 존재하는 객체는 삭제 후 7일간 보관돼요.

가비지 컬렉션 규칙 설정 방법

보존 규칙을 정의하려면 lakectl 명령, lakeFS 웹 UI, 또는 API를 사용하세요:

CLIWeb UI

GC 규칙을 담은 JSON 파일을 만드세요:

cat <<EOT >> example_repo_gc_rules.json
{
  "default_retention_days": 14,
  "branches": [
    {"branch_id": "main", "retention_days": 21},
    {"branch_id": "dev", "retention_days": 7}
  ]
}
EOT

lakectl로 GC 규칙을 설정하세요:

lakectl gc set-config lakefs://example-repo -f example_repo_gc_rules.json

lakeFS 웹 UI에서:

  • 리포지토리 메인 페이지로 이동해요.

  • Settings -> Data Retention 으로 가요.

  • Garbage Collection 섹션에서 Create rule을 클릭하거나, Edit rule로 기존 구성을 변경해요.

  • GC 규칙을 JSON으로 텍스트 박스에 붙여넣고 변경을 저장해요.

가비지 컬렉션 작업 실행 방법

작업을 실행하려면 다음 spark-submit 명령을 사용하세요(또는 여러분이 선호하는 Spark 실행 방식을 써도 돼요).

Spark 버전에 맞는 올바른 jar를 사용하세요:

lakeFS EnterpriselakeFS Community

Spark version Jar URL
3.x https://treeverse-clients-us-east.s3.amazonaws.com/lakefs-spark-client-enterprise/0.25.0/lakefs-spark-client-enterprise_2.12-assembly-0.25.0.jar
4.x https://treeverse-clients-us-east.s3.amazonaws.com/lakefs-spark-client-enterprise/0.25.0/lakefs-spark-client-enterprise_2.13-assembly-0.25.0.jar
Spark version Jar URL
3.x https://treeverse-clients-us-east.s3.amazonaws.com/lakefs-spark-client/0.22.0/lakefs-spark-client_2.12-assembly-0.22.0.jar
4.x https://treeverse-clients-us-east.s3.amazonaws.com/lakefs-spark-client/0.22.0/lakefs-spark-client_2.13-assembly-0.22.0.jar

Note

Spark 4.x는 Java 17+가 필요해요.

AWSAzureGCP

lakeFS EnterpriselakeFS Community

spark-submit --class io.treeverse.gc.GarbageCollection \
    --packages org.apache.hadoop:hadoop-aws:2.7.7 \
    -c spark.hadoop.lakefs.api.url=https://lakefs.example.com:8000/api/v1  \
    -c spark.hadoop.lakefs.api.access_key=<LAKEFS_ACCESS_KEY> \
    -c spark.hadoop.lakefs.api.secret_key=<LAKEFS_SECRET_KEY> \
    -c spark.hadoop.fs.s3a.access.key=<S3_ACCESS_KEY> \
    -c spark.hadoop.fs.s3a.secret.key=<S3_SECRET_KEY> \
    https://treeverse-clients-us-east.s3.amazonaws.com/lakefs-spark-client-enterprise/0.25.0/lakefs-spark-client-enterprise_<SCALA_VERSION>-assembly-0.25.0.jar \
    example-repo us-east-1
spark-submit --class io.treeverse.gc.GarbageCollection \
    --packages org.apache.hadoop:hadoop-aws:2.7.7 \
    -c spark.hadoop.lakefs.api.url=https://lakefs.example.com:8000/api/v1  \
    -c spark.hadoop.lakefs.api.access_key=<LAKEFS_ACCESS_KEY> \
    -c spark.hadoop.lakefs.api.secret_key=<LAKEFS_SECRET_KEY> \
    -c spark.hadoop.fs.s3a.access.key=<S3_ACCESS_KEY> \
    -c spark.hadoop.fs.s3a.secret.key=<S3_SECRET_KEY> \
    https://treeverse-clients-us-east.s3.amazonaws.com/lakefs-spark-client/0.22.0/lakefs-spark-client_<SCALA_VERSION>-assembly-0.22.0.jar \
    example-repo us-east-1

계정 키로 스토리지에 접근하고 싶다면:

lakeFS EnterpriselakeFS Community

spark-submit --class io.treeverse.gc.GarbageCollection \
    --packages org.apache.hadoop:hadoop-aws:3.2.1 \
    -c spark.hadoop.lakefs.api.url=https://lakefs.example.com:8000/api/v1  \
    -c spark.hadoop.lakefs.api.access_key=<LAKEFS_ACCESS_KEY> \
    -c spark.hadoop.lakefs.api.secret_key=<LAKEFS_SECRET_KEY> \
    -c spark.hadoop.fs.azure.account.key.<AZURE_STORAGE_ACCOUNT>.dfs.core.windows.net=<AZURE_STORAGE_ACCESS_KEY> \
    https://treeverse-clients-us-east.s3.amazonaws.com/lakefs-spark-client-enterprise/0.25.0/lakefs-spark-client-enterprise_<SCALA_VERSION>-assembly-0.25.0.jar \
    example-repo
spark-submit --class io.treeverse.gc.GarbageCollection \
    --packages org.apache.hadoop:hadoop-aws:3.2.1 \
    -c spark.hadoop.lakefs.api.url=https://lakefs.example.com:8000/api/v1  \
    -c spark.hadoop.lakefs.api.access_key=<LAKEFS_ACCESS_KEY> \
    -c spark.hadoop.lakefs.api.secret_key=<LAKEFS_SECRET_KEY> \
    -c spark.hadoop.fs.azure.account.key.<AZURE_STORAGE_ACCOUNT>.dfs.core.windows.net=<AZURE_STORAGE_ACCESS_KEY> \
    https://treeverse-clients-us-east.s3.amazonaws.com/lakefs-spark-client/0.22.0/lakefs-spark-client_<SCALA_VERSION>-assembly-0.22.0.jar \
    example-repo

Azure 서비스 프린시펄로 스토리지에 접근하고 싶다면:

lakeFS EnterpriselakeFS Community

spark-submit --class io.treeverse.gc.GarbageCollection \
    --packages org.apache.hadoop:hadoop-aws:3.2.1 \
    -c spark.hadoop.lakefs.api.url=https://lakefs.example.com:8000/api/v1  \
    -c spark.hadoop.lakefs.api.access_key=<LAKEFS_ACCESS_KEY> \
    -c spark.hadoop.lakefs.api.secret_key=<LAKEFS_SECRET_KEY> \
    -c spark.hadoop.fs.azure.account.auth.type.<AZURE_STORAGE_ACCOUNT>.dfs.core.windows.net=OAuth \
    -c spark.hadoop.fs.azure.account.oauth.provider.type.<AZURE_STORAGE_ACCOUNT>.dfs.core.windows.net=org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider \
    -c spark.hadoop.fs.azure.account.oauth2.client.id.<AZURE_STORAGE_ACCOUNT>.dfs.core.windows.net=<application-id> \
    -c spark.hadoop.fs.azure.account.oauth2.client.secret.<AZURE_STORAGE_ACCOUNT>.dfs.core.windows.net=<service-credential-key> \
    -c spark.hadoop.fs.azure.account.oauth2.client.endpoint.<AZURE_STORAGE_ACCOUNT>.dfs.core.windows.net=https://login.microsoftonline.com/<directory-id>/oauth2/token \
    https://treeverse-clients-us-east.s3.amazonaws.com/lakefs-spark-client-enterprise/0.25.0/lakefs-spark-client-enterprise_<SCALA_VERSION>-assembly-0.25.0.jar \
    example-repo
spark-submit --class io.treeverse.gc.GarbageCollection \
    --packages org.apache.hadoop:hadoop-aws:3.2.1 \
    -c spark.hadoop.lakefs.api.url=https://lakefs.example.com:8000/api/v1  \
    -c spark.hadoop.lakefs.api.access_key=<LAKEFS_ACCESS_KEY> \
    -c spark.hadoop.lakefs.api.secret_key=<LAKEFS_SECRET_KEY> \
    -c spark.hadoop.fs.azure.account.auth.type.<AZURE_STORAGE_ACCOUNT>.dfs.core.windows.net=OAuth \
    -c spark.hadoop.fs.azure.account.oauth.provider.type.<AZURE_STORAGE_ACCOUNT>.dfs.core.windows.net=org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider \
    -c spark.hadoop.fs.azure.account.oauth2.client.id.<AZURE_STORAGE_ACCOUNT>.dfs.core.windows.net=<application-id> \
    -c spark.hadoop.fs.azure.account.oauth2.client.secret.<AZURE_STORAGE_ACCOUNT>.dfs.core.windows.net=<service-credential-key> \
    -c spark.hadoop.fs.azure.account.oauth2.client.endpoint.<AZURE_STORAGE_ACCOUNT>.dfs.core.windows.net=https://login.microsoftonline.com/<directory-id>/oauth2/token \
    https://treeverse-clients-us-east.s3.amazonaws.com/lakefs-spark-client/0.22.0/lakefs-spark-client_<SCALA_VERSION>-assembly-0.22.0.jar \
    example-repo

Note

  • Azure에서 GC는 Spark 3.3.0에서만 테스트되었지만, 다른 Spark·Hadoop 버전에서도 동작할 수 있어요.

  • 환경에 hadoop-azure 패키지가 없다면 --packages org.apache.hadoop:hadoop-azure:3.2.1로 spark-submit에 패키지를 추가해야 해요.

  • Azure blob에서 GC가 동작하려면 소프트 삭제를 비활성화해야 해요.

GCP에서 가비지 컬렉션이 동작하려면 서비스 계정 키 JSON 파일을 제공해야 해요. 사용되는 서비스 계정은 리포지토리 네임스페이스(버킷)에 대해 Storage Object User 권한을 갖고 있어야 해요.

lakeFS EnterpriselakeFS Community

spark-submit --class  io.treeverse.gc.GarbageCollection \
    -c spark.hadoop.lakefs.api.url=https://lakefs.example.com:8000/api/v1  \
    -c spark.hadoop.lakefs.api.access_key=<LAKEFS_ACCESS_KEY> \
    -c spark.hadoop.lakefs.api.secret_key=<LAKEFS_SECRET_KEY> \
    -c spark.hadoop.google.cloud.auth.service.account.enable=true \
    -c spark.hadoop.google.cloud.auth.service.account.json.keyfile=<PATH_TO_JSON_KEYFILE> \
    -c spark.hadoop.fs.gs.impl=com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem \
    -c spark.hadoop.fs.AbstractFileSystem.gs.impl=com.google.cloud.hadoop.fs.gcs.GoogleHadoopFS \
    https://treeverse-clients-us-east.s3.amazonaws.com/lakefs-spark-client-enterprise/0.25.0/lakefs-spark-client-enterprise_<SCALA_VERSION>-assembly-0.25.0.jar \
    example-repo
spark-submit --class  io.treeverse.gc.GarbageCollection \
    -c spark.hadoop.lakefs.api.url=https://lakefs.example.com:8000/api/v1  \
    -c spark.hadoop.lakefs.api.access_key=<LAKEFS_ACCESS_KEY> \
    -c spark.hadoop.lakefs.api.secret_key=<LAKEFS_SECRET_KEY> \
    -c spark.hadoop.google.cloud.auth.service.account.enable=true \
    -c spark.hadoop.google.cloud.auth.service.account.json.keyfile=<PATH_TO_JSON_KEYFILE> \
    -c spark.hadoop.fs.gs.impl=com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem \
    -c spark.hadoop.fs.AbstractFileSystem.gs.impl=com.google.cloud.hadoop.fs.gcs.GoogleHadoopFS \
    https://treeverse-clients-us-east.s3.amazonaws.com/lakefs-spark-client/0.22.0/lakefs-spark-client_<SCALA_VERSION>-assembly-0.22.0.jar \
    example-repo

Mark와 Sweep 단계

작업을 두 단계로 나눌 수 있어요:

  • Mark: 실제로 제거하지 않고 제거 대상 객체를 찾아요.

  • Sweep: 객체를 제거해요.

Mark-only 모드

GC가 mark 단계만 실행하게 하려면 spark-submit 명령에 다음을 추가하세요:

spark.hadoop.lakefs.gc.do_sweep=false

mark-only 모드에서는 만료된 객체의 키를 <REPOSITORY_STORAGE_NAMESPACE>/_lakefs/retention/gc/unified/<MARK_ID>/ 아래에 기록해요. MARK_ID는 작업이 생성하며 드라이버 출력에서 찾을 수 있어요:

Report for mark_id=gmc6523jatlleurvdm30 path=s3a://example-bucket/_lakefs/retention/gc/unified/gmc6523jatlleurvdm30

Sweep-only 모드

GC가 sweep 단계만 실행하게 하려면 spark-submit 명령에 다음 속성을 추가하세요:

spark.hadoop.lakefs.gc.do_mark=false
spark.hadoop.lakefs.gc.mark_id=<MARK_ID> # Replace <MARK_ID> with the identifier you obtained from a previous mark-only run

가비지 컬렉션과 얕은 복사(shallow copy)

얕은 복사는 copy object API를 통해 제공되는 Enterprise 기능이에요. 데이터를 복제하지 않고 소스 항목과 동일한 물리적 객체를 가리키는 새 lakeFS 항목을 만들어요. 소스와 얕은 복사가 하나의 물리적 주소를 공유하기 때문에, 가비지 컬렉션이 얕은 복사가 아직 참조 중인 객체를 삭제해 버릴 수 있어요.

언제 이런 일이 생기나요?

GC는 모든 브랜치의 커밋·비커밋 항목을 스캔해 어떤 물리적 객체를 유지할지 결정해요. 어떤 항목도 그 물리적 주소를 참조하지 않으면 객체는 삭제 대상이 돼요. 위험은 이런 경우에 생겨요:

  • 객체를 얕은 복사했을 때 (소스와 복사본이 같은 물리적 주소를 공유)

  • 소스 항목이 삭제되었거나 소스의 브랜치가 제거되었을 때

  • GC가 실행되어 물리적 주소에 남은 참조가 없음을 발견했을 때 — 얕은 복사가 아직 커밋되지 않았거나 그 브랜치가 삭제된 경우 놓칠 수 있어요.

  • GC가 물리적 객체를 삭제해 얕은 복사가 사라진 객체(410 Gone)를 가리키게 될 때.

데이터 손실을 피하는 방법

  • 얕은 복사는 빠르게 커밋하세요. 커밋된 항목은 GC가 항상 스캔해요. 커밋되지 않은(staged) 얕은 복사는 GC의 mark와 sweep 단계 사이 구간에서 더 취약해요.

  • 얕은 복사를 커밋하기 전에 소스 항목을 삭제하지 마세요. 소스나 복사본 중 하나라도 커밋되어 브랜치 HEAD에서 도달 가능하면 GC는 기반 객체를 유지해요.

  • 얕은 복사를 담고 있는 브랜치는 유지하세요. 브랜치를 삭제하면 그 항목들이 GC 고려 대상에서 빠져요. 그 브랜치가 공유 물리적 주소의 마지막 참조였다면 GC는 객체를 삭제해요.

  • 적절한 보존 규칙을 설정하세요. 브랜치 보존 일수는 안전 완충 역할을 해요 — 소스 항목이 브랜치에서 삭제된 후에도 GC는 설정된 보존 기간 동안 객체를 유지해요.

가비지 컬렉션 참고 사항

  • 객체가 제거되려면 어떤 브랜치의 HEAD에도 존재하지 않아야 해요. 오래된 객체를 계속 붙잡는 것을 막으려면 오래된(stale) 브랜치를 제거하세요. 예를 들어 main에 머지된 뒤 방치된 브랜치를 생각해 보세요. 이후 main에서 삭제된 객체는 항상 그 오래된 브랜치에도 존재해서 제거되지 못해요.

  • lakeFS는 리포지토리의 스토리지 네임스페이스 밖의 객체는 절대 삭제하지 않아요. 특히 lakectl import나 UI 임포트 마법사로 가져온 객체는 GC 작업의 영향을 받지 않아요.

  • GC 작업이 실행되는 동안 삭제된 객체가 다시 살아나는 경우(예: 커밋 되돌리기)에는 그 객체가 삭제될 수도 있고 안 될 수도 있어요.

  • 가비지 컬렉션은 커밋을 제거하지 않아요: 제거된 객체를 담은 커밋도 계속 사용할 수 있지만, lakeFS에서 그 객체를 읽으려 하면 410 Gone HTTP 상태가 반환돼요.

더 알아보기 (Learn more)

공식 문서의 가비지 컬렉션 페이지는 https://docs.lakefs.io/admin/garbage-collection 에서 확인할 수 있어요.