データベース接続を管理する

このページでは、Cloud SQL データベース接続を効果的に使用するアプリケーションの作成に関するベスト プラクティスと言語に固有のコードサンプルについて説明します。

これらのサンプルは、GitHub で入手できる完全なウェブ アプリケーションからの抜粋です。詳細

Cloud SQL に接続されたサンプル ウェブ アプリケーションを実行する手順については、ご使用の環境に該当するリンクをクリックしてください。

接続プール

接続プールは、接続レイテンシとパフォーマンスを向上させるために共有され、再利用されるデータベース接続のキャッシュです。アプリケーションでデータベース接続が必要な場合は、プールから一時的に 1 つを借ります。アプリケーションでの接続が終了すると、接続がプールに戻されます。アプリケーションで次にデータベース接続が必要とされるときに再利用できます。

接続の開閉

接続プールを使用する場合は、使用後に接続が必ずプールに返されるように、接続の開閉を適切に行う必要があります。返されなかった接続(「リーク」した接続)は再利用されないため、リソースが無駄に消費され、アプリケーションのパフォーマンスのボトルネックになることがあります。

Python

# Preparing a statement before hand can help protect against injections.
stmt = sqlalchemy.text(
    "INSERT INTO votes (time_cast, candidate) VALUES (:time_cast, :candidate)"
)
try:
    # Using a with statement ensures that the connection is always released
    # back into the pool at the end of statement (even if an error occurs)
    with db.connect() as conn:
        conn.execute(stmt, parameters={"time_cast": time_cast, "candidate": team})
        conn.commit()
except Exception as e:
    # If something goes wrong, handle the error in this section. This might
    # involve retrying or adjusting parameters depending on the situation.
    # ...

Java

// Using a try-with-resources statement ensures that the connection is always released back
// into the pool at the end of the statement (even if an error occurs)
try (Connection conn = pool.getConnection()) {

  // PreparedStatements can be more efficient and project against injections.
  String stmt = "INSERT INTO votes (time_cast, candidate) VALUES (?, ?);";
  try (PreparedStatement voteStmt = conn.prepareStatement(stmt);) {
    voteStmt.setTimestamp(1, now);
    voteStmt.setString(2, team);

    // Finally, execute the statement. If it fails, an error will be thrown.
    voteStmt.execute();
  }
} catch (SQLException ex) {
  // If something goes wrong, handle the error in this section. This might involve retrying or
  // adjusting parameters depending on the situation.
  // ...
}

Node.js

/**
 * Insert a vote record into the database.
 *
 * @param {object} pool The Knex connection object.
 * @param {object} vote The vote record to insert.
 * @returns {Promise}
 */
const insertVote = async (pool, vote) => {
  try {
    return await pool('votes').insert(vote);
  } catch (err) {
    throw Error(err);
  }
};

C#

using Npgsql;
using System;

namespace CloudSql
{
    public class PostgreSqlTcp
    {
        public static NpgsqlConnectionStringBuilder NewPostgreSqlTCPConnectionString()
        {
            // Equivalent connection string:
            // "Uid=<DB_USER>;Pwd=<DB_PASS>;Host=<INSTANCE_HOST>;Database=<DB_NAME>;"
            var connectionString = new NpgsqlConnectionStringBuilder()
            {
                // Note: Saving credentials in environment variables is convenient, but not
                // secure - consider a more secure solution such as
                // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
                // keep secrets safe.
                Host = Environment.GetEnvironmentVariable("INSTANCE_HOST"),     // e.g. '127.0.0.1'
                // Set Host to 'cloudsql' when deploying to App Engine Flexible environment
                Username = Environment.GetEnvironmentVariable("DB_USER"), // e.g. 'my-db-user'
                Password = Environment.GetEnvironmentVariable("DB_PASS"), // e.g. 'my-db-password'
                Database = Environment.GetEnvironmentVariable("DB_NAME"), // e.g. 'my-database'

                // The Cloud SQL proxy provides encryption between the proxy and instance.
                SslMode = SslMode.Disable,
            };
            connectionString.Pooling = true;
            // Specify additional properties here.
            return connectionString;
        }
    }
}

Go

insertVote := "INSERT INTO votes(candidate, created_at) VALUES($1, NOW())"
_, err := db.Exec(insertVote, team)

