פתרון בעיות באמצעות תגי בקשות ותגי עסקאות

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

‫Spanner תומך בשני סוגים של תגים: תגי request ותגי transaction. כפי שהשמות שלהם מרמזים, אפשר להוסיף תגי עסקאות לעסקאות, ותגי בקשות לממשקי API של שאילתות וקריאות בודדות. אפשר להגדיר תג עסקה בהיקף העסקה ולהגדיר תגי בקשה נפרדים לכל בקשת API רלוונטית בעסקה. תגי בקשה ותגי עסקאות שמוגדרים בקוד האפליקציה מאוכלסים בעמודות של הטבלאות הבאות של נתונים סטטיסטיים.

טבלת נתונים סטטיסטיים סוג התגים שמופיעים בטבלת הנתונים הסטטיסטיים
נתונים סטטיסטיים של שאילתות TopN בקשת תגים
TopN Read Statistics בקשת תגים
TopN Transaction Statistics תגי עסקאות
TopN Lock Statistics תגי עסקאות

בקשת תגים

אפשר להוסיף תג בקשה אופציונלי לשאילתה או לבקשת קריאה. ב-Spanner, הנתונים הסטטיסטיים מקובצים לפי תג בקשה, שמופיע בשדה REQUEST_TAG של הטבלאות query statistics ו-read statistics.

מתי כדאי להשתמש בתגי בקשה

ריכזנו כאן כמה תרחישים שבהם כדאי להשתמש בתגי בקשות.

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

איך מקצים תגי בקשות

בדוגמה הבאה מוצג איך להגדיר תגי בקשות באמצעות ספריות הלקוח של Spanner.

C++‎

void SetRequestTag(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  spanner::SqlStatement select(
      "SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
  using RowType = std::tuple<std::int64_t, std::int64_t, std::string>;

  auto opts = google::cloud::Options{}.set<spanner::RequestTagOption>(
      "app=concert,env=dev,action=select");
  auto rows = client.ExecuteQuery(std::move(select), std::move(opts));
  for (auto& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::move(row).status();
    std::cout << "SingerId: " << std::get<0>(*row)
              << " AlbumId: " << std::get<1>(*row)
              << " AlbumTitle: " << std::get<2>(*row) << "\n";
  }
}

C#‎


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

public class RequestTagAsyncSample
{
    public class Album
    {
        public int SingerId { get; set; }
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
    }

    public async Task<List<Album>> RequestTagAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        using var cmd = connection.CreateSelectCommand(
            $"SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
        // Sets the request tag to "app=concert,env=dev,action=select".
        // This request tag will only be set on this request.
        cmd.Tag = "app=concert,env=dev,action=select";

        var albums = new List<Album>();
        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            var album = new Album
            {
                SingerId = reader.GetFieldValue<int>("SingerId"),
                AlbumId = reader.GetFieldValue<int>("AlbumId"),
                AlbumTitle = reader.GetFieldValue<string>("AlbumTitle")
            };
            albums.Add(album);
            Console.WriteLine($"SingerId: {album.SingerId}, AlbumId: {album.AlbumId}, AlbumTitle: {album.AlbumTitle}");
        }
        return albums;
    }
}

המשך


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
)

// queryWithTag reads from a database with request tag set
func queryWithTag(w io.Writer, db string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := client.Single().QueryWithOptions(ctx, stmt, spanner.QueryOptions{RequestTag: "app=concert,env=dev,action=select"})
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID, albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}
}

Java

static void setRequestTag(DatabaseClient databaseClient) {
  // Sets the request tag to "app=concert,env=dev,action=select".
  // This request tag will only be set on this request.
  try (ResultSet resultSet = databaseClient
      .singleUse()
      .executeQuery(
          Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"),
          Options.tag("app=concert,env=dev,action=select"))) {
    while (resultSet.next()) {
      System.out.printf(
          "SingerId: %d, AlbumId: %d, AlbumTitle: %s\n",
          resultSet.getLong(0),
          resultSet.getLong(1),
          resultSet.getString(2));
    }
  }
}

Node.js

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

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

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

async function queryTags() {
  // Gets a reference to a Cloud Spanner instance and database.
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  // Execute a query with a request tag.
  const [albums] = await database.run({
    sql: 'SELECT SingerId, AlbumId, AlbumTitle FROM Albums',
    requestOptions: {requestTag: 'app=concert,env=dev,action=select'},
    json: true,
  });
  albums.forEach(album => {
    console.log(
      `SingerId: ${album.SingerId}, AlbumId: ${album.AlbumId}, AlbumTitle: ${album.AlbumTitle}`,
    );
  });
  await database.close();
}
queryTags();

PHP

use Google\Cloud\Spanner\SpannerClient;

