세션

이 페이지에서는 클라이언트 라이브러리를 만들거나, REST 또는 RPC API를 사용하거나, Google 클라이언트 라이브러리를 사용할 때의 세션 권장사항을 포함하여 Spanner의 고급 세션 개념을 설명합니다.

세션 개요

세션은 Spanner 데이터베이스 서비스와의 통신 채널을 나타냅니다. 세션은 Spanner 데이터베이스에서 데이터를 읽거나, 쓰거나, 수정하는 트랜잭션을 수행하는 데 사용됩니다. 각 세션은 단일 데이터베이스에 적용합니다.

세션은 한 번에 트랜잭션 한 개만 실행할 수 있습니다. 독립형 읽기, 쓰기, 쿼리는 트랜잭션을 내부적으로 사용하고 하나의 트랜잭션 제한을 위한 카운트를 수행합니다.

세션 풀의 성능 이점

세션을 만드는 데는 많은 리소스가 사용됩니다. 데이터베이스 작업이 수행될 때마다 성능 비용을 방지하기 위해 클라이언트는 사용 준비가 완료된 사용 가능한 세션 풀인 세션 풀을 유지해야 합니다. 풀은 기존 세션을 저장하고 요청 시 적합한 유형의 세션을 반환할 뿐만 아니라 미사용 세션을 정리해야 합니다. 세션 풀을 구현하는 방법의 예시를 보려면 Go 클라이언트 라이브러리 또는 자바 클라이언트 라이브러리와 같은 Spanner 클라이언트 라이브러리 중 하나의 소스 코드를 참조하세요.

세션은 오래 지속되도록 계획되므로, 세션이 데이터베이스 작업에 사용된 후에는 클라이언트가 세션을 재사용할 수 있도록 풀에 반환해야 합니다.

gRPC 채널 개요

gRPC 채널은 Spanner 클라이언트에서 통신에 사용합니다. 하나의 gRPC 채널은 TCP 연결과 거의 동일합니다. 하나의 gRPC 채널은 최대 100개의 동시 요청을 처리할 수 있습니다. 즉, 애플리케이션에는 애플리케이션이 실행되는 동시 요청 수를 100으로 나눈 값 이상의 gRPC 채널이 필요합니다.

Spanner 클라이언트는 생성될 때 gRPC 채널 풀을 만듭니다.

Google 클라이언트 라이브러리 사용 시 권장사항

다음은 Spanner에 Google 클라이언트 라이브러리를 사용할 때의 권장사항을 설명합니다.

풀의 세션 수 및 gRPC 채널 수 구성

클라이언트 라이브러리에는 세션 풀의 기본 세션 수와 채널 풀의 기본 gRPC 채널 수가 있습니다. 대부분의 경우에 두 기본값 모두 적합합니다. 각 프로그래밍 언어의 기본 최소 및 최대 세션 수와 기본 gRPC 채널 수는 다음과 같습니다.

C++

MinSessions: 100
MaxSessions: 400
NumChannels: 4

C#

MinSessions: 100
MaxSessions: 400
NumChannels: 4

Go

MinSessions: 100
MaxSessions: 400
NumChannels: 4

Java

MinSessions: 100
MaxSessions: 400
NumChannels: 4

Node.js

Node.js 클라이언트는 여러 gRPC 채널을 지원하지 않습니다. 따라서 단일 클라이언트의 세션 풀 크기를 100개 세션 이상으로 늘리는 대신 여러 클라이언트를 만드는 것이 좋습니다.

MinSessions: 25
MaxSessions: 100

PHP

PHP 클라이언트는 구성 가능한 수의 gRPC 채널을 지원하지 않습니다.

MinSessions: 1
MaxSessions: 500

Python

Python은 세션을 관리하는 데 사용할 수 있는 네 가지 세션 풀 유형을 지원합니다.

Ruby

Ruby 클라이언트는 여러 gRPC 채널을 지원하지 않습니다. 따라서 단일 클라이언트의 세션 풀 크기를 100개 세션 이상으로 늘리는 대신 여러 클라이언트를 만드는 것이 좋습니다.