Ruby

@vote = Vote.new candidate: candidate

# ActiveRecord creates and executes your SQL and automatically
# handles the opening and closing of the database connection.
if @vote.save
  render json: "Vote successfully cast for \"#{@vote.candidate}\" at #{@vote.time_cast} PST!"
else
  render json: @vote.errors, status: :unprocessable_entity
end

PHP

// Use prepared statements to guard against SQL injection.
$sql = 'INSERT INTO votes (time_cast, candidate) VALUES (NOW(), :voteValue)';

try {
    $statement = $conn->prepare($sql);
    $statement->bindParam('voteValue', $value);

    $res = $statement->execute();
} catch (PDOException $e) {
    throw new RuntimeException(
        'Could not insert vote into database. The PDO exception was ' .
        $e->getMessage(),
        $e->getCode(),
        $e
    );
}

接続数

すべてのデータベース接続で、クライアント側とサーバー側のリソースが使用されます。さらに、Cloud SQL では、超えてはならない接続総数の制限が課されます。作成して使用する接続の数を少なくすると、オーバーヘッドが減り、接続上限を超えずに済みます。

Python

# Pool size is the maximum number of permanent connections to keep.
pool_size=5,
# Temporarily exceeds the set pool_size if no connections are available.
max_overflow=2,
# The total number of concurrent connections for your application will be
# a total of pool_size and max_overflow.

Java

// maximumPoolSize limits the total number of concurrent connections this pool will keep. Ideal
// values for this setting are highly variable on app design, infrastructure, and database.
config.setMaximumPoolSize(5);
// minimumIdle is the minimum number of idle connections Hikari maintains in the pool.
// Additional connections will be established to meet this value unless the pool is full.
config.setMinimumIdle(5);

Node.js

// 'max' limits the total number of concurrent connections this pool will keep. Ideal
// values for this setting are highly variable on app design, infrastructure, and database.
config.pool.max = 5;
// 'min' is the minimum number of idle connections Knex maintains in the pool.
// Additional connections will be established to meet this value unless the pool is full.
config.pool.min = 5;

C#

// MaxPoolSize sets maximum number of connections allowed in the pool.
connectionString.MaxPoolSize = 5;
// MinPoolSize sets the minimum number of connections in the pool.
connectionString.MinPoolSize = 0;

Go

// Set maximum number of connections in idle connection pool.
db.SetMaxIdleConns(5)

// Set maximum number of open connections to the database.
db.SetMaxOpenConns(7)

Ruby

# 'pool' is the maximum number of permanent connections to keep.
pool: 5

PHP

現在、PDO には接続上限を構成する機能はありません。

指数バックオフ

アプリケーションでデータベースに接続しようとしたものの、成功しない場合、データベースが一時的に使用できなくなる可能性があります。この場合、繰り返し接続リクエストを送信するとリソースが浪費されます。データベースに再びアクセスできるようにするには、追加の接続リクエストを送信する前に待機することをおすすめします。指数バックオフやその他の遅延メカニズムを使用すると、この目標を達成できます。

この再試行は、最初に接続するときや、プールから最初に接続を取得したときにのみ有効です。トランザクションの途中でエラーが発生した場合、アプリケーションはトランザクションの最初から再試行する必要があります。そのためプールの構成が適切でも、接続が失われるとアプリケーションにエラーが表示されることがあります。

Python

# SQLAlchemy automatically uses delays between failed connection attempts,
# but provides no arguments for configuration.

Java

// Hikari automatically delays between failed connection attempts, eventually reaching a
// maximum delay of `connectionTimeout / 2` between attempts.

Node.js

// 'knex' uses a built-in retry strategy which does not implement backoff.
// 'createRetryIntervalMillis' is how long to idle after failed connection creation before trying again
config.pool.createRetryIntervalMillis = 200; // 0.2 seconds

C#

Policy
    .Handle<NpgsqlException>()
    .WaitAndRetry(new[]
    {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(2),
        TimeSpan.FromSeconds(5)
    })
    .Execute(() => connection.Open());

Go

現在、database/sql パッケージには、指数バックオフを構成する機能はありません。

Ruby

# ActiveRecord automatically uses delays between failed connection attempts,
# but provides no arguments for configuration.

PHP

現在、PDO には指数バックオフを構成する機能はありません。

