소프트 삭제 사용 중지

개요 사용

이 페이지에서는 조직의 새 버킷과 기존 버킷에서 소프트 삭제 기능을 중지하는 방법을 설명합니다.

데이터 손실이 방지되도록 기본적으로 새 버킷에 소프트 삭제가 사용 설정되어 있습니다. 필요한 경우 소프트 삭제 정책을 수정하여 기존 버킷의 소프트 삭제를 중지하고 조직 전체의 기본 태그를 설정하여 새 버킷의 소프트 삭제를 기본적으로 중지할 수 있습니다. 소프트 삭제를 중지하면 실수로 또는 악의적으로 삭제된 데이터를 포함하여 삭제된 데이터를 복구할 수 없습니다.

필요한 역할

소프트 삭제를 중지하는 데 필요한 권한을 얻으려면 관리자에게 조직 수준에 대한 다음 IAM 역할을 부여해 달라고 요청하세요.

이러한 사전 정의된 역할에는 소프트 삭제를 중지하는 데 필요한 권한이 포함되어 있습니다. 필요한 정확한 권한을 보려면 필수 권한 섹션을 펼치세요.

필수 권한

소프트 삭제를 중지하려면 다음 권한이 필요합니다.

  • storage.buckets.get
  • storage.buckets.update
  • storage.buckets.list (이 권한은 Google Cloud 콘솔을 사용하여 이 페이지의 안내를 수행하려는 경우에만 필요)

    태그 관리자(roles/resourcemanager.tagAdmin) 역할의 일부로 포함된 필수 권한은 태그 관리를 위한 필수 권한을 참고하세요.

역할 부여 방법에 대한 자세한 내용은 버킷에 IAM 사용 또는 프로젝트에 대한 액세스 관리를 참조하세요.

특정 버킷의 소프트 삭제 사용 중지

시작하기 전에 다음 사항을 고려하세요.

  • 중지 기간에 소프트 삭제된 객체가 있는 버킷에서 소프트 삭제 정책을 중지하면 기존의 소프트 삭제된 객체는 이전에 적용된 보관 기간이 만료될 때까지 유지됩니다.

  • 버킷에서 소프트 삭제 정책을 중지한 후 새로 삭제된 객체는 Cloud Storage에 보관되지 않습니다.

다음 안내에 따라 특정 버킷의 소프트 삭제를 사용 중지합니다.

콘솔

  1. Google Cloud 콘솔에서 Cloud Storage 버킷 페이지로 이동합니다.

    버킷으로 이동

  2. 버킷 목록에서 소프트 삭제 정책을 사용 중지하려는 버킷의 이름을 클릭합니다.

  3. 보호 탭을 클릭합니다.

  4. 소프트 삭제 정책 섹션에서 사용 중지를 클릭하여 소프트 삭제 정책을 사용 중지합니다.

  5. 확인을 클릭합니다.

Google Cloud 콘솔에서 실패한 Cloud Storage 작업에 대한 자세한 오류 정보를 가져오는 방법은 문제 해결을 참고하세요.

명령줄

--clear-soft-delete 플래그와 함께 gcloud storage buckets update 명령어를 실행합니다.

  gcloud storage buckets update --clear-soft-delete gs://BUCKET_NAME

각 항목의 의미는 다음과 같습니다.

  • BUCKET_NAME은 버킷의 이름입니다. 예를 들면 my-bucket입니다.

REST API

JSON API

  1. Authorization 헤더에 대한 액세스 토큰을 생성하려면 gcloud CLI가 설치 및 초기화되어 있어야 합니다.

  2. 다음 정보를 포함하는 JSON 파일을 만듭니다.

    {
      "softDeletePolicy": {
        "retentionDurationSeconds": "0"
      }
    }
  3. cURL를 사용하여 PATCH 버킷 요청으로 JSON API를 호출합니다.

    curl -X PATCH --data-binary @JSON_FILE_NAME \
      -H "Authorization: Bearer $(gcloud auth print-access-token)" \
      -H "Content-Type: application/json" \
      "https://storage.googleapis.com/storage/v1/b/BUCKET_NAME"

    각 항목의 의미는 다음과 같습니다.

    • JSON_FILE_NAME은 2단계에서 만든 JSON 파일의 경로입니다.
    • BUCKET_NAME은 관련 버킷의 이름입니다. 예를 들면 my-bucket입니다.

프로젝트에서 가장 큰 100개의 버킷에 대해 소프트 삭제 사용 중지

