실수로 인한 데이터베이스 삭제 방지

이 페이지에서는 Spanner 데이터베이스가 실수로 삭제되지 않도록 보호하는 방법을 설명합니다.

Spanner 데이터베이스 삭제 보호는 데이터베이스 삭제에 필요한 IAM 권한이 있는 사용자 또는 서비스 계정이 기존 데이터베이스를 실수로 삭제하지 않도록 방지합니다. 데이터베이스 삭제 보호를 사용 설정하면 애플리케이션 및 서비스에 중요한 데이터베이스를 안전하게 보호할 수 있습니다. PITR(point-in-time recovery)백업 기능과 함께 데이터베이스 삭제 보호를 사용하여 Spanner 데이터베이스에 대한 포괄적인 데이터 보호 기능 집합을 제공합니다.

기본적으로 삭제 보호 설정은 새 데이터베이스를 만들 때 사용 중지됩니다. 데이터베이스 만들기가 성공한 후 삭제 보호 설정을 사용 설정할 수 있습니다. 또한 기존 데이터베이스에 대해 이 설정을 사용 설정할 수 있습니다. 여러 데이터베이스를 보호하려면 각 데이터베이스에서 개별적으로 이 설정을 사용 설정합니다. 삭제 보호를 사용 설정하거나 사용 중지해도 데이터베이스에 성능 영향을 주지 않습니다. 데이터베이스 보호가 사용 설정된 데이터베이스를 삭제해야 할 경우에는 데이터베이스를 삭제하기 전에 보호를 사용 중지해야 합니다.

제한사항

다음과 같은 시나리오에서 데이터베이스 삭제 보호를 사용 설정할 수 없습니다.

  • 데이터베이스를 삭제하는 경우
  • 백업에서 데이터베이스를 복원하는 경우 (복원 작업이 완료된 후 데이터베이스 보호를 사용 설정할 수 있습니다.)

또한 데이터베이스와 백업에서 복원된 데이터베이스의 백업은 소스 데이터베이스의 데이터베이스 삭제 보호 설정을 상속하지 않습니다. 백업에서 데이터베이스를 복원한 후 데이터베이스 삭제 보호를 별도로 사용 설정해야 합니다.

프로젝트를 삭제해도 Spanner 데이터베이스 삭제 보호는 데이터베이스 또는 인스턴스 삭제를 방지하지 않습니다. 프로젝트를 삭제할 때 어떤 일이 발생하는지에 대한 자세한 내용은 프로젝트 종료(삭제)를 참조하세요.

IAM으로 액세스 제어

데이터베이스의 삭제 보호 설정을 사용 설정하려면 특정 IAM 권한이 있어야 합니다.

데이터베이스 삭제 보호를 사용 설정하거나 사용 중지하려면 spanner.databases.update 권한이 있어야 합니다. 데이터베이스 구성 상태만 봐야 하는 경우에는 spanner.databases.list 또는 spanner.databases.get 권한이 있어야 합니다. Spanner IAM 권한을 부여하는 방법은 IAM 권한 적용을 참조하세요.

데이터베이스에 대해 사전 정의된 Spanner 데이터베이스 관리자 roles/spanner.databaseAdmin 역할이 있는 경우 데이터베이스 삭제 보호를 업데이트하고 사용 설정할 수 있습니다.

기존 데이터베이스에서 데이터베이스 삭제 보호 설정을 사용 설정하면 데이터베이스가 실수로 삭제되지 않도록 할 수 있습니다.

데이터베이스 삭제 보호 사용 설정

gcloud CLI, 클라이언트 라이브러리, REST, RPC API를 사용하여 데이터베이스 삭제 보호를 사용 설정할 수 있습니다. Google Cloud 콘솔을 사용해서는 데이터베이스 삭제 보호를 사용 설정할 수 없습니다.

gcloud

데이터베이스의 삭제 보호 설정을 사용 설정하려면 다음 명령어를 실행합니다.

  gcloud spanner databases update
  DATABASE_ID --instance=INSTANCE_ID
  --enable-drop-protection [--async]

다음 옵션은 필수사항입니다.

