colopl/laravel-spanner

Google Cloud Spanner 的 Laravel 数据库驱动程序

v8.2.0 2024-08-07 01:48 UTC

README

Google Cloud Spanner 的 Laravel 数据库驱动程序

License Latest Stable Version Minimum PHP Version

要求

  • PHP >= 8.2
  • Laravel >= 11
  • gRPC 扩展
  • protobuf 扩展(建议以提高性能)
  • sysvmsgsysvsemsysvshm 扩展(建议以提高性能)

安装

将 JSON 凭据文件路径放入环境变量:GOOGLE_APPLICATION_CREDENTIALS

export GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json

通过 composer 安装

composer require colopl/laravel-spanner

将连接配置添加到 config/database.php

[
    'connections' => [
        'spanner' => [
            'driver' => 'spanner',
            'instance' => '<Cloud Spanner instanceId here>',
            'database' => '<Cloud Spanner database name here>',
        ]
    ]
];

就这么多。您可以使用数据库连接像平常一样。

$conn = DB::connection('spanner');
$conn->...

其他配置

您可以将 SpannerClient 配置和 CacheSessionPool 选项传递如下。有关更多信息,请参阅Google 客户端库文档

[
    'connections' => [
        'spanner' => [
            'driver' => 'spanner',
            'instance' => '<Cloud Spanner instanceId here>',
            'database' => '<Cloud Spanner database name here>',
            
            // Spanner Client configurations
            'client' => [
                'projectId' => 'xxx',
                ...
            ],
            
            // CacheSessionPool options
            'session_pool' => [
                'minSessions' => 10,
                'maxSessions' => 500,
            ],
        ]
    ]
];

推荐设置

请注意,以下内容不是必需的,但强烈建议以提高性能。

  • 安装 protobuf pecl 扩展以加快网络通信。
  • 安装 sysvmsgsysvsemsysvshm 扩展以加快会话管理。
  • 将缓存目录(默认为 ./storage/framework/spanner)挂载到 tmpfs 以提高会话 IO 性能。可以通过在 config/database.php 文件中设置 connections.{name}.cache_path 来更改缓存路径。

不支持的功能

  • STRUCT 数据类型
  • 插入/更新 JSON 数据类型
  • 显式只读事务(快照)

限制

SQL 模式

当前仅支持在 GoogleSQL 上运行的 Spanner(不支持 PostgreSQL 模式)。

查询

  • 在一个查询中绑定超过 950 个参数会导致服务器错误。为了绕过此限制,当传递的参数超过由 parameter_unnest_threshold 配置(默认为 900)设置的限制时,此驱动程序将尝试在内部切换到使用 Query.Builder::whereInUnnest(...)。您可以通过将其值设置为 false 来关闭此功能。

Eloquent

如果您使用交错键,您必须在其 interleaveKeys 属性中定义它们,否则您将无法保存。有关更详细的说明,请参阅 Colopl\Spanner\Tests\Eloquent\ModelTest

更多信息

迁移

由于 Cloud Spanner 不支持 AUTO_INCREMENT 属性,Blueprint::increments(及其所有变体)将创建一个类型为 STRING(36) DEFAULT (GENERATE_UUID()) 的列以生成和填充列,并将其标记为主键。

事务

Google Cloud Spanner 有时请求事务重试(例如 UNAVAILABLEABORTED),即使逻辑是正确的。因此,请勿手动管理事务。

您应该始终使用 transaction 方法,该方法内部处理重试请求。

// BAD: Do not use transactions manually!!
try {
    DB::beginTransaction();
    ...
    DB::commit();
} catch (\Throwable $ex) {
    DB::rollBack();
}

// GOOD: You should always use transaction method
DB::transaction(function() {
    ...
});

Google Cloud Spanner 为所有数据操作创建事务,即使您没有显式创建事务。

特别是,在 SELECT 语句中,事务类型根据它是显式还是隐式而有所不同。

// implicit transaction (Read-only transaction)
$conn->select('SELECT ...');

// explicit transaction (Read-write transaction)
$conn->transaction(function() {
    $conn->select('SELECT ...');
});

// implicit transaction (Read-write transaction)
$conn->insert('INSERT ...');

// explicit transaction (Read-write transaction)
$conn->transaction(function() {
    $conn->insert('INSERT ...');
});

有关更多信息,请参阅Cloud Spanner关于事务的文档

陈旧读取

您可以使用以下陈旧读取(时间戳边界)

// There are four types of timestamp bounds: ExactStaleness, MaxStaleness, MinReadTimestamp and ReadTimestamp.
$timestampBound = new ExactStaleness(10);

// by Connection
$connection->selectWithTimestampBound('SELECT ...', $bindings, $timestampBound);

// by Query Builder
$queryBuilder
    ->withStaleness($timestampBound)
    ->get();

陈旧读取始终以singleUse选项作为只读事务运行。因此,您不能将其作为读写事务运行。

数据提升

数据提升创建快照并在不影响现有工作负载的情况下并行运行查询。

您可以在此处了解更多关于它的信息。

以下是如何使用它的示例。

// Using Connection
$connection->selectWithOptions('SELECT ...', $bindings, ['dataBoostEnabled' => true]);

// Using Query Builder
$queryBuilder
    ->useDataBoost()
    ->setRequestTimeoutSeconds(60)
    ->get();

注意

这将在后台创建一个新会话,该会话不会与当前会话池共享。这意味着,使用数据提升运行的查询将不会与可能正在进行的任何事务关联。

请求标签和事务标签

Spanner允许您为您的查询和事务附加标签,这些标签可以用于故障排除

