有向読み取り

このページでは、Spanner の有向読み取りとその使用方法について説明します。

Spanner の有向読み取りでは、マルチリージョン インスタンス構成またはカスタム リージョン構成(オプションの読み取り専用リージョンを含む)内の特定のレプリカ タイプまたはリージョンに、読み取り専用トランザクション単一読み取りを柔軟にルーティングできます。

利点

有向読み取りには次のような利点があります。

  • 複数のリージョン間でワークロードのロード バランシングをより細かく制御し、CPU 使用率を均等にして、Spanner インスタンスのオーバープロビジョニングを回避します。
  • ワークロードの分離を有効にします。分析ワークロードと変更ストリームの読み取りを特定の Spanner レプリカに転送することで、同じ Spanner データベースで実行されているトランザクション ワークロードへの影響を最小限に抑えることができます。

サポートされているクエリ オペレーション

クエリ オペレーション 有向読み取りはサポートされていますか?
ステイル読み取り
強力な読み取り
読み取り / 書き込みトランザクション ×

読み取り / 書き込みトランザクションパーティション化 DML タイプの一括更新では、有向読み取りはサポートされていません。これは、読み取り / 書き込みトランザクションをリーダー リージョンで処理する必要があるためです。読み取り / 書き込みトランザクションで有向読み取りが使用されている場合、トランザクションは BAD_REQUEST エラーで失敗します。

制限事項

Spanner の有向読み取りには次の制限があります。

始める前に

有向読み取りを使用する前に、次の点を考慮してください。

  • アプリケーションに最も近いレプリカまたはリージョン以外のレプリカまたはリージョンに読み取りをルーティングすると、アプリケーションに追加のレイテンシが発生する可能性があります。
  • 以下に基づいてトラフィックをルーティングします。
    • リージョン名(例: us-central1)。
    • レプリカタイプ(有効な値: READ_ONLYREAD_WRITE)。
  • 有向読み取りの自動フェイルオーバー オプションはデフォルトで有効になっています。自動フェイルオーバー オプションが有効で、指定したすべてのレプリカが使用不可または異常な場合、Spannerは includeReplicas リスト外のレプリカにリクエストをルーティングします。自動フェイルオーバー オプションを無効にし、指定したすべてのレプリカが使用不能であるか、異常な場合、有向読み取りリクエストは失敗します。

有向読み取りパラメータ

REST API または RPC API を使用して有向読み取りを行う場合は、これらのフィールドを directedReadOptions パラメータで定義する必要があります。includeReplicas または excludeReplicas のいずれか一つのみを含めることができます。両方は使用できません。

  • includeReplicas: replicaSelections の繰り返しセットが含まれます。このリストは、特定のリージョンまたはレプリカタイプへの有向読み取りを考慮する順序を示しています。最大 10 個の includeReplicas を指定できます。

    • replicaSelections: 有向読み取りリクエストを処理する location またはレプリカ type で構成されます。includeReplicas を使用する場合は、次のフィールドのうち少なくとも 1 つを指定する必要があります。

      • location: 有向読み取りリクエストを処理するロケーション。ロケーションは、データベースのマルチリージョン構成内のリージョンのいずれかである必要があります。ロケーションがデータベースのマルチリージョン構成内のどのリージョンにもない場合、リクエストは想定どおりにルーティングされません。代わりに、最も近いリージョンによって処理されます。たとえば、マルチリージョン インスタンス構成 nam6 のデータベース上のロケーション us-central1 に読み取りを指定することができます。

        location パラメータを leader または non-leader 文字列リテラルで指定することもできます。leader 値を入力すると、Spanner はリクエストをデータベースのリーダー レプリカに転送します。逆に、non-leader 値を入力すると、Spanner は最も近いリーダー以外のレプリカでリクエストを処理します。

      • type: 有向読み取りリクエストを処理するレプリカタイプ。有効なタイプは READ_WRITEREAD_ONLY です。

    • autoFailoverDisabled: これはデフォルトで False に設定されています。つまり、自動フェイルオーバーが有効になります。自動フェイルオーバー オプションが有効で、指定したすべてのレプリカが使用不可または異常な場合、Spannerは includeReplicas リスト外のレプリカにリクエストをルーティングします。自動フェイルオーバー オプションを無効にし、指定したすべてのレプリカが使用不能であるか、異常な場合、有向読み取りリクエストは失敗します。有効な値は、無効の場合は TRUE、有効の場合は FALSE です。

  • excludeReplicas: リクエストの処理から除外される replicaSelections の繰り返しセットが含まれます。Spanner は、このリストのレプリカにリクエストをルーティングしません。

    • replicaSelections: 有向読み取りリクエストの処理から除外されるロケーションまたはレプリカタイプ。excludeReplicas を使用する場合は、次のフィールドのうち少なくとも 1 つを指定する必要があります。
      • location: 有効読み取りリクエストの処理から除外されるロケーション。
      • type: 有向読み取りリクエストの処理から除外されるレプリカタイプ。有向なタイプは READ_WRITEREAD_ONLY です。