MinSessions: 10
MaxSessions: 100

애플리케이션이 사용하는 세션 수는 애플리케이션이 실행하는 동시 트랜잭션 수와 동일합니다. 단일 애플리케이션 인스턴스가 기본 세션 풀에서 처리할 수 있는 것보다 많은 동시 트랜잭션을 실행할 것으로 예상되는 경우에만 기본 세션 풀 설정을 수정해야 합니다.

동시 실행이 많은 애플리케이션의 경우 다음이 권장됩니다.

  1. MinSessions를 단일 클라이언트가 실행할 것으로 예상되는 동시 트랜잭션 수로 설정합니다.
  2. MaxSessions를 단일 클라이언트가 실행할 수 있는 최대 동시 트랜잭션 수로 설정합니다.
  3. 애플리케이션 전체 기간 동안 예상되는 동시 실행 수가 크게 변경되지 않는 경우 MinSessions=MaxSessions를 설정합니다. 이렇게 하면 세션 풀이 확장되거나 축소되지 않습니다. 세션 풀을 확장하거나 축소해도 일부 리소스가 사용됩니다.
  4. NumChannelsMaxSessions / 100으로 설정합니다. 하나의 gRPC 채널은 최대 100개의 요청을 동시에 처리할 수 있습니다. 높은 꼬리 지연 시간(p95/p99 지연 시간)이 관찰되면 이 값을 늘립니다. 이는 gRPC 채널 정체를 나타낼 수 있습니다.

활성 세션 수를 늘리면 Spanner 데이터베이스 서비스와 클라이언트 라이브러리에서 추가 리소스가 사용됩니다. 애플리케이션의 실제 요구 사항 이상으로 세션 수를 늘리면 시스템 성능이 저하될 수 있습니다.

세션 풀 증가 및 클라이언트 수 증가 비교

애플리케이션의 세션 풀 크기는 단일 애플리케이션 인스턴스가 실행할 수 있는 동시 트랜잭션 수를 결정합니다. 단일 애플리케이션 인스턴스가 처리할 수 있는 최대 동시 실행 수 이상으로 세션 풀 크기를 늘리는 것은 권장되지 않습니다. 애플리케이션이 풀의 세션 수를 초과하는 버스트를 수신하면 세션이 사용 가능해질 때까지 기다리는 동안 요청이 큐에 추가됩니다.

클라이언트 라이브러리에서 사용하는 리소스는 다음과 같습니다.

  1. 각 gRPC 채널은 하나의 TCP 연결을 사용합니다.
  2. 각 gRPC 호출에는 스레드가 필요합니다. 클라이언트 라이브러리에서 사용하는 최대 스레드 수는 애플리케이션이 실행하는 최대 동시 쿼리 수와 동일합니다. 이러한 스레드는 애플리케이션이 자체 비즈니스 로직에 사용하는 스레드 위에 있습니다.

단일 애플리케이션 인스턴스가 처리할 수 있는 최대 스레드 수 이상으로 세션 풀의 크기를 늘리는 것은 권장되지 않습니다. 대신 애플리케이션 인스턴스의 수를 늘리세요.

쓰기 세션 비율 관리

일부 클라이언트 라이브러리의 경우, Spanner는 읽기-쓰기 트랜잭션을 위해 세션의 일부를 예약하는데, 이를 쓰기 세션 비율이라고 합니다. 앱이 쓰기 세션을 모두 사용하면 Spanner는 읽기 전용 트랜잭션에도 읽기-쓰기 세션을 사용합니다. 읽기-쓰기 세션에는 spanner.databases.beginOrRollbackReadWriteTransaction이 필요합니다. 사용자가 spanner.databaseReader IAM 역할에 포함되었으면 호출이 실패하고 Spanner에서 다음 오류 메시지를 반환합니다.

generic::permission_denied: Resource %resource% is missing IAM permission:
spanner.databases.beginOrRollbackReadWriteTransaction

쓰기 세션 비율을 유지하는 클라이언트 라이브러리의 경우, 쓰기 세션 비율을 설정할 수 있습니다.

C++