Google Cloud 콘솔을 사용하면 소프트 삭제된 바이트가 가장 많거나 활성 바이트에 대해 소프트 삭제된 바이트의 비율이 가장 높은 버킷을 기준으로 최대 100개의 버킷에 대해 소프트 삭제를 한 번에 사용 중지할 수 있으므로 소프트 삭제 비용에 가장 큰 영향을 미치는 버킷을 관리할 수 있습니다.

  1. Google Cloud 콘솔에서 Cloud Storage 버킷 페이지로 이동합니다.

    버킷으로 이동

  2. Cloud Storage 페이지에서 설정을 클릭합니다.

  3. 소프트 삭제 탭을 클릭합니다.

  4. 삭제된 바이트별 상위 버킷 목록에서 소프트 삭제를 사용 중지할 버킷을 선택합니다.

  5. 소프트 삭제 사용 중지를 클릭합니다.

    선택한 버킷에서 소프트 삭제가 사용 중지됩니다.

프로젝트 내 여러 버킷 또는 모든 버킷에 대해 소프트 삭제 사용 중지

Google Cloud CLI를 사용하여 --project 플래그와 * 와일드 카드를 사용하여 gcloud storage buckets update 명령어를 실행하여 프로젝트 내의 여러 버킷 또는 모든 버킷에 대해 소프트 삭제를 일괄적으로 사용 중지합니다.

gcloud storage buckets update --project=PROJECT_ID --clear-soft-delete gs://*

각 항목의 의미는 다음과 같습니다.

  • PROJECT_ID는 프로젝트의 ID입니다. 예를 들면 my-project입니다.

폴더 내 모든 버킷에서 소프트 삭제 사용 중지

Google Cloud CLI를 사용하여 gcloud projects listgcloud storage buckets update 명령어를 실행하여 지정된 폴더의 모든 프로젝트에서 버킷의 소프트 삭제를 사용 중지합니다.

  1. gcloud projects listgcloud storage buckets update 명령어를 실행하여 지정된 폴더 아래의 모든 버킷을 나열한 다음 폴더 내의 모든 버킷에 대해 조용히 삭제를 사용 중지합니다.

    gcloud projects list --filter="parent.id: FOLDER_ID" --format="value(projectId)" | while read project
    do
    gcloud storage buckets update --project=$project --clear-soft-delete gs://*
    done
    

    각 항목의 의미는 다음과 같습니다.

    • FOLDER_ID은 폴더의 이름입니다. 예를 들면 123456입니다.

조직 수준에서 소프트 삭제 중지

Google Cloud CLI를 사용하여 --clear-soft-delete 플래그와 * 와일드 카드를 사용하여 gcloud storage buckets update 명령어를 실행하여 조직 수준에서 조용히 삭제를 사용 중지합니다.

  1. --clear-soft-delete 플래그와 * 와일드 카드를 사용하여 gcloud storage buckets update 명령어를 실행하여 조직 내의 모든 버킷에 대해 소프트 삭제를 사용 중지합니다.

    gcloud projects list --format="value(projectId)" | while read project
    do
    gcloud storage buckets update --project=$project --clear-soft-delete gs://*
    done
    

Cloud Storage는 기존 버킷에서 소프트 삭제를 중지합니다. 이미 소프트 삭제된 객체는 소프트 삭제 보관 기간이 완료될 때까지 버킷에 남아 있다가 그 후에 영구 삭제됩니다.

새 버킷의 소프트 삭제 중지

기본적으로 소프트 삭제가 새 버킷에 사용 설정되지만 태그를 사용하여 소프트 삭제가 기본적으로 사용 설정되지 않도록 할 수 있습니다. 태그는 storage.defaultSoftDeletePolicy 키를 사용하여 조직 수준에서 0d(0일) 소프트 삭제 정책을 적용합니다. 이 정책은 이 기능을 중지하고 향후 삭제된 데이터가 보관되지 않도록 합니다.