REST リクエスト本文の例を表示するには、[有向読み取りを使用する] セクションの [REST] タブをクリックします。

有向読み取りを使用する

有向読み取りを行うには、Spanner クライアント ライブラリ、REST API、RPC API を使用します。

クライアント ライブラリ

C++

void DirectedRead(std::string const& project_id, std::string const& instance_id,
                  std::string const& database_id) {
  namespace spanner = ::google::cloud::spanner;

  // Create a client with a DirectedReadOption.
  auto client = spanner::Client(
      spanner::MakeConnection(
          spanner::Database(project_id, instance_id, database_id)),
      google::cloud::Options{}.set<spanner::DirectedReadOption>(
          spanner::ExcludeReplicas({spanner::ReplicaSelection("us-east4")})));

  spanner::SqlStatement select(
      "SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
  using RowType = std::tuple<std::int64_t, std::int64_t, std::string>;

  // A DirectedReadOption on the operation will override the option set
  // at the client level.
  auto rows = client.ExecuteQuery(
      std::move(select),
      google::cloud::Options{}.set<spanner::DirectedReadOption>(
          spanner::IncludeReplicas(
              {spanner::ReplicaSelection(spanner::ReplicaType::kReadWrite)},
              /*auto_failover_disabled=*/true)));
  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";
  }
  std::cout << "Read completed for [spanner_directed_read]\n";
}