DATABASE_ID
데이터베이스의 ID입니다.
INSTANCE_ID
데이터베이스의 인스턴스 ID입니다.

다음 옵션은 선택사항입니다.

--async
진행 중인 작업이 완료되기를 기다리지 않고 즉시 반환합니다.

클라이언트 라이브러리

C++

void UpdateDatabase(google::cloud::spanner_admin::DatabaseAdminClient client,
                    std::string const& project_id,
                    std::string const& instance_id,
                    std::string const& database_id, bool drop_protection) {
  google::cloud::spanner::Database db(project_id, instance_id, database_id);
  google::spanner::admin::database::v1::Database database;
  database.set_name(db.FullName());
  database.set_enable_drop_protection(drop_protection);
  google::protobuf::FieldMask update_mask;
  update_mask.add_paths("enable_drop_protection");
  auto updated = client.UpdateDatabase(database, update_mask).get();
  if (!updated) throw std::move(updated).status();
  std::cout << "Database " << updated->name() << " successfully updated.\n";
}

C#


using Google.Cloud.Spanner.Admin.Database.V1;
using Google.Cloud.Spanner.Common.V1;
using Google.Protobuf.WellKnownTypes;
using System;
using System.Threading.Tasks;

public class UpdateDatabaseAsyncSample
{
    public async Task<Database> UpdateDatabaseAsync(string projectId, string instanceId, string databaseId)
    {
        var databaseAdminClient = await DatabaseAdminClient.CreateAsync();
        var databaseName = DatabaseName.Format(projectId, instanceId, databaseId);
        Database databaseToUpdate = await databaseAdminClient.GetDatabaseAsync(databaseName);

        databaseToUpdate.EnableDropProtection = true;
        var updateDatabaseRequest = new UpdateDatabaseRequest()
        {
            Database = databaseToUpdate,
            UpdateMask = new FieldMask { Paths = { "enable_drop_protection" } }
        };

        var operation = await databaseAdminClient.UpdateDatabaseAsync(updateDatabaseRequest);

        // Wait until the operation has finished.
        Console.WriteLine("Waiting for the operation to finish.");
        var completedResponse = await operation.PollUntilCompletedAsync();

        if (completedResponse.IsFaulted)
        {
            Console.WriteLine($"Error while updating database {databaseId}: {completedResponse.Exception}");
            throw completedResponse.Exception;
        }

        Console.WriteLine($"Updated database {databaseId}.");

        // Return the updated database.
        return completedResponse.Result;
    }
}

Go

import (
	"context"
	"fmt"
	"io"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
	"google.golang.org/genproto/protobuf/field_mask"
)

func updateDatabase(ctx context.Context, w io.Writer, db string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	// Instantiate database admin client.
	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return fmt.Errorf("updateDatabase.NewDatabaseAdminClient: %v", err)
	}
	defer adminClient.Close()

	// Instantiate the request for performing update database operation.
	op, err := adminClient.UpdateDatabase(ctx, &adminpb.UpdateDatabaseRequest{
		Database: &adminpb.Database{
			Name:                 db,
			EnableDropProtection: true,
		},
		UpdateMask: &field_mask.FieldMask{
			Paths: []string{"enable_drop_protection"},
		},
	})
	if err != nil {
		return fmt.Errorf("updateDatabase.UpdateDatabase: %v", err)
	}

	// Wait for update database operation to complete.
	fmt.Fprintf(w, "Waiting for update database operation to complete [%s]\n", db)
	if _, err := op.Wait(ctx); err != nil {
		return fmt.Errorf("updateDatabase.Wait: %v", err)
	}
	fmt.Fprintf(w, "Updated database [%s]\n", db)
	return nil
}

Java