다음 안내를 수행하여 새 버킷을 만들 때 기본적으로 소프트 삭제를 중지합니다. 다음 안내는 특정 소프트 삭제 정책을 요구하는 조직 정책을 설정하는 안내와 다릅니다. 즉, 필요한 경우 정책을 지정하여 특정 버킷에서 소프트 삭제를 계속 사용 설정할 수 있습니다.

  1. Google Cloud CLI를 사용하여 새 버킷의 기본 소프트 삭제 보관 기간을 변경하는 데 사용되는 storage.defaultSoftDeletePolicy 태그를 만듭니다. storage.defaultSoftDeletePolicy 태그 이름만 기본 소프트 삭제 보관 기간을 업데이트합니다.

    gcloud resource-manager tags keys create 명령어를 사용하여 태그 키를 만듭니다.

     gcloud resource-manager tags keys create storage.defaultSoftDeletePolicy \
      --parent=organizations/ORGANIZATION_ID \
      --description="Configures the default softDeletePolicy for new Storage buckets."
    

    각 항목의 의미는 다음과 같습니다.

    • ORGANIZATION_ID는 기본 소프트 삭제 보관 기간을 설정할 조직의 숫자 ID입니다. 예를 들면 12345678901입니다. 조직 ID를 찾는 방법은 조직 리소스 ID 가져오기를 참조하세요.
  2. 0d(0일) 태그 값을 만들고 gcloud resource-manager tags values create 명령어를 사용하여 새 버킷의 소프트 삭제 보관 기간을 기본적으로 중지합니다.

      gcloud resource-manager tags values create 0d \
       --parent=ORGANIZATION_ID/storage.defaultSoftDeletePolicy \
       --description="Disables soft delete for new Storage buckets."
      done
    

    각 항목의 의미는 다음과 같습니다.

    • ORGANIZATION_ID는 기본 소프트 삭제 보관 기간을 설정할 조직의 숫자 ID입니다. 예를 들면 12345678901입니다.
  3. gcloud resource-manager tags bindings create 명령어를 사용하여 태그를 리소스에 연결합니다.

     gcloud resource-manager tags bindings create \
       --tag-value=ORGANIZATION_ID/storage.defaultSoftDeletePolicy/0d \
       --parent=RESOURCE_ID
    

    각 항목의 의미는 다음과 같습니다.

    • ORGANIZATION_ID는 태그가 생성된 조직의 숫자 ID입니다. 예를 들면 12345678901입니다.

    • RESOURCE_ID는 태그 바인딩을 만들려는 조직의 전체 이름입니다. 예를 들어 태그를 organizations/7890123456에 연결하려면 //cloudresourcemanager.googleapis.com/organizations/7890123456을 입력합니다.

지정된 비용 기준을 초과하는 버킷의 소프트 삭제 사용 중지

Python용 Cloud 클라이언트 라이브러리를 사용하면 Python 클라이언트 라이브러리 샘플로 지정된 상대 비용 기준점을 초과하는 버킷의 조용히 삭제를 사용 중지할 수 있습니다. 이 샘플은 다음을 수행합니다.

  1. 각 스토리지 클래스의 상대적 스토리지 비용을 계산합니다.

  2. 버킷에 누적된 소프트 삭제 비용을 평가합니다.

  3. 소프트 삭제 사용에 대한 비용 기준점을 설정하고 설정한 기준점을 초과하는 버킷을 나열하며 기준점을 초과하는 버킷의 소프트 삭제를 사용 중지할 수 있습니다.

Python 클라이언트 라이브러리 설정 및 샘플 사용에 관한 자세한 내용은 Cloud Storage 조용히 삭제 비용 분석 도구 README.md 페이지를 참고하세요.

다음 샘플은 지정된 비용 기준점을 초과하는 버킷의 조용히 삭제를 사용 중지합니다.

from __future__ import annotations

import argparse
import json
import google.cloud.monitoring_v3 as monitoring_client


def get_relative_cost(storage_class: str) -> float:
    """Retrieves the relative cost for a given storage class and location.

    Args:
        storage_class: The storage class (e.g., 'standard', 'nearline').

    Returns:
        The price per GB from the https://cloud.google.com/storage/pricing,
        divided by the standard storage class.
    """
    relative_cost = {
        "STANDARD": 0.023 / 0.023,
        "NEARLINE": 0.013 / 0.023,
        "COLDLINE": 0.007 / 0.023,
        "ARCHIVE": 0.0025 / 0.023,
    }

    return relative_cost.get(storage_class, 1.0)


def get_soft_delete_cost(
    project_name: str,
    soft_delete_window: float,
    agg_days: int,
    lookback_days: int,
) -> dict[str, list[dict[str, float]]]:
    """Calculates soft delete costs for buckets in a Google Cloud project.

    Args:
        project_name: The name of the Google Cloud project.
        soft_delete_window: The time window in seconds for considering
          soft-deleted objects (default is 7 days).
        agg_days: Aggregate results over a time period, defaults to 30-day period
        lookback_days: Look back up to upto days, defaults to 360 days

    Returns:
        A dictionary with bucket names as keys and cost data for each bucket,
        broken down by storage class.
    """

    query_client = monitoring_client.QueryServiceClient()

    # Step 1: Get storage class ratios for each bucket.
    storage_ratios_by_bucket = get_storage_class_ratio(
        project_name, query_client, agg_days, lookback_days
    )

    # Step 2: Fetch soft-deleted bytes and calculate costs using Monitoring API.
    soft_deleted_costs = calculate_soft_delete_costs(
        project_name,
        query_client,
        soft_delete_window,
        storage_ratios_by_bucket,
        agg_days,
        lookback_days,
    )

    return soft_deleted_costs