C#


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

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

    public async Task<List<Album>> DirectedReadsAsync(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");
        // Set directed read options on a query or read command.
        cmd.DirectedReadOptions = new DirectedReadOptions
        {
            IncludeReplicas = new DirectedReadOptions.Types.IncludeReplicas
            {
                AutoFailoverDisabled = true,
                ReplicaSelections =
                {
                    new DirectedReadOptions.Types.ReplicaSelection
                    {
                        Location = "us-central1",
                        Type = DirectedReadOptions.Types.ReplicaSelection.Types.Type.ReadOnly
                    }
                }
            }
        };

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

Go


import (
	"context"
	"fmt"
	"io"

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

//	Shows how to run a query with directed read options.
//	Only one of ExcludeReplicas or IncludeReplicas can be set
//	Each accepts a list of ReplicaSelections which contains Location and Type
//	* `location` - The location must be one of the regions within the
//	multi-region configuration of your database.
//	* `type` - The type of the replica
//	Some examples of using replica_selectors are:
//	* `location:us-east1` --> The "us-east1" replica(s) of any available type
//		will be used to process the request.
//	* `type:READ_ONLY`    --> The "READ_ONLY" type replica(s) in nearest
//	available location will be used to process the
//	request.
//	* `location:us-east1 type:READ_ONLY` --> The "READ_ONLY" type replica(s)
//	in location "us-east1" will be used to process the request.
//		IncludeReplicas also contains an option for AutoFailoverDisabled which when set
//	Spanner will not route requests to a replica outside the
//	IncludeReplicas list when all the specified replicas are unavailable
//	or unhealthy. The default value is `false`

func directedReadOptions(w io.Writer, db string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	ctx := context.Background()
	directedReadOptionsForClient := &sppb.DirectedReadOptions{
		Replicas: &sppb.DirectedReadOptions_ExcludeReplicas_{
			ExcludeReplicas: &sppb.DirectedReadOptions_ExcludeReplicas{
				ReplicaSelections: []*sppb.DirectedReadOptions_ReplicaSelection{
					{
						Location: "us-east4",
					},
				},
			},
		},
	}
	// DirectedReadOptions can be set at client level and will be used in all read-only transaction requests
	client, err := spanner.NewClientWithConfig(ctx, db, spanner.ClientConfig{DirectedReadOptions: directedReadOptionsForClient})
	if err != nil {
		return err
	}
	defer client.Close()

	// DirectedReadOptions set at Request level will override the options set at Client level.
	directedReadOptionsForRequest := &sppb.DirectedReadOptions{
		Replicas: &sppb.DirectedReadOptions_IncludeReplicas_{
			IncludeReplicas: &sppb.DirectedReadOptions_IncludeReplicas{
				ReplicaSelections: []*sppb.DirectedReadOptions_ReplicaSelection{
					{
						Type: sppb.DirectedReadOptions_ReplicaSelection_READ_ONLY,
					},
				},
				AutoFailoverDisabled: true,
			},
		},
	}

	statement := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	// // Read rows while passing directedReadOptions directly to the query.
	iter := client.Single().QueryWithOptions(ctx, statement, spanner.QueryOptions{DirectedReadOptions: directedReadOptionsForRequest})
	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

import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Options;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.Statement;
import com.google.spanner.v1.DirectedReadOptions;
import com.google.spanner.v1.DirectedReadOptions.ExcludeReplicas;
import com.google.spanner.v1.DirectedReadOptions.IncludeReplicas;
import com.google.spanner.v1.DirectedReadOptions.ReplicaSelection;

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

  static void directedRead(String projectId, String instanceId, String databaseId) {
    // Only one of excludeReplicas or includeReplicas can be set
    // Each accepts a list of replicaSelections which contains location and type
    //   * `location` - The location must be one of the regions within the
    //      multi-region configuration of your database.
    //   * `type` - The type of the replica
    // Some examples of using replicaSelectors are:
    //   * `location:us-east1` --> The "us-east1" replica(s) of any available type
    //                             will be used to process the request.
    //   * `type:READ_ONLY`    --> The "READ_ONLY" type replica(s) in nearest
    // .                            available location will be used to process the
    //                             request.
    //   * `location:us-east1 type:READ_ONLY` --> The "READ_ONLY" type replica(s)
    //                          in location "us-east1" will be used to process
    //                          the request.
    //  includeReplicas also contains an option called autoFailoverDisabled, which when set to true
    //  will instruct Spanner to not route requests to a replica outside the
    //  includeReplicas list when all the specified replicas are unavailable
    //  or unhealthy. Default value is `false`.
    final DirectedReadOptions directedReadOptionsForClient =
        DirectedReadOptions.newBuilder()
            .setExcludeReplicas(
                ExcludeReplicas.newBuilder()
                    .addReplicaSelections(
                        ReplicaSelection.newBuilder().setLocation("us-east4").build())
                    .build())
            .build();

    // You can set default `DirectedReadOptions` for a Spanner client. These options will be applied
    // to all read-only transactions that are executed by this client, unless specific
    // DirectedReadOptions are set for a query.
    // Directed read can only be used for read-only transactions. The default options will be
    // ignored for any read/write transaction that the client executes.
    try (Spanner spanner =
        SpannerOptions.newBuilder()
            .setProjectId(projectId)
            .setDirectedReadOptions(directedReadOptionsForClient)
            .build()
            .getService()) {
      final DatabaseClient dbClient =
          spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));

      // DirectedReadOptions at request level will override the options set at
      // client level (through SpannerOptions).
      final DirectedReadOptions directedReadOptionsForRequest =
          DirectedReadOptions.newBuilder()
              .setIncludeReplicas(
                  IncludeReplicas.newBuilder()
                      .addReplicaSelections(
                          ReplicaSelection.newBuilder()
                              .setType(ReplicaSelection.Type.READ_WRITE)
                              .build())
                      .setAutoFailoverDisabled(true)
                      .build())
              .build();

      // Read rows while passing DirectedReadOptions directly to the query.
      try (ResultSet rs =
          dbClient
              .singleUse()
              .executeQuery(
                  Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"),
                  Options.directedRead(directedReadOptionsForRequest))) {
        while (rs.next()) {
          System.out.printf(
              "SingerId: %d, AlbumId: %d, AlbumTitle: %s\n",
              rs.getLong(0), rs.getLong(1), rs.getString(2));
        }
        System.out.println("Successfully executed read-only transaction with directedReadOptions");
      }
    }
  }
}

