מניעת מחיקה לא מכוונת של מסד נתונים

בדף הזה מוסבר איך להגן על מסדי נתונים של Spanner מפני מחיקה בטעות.

ההגנה מפני מחיקה של מסד נתונים ב-Spanner מונעת מחיקה מקרית של מסדי נתונים קיימים על ידי משתמשים או חשבונות שירות שיש להם הרשאות IAM שנדרשות למחיקת מסד הנתונים. הפעלת הגנה מפני מחיקה של מסד נתונים מאפשרת להגן על מסדי נתונים שחשובים לאפליקציות ולשירותים שלכם. כדי לספק מערך מקיף של יכולות להגנה על נתונים במסדי הנתונים של Spanner, כדאי להשתמש בהגנה מפני מחיקת מסד נתונים יחד עם שחזור לנקודת זמן מסוימת ותכונות גיבוי.

כברירת מחדל, הגדרת ההגנה מפני מחיקה מושבתת כשיוצרים מסד נתונים חדש. אחרי שהמסד נתונים נוצר בהצלחה, אפשר להפעיל את ההגדרה 'הגנה מפני מחיקה'. בנוסף, אפשר להפעיל את ההגדרה הזו במסד נתונים קיים. אם רוצים להגן על כמה מסדי נתונים, צריך להפעיל את ההגדרה בכל מסד נתונים בנפרד. הפעלה או השבתה של הגנה מפני מחיקה לא משפיעות על הביצועים של מסד הנתונים. אם אתם צריכים למחוק מסד נתונים שההגנה עליו מופעלת, אתם צריכים להשבית את ההגנה לפני שתוכלו למחוק את מסד הנתונים.

מגבלות

אי אפשר להפעיל הגנה מפני מחיקת מסד נתונים בתרחישים הבאים:

  • אם מסד הנתונים נמחק.
  • אם מסד הנתונים משוחזר מגיבוי. (אחרי שפעולת השחזור מסתיימת, אפשר להפעיל את ההגנה על מסד הנתונים).

בנוסף, גיבויים של מסד נתונים ומסדי נתונים ששוחזרו מגיבוי לא מקבלים בירושה את הגדרת ההגנה מפני מחיקה של מסד הנתונים המקורי. אחרי שמשחזרים מסד נתונים מגיבוי, צריך להפעיל בנפרד את ההגנה על מחיקת מסד הנתונים.

אם מוחקים פרויקט, ההגנה מפני מחיקה של מסד נתונים או מופע ב-Spanner לא מונעת את המחיקה שלהם. מידע נוסף על מה שקורה כשמוחקים פרויקט זמין במאמר סגירה (מחיקה) של פרויקטים.

בקרת גישה באמצעות IAM

כדי להפעיל את הגדרת ההגנה מפני מחיקה של מסד הנתונים, אתם צריכים הרשאות IAM מסוימות.

כדי להפעיל או להשבית את ההגנה מפני מחיקה של מסד נתונים, צריכה להיות לכם הרשאת spanner.databases.update. אם אתם רק רוצים לראות את הסטטוס של הגדרת מסד הנתונים, אתם צריכים הרשאה מסוג spanner.databases.list או spanner.databases.get. במאמר הקצאת הרשאות IAM מוסבר איך להעניק הרשאות IAM ל-Spanner.

אם יש לכם את התפקיד המוגדר מראש Spanner Database Admin roles/spanner.databaseAdmin עבור מסד הנתונים, אתם יכולים לעדכן ולהפעיל את ההגנה מפני מחיקת מסד הנתונים.

אפשר להפעיל את ההגדרה של הגנה מפני מחיקה של מסד נתונים במסד נתונים קיים כדי למנוע מחיקה מקרית של מסד הנתונים.

הפעלת הגנה מפני מחיקת מסד נתונים

אפשר להפעיל הגנה מפני מחיקה של מסד נתונים באמצעות ה-CLI של gcloud, ספריות הלקוח וממשקי ה-API של REST או RPC. אי אפשר להפעיל הגנה מפני מחיקת מסד נתונים באמצעות מסוף Google Cloud .

gcloud

כדי להפעיל את הגדרת ההגנה מפני מחיקה של מסד נתונים, מריצים את הפקודה הבאה:

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

חובה לציין את האפשרויות הבאות:

DATABASE_ID
המזהה של מסד הנתונים.
INSTANCE_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: %w", 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: %w", 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: %w", 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
מזהה הפרויקט של מסד הנתונים.
INSTANCE_ID
המזהה של המופע של מסד הנתונים.
DATABASE_ID
המזהה של מסד הנתונים.

אם ההגנה מפני מחיקה מופעלת, הפרמטר enableDropProtection: true יופיע בפלט.

השבתה של הגנה מפני מחיקת מסד נתונים

אפשר להשבית את ההגנה מפני מחיקת מסד נתונים אם המסד כבר לא צריך את ההגנה הזו, או אם צריך למחוק מסד נתונים שההגדרה הזו מופעלת בו.

אם רוצים למחוק מופע שיש בו מסד נתונים אחד או יותר עם הגנה מפני מחיקה, קודם צריך להשבית את ההגנה מפני מחיקה בכל מסדי הנתונים במופע הזה, ורק אחר כך אפשר למחוק את המופע.

gcloud

כדי להשבית את הגדרת ההגנה מפני מחיקה של מסד נתונים, מריצים את הפקודה הבאה:

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

חובה לציין את האפשרויות הבאות:

DATABASE_ID
המזהה של מסד הנתונים.
INSTANCE_ID
המזהה של המופע של מסד הנתונים.

האפשרויות הבאות הן אופציונליות:

--async
חזרה מיידית, בלי המתנה שהפעולה תסתיים.

המאמרים הבאים