모든 C++ 세션은 동일합니다. 읽기 또는 읽기-쓰기 전용 세션이 없습니다.

C#

C#의 기본 쓰기 세션 비율은 0.2입니다. SessionPoolOptions의 WriteSessionsFraction 필드를 사용하여 이 비율을 변경할 수 있습니다.

Go

모든 Go 세션은 동일합니다. 읽기 또는 읽기-쓰기 전용 세션이 없습니다.

Java

모든 자바 세션은 동일합니다. 읽기 또는 읽기-쓰기 전용 세션이 없습니다.

Node.js

모든 Node.js 세션은 동일합니다. 읽기 또는 읽기-쓰기 전용 세션이 없습니다.

PHP

모든 PHP 세션은 동일합니다. 읽기 또는 읽기-쓰기 전용 세션이 없습니다.

Python

Python은 읽기 및 읽기-쓰기 세션 관리에 사용할 수 있는 네 가지 세션 풀 유형을 지원합니다.

Ruby

Ruby의 기본 쓰기 세션 비율은 0.3입니다. 이 비율은 client initialize 메서드를 사용하여 변경할 수 있습니다.

클라이언트 라이브러리를 만들거나 REST/RPC를 사용할 때의 권장사항

다음은 Spanner의 클라이언트 라이브러리에서 세션을 구현하거나 REST 또는 RPC API에서 세션을 사용할 때의 권장사항입니다.

이 권장사항은 클라이언트 라이브러리를 개발하거나 REST/RPC API를 사용하는 경우에만 적용됩니다. Spanner용 Google 클라이언트 라이브러리 중 하나를 사용하는 경우 Google 클라이언트 라이브러리 사용 시 권장사항을 참조하세요.

세션 풀 생성 및 크기 조정

클라이언트 프로세스에 대한 세션 풀의 최적 크기를 결정하려면 하한을 예상되는 동시 트랜잭션 수로 설정하고 상한을 초기 테스트 수(예: 100)로 설정합니다. 상한이 적합하지 않으면 수를 올립니다. 활성 세션 수를 늘리면 Spanner 데이터베이스 서비스에 추가 리소스가 사용되므로, 사용되지 않는 세션을 삭제하지 못할 경우 성능이 저하될 수 있습니다. RPC API를 사용하는 사용자의 경우 gRPC 채널별 세션이 100개를 초과하지 않는 것이 좋습니다.

삭제된 세션 처리

세션을 삭제하는 방법에는 세 가지가 있습니다.

  • 클라이언트가 세션을 삭제할 수 있습니다.
  • Spanner 데이터베이스 서비스는 1시간 넘게 유휴 상태인 세션을 삭제할 수 있습니다.
  • 세션이 28일보다 오래된 경우 Spanner 데이터베이스 서비스가 세션을 삭제할 수 있습니다.

삭제된 세션을 사용하려고 하면 NOT_FOUND 오류가 발생합니다. 이 오류가 발생하면 새 세션을 만들어 사용하고, 새 세션을 풀에 추가하고, 풀에서 삭제된 세션을 제거합니다.

유휴 세션 유지

Spanner 데이터베이스 서비스는 미사용 세션을 중단할 권한을 갖습니다. 데이터베이스 사용이 단기간에 크게 늘어날 것으로 예상되는 경우와 같이 유휴 세션을 계속 유지해야 하는 이유가 분명한 경우에는 세션이 중단되지 않도록 할 수 있습니다. 세션을 유지하기 위해 SQL 쿼리 SELECT 1 실행과 같이 리소스를 많이 사용하지 않는 작업을 수행합니다. 단기적으로 필요하지 않은 유휴 세션이 있는 경우, Spanner가 이 세션을 중단하도록 한 후 다음에 세션이 필요할 때 세션을 새로 만듭니다.

세션을 계속 유지해야 하는 상황 중 하나로, 정기적으로 급증하는 데이터베이스 사용을 처리하는 경우가 있습니다. 매일 오전 9시부터 오후 6시까지 데이터베이스 사용량이 과중하게 발생하는 경우, 일부 유휴 세션이 최대 사용량 기간 동안에 필요할 수 있으므로 일부 유휴 세션을 이 기간 중에 유지해야 합니다. 오후 6시 이후에는 Spanner가 유휴 세션을 중단하도록 할 수 있습니다. 매일 오전 9시 이전에 예상되는 수요에 맞게 새 세션을 여러 개 만들 수 있습니다.

