

# Lock:tuple
<a name="wait-event.locktuple"></a>

`Lock:tuple` 이벤트는 백엔드 프로세스가 튜플에 대한 잠금 획득을 대기 중일 때 발생합니다.

**Topics**
+ [지원되는 엔진 버전](#wait-event.locktuple.context.supported)
+ [컨텍스트](#wait-event.locktuple.context)
+ [대기 증가의 가능한 원인](#wait-event.locktuple.causes)
+ [작업](#wait-event.locktuple.actions)

## 지원되는 엔진 버전
<a name="wait-event.locktuple.context.supported"></a>

이 대기 이벤트 정보는 모든 RDS for PostgreSQL 버전에서 지원됩니다.

## 컨텍스트
<a name="wait-event.locktuple.context"></a>

이벤트 `Lock:tuple`은 다른 백엔드가 동일한 튜플에서 충돌하는 잠금을 보유하는 동안 백엔드가 튜플에 대한 잠금을 얻기 위해 대기 중임을 나타냅니다. 다음 테이블에서는 세션이 `Lock:tuple` 이벤트를 생성하는 시나리오를 가정합니다.


|  Time  |  세션 1  |  세션 2  |  세션 3  | 
| --- | --- | --- | --- | 
|  t1  |  트랜잭션을 시작합니다.  |    |    | 
|  t2  |  1행을 업데이트합니다.  |    |    | 
|  t3  |    |  1행을 업데이트합니다. 세션은 튜플에 대한 배타적 잠금을 획득한 다음 세션 1이 커밋하거나 롤백하여 잠금을 해제할 때까지 기다립니다.  |    | 
|  t4  |    |    |  1행을 업데이트합니다. 세션은 세션 2가 튜플에서 배타적 잠금을 해제할 때까지 기다립니다.  | 

또는 벤치마킹 도구 `pgbench`를 사용하여 이 대기 이벤트를 시뮬레이션할 수 있습니다. 테이블의 동일한 행을 사용자 정의 SQL 파일로 업데이트하도록 많은 수의 동시 세션을 구성합니다.

충돌하는 잠금 모드에 대한 자세한 내용은 PostgreSQL 설명서의 [명시적 잠금](https://www.postgresql.org/docs/current/explicit-locking.html)을 참조하세요. `pgbench`에 관한 더 자세한 내용은 PostgreSQL 설명서의 [pgbench](https://www.postgresql.org/docs/current/pgbench.html) 섹션을 참조하세요.

## 대기 증가의 가능한 원인
<a name="wait-event.locktuple.causes"></a>

이 이벤트가 정상보다 많이 발생해 성능 문제를 일으킬 수 있는 경우 일반적인 원인은 다음과 같습니다.
+ 많은 수의 동시 세션이 `UPDATE` 또는 `DELETE` 문을 실행하여 동일한 튜플에 대해 충돌하는 잠금을 얻으려고 시도합니다.
+ 높은 동시 세션이 `FOR UPDATE` 또는 `FOR NO KEY UPDATE` 잠금 모드를 통해 `SELECT` 문을 실행하고 있습니다.
+ 다양한 요인으로 인해 애플리케이션이나 연결 풀이 더 많은 세션을 열어 동일한 작업을 실행할 수 있습니다. 새 세션이 동일한 행을 수정하려고 하면 DB 로드가 급증할 수 있으며, `Lock:tuple`이 표시될 수 있습니다.

자세한 내용은 PostgreSQL 설명서의 [행 수준 잠금](https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-ROWS)을 참조하세요.

## 작업
<a name="wait-event.locktuple.actions"></a>

대기 이벤트의 원인에 따라 다른 작업을 권장합니다.

**Topics**
+ [애플리케이션 로직 조사](#wait-event.locktuple.actions.problem)
+ [차단 세션 찾기](#wait-event.locktuple.actions.find-blocker)
+ [높은 동시성 저감](#wait-event.locktuple.actions.concurrency)
+ [병목 현상 해결](#wait-event.locktuple.actions.bottlenecks)

### 애플리케이션 로직 조사
<a name="wait-event.locktuple.actions.problem"></a>

차단 세션이 오랜 시간 동안 `idle in transaction` 상태인지 확인하세요. 그렇다면 차단 세션을 단기 솔루션으로 종료하는 것이 좋습니다. `pg_terminate_backend` 함수를 사용할 수 있습니다. 이 함수에 대한 자세한 내용은 PostgreSQL 설명서의 [서버 신호 전송 함수](https://www.postgresql.org/docs/13/functions-admin.html#FUNCTIONS-ADMIN-SIGNAL)를 참조하세요.

장기 솔루션의 경우 다음을 수행합니다.
+ 애플리케이션 로직을 조정합니다.
+ `idle_in_transaction_session_timeout` 파라미터를 사용합니다. 이 파라미터는 지정된 시간보다 오랫동안 유휴 상태인 열린 트랜잭션으로 세션을 종료합니다. 자세한 내용은 PostgreSQL 설명서의 [클라이언트 인증](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-IDLE-IN-TRANSACTION-SESSION-TIMEOUT)을 참조하세요.
+ autocommit을 최대한 많이 사용합니다. 자세한 내용은 PostgreSQL 설명서의 [AUTOCOMMIT 설정](https://www.postgresql.org/docs/current/ecpg-sql-set-autocommit.html)을 참조하세요.

### 차단 세션 찾기
<a name="wait-event.locktuple.actions.find-blocker"></a>

`Lock:tuple` 대기 이벤트가 발생하는 동안 어떤 잠금이 서로 의존하는지 파악하여 차단 및 차단된 세션을 식별합니다. 자세한 내용은 PostgreSQL 위키의 [잠금 종속성 정보](https://wiki.postgresql.org/wiki/Lock_dependency_information)를 참조하세요.

다음 예에서는 `tuple` 필터링과 `wait_time` 정렬을 통해 모든 세션을 쿼리합니다.

```
SELECT blocked_locks.pid AS blocked_pid,
         blocking_locks.pid AS blocking_pid,
         blocked_activity.usename AS blocked_user,
         blocking_activity.usename AS blocking_user,
         now() - blocked_activity.xact_start AS blocked_transaction_duration,
         now() - blocking_activity.xact_start AS blocking_transaction_duration,
         concat(blocked_activity.wait_event_type,':',blocked_activity.wait_event) AS blocked_wait_event,
         concat(blocking_activity.wait_event_type,':',blocking_activity.wait_event) AS blocking_wait_event,
         blocked_activity.state AS blocked_state,
         blocking_activity.state AS blocking_state,
         blocked_locks.locktype AS blocked_locktype,
         blocking_locks.locktype AS blocking_locktype,
         blocked_activity.query AS blocked_statement,
         blocking_activity.query AS blocking_statement
    FROM pg_catalog.pg_locks blocked_locks
    JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
    JOIN pg_catalog.pg_locks blocking_locks
        ON blocking_locks.locktype = blocked_locks.locktype
        AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
        AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
        AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
        AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
        AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
        AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
        AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
        AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
        AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
        AND blocking_locks.pid != blocked_locks.pid
    JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
    WHERE NOT blocked_locks.GRANTED;
```

### 높은 동시성 저감
<a name="wait-event.locktuple.actions.concurrency"></a>

`Lock:tuple` 이벤트는 지속적으로 발생할 수 있으며, 특히 작업 부하가 많은 시간에 발생할 수 있습니다. 이 경우 매우 바쁜 행에 대해 높은 동시성을 줄이는 것이 좋습니다. 종종 몇 개의 행만 대기열이나 불리언 로직을 제어하므로 이러한 행을 매우 바쁘게 만듭니다.

비즈니스 요구 사항, 애플리케이션 로직 및 워크로드 유형에 따라 다양한 접근 방식을 사용하여 동시성을 줄일 수 있습니다. 예를 들면, 다음을 수행할 수 있습니다.
+ 테이블 및 데이터 로직을 재설계하여 높은 동시성을 줄입니다.
+ 행 수준에서 높은 동시성을 줄이기 위해 애플리케이션 로직을 변경합니다.
+ 행 수준 잠금으로 쿼리를 활용하고 재설계합니다.
+ `NOWAIT` 절을 사용해 연산을 재시도합니다.
+ 낙관적 및 하이브리드 잠금 로직 동시성 제어를 사용하는 것이 좋습니다.
+ 데이터베이스 격리 수준을 변경하는 것이 좋습니다.

### 병목 현상 해결
<a name="wait-event.locktuple.actions.bottlenecks"></a>

`Lock:tuple`은 CPU 부족 또는 Amazon EBS 대역폭의 최대 사용량과 같은 병목 현상이 발생시킬 수 있습니다. 병목 현상을 줄이려면 다음 방법을 고려하세요.
+ 인스턴스 클래스 유형을 확장하세요.
+ 리소스 집약적인 쿼리를 최적화하세요.
+ 애플리케이션 로직을 변경하세요.
+ 거의 액세스하지 않는 데이터를 아카이브하세요.