接続タイムアウト

接続の試行は、さまざまな理由で失敗することがあります。ネットワーク通信が保証されることはなく、データベースが一時的に応答しなくなる可能性があります。アプリケーションが切断された接続や失敗した接続を適切に処理するようにします。

Python

# 'pool_timeout' is the maximum number of seconds to wait when retrieving a
# new connection from the pool. After the specified amount of time, an
# exception will be thrown.
pool_timeout=30,  # 30 seconds

Java

// setConnectionTimeout is the maximum number of milliseconds to wait for a connection checkout.
// Any attempt to retrieve a connection from this pool that exceeds the set limit will throw an
// SQLException.
config.setConnectionTimeout(10000); // 10 seconds
// idleTimeout is the maximum amount of time a connection can sit in the pool. Connections that
// sit idle for this many milliseconds are retried if minimumIdle is exceeded.
config.setIdleTimeout(600000); // 10 minutes

Node.js

// 'acquireTimeoutMillis' is the number of milliseconds before a timeout occurs when acquiring a
// connection from the pool. This is slightly different from connectionTimeout, because acquiring
// a pool connection does not always involve making a new connection, and may include multiple retries.
// when making a connection
config.pool.acquireTimeoutMillis = 60000; // 60 seconds
// 'createTimeoutMillis` is the maximum number of milliseconds to wait trying to establish an
// initial connection before retrying.
// After acquireTimeoutMillis has passed, a timeout exception will be thrown.
config.pool.createTimeoutMillis = 30000; // 30 seconds
// 'idleTimeoutMillis' is the number of milliseconds a connection must sit idle in the pool
// and not be checked out before it is automatically closed.
config.pool.idleTimeoutMillis = 600000; // 10 minutes

C#

// Timeout sets the time to wait (in seconds) while
// trying to establish a connection before terminating the attempt.
connectionString.Timeout = 15;

Go

現在、database/sql パッケージには、接続タイムアウトを構成する機能はありません。タイムアウトはドライバレベルで構成されます。

Ruby

# 'timeout' is the maximum number of seconds to wait when retrieving a
# new connection from the pool. After the specified amount of time, an
# ActiveRecord::ConnectionTimeoutError will be raised.
timeout: 5000

PHP

// Here we set the connection timeout to five seconds and ask PDO to
// throw an exception if any errors occur.
[
    PDO::ATTR_TIMEOUT => 5,
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]

接続の存続期間

接続の存続期間を制限することで、放棄された接続が蓄積しないようにできます。接続プールを使用すると、接続の存続期間を制限できます。

Python

# 'pool_recycle' is the maximum number of seconds a connection can persist.
# Connections that live longer than the specified amount of time will be
# re-established
pool_recycle=1800,  # 30 minutes

Java

// maxLifetime is the maximum possible lifetime of a connection in the pool. Connections that
// live longer than this many milliseconds will be closed and reestablished between uses. This
// value should be several minutes shorter than the database's timeout value to avoid unexpected
// terminations.
config.setMaxLifetime(1800000); // 30 minutes

Node.js

現在、knex Node.js ライブラリは接続期間の制御機能を提供していません。

C#

// ConnectionIdleLifetime sets the time (in seconds) to wait before
// closing idle connections in the pool if the count of all
// connections exceeds MinPoolSize.
connectionString.ConnectionIdleLifetime = 300;

Go

// Set Maximum time (in seconds) that a connection can remain open.
db.SetConnMaxLifetime(1800 * time.Second)

Ruby

現在、ActiveRecord は接続期間の制御機能を提供していません。

PHP

現在、PDO は接続期間の制御機能を提供していません。

完全なアプリケーションを表示するには、下のリンクをクリックしてください。

Python

Python プログラミング言語用の完全なアプリケーションを表示します。

Java

Java プログラミング言語用の完全なアプリケーションを表示します。

Node.js

Node.js プログラミング言語用の完全なアプリケーションを表示します。

C#

C# プログラミング言語用の完全なアプリケーションを表示します。

Go

Go プログラミング言語用の完全なアプリケーションを表示します。

Ruby

Ruby プログラミング言語用の完全なアプリケーションを表示します。

PHP

PHP プログラミング言語用の完全なアプリケーションを表示します。

次のステップ