또 다른 상황은 Spanner를 사용하지만 그 과정에서 연결 오버헤드를 방지해야 하는 경우입니다. 연결 오버헤드를 방지하기 위해 일련의 세션을 유지할 수 있습니다.

클라이언트 라이브러리 사용자로부터 세션 세부정보 숨기기

클라이언트 라이브러리를 만드는 경우, 세션을 클라이언트 라이브러리 소비자에게 노출시키지 마세요. 복잡하지 않고 세션을 만들고 유지하는 클라이언트가 데이터베이스를 호출할 수 있도록 합니다. 클라이언트 라이브러리 소비자로부터 세션 세부정보를 숨기는 클라이언트 라이브러리의 예에 대해서는 자바용 Spanner 클라이언트 라이브러리를 참조하세요.

Idempotent가 아닌 쓰기 트랜잭션 오류 처리

재생 보호 기능이 없는 쓰기 트랜잭션은 두 번 이상 변형을 적용할 수 있습니다. 변형이 idempotent가 아닌 경우 변형이 두 번 이상 적용되면 오류가 발생할 수 있습니다. 예를 들어 쓰기 시도 전에 행이 존재하지 않았더라도 ALREADY_EXISTS 오류가 발생하면서 삽입이 실패할 수 있습니다. 백엔드 서버가 변형을 커밋했지만 이를 클라이언트에 알릴 수 없는 경우에 이러한 상황이 발생할 수 있습니다. 이런 경우 변형이 재시도되어 ALREADY_EXISTS 오류가 발생할 수 있습니다.

자체 클라이언트 라이브러리를 구현하거나 REST API를 사용하는 경우 이 상황을 해결할 수 있는 방법은 다음과 같습니다.

  • Idempotent가 되도록 쓰기를 구조화합니다.
  • 재생 보호 기능이 있는 쓰기를 사용합니다.
  • 'upsert' 논리를 수행하는 메서드를 구현합니다. 즉, 새 항목인 경우에는 insert이고 기존 항목인 경우에는 update입니다.
  • 클라이언트를 대신하여 오류를 처리합니다.

안정적 연결 유지

최상의 성능을 위해서는 세션을 호스팅하는 데 사용하는 연결이 안정적이어야 합니다. 세션을 호스팅하는 연결이 변경되면 Spanner는 세션의 활성 트랜잭션을 취소할 수 있으며 Spanner가 세션 메타데이터를 업데이트하는 동안 데이터베이스에 약간의 추가 부하가 발생합니다. 소수의 연결이 산발적으로 변경되는 것은 무방하지만 동시에 다수의 연결이 변경될 수 있는 상황은 피해야 합니다. 클라이언트와 Spanner 사이에서 프록시를 사용하는 경우, 각 세션의 연결 안정성을 유지해야 합니다.

활성 세션 모니터링

명령줄, REST API 또는 RPC API에서 ListSessions 명령어를 사용하여 데이터베이스의 활성 세션을 모니터링할 수 있습니다. ListSessions는 지정된 데이터베이스의 활성 세션을 표시합니다. 이 명령어는 세션 유출의 원인을 찾아야 하는 경우에 유용합니다. 세션 유출은 세션이 생성되지만 재사용을 위해 세션 풀로 반환되지 않는 문제입니다.

ListSessions를 사용하면 세션이 생성된 시기와 세션이 마지막으로 사용된 시기를 포함하여 활성 세션에 대한 메타데이터를 확인할 수 있습니다. 이 데이터를 분석하면 세션 문제를 올바르게 해결할 수 있습니다. 대부분의 활성 세션에 최근 approximate_last_use_time이 없으면 애플리케이션에서 세션이 제대로 재사용되지 않는 것일 수 있습니다. approximate_last_use_time 필드에 대한 자세한 내용은 RPC API 참조를 확인하세요.

