כתיבה שעברה אופטימיזציה של קצב העברת הנתונים

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

סקירה כללית

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

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

התנהגות ברירת מחדל

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

תרחישים נפוצים לדוגמה

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

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

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

הגדרת זמני השהיה שונים להעלאת שינויים

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

מגבלות

אפשר להגדיר זמן השהיה של אישור בין 0 ל-500 אלפיות השנייה. הגדרת השהיות של אישור מעל 500 אלפיות השנייה גורמת לשגיאה.

הגדרת השהיה מקסימלית של ביצוע פעולות בבקשות לביצוע פעולות

הפרמטר של עיכוב השליחה המקסימלי הוא חלק מהשיטה CommitRequest. אפשר לגשת לשיטה הזו באמצעות RPC API,‏ API בארכיטקטורת REST או באמצעות ספריית הלקוח של Cloud Spanner.

C#‎


using Google.Cloud.Spanner.Data;
using System;
using System.Threading.Tasks;

public class CommitDelayAsyncSample
{
    public async Task<int> CommitDelayAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        return await connection.RunWithRetriableTransactionAsync(async transaction =>
        {
            transaction.TransactionOptions.MaxCommitDelay = TimeSpan.FromMilliseconds(100);

            using var insertSingerCmd = connection.CreateInsertCommand("Singers",
                new SpannerParameterCollection
                {
                    { "SingerId", SpannerDbType.Int64, 1 },
                    { "FirstName", SpannerDbType.String, "Marc" },
                    { "LastName", SpannerDbType.String, "Richards" }
                });
            insertSingerCmd.Transaction = transaction;
            int rowsInserted = await insertSingerCmd.ExecuteNonQueryAsync();

            using var insertAlbumCmd = connection.CreateInsertCommand("Albums",
                new SpannerParameterCollection
                {
                    { "SingerId", SpannerDbType.Int64, 1 },
                    { "AlbumId", SpannerDbType.Int64, 2 },
                    { "AlbumTitle", SpannerDbType.String, "Go, Go, Go" }
                });
            insertAlbumCmd.Transaction = transaction;
            rowsInserted += await insertAlbumCmd.ExecuteNonQueryAsync();

            return rowsInserted;
        });
    }
}

Go


import (
	"context"
	"fmt"
	"io"
	"time"

	"cloud.google.com/go/spanner"
)

func setMaxCommitDelay(w io.Writer, db string) error {
	// db is the fully-qualified database name of the form `projects/<project>/instances/<instance-id>/database/<database-id>`
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return fmt.Errorf("setMaxCommitDelay.NewClient: %w", err)
	}
	defer client.Close()

	commitDelay := 100 * time.Millisecond
	resp, err := client.ReadWriteTransactionWithOptions(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		stmt := spanner.Statement{
			SQL: `INSERT Singers (SingerId, FirstName, LastName)
					VALUES (111, 'Virginia', 'Watson')`,
		}
		rowCount, err := txn.Update(ctx, stmt)
		if err != nil {
			return err
		}
		fmt.Fprintf(w, "%d record(s) inserted.\n", rowCount)
		return nil
	}, spanner.TransactionOptions{CommitOptions: spanner.CommitOptions{MaxCommitDelay: &commitDelay, ReturnCommitStats: true}})
	if err != nil {
		return fmt.Errorf("setMaxCommitDelay.ReadWriteTransactionWithOptions: %w", err)
	}
	fmt.Fprintf(w, "%d mutations in transaction\n", resp.CommitStats.MutationCount)
	return nil
}

Java


import com.google.cloud.spanner.CommitResponse;
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Mutation;
import com.google.cloud.spanner.Options;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import java.time.Duration;
import java.util.Arrays;

public class SetMaxCommitDelaySample {

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

    try (Spanner spanner =
        SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) {
      final DatabaseClient databaseClient = spanner
          .getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
      setMaxCommitDelay(databaseClient);
    }
  }

  static void setMaxCommitDelay(DatabaseClient databaseClient) {
    final CommitResponse commitResponse = databaseClient.writeWithOptions(Arrays.asList(
        Mutation.newInsertOrUpdateBuilder("Albums")
            .set("SingerId")
            .to("1")
            .set("AlbumId")
            .to("1")
            .set("MarketingBudget")
            .to("200000")
            .build(),
        Mutation.newInsertOrUpdateBuilder("Albums")
            .set("SingerId")
            .to("2")
            .set("AlbumId")
            .to("2")
            .set("MarketingBudget")
            .to("400000")
            .build()
    ), Options.maxCommitDelay(Duration.ofMillis(100)));

    System.out.println(
        "Updated data with timestamp + " + commitResponse.getCommitTimestamp() + ".");
  }
}

Node.js

const {Spanner, protos} = require('@google-cloud/spanner');

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

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

async function setMaxCommitDelay() {
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  database.runTransaction(async (err, transaction) => {
    if (err) {
      console.error(err);
      return;
    }
    try {
      const [rowCount] = await transaction.runUpdate({
        sql: 'INSERT Singers (SingerId, FirstName, LastName) VALUES (111, @firstName, @lastName)',
        params: {
          firstName: 'Virginia',
          lastName: 'Watson',
        },
      });

      console.log(
        `Successfully inserted ${rowCount} record into the Singers table.`,
      );

      await transaction.commit({
        maxCommitDelay: protos.google.protobuf.Duration({
          seconds: 0, // 0 seconds
          nanos: 100000000, // 100 milliseconds
        }),
      });
    } catch (err) {
      console.error('ERROR:', err);
    } finally {
      // Close the database when finished.
      database.close();
    }
  });
}
setMaxCommitDelay();

Python

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

def insert_singers(transaction):
    row_ct = transaction.execute_update(
        "INSERT Singers (SingerId, FirstName, LastName) "
        " VALUES (111, 'Grace', 'Bennis')"
    )

    print("{} record(s) inserted.".format(row_ct))

database.run_in_transaction(
    insert_singers, max_commit_delay=datetime.timedelta(milliseconds=100)
)

Ruby

require "google/cloud/spanner"

##
# This is a snippet for showcasing how to pass max_commit_delay in  commit_options.
#
# @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_set_max_commit_delay project_id:, instance_id:, database_id:
  # Instantiates a client
  spanner = Google::Cloud::Spanner.new project: project_id
  client  = spanner.client instance_id, database_id

  records = [
    { SingerId: 1, AlbumId: 1, MarketingBudget: 200_000 },
    { SingerId: 2, AlbumId: 2, MarketingBudget: 400_000 }
  ]
  # max_commit_delay is the amount of latency in millisecond, this request
  # is willing to incur in order to improve throughput.
  # The commit delay must be at least 0ms and at most 500ms.
  # Default value is nil.
  commit_options = {
    return_commit_stats: true,
    max_commit_delay: 100
  }
  resp = client.upsert "Albums", records, commit_options: commit_options
  puts "Updated data with #{resp.stats.mutation_count} mutations."
end

מעקב אחרי זמן האחזור של בקשות כתיבה

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