def calculate_soft_delete_costs(
    project_name: str,
    query_client: monitoring_client.QueryServiceClient,
    soft_delete_window: float,
    storage_ratios_by_bucket: dict[str, float],
    agg_days: int,
    lookback_days: int,
) -> dict[str, list[dict[str, float]]]:
    """Calculates the relative cost of enabling soft delete for each bucket in a
       project for certain time frame in secs.

    Args:
        project_name: The name of the Google Cloud project.
        query_client: A Monitoring API query client.
        soft_delete_window: The time window in seconds for considering
          soft-deleted objects (default is 7 days).
        storage_ratios_by_bucket: A dictionary of storage class ratios per bucket.
        agg_days: Aggregate results over a time period, defaults to 30-day period
        lookback_days: Look back up to upto days, defaults to 360 days

    Returns:
        A dictionary with bucket names as keys and a list of cost data
        dictionaries
        for each bucket, broken down by storage class.
    """
    soft_deleted_bytes_time = query_client.query_time_series(
        monitoring_client.QueryTimeSeriesRequest(
            name=f"projects/{project_name}",
            query=f"""
                    {{  # Fetch 1: Soft-deleted (bytes seconds)
                        fetch gcs_bucket :: storage.googleapis.com/storage/v2/deleted_bytes
                        | value val(0) * {soft_delete_window}\'s\'  # Multiply by soft delete window
                        | group_by [resource.bucket_name, metric.storage_class], window(), .sum;

                        # Fetch 2: Total byte-seconds (active objects)
                        fetch gcs_bucket :: storage.googleapis.com/storage/v2/total_byte_seconds
                        | filter metric.type != 'soft-deleted-object'
                        | group_by [resource.bucket_name, metric.storage_class], window(1d), .mean  # Daily average
                        | group_by [resource.bucket_name, metric.storage_class], window(), .sum  # Total over window

                    }}  # End query definition
                    | every {agg_days}d  # Aggregate over larger time intervals
                    | within {lookback_days}d  # Limit data range for analysis
                    | ratio  # Calculate ratio (soft-deleted (bytes seconds)/ total (bytes seconds))
                    """,
        )
    )

    buckets: dict[str, list[dict[str, float]]] = {}
    missing_distribution_storage_class = []
    for data_point in soft_deleted_bytes_time.time_series_data:
        bucket_name = data_point.label_values[0].string_value
        storage_class = data_point.label_values[1].string_value
        # To include location-based cost analysis:
        # 1. Uncomment the line below:
        # location = data_point.label_values[2].string_value
        # 2. Update how you calculate 'relative_storage_class_cost' to factor in location
        soft_delete_ratio = data_point.point_data[0].values[0].double_value
        distribution_storage_class = bucket_name + " - " + storage_class
        storage_class_ratio = storage_ratios_by_bucket.get(
            distribution_storage_class
        )
        if storage_class_ratio is None:
            missing_distribution_storage_class.append(
                distribution_storage_class)
        buckets.setdefault(bucket_name, []).append({
            # Include storage class and location data for additional plotting dimensions.
            # "storage_class": storage_class,
            # 'location': location,
            "soft_delete_ratio": soft_delete_ratio,
            "storage_class_ratio": storage_class_ratio,
            "relative_storage_class_cost": get_relative_cost(storage_class),
        })

    if missing_distribution_storage_class:
        print(
            "Missing storage class for following buckets:",
            missing_distribution_storage_class,
        )
        raise ValueError("Cannot proceed with missing storage class ratios.")

    return buckets