Node.js

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

// Only one of excludeReplicas or includeReplicas can be set
// Each accepts a list of replicaSelections which contains location and type
//   * `location` - The location must be one of the regions within the
//      multi-region configuration of your database.
//   * `type` - The type of the replica
// Some examples of using replicaSelectors are:
//   * `location:us-east1` --> The "us-east1" replica(s) of any available type
//                             will be used to process the request.
//   * `type:READ_ONLY`    --> The "READ_ONLY" type replica(s) in nearest
//.                            available location will be used to process the
//                             request.
//   * `location:us-east1 type:READ_ONLY` --> The "READ_ONLY" type replica(s)
//                          in location "us-east1" will be used to process
//                          the request.
//  includeReplicas also contains an option for autoFailover which when set
//  Spanner will not route requests to a replica outside the
//  includeReplicas list when all the specified replicas are unavailable
//  or unhealthy. The default value is `false`
const directedReadOptionsForClient = {
  excludeReplicas: {
    replicaSelections: [
      {
        location: 'us-east4',
      },
    ],
  },
};

// Instantiates a client with directedReadOptions
const spanner = new Spanner({
  projectId: projectId,
  directedReadOptions: directedReadOptionsForClient,
});

async function spannerDirectedReads() {
  // Gets a reference to a Cloud Spanner instance and backup
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);
  const directedReadOptionsForRequest = {
    includeReplicas: {
      replicaSelections: [
        {
          type: protos.google.spanner.v1.DirectedReadOptions.ReplicaSelection
            .Type.READ_ONLY,
        },
      ],
      autoFailoverDisabled: true,
    },
  };

  await database.getSnapshot(async (err, transaction) => {
    if (err) {
      console.error(err);
      return;
    }
    try {
      // Read rows while passing directedReadOptions directly to the query.
      // These will override the options passed at Client level.
      const [rows] = await transaction.run({
        sql: 'SELECT SingerId, AlbumId, AlbumTitle FROM Albums',
        directedReadOptions: directedReadOptionsForRequest,
      });
      rows.forEach(row => {
        const json = row.toJSON();
        console.log(
          `SingerId: ${json.SingerId}, AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`
        );
      });
      console.log(
        'Successfully executed read-only transaction with directedReadOptions'
      );
    } catch (err) {
      console.error('ERROR:', err);
    } finally {
      transaction.end();
      // Close the database when finished.
      await database.close();
    }
  });
}
spannerDirectedReads();

PHP

use Google\Cloud\Spanner\SpannerClient;
use Google\Cloud\Spanner\V1\DirectedReadOptions\ReplicaSelection\Type as ReplicaType;