import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
import com.google.common.collect.Lists;
import com.google.protobuf.FieldMask;
import com.google.spanner.admin.database.v1.Database;
import com.google.spanner.admin.database.v1.DatabaseName;
import com.google.spanner.admin.database.v1.UpdateDatabaseMetadata;
import com.google.spanner.admin.database.v1.UpdateDatabaseRequest;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class UpdateDatabaseSample {

  static void updateDatabase() {
    // TODO(developer): Replace these variables before running the sample.
    final String projectId = "my-project";
    final String instanceId = "my-instance";
    final String databaseId = "my-database";

    updateDatabase(projectId, instanceId, databaseId);
  }

  static void updateDatabase(
      String projectId, String instanceId, String databaseId) {
    try (Spanner spanner =
        SpannerOptions.newBuilder().setProjectId(projectId).build().getService();
        DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) {
      final Database database =
          Database.newBuilder()
              .setName(DatabaseName.of(projectId, instanceId, databaseId).toString())
              .setEnableDropProtection(true).build();
      final UpdateDatabaseRequest updateDatabaseRequest =
          UpdateDatabaseRequest.newBuilder()
              .setDatabase(database)
              .setUpdateMask(
                  FieldMask.newBuilder().addAllPaths(
                      Lists.newArrayList("enable_drop_protection")).build())
              .build();
      OperationFuture<Database, UpdateDatabaseMetadata> operation =
          databaseAdminClient.updateDatabaseAsync(updateDatabaseRequest);
      System.out.printf("Waiting for update operation for %s to complete...\n", databaseId);
      Database updatedDb = operation.get(5, TimeUnit.MINUTES);
      System.out.printf("Updated database %s.\n", updatedDb.getName());
    } catch (ExecutionException | TimeoutException e) {
      // If the operation failed during execution, expose the cause.
      throw SpannerExceptionFactory.asSpannerException(e.getCause());
    } catch (InterruptedException e) {
      // Throw when a thread is waiting, sleeping, or otherwise occupied,
      // and the thread is interrupted, either before or during the activity.
      throw SpannerExceptionFactory.propagateInterrupt(e);
    }
  }
}

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// const projectId = 'my-project-id';

// Imports the Google Cloud client library
const {Spanner, protos} = require('@google-cloud/spanner');

// creates a client
const spanner = new Spanner({
  projectId: projectId,
});

const databaseAdminClient = spanner.getDatabaseAdminClient();

async function updateDatabase() {
  // Update the database metadata fields
  try {
    console.log(
      `Updating database ${databaseAdminClient.databasePath(
        projectId,
        instanceId,
        databaseId
      )}.`
    );
    const [operation] = await databaseAdminClient.updateDatabase({
      database: {
        name: databaseAdminClient.databasePath(
          projectId,
          instanceId,
          databaseId
        ),
        enableDropProtection: true,
      },
      // updateMask contains the fields to be updated in database
      updateMask: (protos.google.protobuf.FieldMask = {
        paths: ['enable_drop_protection'],
      }),
    });
    console.log(
      `Waiting for update operation for ${databaseId} to complete...`
    );
    await operation.promise();
    console.log(`Updated database ${databaseId}.`);
  } catch (err) {
    console.log('ERROR:', err);
  }
}
updateDatabase();

PHP

use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient;
use Google\Cloud\Spanner\Admin\Database\V1\Database;
use Google\Cloud\Spanner\Admin\Database\V1\GetDatabaseRequest;
use Google\Cloud\Spanner\Admin\Database\V1\UpdateDatabaseRequest;
use Google\Protobuf\FieldMask;