/**
 * Executes a read with a request tag.
 * Example:
 * ```
 * spanner_set_request_tag($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function set_request_tag(string $instanceId, string $databaseId): void
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $snapshot = $database->snapshot();
    $results = $snapshot->execute(
        'SELECT SingerId, AlbumId, AlbumTitle FROM Albums',
        [
            'requestOptions' => [
                'requestTag' => 'app=concert,env=dev,action=select'
            ]
        ]
    );
    foreach ($results as $row) {
        printf('SingerId: %s, AlbumId: %s, AlbumTitle: %s' . PHP_EOL,
            $row['SingerId'], $row['AlbumId'], $row['AlbumTitle']);
    }
}

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)

with database.snapshot() as snapshot:
    results = snapshot.execute_sql(
        "SELECT SingerId, AlbumId, AlbumTitle FROM Albums",
        request_options={"request_tag": "app=concert,env=dev,action=select"},
    )

    for row in results:
        print("SingerId: {}, AlbumId: {}, AlbumTitle: {}".format(*row))

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client = spanner.client instance_id, database_id

client.execute(
  "SELECT SingerId, AlbumId, MarketingBudget FROM Albums",
  request_options: { tag: "app=concert,env=dev,action=select" }
).rows.each do |row|
  puts "#{row[:SingerId]} #{row[:AlbumId]} #{row[:MarketingBudget]}"
end

איך מציגים תגי בקשות בטבלת הנתונים הסטטיסטיים

השאילתה הבאה מחזירה את נתוני השאילתה במרווחי זמן של 10 דקות.

SELECT t.text,
       t.request_tag,
       t.execution_count,
       t.avg_latency_seconds,
       t.avg_rows,
       t.avg_bytes
FROM SPANNER_SYS.QUERY_STATS_TOP_10MINUTE AS t
LIMIT 3;

הנתונים הבאים הם דוגמה לתוצאות שמוחזרות מהשאילתה.

טקסט request_tag execution_count avg_latency_seconds avg_rows avg_bytes
SELECT SingerId, AlbumId, AlbumTitle FROM Albums app=concert,env=dev,action=select 212 0.025 21 2365
select * from orders; app=catalogsearch,env=dev,action=list 55 0.02 16 33.35
SELECT SingerId, FirstName, LastName FROM Singers; [empty string] 154 0.048 42 486.33

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

לגבי השאילתות שתויגו, הנתונים הסטטיסטיים מצטברים לפי תג (לדוגמה, לתג הבקשה app=concert,env=dev,action=select יש זמן אחזור ממוצע של 0.025 שניות). אם לא הוקצה תג, הנתונים הסטטיסטיים מצטברים לפי שאילתה (לדוגמה, השאילתה בשורה השלישית כוללת חביון ממוצע של 0.048 שניות).

תגי עסקאות

אפשר להוסיף תג עסקה אופציונלי לעסקאות ספציפיות. ב-Spanner, הנתונים הסטטיסטיים מקובצים לפי תג העסקה, שמופיע בשדה TRANSACTION_TAG של טבלאות transaction statistics.

מתי כדאי להשתמש בתגי עסקאות

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

  • איתור המקור של עסקה בעייתית:‏ Spanner אוסף נתונים סטטיסטיים של עסקאות קריאה-כתיבה בטבלת הנתונים הסטטיסטיים של העסקאות. אם אתם מוצאים עסקאות איטיות בטבלת נתוני העסקאות, ואם כבר הקציתם להן תגים, תוכלו לזהות את המקור (האפליקציה או המיקרו-שירות) שקורא לעסקאות האלה על סמך המידע בתג.
  • זיהוי עסקאות בטבלאות נתונים סטטיסטיים: הקצאת תגי עסקאות עוזרת לסנן שורות בטבלת הנתונים הסטטיסטיים של העסקאות לפי התגים שמעניינים אתכם. אם לא משתמשים בתגי עסקאות, יכול להיות שתהליך גילוי הפעולות שמיוצגות על ידי נתון מסוים יהיה מסורבל. לדוגמה, כדי לזהות עסקה לא מתויגת בנתונים הסטטיסטיים של העסקאות, צריך לבדוק את הטבלאות והעמודות שקשורות לעסקה.
  • איך בודקים אם טרנזקציות מאפליקציה או ממיקרו-שירות מסוימים מתבצעות לאט: תגי טרנזקציות יכולים לעזור לזהות אם לטרנזקציות מאפליקציה או ממיקרו-שירות מסוימים יש השהיות ארוכות יותר.
  • קיבוץ נתונים סטטיסטיים של קבוצת טרנזקציות: אפשר להשתמש בתגי טרנזקציות כדי לעקוב אחרי הביצועים של קבוצת טרנזקציות דומות, להשוות ביניהם ולדווח עליהם.
  • איך מוצאים אילו עסקאות ניגשות לעמודות שמעורבות בקונפליקט הנעילה: תגי עסקאות יכולים לעזור לזהות עסקאות בודדות שגורמות לקונפליקטים של נעילה בטבלאות Lock statistics.
  • הזרמת נתוני שינויים של משתמשים מ-Spanner באמצעות סנכרון שינויים בזרמי נתונים: רשומות של נתוני סנכרון שינויים בזרמי נתונים מכילות תגי טרנזקציות של הטרנזקציות ששינו את נתוני המשתמשים. כך הקורא של זרם השינויים יכול לשייך שינויים לסוג העסקה על סמך תגים.

איך מקצים תגי עסקאות

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

C++‎

void SetTransactionTag(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  using ::google::cloud::StatusOr;

  // Sets the transaction tag to "app=concert,env=dev". This will be
  // applied to all the individual operations inside this transaction.
  auto commit_options =
      google::cloud::Options{}.set<spanner::TransactionTagOption>(
          "app=concert,env=dev");
  auto commit = client.Commit(
      [&client](
          spanner::Transaction const& txn) -> StatusOr<spanner::Mutations> {
        spanner::SqlStatement update_statement(
            "UPDATE Venues SET Capacity = CAST(Capacity/4 AS INT64)"
            "  WHERE OutdoorVenue = false");
        // Sets the request tag to "app=concert,env=dev,action=update".
        // This will only be set on this request.
        auto update = client.ExecuteDml(
            txn, std::move(update_statement),
            google::cloud::Options{}.set<spanner::RequestTagOption>(
                "app=concert,env=dev,action=update"));
        if (!update) return std::move(update).status();

        spanner::SqlStatement insert_statement(
            "INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, "
            "                    LastUpdateTime)"
            " VALUES (@venueId, @venueName, @capacity, @outdoorVenue, "
            "         PENDING_COMMIT_TIMESTAMP())",
            {
                {"venueId", spanner::Value(81)},
                {"venueName", spanner::Value("Venue 81")},
                {"capacity", spanner::Value(1440)},
                {"outdoorVenue", spanner::Value(true)},
            });
        // Sets the request tag to "app=concert,env=dev,action=insert".
        // This will only be set on this request.
        auto insert = client.ExecuteDml(
            txn, std::move(insert_statement),
            google::cloud::Options{}.set<spanner::RequestTagOption>(
                "app=concert,env=dev,action=select"));
        if (!insert) return std::move(insert).status();
        return spanner::Mutations{};
      },
      commit_options);
  if (!commit) throw std::move(commit).status();
}

C#‎


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

public class TransactionTagAsyncSample
{
    public async Task<int> TransactionTagAsync(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 =>
        {
            // Sets the transaction tag to "app=concert,env=dev".
            // This transaction tag will be applied to all the individual operations inside
            // the transaction.
            transaction.TransactionOptions.Tag = "app=concert,env=dev";

            // Sets the request tag to "app=concert,env=dev,action=update".
            // This request tag will only be set on this request.
            var updateCommand =
                connection.CreateDmlCommand("UPDATE Venues SET Capacity = DIV(Capacity, 4) WHERE OutdoorVenue = false");
            updateCommand.Tag = "app=concert,env=dev,action=update";
            updateCommand.Transaction = transaction;
            int rowsModified = await updateCommand.ExecuteNonQueryAsync();

            var insertCommand = connection.CreateDmlCommand(
                @"INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime)
                    VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())",
                new SpannerParameterCollection
                {
                    {"venueId", SpannerDbType.Int64, 81},
                    {"venueName", SpannerDbType.String, "Venue 81"},
                    {"capacity", SpannerDbType.Int64, 1440},
                    {"outdoorVenue", SpannerDbType.Bool, true}
                }
            );
            // Sets the request tag to "app=concert,env=dev,action=insert".
            // This request tag will only be set on this request.
            insertCommand.Tag = "app=concert,env=dev,action=insert";
            insertCommand.Transaction = transaction;
            rowsModified += await insertCommand.ExecuteNonQueryAsync();
            return rowsModified;
        });
    }
}

המשך


import (
	"context"
	"fmt"
	"io"

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

// readWriteTransactionWithTag executes the update and insert queries on venues table with appropriate transaction and requests tag
func readWriteTransactionWithTag(w io.Writer, db string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	_, err = client.ReadWriteTransactionWithOptions(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		stmt := spanner.Statement{
			SQL: `UPDATE Venues SET Capacity = CAST(Capacity/4 AS INT64) WHERE OutdoorVenue = false`,
		}
		_, err := txn.UpdateWithOptions(ctx, stmt, spanner.QueryOptions{RequestTag: "app=concert,env=dev,action=update"})
		if err != nil {
			return err
		}
		fmt.Fprint(w, "Venue capacities updated.")
		stmt = spanner.Statement{
			SQL: `INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime) 
                   VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())`,
			Params: map[string]interface{}{
				"venueId":      81,
				"venueName":    "Venue 81",
				"capacity":     1440,
				"outdoorVenue": true,
			},
		}
		_, err = txn.UpdateWithOptions(ctx, stmt, spanner.QueryOptions{RequestTag: "app=concert,env=dev,action=insert"})
		if err != nil {
			return err
		}
		fmt.Fprint(w, "New venue inserted.")
		return nil
	}, spanner.TransactionOptions{TransactionTag: "app=concert,env=dev"})
	return err
}

Java

static void setTransactionTag(DatabaseClient databaseClient) {
  // Sets the transaction tag to "app=concert,env=dev".
  // This transaction tag will be applied to all the individual operations inside this
  // transaction.
  databaseClient
      .readWriteTransaction(Options.tag("app=concert,env=dev"))
      .run(transaction -> {
        // Sets the request tag to "app=concert,env=dev,action=update".
        // This request tag will only be set on this request.
        transaction.executeUpdate(
            Statement.of("UPDATE Venues"
                + " SET Capacity = CAST(Capacity/4 AS INT64)"
                + " WHERE OutdoorVenue = false"),
            Options.tag("app=concert,env=dev,action=update"));
        System.out.println("Venue capacities updated.");

        Statement insertStatement = Statement.newBuilder(
            "INSERT INTO Venues"
                + " (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime)"
                + " VALUES ("
                + " @venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP()"
                + " )")
            .bind("venueId")
            .to(81)
            .bind("venueName")
            .to("Venue 81")
            .bind("capacity")
            .to(1440)
            .bind("outdoorVenue")
            .to(true)
            .build();

        // Sets the request tag to "app=concert,env=dev,action=insert".
        // This request tag will only be set on this request.
        transaction.executeUpdate(
            insertStatement,
            Options.tag("app=concert,env=dev,action=insert"));
        System.out.println("New venue inserted.");

        return null;
      });
}

Node.js

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

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

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

async function transactionTag() {
  // Gets a reference to a Cloud Spanner instance and database.
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);

  // Run a transaction with a transaction tag that will automatically be
  // included with each request in the transaction.
  try {
    await database.runTransactionAsync(
      {requestOptions: {transactionTag: 'app=cart,env=dev'}},
      async tx => {
        // Set the request tag to "app=concert,env=dev,action=update".
        // This request tag will only be set on this request.
        await tx.runUpdate({
          sql: 'UPDATE Venues SET Capacity = DIV(Capacity, 4) WHERE OutdoorVenue = false',
          requestOptions: {requestTag: 'app=concert,env=dev,action=update'},
        });
        console.log('Updated capacity of all indoor venues to 1/4.');

        await tx.runUpdate({
          sql: `INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime)
                VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())`,
          params: {
            venueId: 81,
            venueName: 'Venue 81',
            capacity: 1440,
            outdoorVenue: true,
          },
          types: {
            venueId: {type: 'int64'},
            venueName: {type: 'string'},
            capacity: {type: 'int64'},
            outdoorVenue: {type: 'bool'},
          },
          requestOptions: {requestTag: 'app=concert,env=dev,action=update'},
        });
        console.log('Inserted new outdoor venue');

        await tx.commit();
      },
    );
  } catch (err) {
    console.error('ERROR:', err);
  } finally {
    await database.close();
  }
}
transactionTag();

PHP

use Google\Cloud\Spanner\SpannerClient;
use Google\Cloud\Spanner\Transaction;

/**
 * Executes a transaction with a transaction tag.
 * Example:
 * ```
 * spanner_set_transaction_tag($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function set_transaction_tag(string $instanceId, string $databaseId): void
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $database->runTransaction(function (Transaction $t) {
        $t->executeUpdate(
            'UPDATE Venues SET Capacity = CAST(Capacity/4 AS INT64) WHERE OutdoorVenue = false',
            [
                'requestOptions' => ['requestTag' => 'app=concert,env=dev,action=update']
            ]
        );
        print('Venue capacities updated.' . PHP_EOL);
        $t->executeUpdate(
            'INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime) '
            . 'VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())',
            [
                'parameters' => [
                    'venueId' => 81,
                    'venueName' => 'Venue 81',
                    'capacity' => 1440,
                    'outdoorVenue' => true,
                ],
                'requestOptions' => ['requestTag' => 'app=concert,env=dev,action=insert']
            ]
        );
        print('New venue inserted.' . PHP_EOL);
        $t->commit();
    }, [
        'requestOptions' => ['transactionTag' => 'app=concert,env=dev']
    ]);
}

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 update_venues(transaction):
    # Sets the request tag to "app=concert,env=dev,action=update".
    #  This request tag will only be set on this request.
    transaction.execute_update(
        "UPDATE Venues SET Capacity = CAST(Capacity/4 AS INT64) WHERE OutdoorVenue = false",
        request_options={"request_tag": "app=concert,env=dev,action=update"},
    )
    print("Venue capacities updated.")

    # Sets the request tag to "app=concert,env=dev,action=insert".
    # This request tag will only be set on this request.
    transaction.execute_update(
        "INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime) "
        "VALUES (@venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP())",
        params={
            "venueId": 81,
            "venueName": "Venue 81",
            "capacity": 1440,
            "outdoorVenue": True,
        },
        param_types={
            "venueId": param_types.INT64,
            "venueName": param_types.STRING,
            "capacity": param_types.INT64,
            "outdoorVenue": param_types.BOOL,
        },
        request_options={"request_tag": "app=concert,env=dev,action=insert"},
    )
    print("New venue inserted.")

database.run_in_transaction(update_venues, transaction_tag="app=concert,env=dev")

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client = spanner.client instance_id, database_id

client.transaction request_options: { tag: "app=cart,env=dev" } do |tx|
  tx.execute_update \
    "UPDATE Venues SET Capacity = CAST(Capacity/4 AS INT64) WHERE OutdoorVenue = false",
    request_options: { tag: "app=concert,env=dev,action=update" }

  puts "Venue capacities updated."

  tx.execute_update \
    "INSERT INTO Venues (VenueId, VenueName, Capacity, OutdoorVenue) " \
    "VALUES (@venue_id, @venue_name, @capacity, @outdoor_venue)",
    params: {
      venue_id: 81,
      venue_name: "Venue 81",
      capacity: 1440,
      outdoor_venue: true
    },
    request_options: { tag: "app=concert,env=dev,action=insert" }

  puts "New venue inserted."
end

איך רואים תגי עסקאות בטבלת נתוני העסקאות

השאילתה הבאה מחזירה את הנתונים הסטטיסטיים של העסקאות במרווחי זמן של 10 דקות.

SELECT t.fprint,
       t.transaction_tag,
       t.read_columns,
       t.commit_attempt_count,
       t.avg_total_latency_seconds
FROM SPANNER_SYS.TXN_STATS_TOP_10MINUTE AS t
LIMIT 3;

הנתונים הבאים הם דוגמה לתוצאות שמוחזרות מהשאילתה.

fprint transaction_tag read_columns commit_attempt_count avg_total_latency_seconds
40015598317 app=concert,env=dev [Venues._exists,
Venues.VenueId,
Venues.VenueName,
Venues.Capacity]
278802 0.3508
20524969030 app=product,service=payment [Singers.SingerInfo] 129012 0.0142
77848338483 [empty string] [Singers.FirstName, Singers.LastName, Singers._exists] 5357 0.048

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

לגבי העסקאות שתויגו, הנתונים הסטטיסטיים מצטברים לפי תג עסקה (לדוגמה, לתג העסקה app=concert,env=dev a יש זמן אחזור ממוצע של 0.3508 שניות). אם לא הוקצה תג, הנתונים הסטטיסטיים מצטברים לפי FPRINT (למשל, 77848338483 בשורה השלישית הוא זמן האחזור הממוצע של 0.048 שניות).

איך מציגים תגי עסקאות בטבלת הנתונים הסטטיסטיים של הנעילה

השאילתה הבאה מחזירה את נתוני הנעילה במרווחי זמן של 10 דקות.

הפונקציה CAST() ממירה את השדה row_range_start_key BYTES למחרוזת.

SELECT
   CAST(s.row_range_start_key AS STRING) AS row_range_start_key,
   s.lock_wait_seconds,
   s.sample_lock_requests
FROM SPANNER_SYS.LOCK_STATS_TOP_10MINUTE s
LIMIT 2;

הנתונים הבאים הם דוגמה לתוצאות שמוחזרות מהשאילתה.

row_range_start_key lock_wait_seconds sample_lock_requests
שירים(2,1,1) 0.61 ‫LOCK_MODE: ReaderShared
COLUMN: Singers.SingerInfo
TRANSACTION_TAG: app=product,service=shipping

LOCK_MODE: WriterShared
COLUMN: Singers.SingerInfo
TRANSACTION_TAG: app=product,service=payment
אלבומים(2 ומעלה) 0.48 ‫LOCK_MODE: ReaderShared
COLUMN: users._exists1
TRANSACTION_TAG: [empty string]

LOCK_MODE: WriterShared
COLUMN: users._exists
TRANSACTION_TAG: [empty string]

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

הקצאת תגים באופן אוטומטי

ספריות הלקוח של Spanner תומכות בתיוג אוטומטי של מחסנית הקריאות. כשהתיוג האוטומטי מופעל, ספריית הלקוח בודקת את מחסנית הקריאות לביצוע של האפליקציה כדי לקבוע איזו מחלקה ואיזו method יזמו את הבקשה למסד הנתונים. המערכת יוצרת באופן אוטומטי תג על סמך השמות האלה (לדוגמה, OrderDao.placeOrder ב-Java או db__OrderDao__PlaceOrder ב-Go) ומאכלסת את request_tag או transaction_tag.

התיוג האוטומטי נתמך בספריות הלקוח הבאות:

  • ‫Java בגרסה 6.118.0 ואילך.
  • ‫Go בגרסה 1.92.0 ואילך.

שיקולים לגבי תיוג אוטומטי

לפני שמשתמשים בתיוג אוטומטי, כדאי להביא בחשבון את הנקודות הבאות:

  • תקורה של מעבד וחביון: בדיקה של מחסניות קריאות בזמן ריצה באמצעות רפלקציה או מעקב אחר המתקשר בזמן ריצה בכל בקשה ועסקה יוצרת תקורה של מעבד וחביון. לפני שמפעילים אוטומטית תיוג אוטומטי באופן גלובלי בסביבות ייצור עם QPS גבוה, כדאי להעריך את ההשפעה על הביצועים של השירותים.
  • הבדלים מתיוג של זוגות מפתח/ערך: תגים שנוצרו אוטומטית (כמו OrderDao.placeOrder) מזהים סמלים של מקור הקוד ולא מטא-נתונים מובנים של זוגות מפתח/ערך (כמו app=cart,env=dev). אם שאילתות המעקב שלכם מסננות טבלאות נתונים סטטיסטיים באמצעות קידומות של מפתחות כמו STARTS_WITH(request_tag, "app="), פעולות שתויגו אוטומטית לא יתאימו למסננים האלה.
  • עוצמת התג: שמות של שיטות דינמיות או עמוסות מדי יכולים להגדיל את עוצמת התג בטבלאות הנתונים הסטטיסטיים. אם מספר התגים הייחודיים חורג מיכולת המעקב של Spanner במהלך מרווח זמן מסוים, Spanner נותן עדיפות לפעולות מעקב עם צריכת המשאבים הגבוהה ביותר.
  • חשיפת מידע: התגים מאוחסנים בטבלאות נתונים סטטיסטיים של SPANNER_SYS בטקסט פשוט, ולכן שמות של שיטות וסיווגים פנימיים גלויים לכל מי שיש לו גישה לשאילתות של נתונים סטטיסטיים של מסד הנתונים. אם הגישה למעקב משותפת בין גבולות, מומלץ להימנע משימוש במינוח עסקי רגיש בשמות של שיטות או מחלקות.

הפעלה והשבתה של תיוג אוטומטי

אפשר להגדיר תיוג אוטומטי בקוד, או להגדיר הגדרות באופן גלובלי באמצעות משתני סביבה.

כללי עדיפות להגדרות

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

  1. השבתה של שינוי ברירת המחדל: אם הערך של SPANNER_DISABLE_AUTO_TAGGING=true הוא true, התיוג האוטומטי מושבת לחלוטין בכל מקום, והוא מבטל את ההגדרות שנקבעו באמצעות תכנות.
  2. הפעלה מאולצת של שינוי ברירת המחדל: אם ההגדרה SPANNER_ENABLE_AUTO_TAGGING=true מוגדרת, התיוג האוטומטי מופעל באופן גלובלי, ומשנה את הגדרות ברירת המחדל של ההגדרות שנקבעו באמצעות תכנות.
  3. הגדרה באמצעות קוד: אם לא מוגדרים משתני סביבה לשינוי ברירת המחדל, הספרייה משתמשת בהגדרה באמצעות קוד שמוגדרת ב-SpannerOptions (Java) או ב-ClientConfig (Go).
  4. התנהגות ברירת המחדל: אם לא מגדירים תיוג אוטומטי, הוא מושבת כברירת מחדל.

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

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

# Force-enable globally
export SPANNER_ENABLE_AUTO_TAGGING=true

# Force-disable globally (overrides code configuration)
export SPANNER_DISABLE_AUTO_TAGGING=true

הגדרה פרוגרמטית

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

המשך

package main

import (
    "context"
    "cloud.google.com/go/spanner"
)

func main() {
    ctx := context.Background()
    config := spanner.ClientConfig{
        // Enable call-stack autotagging
        EnableAutoTagging: true,
        // Optional: Specify target application package prefixes
        AutoTaggingPackages: []string{"github.com/mycompany/orderingservice"},
        // Optional: Set maximum stack depth to inspect (default is 50)
        AutoTaggingTracerLimit: 50,
    }
    client, err := spanner.NewClientWithConfig(ctx, "projects/p/instances/i/databases/d", config)
    if err != nil {
        panic(err)
    }
    defer client.Close()
}

Java

import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import java.util.Arrays;

SpannerOptions options = SpannerOptions.newBuilder()
    // Enable call-stack autotagging
    .enableAutoTagging()
    // Optional: Specify target application package prefixes
    .setAutoTaggingPackages(Arrays.asList("com.mycompany.orderingservice."))
    // Optional: Set maximum stack depth to inspect (default is 50)
    .setAutoTaggingTracerLimit(100)
    .build();
Spanner spanner = options.getService();

מיפוי בין שיטות API לתגי בקשה ועסקה

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

API Methods מצבי עסקה Request Tag תג עסקה
קריאה,
סטרימינג
עסקה עם הרשאת קריאה בלבד כן לא
עסקת קריאה וכתיבה כן כן
ExecuteSql,
ExecuteStreamingSql1
טרנזקציה לקריאה בלבד1 כן1 לא
עסקת קריאה וכתיבה כן כן
ExecuteBatchDml עסקת קריאה וכתיבה כן כן
BeginTransaction עסקת קריאה וכתיבה לא כן
שמירה (commit) עסקת קריאה וכתיבה לא כן

1 בשאילתות של שינוי סטרימינג שמופעלות באמצעות המחבר Apache Beam SpannerIO Dataflow, ‏ REQUEST_TAG מכיל שם של משימת Dataflow.

מגבלות

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

  • האורך של מחרוזת תגים מוגבל ל-50 תווים. מחרוזות שחורגות מהמגבלה הזו נחתכות.
  • מותר להשתמש בתג רק בתווי ASCII ‏ (32-126). תווי Unicode שרירותיים מוחלפים בקווים תחתונים.
  • כל התווים של קו תחתון (‎_‎) בתחילת המחרוזת מוסרים.
  • התגים הם תלויי אותיות רישיות. לדוגמה, אם מוסיפים את תג הבקשה APP=cart,ENV=dev לקבוצה אחת של שאילתות, ואת התג app=cart,env=dev לקבוצה אחרת של שאילתות, Spanner יצבור נתונים סטטיסטיים בנפרד לכל תג.
  • יכול להיות שבתנאים הבאים לא יופיעו תגים בטבלאות הנתונים הסטטיסטיים:

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

מתן שמות לתגים

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

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

לדוגמה, נניח שאתם משתמשים במסד נתונים של Spanner לתרחיש שימוש של מסחר אלקטרוני. כדאי לכלול מידע על האפליקציה, על סביבת הפיתוח ועל הפעולה שהשאילתה מבצעת בתג הבקשה שאתם מתכוונים להקצות לשאילתה מסוימת. אפשר להקצות את מחרוזת התג בפורמט מפתח/ערך כ-app=cart,env=dev,action=update. כלומר, השאילתה מופעלת מאפליקציית עגלת הקניות בסביבת הפיתוח, והיא משמשת לעדכון עגלת הקניות.

נניח שיש לכם שאילתה נוספת מאפליקציית חיפוש בקטלוג, ואתם מקצים את מחרוזת התג כ-app=catalogsearch,env=dev,action=list. אם אחת מהשאילתות האלה מופיעה בטבלת הנתונים הסטטיסטיים של השאילתות כשאילתה עם זמן אחזור גבוה, אפשר לזהות את המקור באמצעות התג.

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

Tag keys דוגמאות לצמד תג/ערך תיאור
בקשת הצטרפות app=cart
app=frontend
app=catalogsearch
עוזר לזהות את האפליקציה שמפעילה את הפעולה.
סביבה env=prod
env=dev
env=test
env=staging
עוזר לזהות את הסביבה שמשויכת לפעולה.
Framework framework=spring
framework=django
framework=jetty
עוזרת לזהות את המסגרת שמשויכת לפעולה.
פעולה action=list
action=retrieve
action=update
עוזר לזהות את הפעולה שבוצעה על ידי הפעולה.
שירות service=payment
service=shipping
עוזר לזהות את המיקרו-שירות שקורא לפעולה.

הערות חשובות

  • כשמקצים REQUEST_TAG, הנתונים הסטטיסטיים של כמה שאילתות עם אותו מחרוזת תגים מקובצים בשורה אחת בטבלה query statistics. רק הטקסט של אחת מהשאילתות האלה מוצג בשדה TEXT.
  • כשמקצים REQUEST_TAG, הנתונים הסטטיסטיים של כמה קריאות עם אותו מחרוזת תגים מקובצים בשורה אחת בטבלה read statistics. קבוצת כל העמודות שנקראות מתווספת לשדה READ_COLUMNS.
  • כשמקצים TRANSACTION_TAG, הנתונים הסטטיסטיים של עסקאות עם אותו מחרוזת תגים מקובצים בשורה אחת בטבלה transaction statistics. קבוצת כל העמודות שנכתבות על ידי העסקאות מתווספת לשדה WRITE_CONSTRUCTIVE_COLUMNS, וקבוצת כל העמודות שנקראות מתווספת לשדה READ_COLUMNS.

תרחישים לפתרון בעיות באמצעות תגים

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

איתור המקור של עסקה בעייתית

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

SELECT
 fprint,
 transaction_tag,
 ROUND(avg_total_latency_seconds,4) as avg_total_latency_sec,
 ROUND(avg_commit_latency_seconds,4) as avg_commit_latency_sec,
 commit_attempt_count,
 commit_abort_count
FROM SPANNER_SYS.TXN_STATS_TOP_10MINUTE
WHERE interval_end = "2020-05-17T18:40:00"
ORDER BY avg_total_latency_seconds DESC;

בטבלה הבאה מפורטים נתונים לדוגמה שמוחזרים מהשאילתה שלנו, שבה יש שלוש אפליקציות – cart,‏ product ו-frontend – שבבעלותן או שהן שולחות שאילתות לאותו מסד נתונים.

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

fprint transaction_tag avg_total_latency_sec avg_commit_latency_sec commit_attempt_count commit_abort_count
7129109266372596045 app=cart,service=order 0.3508 0.0139 278802 142205
9353100217060788102 app=cart,service=redis 0.1633 0.0142 129012 27177
9353100217060788102 app=product,service=payment 0.1423 0.0133 5357 636
898069986622520747 app=product,service=shipping 0.0159 0.0118 4269 1
9521689070912159706 app=frontend,service=ads 0.0093 0.0045 164 0
11079878968512225881 [empty string] 0.031 0.015 14 0

באופן דומה, אפשר להשתמש בתג Request כדי למצוא את המקור של שאילתה בעייתית מטבלת query statistics ואת המקור של קריאה בעייתית מטבלת read statistics.

איך מוצאים את זמן האחזור ונתונים סטטיסטיים אחרים של טרנזקציות מאפליקציה או ממיקרו-שירות מסוימים

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

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

השאילתה הבאה מחזירה את נתוני העסקאות של אפליקציית התשלומים במרווחי זמן של 10 דקות.

SELECT
  transaction_tag,
  avg_total_latency_sec,
  avg_commit_latency_sec,
  commit_attempt_count,
  commit_abort_count
FROM SPANNER_SYS.TXN_STATS_TOP_10MINUTE
WHERE STARTS_WITH(transaction_tag, "app=payment")
LIMIT 3;

הנה פלט לדוגמה:

transaction_tag avg_total_latency_sec avg_commit_latency_sec commit_attempt_count commit_abort_count
app=payment,action=update 0.3508 0.0139 278802 142205
app=payment,action=transfer 0.1633 0.0142 129012 27177
app=payment, action=retrieve 0.1423 0.0133 5357 636

באופן דומה, אפשר למצוא שאילתות או קריאות מאפליקציה ספציפית בטבלה query statistics או read statistics באמצעות תגי בקשה.

איתור העסקאות שמעורבות בקונפליקט נעילה

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

SELECT CAST(s.row_range_start_key AS STRING) AS row_range_start_key,
       t.total_lock_wait_seconds,
       s.lock_wait_seconds,
       s.lock_wait_seconds/t.total_lock_wait_seconds frac_of_total,
       s.sample_lock_requests
FROM spanner_sys.lock_stats_total_10minute t, spanner_sys.lock_stats_top_10minute s
WHERE
  t.interval_end = "2020-05-17T18:40:00" and s.interval_end = t.interval_end;

הנה דוגמה לפלט מהשאילתה:

row_range_start_key total_lock_wait_seconds lock_wait_seconds frac_of_total sample_lock_requests
זמרים(32) 2.37 1.76 1 LOCK_MODE: WriterShared
COLUMN: Singers.SingerInfo
TRANSACTION_TAG:
app=cart,service=order

LOCK_MODE: ReaderShared
COLUMN: Singers.SingerInfo
TRANSACTION_TAG:
app=cart,service=redis

מטבלת התוצאות הזו אפשר לראות שההתנגשות התרחשה בטבלה Singersבמפתח SingerId=32. ‫Singers.SingerInfo היא העמודה שבה התרחש העימות בנוגע לנעילה בין ReaderShared לבין WriterShared. אפשר גם לזהות את העסקאות התואמות (app=cart,service=order ו-app=cart,service=redis) שבהן מתרחש העימות.

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

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