您可以根据以下方式设置请求标签和事务标签。

$requestPath = request()->path();
$tag = 'url=' . $requestPath;
$connection->setRequestTag($tag);
$connection->setTransactionTag($tag);

数据类型

Google Cloud Spanner的一些数据类型在PHP中没有相应的内置类型。您可以使用以下类,通过Google Cloud PHP客户端

  • BYTES: Google\Cloud\Spanner\Bytes
  • DATE: Google\Cloud\Spanner\Date
  • NUMERIC: Google\Cloud\Spanner\Numeric
  • TIMESTAMP: Google\Cloud\Spanner\Timestamp

在获取行时,库将以下列类型转换为以下类型

  • Timestamp -> 带默认时区的Carbon
  • Numeric -> string

请注意,如果您在没有QueryBuilder的情况下执行查询,则不会进行这些转换。

分区DML

您可以按以下方式运行分区DML。

// by Connection
$connection->runPartitionedDml('UPDATE ...');


// by Query Builder
$queryBuilder->partitionedUpdate($values);
$queryBuilder->partitionedDelete();

但是,分区DML有一些限制。有关更多信息,请参阅Cloud Spanner关于分区DML的文档

交错

您可以按以下方式定义交错表

$schemaBuilder->create('user_items', function (Blueprint $table) {
    $table->uuid('user_id');
    $table->uuid('id');
    $table->uuid('item_id');
    $table->integer('count');
    $table->timestamps();

    $table->primary(['user_id', 'id']);
    
    // interleaved table
    $table->interleaveInParent('users')->cascadeOnDelete();
    
    // interleaved index
    $table->index(['userId', 'created_at'])->interleaveIn('users');
});

行删除策略

您可以按以下方式定义行删除策略

$schemaBuilder->create('user', function (Blueprint $table) {
    $table->uuid('user_id');
    $table->timestamps();
    
    // create a policy
    $table->deleteRowsOlderThan(['updated_at'], 365);
});

$schemaBuilder->table('user', function (Blueprint $table) {
    // add policy
    $table->addRowDeletionPolicy('udpated_at', 100);

    // replace policy
    $table->replaceRowDeletionPolicy('udpated_at', 100);

    // drop policy
    $table->dropRowDeletionPolicy();
});

序列

如果您想将简单的序列用作主键,可以使用useSequence()方法。如果未提供$name,则调用useSequence()将创建一个名为user_id_sequence的序列,其start_with_counter设置为1和1,000,000之间的随机值。

$schemaBuilder->create('user', function (Blueprint $table) {
    $table->integer('id')->useSequence();
});

如果您想获得更多灵活性,也可以直接创建、修改和删除序列,如下所示。

$schemaBuilder->create('user_items', function (Blueprint $table) {
    $table->createSequence('sequence_name');
    $table->integer('id')->useSequence('sequence_name');
    
    $table->alterSequence('sequence_name')
        ->startWithCounter(100)
        ->skipRangeMin(1)
        ->skipRangeMax(10);
    
    $table->dropSequence('sequence_name');
});

辅助索引选项

您可以按以下方式定义Spanner特定的索引选项,如null过滤存储

$schemaBuilder->table('user_items', function (Blueprint $table) {
    $table->index('userId')
        // Interleave in parent table
        ->interleaveIn('user')
        // Add null filtering
        ->nullFiltered()
        // Add storing
        ->storing(['itemId', 'count']);
});

突变

您可以使用突变插入、更新和删除数据以修改数据,而不是使用DML来提高性能。

$queryBuilder->insertUsingMutation($values);
$queryBuilder->updateUsingMutation($values);
$queryBuilder->insertOrUpdateUsingMutation($values);
$queryBuilder->deleteUsingMutation($values);

请注意,mutation API 的操作方式与 DML 不同。在一个事务中的所有 mutations 调用都会排队,并在你提交时批量发送。这意味着,如果你通过上述函数进行了任何修改,然后在提交之前尝试 SELECT 相同的记录,返回的结果将不会包括你在事务中进行的任何修改。

SessionPool 和 AuthCache

为了提高每个请求的第一个连接的性能,我们使用了 AuthCacheCacheSessionPool

默认情况下,这个库使用 Filesystem Cache Adapter 作为缓存池。如果你想使用自己的缓存池,你可以扩展 ServiceProvider 并将其注入到 Colopl\Spanner\Connection 构造函数中。

每个会话的初始化大约需要一秒钟,因此建议在服务器的启动阶段预热会话。这可以通过运行 php artisan spanner:warmup 命令实现。你可以通过设置 config/database.php 中的 connections.{name}.session_pool.maxSessions 选项来设置要预热的会话数量。

同样,会话在使用后保持活跃 60 分钟,因此建议在服务器的关闭阶段删除会话。这可以通过运行 php artisan spanner:cooldown 命令实现。

队列工作员

每个作业处理完成后,连接将断开,这样会话就可以释放到会话池中。这允许会话通过 maintainSessionPool() 进行更新或过期。

Laravel Tinker

你可以使用 Laravel Tinker,例如使用 php artisan tinker 命令。但是,当你访问 Cloud Spanner 时,你的会话可能会挂起。这是已知的问题,当 PHP 分叉进程时发生 gRPC 问题。解决方案是在 php.ini 中添加以下行。

grpc.enable_fork_support=1

开发

测试

你可以通过以下命令在 docker 上运行测试。请注意,必须设置一些环境变量。为了设置变量,将 .env.sample 重命名为 .env 并编辑定义的变量的值。

make test

许可

Apache 2.0 - 更多信息请参阅 LICENSE