ListSessions 사용에 대한 자세한 내용은 REST API 참조, RPC API 참조 또는 gcloud 명령줄 도구 참조를 확인하세요.

세션 유출 자동 삭제

세션 풀의 모든 세션을 사용할 때는 각 새 트랜잭션이 세션이 풀로 반환될 때까지 기다립니다. 세션이 생성되지만 재사용을 위해 세션 풀로 반환되지 않는 경우, 이를 세션 유출이라고 합니다. 세션 유출이 발생하면 열린 세션을 기다리는 트랜잭션이 무기한 막히고 애플리케이션이 차단됩니다. 세션 유출은 종종 매우 오랜 시간 실행되면서 커밋되지 않은 문제적 트랜잭션으로 인해 발생합니다.

이러한 비활성 트랜잭션을 자동으로 해결하도록 세션 풀을 설정할 수 있습니다. 비활성 라이브러리를 자동으로 해결하도록 클라이언트 라이브러리를 사용 설정하면 세션 유출을 일으킬 수 있는 문제적 트랜잭션을 식별하고, 이를 세션 풀에서 삭제하고, 이를 새 세션으로 바꿉니다.

로깅은 문제적 트랜잭션을 식별하는 데도 도움이 됩니다. 로깅이 사용 설정되면 세션 풀의 95% 이상이 사용될 때 기본적으로 경고 로그가 공유됩니다. 세션 사용량이 95%를 초과하면 세션 풀에서 허용되는 최대 세션을 늘리거나 세션 유출이 발생할 수 있습니다. 경고 로그에는 예상보다 오래 실행되는 트랜잭션의 스택 트레이스가 포함되어 있으며 세션 풀 사용률이 높은 원인을 식별하는 데 도움이 될 수 있습니다. 경고 로그는 로그 내보내기 구성에 따라 푸시됩니다.

비활성 트랜잭션을 자동으로 해결하도록 클라이언트 라이브러리 사용 설정

클라이언트 라이브러리가 경고 로그를 전송하고 비활성 트랜잭션을 자동으로 해결하도록 설정하거나, 클라이언트 라이브러리가 경고 로그만 수신하도록 사용 설정할 수 있습니다.

Java

경고 로그를 수신하고 비활성 트랜잭션을 삭제하려면 setWarnAndCloseIfInactiveTransactions를 사용합니다.

 final SessionPoolOptions sessionPoolOptions = SessionPoolOptions.newBuilder().setWarnAndCloseIfInactiveTransactions().build()

 final Spanner spanner =
         SpannerOptions.newBuilder()
             .setSessionPoolOption(sessionPoolOptions)
             .build()
             .getService();
 final DatabaseClient client = spanner.getDatabaseClient(databaseId);

경고 로그만 수신하려면 setWarnIfInactiveTransactions를 사용합니다.

 final SessionPoolOptions sessionPoolOptions = SessionPoolOptions.newBuilder().setWarnIfInactiveTransactions().build()

 final Spanner spanner =
         SpannerOptions.newBuilder()
             .setSessionPoolOption(sessionPoolOptions)
             .build()
             .getService();
 final DatabaseClient client = spanner.getDatabaseClient(databaseId);

Go

경고 로그를 수신하고 비활성 트랜잭션을 삭제하려면 InactiveTransactionRemovalOptions와 함께 SessionPoolConfig를 사용합니다.

 client, err := spanner.NewClientWithConfig(
     ctx, database, spanner.ClientConfig{SessionPoolConfig: spanner.SessionPoolConfig{
         InactiveTransactionRemovalOptions: spanner.InactiveTransactionRemovalOptions{
         ActionOnInactiveTransaction: spanner.WarnAndClose,
         }
     }},
 )
 if err != nil {
     return err
 }
 defer client.Close()

경고 로그만 수신하려면 customLogger를 사용합니다.

 customLogger := log.New(os.Stdout, "spanner-client: ", log.Lshortfile)
 // Create a logger instance using the golang log package
 cfg := spanner.ClientConfig{
         Logger: customLogger,
     }
 client, err := spanner.NewClientWithConfig(ctx, db, cfg)