/**
 * Queries sample data from the database with directed read options.
 * Example:
 * ```
 * directed_read($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function directed_read(string $instanceId, string $databaseId): void
{
    $directedReadOptionsForClient = [
        'directedReadOptions' => [
            'excludeReplicas' => [
                'replicaSelections' => [
                    [
                        'location' => 'us-east4'
                    ]
                ]
            ]
        ]
    ];

    $directedReadOptionsForRequest = [
        'directedReadOptions' => [
            'includeReplicas' => [
                'replicaSelections' => [
                    [
                        'type' => ReplicaType::READ_WRITE
                    ]
                ],
                'autoFailoverDisabled' => true
            ]
        ]
    ];

    $spanner = new SpannerClient($directedReadOptionsForClient);
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);
    $snapshot = $database->snapshot();

    // directedReadOptions at Request level will override the options set at
    // Client level
    $results = $snapshot->execute(
        'SELECT SingerId, AlbumId, AlbumTitle FROM Albums',
        $directedReadOptionsForRequest
    );

    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"

directed_read_options_for_client = {
    "exclude_replicas": {
        "replica_selections": [
            {
                "location": "us-east4",
            },
        ],
    },
}

# directed_read_options can be set at client level and will be used in all
# read-only transaction requests
spanner_client = spanner.Client(
    directed_read_options=directed_read_options_for_client
)
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

directed_read_options_for_request = {
    "include_replicas": {
        "replica_selections": [
            {
                "type_": DirectedReadOptions.ReplicaSelection.Type.READ_ONLY,
            },
        ],
        "auto_failover_disabled": True,
    },
}

with database.snapshot() as snapshot:
    # Read rows while passing directed_read_options directly to the query.
    # These will override the options passed at Client level.
    results = snapshot.execute_sql(
        "SELECT SingerId, AlbumId, AlbumTitle FROM Albums",
        directed_read_options=directed_read_options_for_request,
    )

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

Ruby

require "google/cloud/spanner"

##
# This is a snippet for showcasing how to pass in directed read 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_directed_read project_id:, instance_id:, database_id:
  # Only one of exclude_replicas or include_replicas can be set.
  # Each accepts a list of replica_selections which contains location and type
  #   * `location` - The location must be one of the regions within the
  #      multi-region configuration of your database.
  #   * `type` - The type of the replica
  # Some examples of using replicaSelectors are:
  #   * `location:us-east1` --> The "us-east1" replica(s) of any available type
  #                             will be used to process the request.
  #   * `type:READ_ONLY`    --> The "READ_ONLY" type replica(s) in the nearest
  # .                            available location will be used to process the
  #                             request.
  #   * `location:us-east1 type:READ_ONLY` --> The "READ_ONLY" type replica(s)
  #                          in location "us-east1" will be used to process
  #                          the request.
  #  include_replicas also contains an option for auto_failover_disabled. If set
  #  Spanner will not route requests to a replica outside the
  #  include_replicas list even if all the specified replicas are
  #  unavailable or unhealthy. The default value is `false`.
  directed_read_options_for_client = {
    include_replicas: {
      replica_selections: [{ location: "us-east4" }]
    }
  }

  # Instantiates a client with directedReadOptions
  spanner = Google::Cloud::Spanner.new project: project_id
  client  = spanner.client instance_id, database_id, directed_read_options: directed_read_options_for_client

  directed_read_options = {
    include_replicas: {
      replica_selections: [{ type: "READ_WRITE" }],
      auto_failover_disabled: true
    }
  }

  result = client.execute_sql "SELECT SingerId, AlbumId, AlbumTitle FROM Albums", directed_read_options: directed_read_options
  result.rows.each do |row|
    puts "SingerId: #{row[:SingerId]}"
    puts "AlbumId: #{row[:AlbumId]}"
    puts "AlbumTitle: #{row[:AlbumTitle]}"
  end
  puts "Successfully executed read-only transaction with directed_read_options"
end

REST

次の REST API を使用して、有向読み取りを行います。

たとえば、executeSQL を使用して us-central1 で有向読み取りを行うには、次のようにします。

  1. projects.instances.databases.sessions.executeSql をクリックします。

  2. [session] に以下のように入力します。

    projects/<VAR>PROJECT-ID</VAR>/instances/<VAR>INSTANCE-ID</VAR>/databases/<VAR>DATABASE-ID</VAR>/sessions/<VAR>SESSION-ID</VAR>
    

    以下を置き換えます。

    • PROJECT-ID: プロジェクト ID。
    • INSTANCE-ID: インスタンス ID。
    • DATABASE-ID: データベース ID。
    • SESSION-ID: セッション ID。 セッションを作成するときに SESSION-ID の値を取得します。
  3. [Request body] で以下のように入力します。

    {
      "directedReadOptions": {
        "includeReplicas": {
          "replicaSelections": [
            {
              "location": "us-central1",
            }
          ]
        }
      },
      "sql": "SELECT SingerId, AlbumId, AlbumTitle FROM Albums"
    }
    
  4. [実行] をクリックします。レスポンスにクエリの結果が表示されます。

RPC

次の RPC API を使用して、有向読み取りを行います。

モニタリング

Spanner には、インスタンス内の有向読み取りアクティビティをモニタリングするために役立つレイテンシ指標が用意されています。この指標は Cloud Monitoring で使用できます。

  • spanner.googleapis.com/api/read_request_latencies_by_serving_location

この指標は、/serving_location フィールドまたは /is_directed_read フィールドを使用してフィルタリングできます。/serving location フィールドには、リクエストが提供される Spanner サーバーのロケーションを指定しています。/is_directed_read フィールドは、指向読み取りオプションが有効かどうかを示しています。

使用可能な指標の全一覧については、Spanner の指標一覧をご覧ください。

次のステップ