/**
 * Updates the drop protection setting for a database.
 * Example:
 * ```
 * update_database($projectId, $instanceId, $databaseId);
 * ```
 *
 * @param string $projectId The Google Cloud project ID.
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function update_database(string $projectId, string $instanceId, string $databaseId): void
{
    $newUpdateMaskField = new FieldMask([
        'paths' => ['enable_drop_protection']
    ]);
    $databaseAdminClient = new DatabaseAdminClient();
    $databaseFullName = DatabaseAdminClient::databaseName($projectId, $instanceId, $databaseId);
    $database = (new Database())
        ->setEnableDropProtection(true)
        ->setName($databaseFullName);

    printf('Updating database %s', $databaseId);
    $operation = $databaseAdminClient->updateDatabase((new UpdateDatabaseRequest())
        ->setDatabase($database)
        ->setUpdateMask($newUpdateMaskField));

    $operation->pollUntilComplete();

    $database = $databaseAdminClient->getDatabase(
        new GetDatabaseRequest(['name' => $databaseFullName])
    );
    printf(
        'Updated the drop protection for %s to %s' . PHP_EOL,
        $database->getName(),
        $database->getEnableDropProtection()
    );
}

Python

def update_database(instance_id, database_id):
    """Updates the drop protection setting for a database."""
    from google.cloud.spanner_admin_database_v1.types import \
        spanner_database_admin

    spanner_client = spanner.Client()
    database_admin_api = spanner_client.database_admin_api

    request = spanner_database_admin.UpdateDatabaseRequest(
        database=spanner_database_admin.Database(
            name=database_admin_api.database_path(
                spanner_client.project, instance_id, database_id
            ),
            enable_drop_protection=True,
        ),
        update_mask={"paths": ["enable_drop_protection"]},
    )
    operation = database_admin_api.update_database(request=request)
    print(
        "Waiting for update operation for {}/databases/{} to complete...".format(
            database_admin_api.instance_path(spanner_client.project, instance_id),
            database_id,
        )
    )
    operation.result(OPERATION_TIMEOUT_SECONDS)

    print(
        "Updated database {}/databases/{}.".format(
            database_admin_api.instance_path(spanner_client.project, instance_id),
            database_id,
        )
    )

Ruby

require "google/cloud/spanner/admin/database"

##
# This is a snippet for showcasing how to update database.
#
# @param project_id  [String] The ID of the Google Cloud project.
# @param instance_id [String] The ID of the spanner instance.
# @param database_id [String] The ID of the database.
#
def spanner_update_database project_id:, instance_id:, database_id:
  client = Google::Cloud::Spanner::Admin::Database.database_admin project_id: project_id
  db_path = client.database_path project: project_id, instance: instance_id, database: database_id
  database = client.get_database name: db_path

  puts "Updating database #{database.name}"
  database.enable_drop_protection = true
  job = client.update_database database: database, update_mask: { paths: ["enable_drop_protection"] }
  puts "Waiting for update operation for #{database.name} to complete..."
  job.wait_until_done!
  puts "Updated database #{database.name}"
end

데이터베이스에 삭제 보호가 사용 설정되었는지 확인

데이터베이스 구성을 확인하여 데이터베이스에 삭제 보호가 사용 설정되었는지 확인할 수 있습니다.

gcloud

데이터베이스에 삭제 보호가 사용 설정되었는지 확인하려면 gcloud spanner databases describe 명령어를 실행하여 데이터베이스에 대한 자세한 정보를 가져오거나 gcloud spanner databases list를 실행하여 인스턴스 내의 데이터베이스에 대한 자세한 정보를 확인할 수 있습니다.

  gcloud spanner databases describe
  projects/PROJECT_ID/instances/INSTANCE_ID/databases/DATABASE_ID

다음 옵션은 필수사항입니다.

PROJECT_ID
데이터베이스의 프로젝트 ID입니다.
INSTANCE_ID
데이터베이스의 인스턴스 ID입니다.
DATABASE_ID
데이터베이스의 ID입니다.

삭제 보호가 사용 설정되었으면 출력에 enableDropProtection: true 매개변수가 표시됩니다.

데이터베이스 삭제 보호 사용 중지

데이터베이스에 더 이상 이 보호가 필요하지 않거나 이 설정이 사용 설정된 데이터베이스를 삭제해야 하는 경우 데이터베이스 삭제 보호를 사용 중지할 수 있습니다.

삭제 보호가 사용 설정된 데이터베이스가 하나 이상 있는 인스턴스를 삭제하려면 인스턴스를 삭제하기 전에 먼저 해당 인스턴스의 모든 데이터베이스에서 삭제 보호를 사용 중지해야 합니다.

gcloud

데이터베이스의 삭제 보호 설정을 사용 중지하려면 다음 명령어를 실행합니다.

  gcloud spanner databases update
  DATABASE_ID --instance=INSTANCE_ID
  --no-enable-drop-protection [--async]

다음 옵션은 필수사항입니다.

DATABASE_ID
데이터베이스의 ID입니다.
INSTANCE_ID
데이터베이스의 인스턴스 ID입니다.

다음 옵션은 선택사항입니다.

--async
진행 중인 작업이 완료되기를 기다리지 않고 즉시 반환합니다.

다음 단계