데이터베이스 연결 관리

이 페이지에서는 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

try {
  const stmt = 'INSERT INTO votes (time_cast, candidate) VALUES (?, ?)';
  // Pool.query automatically checks out, uses, and releases a connection
  // back into the pool, ensuring it is always returned successfully.
  await pool.query(stmt, [timestamp, team]);
} catch (err) {
  // If something goes wrong, handle the error in this section. This might
  // involve retrying or adjusting parameters depending on the situation.
  // ...
}

C#

insertTimestamp = DateTime.Now;
try
{
    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, time_cast) VALUES(?, 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)
	} else {
		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, vote_value) 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

// 'connectionLimit' is the maximum number of connections the pool is allowed
// to keep at once.
connectionLimit: 5,

C#

// MaximumPoolSize sets maximum number of connections allowed in the pool.
connectionString.MaximumPoolSize = 5;
// MinimumPoolSize sets the minimum number of connections in the pool.
connectionString.MinimumPoolSize = 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

// The mysql module automatically uses exponential delays between failed
// connection attempts.

C#

var connection = Policy
    .HandleResult<DbConnection>(conn => conn.State != ConnectionState.Open)
    .WaitAndRetry(new[]
    {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(2),
        TimeSpan.FromSeconds(5)
    }, (result, timeSpan, retryCount, context) =>
    {
        // Log any warnings here.
    })
    .Execute(() =>
    {
        // Return a new connection.
        // ...
    });

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

// 'connectTimeout' is the maximum number of milliseconds before a timeout
// occurs during the initial connection to the database.
connectTimeout: 10000, // 10 seconds
// 'acquireTimeout' is the maximum number of milliseconds to wait when
// checking out a connection from the pool before a timeout error occurs.
acquireTimeout: 10000, // 10 seconds
// 'waitForConnections' determines the pool's action when no connections are
// free. If true, the request will queued and a connection will be presented
// when ready. If false, the pool will call back with an error.
waitForConnections: true, // Default: true
// 'queueLimit' is the maximum number of requests for connections the pool
// will queue at once before returning an error. If 0, there is no limit.
queueLimit: 0, // Default: 0

C#

// ConnectionTimeout sets the time to wait (in seconds) while
// trying to establish a connection before terminating the attempt.
connectionString.ConnectionTimeout = 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.
$conn_config = [
    PDO::ATTR_TIMEOUT => 5,
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
];

연결 종료

Cloud SQL에서 PROCESS 권한이 있는 mysql 사용자는 다른 mysql 사용자(Cloud SQL 관리 사용자 제외)의 연결에 대해 KILL 문을 실행할 수 있습니다.

mysql 클라이언트를 사용하고 SHOW PROCESSLIST 명령어를 실행하여 인스턴스 연결을 나열할 수 있습니다. Id를 사용하여 연결을 종료합니다. 예를 들면 다음과 같습니다.

mysql> SHOW PROCESSLIST;
mysql> KILL 6;

연결 기간

연결 수명을 제한하면 이탈한 연결이 누적되는 것을 막을 수 있습니다. 연결 수명은 연결 풀을 사용해 제한할 수 있습니다.

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

'mysql' Node.js 라이브러리는 연결 기간을 제어할 수 있는 함수를 현재 제공하지 않습니다.

C#

// ConnectionLifeTime sets the lifetime of a pooled connection
// (in seconds) that a connection lives before it is destroyed
// and recreated. Connections that are returned to the pool are
// destroyed if it's been more than the number of seconds
// specified by ConnectionLifeTime since the connection was
// created. The default value is zero (0) which means the
// connection always returns to pool.
connectionString.ConnectionLifeTime = 1800; // 30 minutes

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 프로그래밍 언어로 빌드된 완전한 애플리케이션을 확인하세요.

다음 단계