def get_storage_class_ratio(
    project_name: str,
    query_client: monitoring_client.QueryServiceClient,
    agg_days: int,
    lookback_days: int,
) -> dict[str, float]:
    """Calculates storage class ratios for each bucket in a project.

    This information helps determine the relative cost contribution of each
    storage class to the overall soft-delete cost.

    Args:
        project_name: The Google Cloud project name.
        query_client: Google Cloud's Monitoring Client's QueryServiceClient.
        agg_days: Aggregate results over a time period, defaults to 30-day period
        lookback_days: Look back up to upto days, defaults to 360 days

    Returns:
        Ratio of Storage classes within a bucket.
    """
    request = monitoring_client.QueryTimeSeriesRequest(
        name=f"projects/{project_name}",
        query=f"""
            {{
            # Fetch total byte-seconds for each bucket and storage class
            fetch gcs_bucket :: storage.googleapis.com/storage/v2/total_byte_seconds
            | group_by [resource.bucket_name, metric.storage_class], window(), .sum;
            # Fetch total byte-seconds for each bucket (regardless of class)
            fetch gcs_bucket :: storage.googleapis.com/storage/v2/total_byte_seconds
            | group_by [resource.bucket_name], window(), .sum
            }}
            | ratio  # Calculate ratios of storage class size to total size
            | every {agg_days}d
            | within {lookback_days}d
            """,
    )

    storage_class_ratio = query_client.query_time_series(request)

    storage_ratios_by_bucket = {}
    for time_series in storage_class_ratio.time_series_data:
        bucket_name = time_series.label_values[0].string_value
        storage_class = time_series.label_values[1].string_value
        ratio = time_series.point_data[0].values[0].double_value

        # Create a descriptive key for the dictionary
        key = f"{bucket_name} - {storage_class}"
        storage_ratios_by_bucket[key] = ratio

    return storage_ratios_by_bucket


def soft_delete_relative_cost_analyzer(
    project_name: str,
    cost_threshold: float = 0.0,
    soft_delete_window: float = 604800,
    agg_days: int = 30,
    lookback_days: int = 360,
    list_buckets: bool = False,
    ) -> str | dict[str, float]: # Note potential string output
    """Identifies buckets exceeding the relative cost threshold for enabling soft delete.

    Args:
        project_name: The Google Cloud project name.
        cost_threshold: Threshold above which to consider removing soft delete.
        soft_delete_window: Time window for calculating soft-delete costs (in
          seconds).
        agg_days: Aggregate results over this time period (in days).
        lookback_days: Look back up to this many days.
        list_buckets: Return a list of bucket names (True) or JSON (False,
          default).

    Returns:
        JSON formatted results of buckets exceeding the threshold and costs
        *or* a space-separated string of bucket names.
    """

    buckets: dict[str, float] = {}
    for bucket_name, storage_sources in get_soft_delete_cost(
        project_name, soft_delete_window, agg_days, lookback_days
    ).items():
        bucket_cost = 0.0
        for storage_source in storage_sources:
            bucket_cost += (
                storage_source["soft_delete_ratio"]
                * storage_source["storage_class_ratio"]
                * storage_source["relative_storage_class_cost"]
            )
        if bucket_cost > cost_threshold:
            buckets[bucket_name] = round(bucket_cost, 4)

    if list_buckets:
        return " ".join(buckets.keys())  # Space-separated bucket names
    else:
        return json.dumps(buckets, indent=2)  # JSON output


def soft_delete_relative_cost_analyzer_main() -> None:
    # Sample run: python storage_soft_delete_relative_cost_analyzer.py <Project Name>
    parser = argparse.ArgumentParser(
        description="Analyze and manage Google Cloud Storage soft-delete costs."
    )
    parser.add_argument(
        "project_name", help="The name of the Google Cloud project to analyze."
    )
    parser.add_argument(
        "--cost_threshold",
        type=float,
        default=0.0,
        help="Relative Cost threshold.",
    )
    parser.add_argument(
        "--soft_delete_window",
        type=float,
        default=604800.0,
        help="Time window (in seconds) for considering soft-deleted objects.",
    )
    parser.add_argument(
        "--agg_days",
        type=int,
        default=30,
        help=(
            "Time window (in days) for aggregating results over a time period,"
            " defaults to 30-day period"
        ),
    )
    parser.add_argument(
        "--lookback_days",
        type=int,
        default=360,
        help=(
            "Time window (in days) for considering the how old the bucket to be."
        ),
    )
    parser.add_argument(
        "--list",
        type=bool,
        default=False,
        help="Return the list of bucketnames seperated by space.",
    )

    args = parser.parse_args()

    response = soft_delete_relative_cost_analyzer(
        args.project_name,
        args.cost_threshold,
        args.soft_delete_window,
        args.agg_days,
        args.lookback_days,
        args.list,
    )
    if not args.list:
        print(
            "To remove soft-delete policy from the listed buckets run:\n"
            # Capture output
            "python storage_soft_delete_relative_cost_analyzer.py"
            " [your-project-name] --[OTHER_OPTIONS] --list > list_of_buckets.txt \n"
            "cat list_of_buckets.txt | gcloud storage buckets update -I "
            "--clear-soft-delete",
            response,
        )
        return
    print(response)


if __name__ == "__main__":
    soft_delete_relative_cost_analyzer_main()

다음 단계