데이터베이스 연결 관리

이 페이지에서는 Cloud SQL 데이터베이스 연결을 효과적으로 사용하는 애플리케이션을 만드는 데 도움이 되는 권장사항과 언어별 코드 샘플을 제공합니다.

이 샘플은 GitHub에서 제공하는 완전한 App Engine 애플리케이션에서 발췌하였습니다. 자세히 알아보기

연결 풀

연결 풀이란 연결 지연 시간 및 성능의 개선을 위해 공유 및 재사용되는 데이터베이스 연결의 캐시를 의미합니다. 애플리케이션에서 데이터베이스 연결이 필요하면 풀에서 임시로 하나를 빌려오고 연결이 필요 없어지면 연결을 풀에 반환합니다. 이 연결은 다음에 애플리케이션에서 데이터베이스 연결이 필요할 때 재사용될 수 있습니다.

연결 열기 및 닫기

연결 풀을 사용할 때는 연결을 제대로 열고 닫아야 합니다. 그래야 사용을 마쳤을 때 항상 연결이 풀로 반환됩니다. 반환되지 않거나 '유출'된 연결은 재사용되지 않으므로 리소스가 낭비되고 애플리케이션에서 성능 병목 현상이 발생할 수 있습니다.

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, time_cast=time_cast, candidate=team)
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.
    # ...

자바

// 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#

insertTimestamp = DateTime.UtcNow;
try
{
    using(var connection = new NpgsqlConnection(_connectionString.ConnectionString))
    {
        connection.OpenWithRetry();
        using (var insertVoteCommand = connection.CreateCommand())
        {
            insertVoteCommand.CommandText =
                @"INSERT INTO votes (candidate, time_cast) VALUES (@candidate, @time_cast)";
            var candidate = insertVoteCommand.CreateParameter();
            candidate.ParameterName = "@candidate";
            candidate.DbType = DbType.String;
            candidate.Value = team;
            insertVoteCommand.Parameters.Add(candidate);
            var timeCast = insertVoteCommand.CreateParameter();
            timeCast.ParameterName = "@time_cast";
            timeCast.DbType = DbType.DateTime;
            timeCast.Value = insertTimestamp;
            insertVoteCommand.Parameters.Add(timeCast);
            await insertVoteCommand.ExecuteNonQueryAsync();
        }
    }
    return Content($"Vote successfully cast for '{team}' at time {insertTimestamp}!");
}
catch (Exception ex)
{
    // If something goes wrong, handle the error in this
    // section. This might involve retrying or adjusting
    // parameters depending on the situation.
    return StatusCode((int)HttpStatusCode.InternalServerError, ex);
}

Go

sqlInsert := "INSERT INTO votes(candidate, created_at, updated_at) VALUES($1, NOW(), NOW())"
if team == "TABS" || team == "SPACES" {
	if _, err := app.db.Exec(sqlInsert, team); err != nil {
		fmt.Fprintf(w, "unable to save vote: %s", err)
		return fmt.Errorf("DB.Exec: %v", err)
	}
	fmt.Fprintf(w, "Vote successfully cast for %s!\n", team)
}
return nil

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.

자바

// 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.
dbPool.SetMaxIdleConns(5)

// Set maximum number of open connections to the database.
dbPool.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.

자바

// 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.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

자바

// 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.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.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.
$connConfig = [
    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
# reestablished
"pool_recycle": 1800,  # 30 minutes

자바

// 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.
dbPool.SetConnMaxLifetime(1800)

Ruby

ActiveRecord는 연결 기간을 제어하는 기능을 현재 제공하지 않습니다.

PHP

PDO는 연결 기간을 제어하는 기능을 현재 제공하지 않습니다.

완전한 애플리케이션을 보려면 아래 링크를 클릭하세요.

Python

Python 프로그래밍 언어로 빌드된 완전한 애플리케이션을 확인하세요.

자바

자바 프로그래밍 언어로 빌드된 완전한 애플리케이션을 확인하세요.

Node.js

Node.js 프로그래밍 언어로 빌드된 완전한 애플리케이션을 확인하세요.

C#

C# 프로그래밍 언어로 빌드된 완전한 애플리케이션을 확인하세요.

Go

Go 프로그래밍 언어로 빌드된 완전한 애플리케이션을 확인하세요.

Ruby

Ruby 프로그래밍 언어로 빌드된 완전한 애플리케이션을 확인하세요.

PHP

PHP 프로그래밍 언어로 빌드된 완전한 애플리케이션을 확인하세요.

다음 단계