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

בדף הזה מוסבר איך להגן על מסדי נתונים של 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;
    }
}

המשך

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(