

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# Amazon QLDB에서 데이터 및 기록 작업
<a name="working-with-data"></a>

**중요**  
지원 종료 알림: 기존 고객은 07/31/2025에 지원이 종료될 때까지 Amazon QLDB를 사용할 수 있습니다. 자세한 내용은 [Amazon QLDB 원장을 Amazon Aurora PostgreSQL로 마이그레이션](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)을 참조하세요.

다음 항목에서는 CRUD(*생성/읽기/업데이트/삭제*) 명령문의 기본 예제를 제공합니다. [QLDB 콘솔](console_QLDB.md)의 *PartiQL 편집기* 또는 [QLDB 쉘](data-shell.md)을 사용하여 이러한 명령문을 수동으로 실행할 수 있습니다. 또한 이 가이드는 원장을 변경할 때 QLDB가 데이터를 처리하는 프로세스를 안내합니다.

QLDB는 [PartiQL](https://partiql.org/) 쿼리 언어를 지원합니다.

QLDB 드라이버를 사용하여 유사한 명령문을 프로그래밍 방식으로 실행하는 방법을 보여주는 코드 예제는 [드라이버 시작하기](getting-started-driver.md)의 자습서를 참조하세요.

**작은 정보**  
다음은 QLDB에서 PartiQL을 사용하기 위한 팁과 모범 사례를 간략하게 요약한 것입니다.  
**동시성 및 트랜잭션 제한 이해** - `SELECT` 쿼리를 포함한 모든 명령문은 30초의 트랜잭션 시간 제한을 포함하여 [OCC(Optimistic Concurrency Control)](concurrency.md) 충돌 및 [트랜잭션 제한](limits.md#limits.fixed)의 영향을 받습니다.
**인덱스 사용** - 카디널리티가 높은 인덱스를 사용하고 대상 쿼리를 실행하여 명령문을 최적화하고 전체 테이블 스캔을 방지합니다. 자세한 내용은 [쿼리 성능 최적화](working.optimize.md) 섹션을 참조하세요.
**동등 조건자 사용** - 인덱싱된 조회에는 *동등* 연산자(`=` 또는 `IN`)가 필요합니다. 부등 연산자(`<`, `>`, `LIKE`, `BETWEEN`)는 인덱싱된 조회에 적합하지 않으며 테이블 전체를 스캔해야 합니다.
**내부 조인만 사용** - QLDB는 내부 조인만 지원합니다. 가장 좋은 방법은 조인하려는 각 테이블에 대해 인덱싱된 필드를 조인하는 것입니다. 조인 기준과 동등 조건자 모두에 대해 높은 카디널리티 인덱스를 선택하세요.

**Topics**
+ [인덱스가 포함된 테이블 생성 및 문서 삽입](working.create.md)
+ [데이터 쿼리](working.userdata.md)
+ [문서 메타데이터 쿼리](working.metadata.md)
+ [BY 절을 사용하여 문서 ID 쿼리하기](working.metadata.by-clause.md)
+ [문서 업데이트 및 삭제](working.revisions.md)
+ [개정 기록 쿼리](working.history.md)
+ [문서 개정본 수정하기](working.redaction.md)
+ [쿼리 성능 최적화](working.optimize.md)
+ [PartiQL 문 통계 가져오기](working.statement-stats.md)
+ [시스템 카탈로그 쿼리](working.catalog.md)
+ [테이블 관리](working.manage-tables.md)
+ [인덱스 관리](working.manage-indexes.md)
+ [Amazon QLDB의 고유 ID](working.unique-id.md)

# 인덱스가 포함된 테이블 생성 및 문서 삽입
<a name="working.create"></a>

**중요**  
지원 종료 알림: 기존 고객은 07/31/2025에 지원이 종료될 때까지 Amazon QLDB를 사용할 수 있습니다. 자세한 내용은 [Amazon QLDB 원장을 Amazon Aurora PostgreSQL로 마이그레이션](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)을 참조하세요.

Amazon QLDB 원장을 생성한 후 첫 번째 단계는 기본 [CREATE TABLE](ql-reference.create-table.md) 명령문이 포함된 테이블을 생성하는 것입니다. 테이블은 [Amazon Ion](ion.md) `struct`의 데이터 세트인 [QLDB 문서](ql-reference.docs.md)로 구성됩니다.

**Topics**
+ [테이블 및 인덱스 생성](#working.create.tables-indexes)
+ [문서 삽입하기](#working.create.insert)

## 테이블 및 인덱스 생성
<a name="working.create.tables-indexes"></a>

테이블은 네임스페이스 없이 대소문자를 구분하는 간단한 이름을 사용합니다. QLDB는 개방형 콘텐츠를 지원하며 스키마를 적용하지 않으므로 테이블을 만들 때 속성이나 데이터 유형을 정의하지 않습니다.

```
CREATE TABLE VehicleRegistration
```

```
CREATE TABLE Vehicle
```

`CREATE TABLE` 명령문은 새 테이블의 시스템 할당 ID를 반환합니다. QLDB의 모든 [시스템 할당 ID](working.unique-id.md)는 Base62로 인코딩된 문자열로 각각 표시되는 범용 고유 식별자(UUID)입니다.

**참고**  
테이블을 생성하는 동안 선택적으로 테이블 리소스의 태그를 정의할 수 있습니다. 자세한 방법은 [테이블 생성 시 태그 지정](working.manage-tables.md#working.manage-tables.tags) 섹션을 참조하세요.

테이블에 인덱스를 생성하여 쿼리 성능을 최적화할 수도 있습니다.

```
CREATE INDEX ON VehicleRegistration (VIN)
```

```
CREATE INDEX ON VehicleRegistration (LicensePlateNumber)
```

```
CREATE INDEX ON Vehicle (VIN)
```

**중요**  
QLDB는 문서를 효율적으로 조회하기 위한 인덱스가 필요합니다. 인덱스가 없으면 QLDB는 문서를 읽을 때 전체 테이블 스캔을 수행해야 합니다. 이로 인해 동시성 충돌 및 트랜잭션 시간 초과를 포함하여 대규모 테이블에서 성능 문제가 발생할 수 있습니다.  
인덱싱된 필드 또는 문서 ID(예: `=` 또는 `IN`)에서 동등 *연산자*를 사용하여 `WHERE` 조건자 절이 포함된 문을 실행하는 것이 좋습니다. 자세한 내용은 [쿼리 성능 최적화](working.optimize.md)을 참조하세요.

인덱스를 생성할 때는 다음 제약 조건에 유의하세요.
+ 인덱스는 단일 최상위 필드에만 생성할 수 있습니다. 복합, 중첩, 고유 및 함수 기반 인덱스는 지원되지 않습니다.
+ `list` 및 `struct`를 비롯한 모든 [Ion 데이터 유형](ql-reference.data-types.md)에 대해 인덱스를 생성할 수 있습니다. 그러나 Ion 유형에 관계없이 전체 Ion 값이 같아야만 인덱스 조회를 수행할 수 있습니다. 예를 들어 `list` 형식을 인덱스로 사용하는 경우 목록 내에서 한 항목씩 인덱스 검색을 수행할 수 없습니다.
+ 동등 조건자(예: `WHERE indexedField = 123` 또는 `WHERE indexedField IN (456, 789)`)를 사용할 때만 쿼리 성능이 향상됩니다.

  QLDB는 쿼리 조건자의 부등을 인정하지 않습니다. 따라서 범위 필터링된 스캔은 구현되지 않습니다.
+ 인덱싱된 필드 이름은 대소문자를 구분하며 최대 128자입니다.
+ QLDB에서의 인덱스 생성은 비동기적으로 이루어집니다. 비어 있지 않은 테이블에서 인덱스 빌드를 완료하는 데 걸리는 시간은 테이블 크기에 따라 다릅니다. 자세한 내용은 [인덱스 관리](working.manage-indexes.md)을 참조하세요.

## 문서 삽입하기
<a name="working.create.insert"></a>

그러면 테이블에 문서를 삽입할 수 있습니다. QLDB 문서는 Amazon Ion 형식으로 저장됩니다. 다음 PartiQL [INSERT](ql-reference.insert.md) 명령문에는 [Amazon QLDB 콘솔 시작하기](getting-started.md)에서 사용된 차량 등록 샘플 데이터의 하위 집합이 포함됩니다.

```
INSERT INTO VehicleRegistration
<< {
    'VIN' : '1N4AL11D75C109151',
    'LicensePlateNumber' : 'LEWISR261LL',
    'State' : 'WA',
    'City' : 'Seattle',
    'PendingPenaltyTicketAmount' : 90.25,
    'ValidFromDate' : `2017-08-21T`,
    'ValidToDate' : `2020-05-11T`,
    'Owners' : {
        'PrimaryOwner' : { 'PersonId' : '294jJ3YUoH1IEEm8GSabOs' },
        'SecondaryOwners' : [ { 'PersonId' : '5Ufgdlnj06gF5CWcOIu64s' } ]
    }
},
{
    'VIN' : 'KM8SRDHF6EU074761',
    'LicensePlateNumber' : 'CA762X',
    'State' : 'WA',
    'City' : 'Kent',
    'PendingPenaltyTicketAmount' : 130.75,
    'ValidFromDate' : `2017-09-14T`,
    'ValidToDate' : `2020-06-25T`,
    'Owners' : {
        'PrimaryOwner' : { 'PersonId': 'IN7MvYtUjkp1GMZu0F6CG9' },
        'SecondaryOwners' : []
    }
} >>
```

```
INSERT INTO Vehicle
<< {
    'VIN' : '1N4AL11D75C109151',
    'Type' : 'Sedan',
    'Year' : 2011,
    'Make' : 'Audi',
    'Model' : 'A5',
    'Color' : 'Silver'
} ,
{
    'VIN' : 'KM8SRDHF6EU074761',
    'Type' : 'Sedan',
    'Year' : 2015,
    'Make' : 'Tesla',
    'Model' : 'Model S',
    'Color' : 'Blue'
} >>
```

**PartiQL 구문 및 시맨틱**
+ 필드 이름은 작은 따옴표로 묶습니다(`'...'`).
+ 문자열 값도 작은 따옴표로 묶습니다(`'...'`).
+ 타임스탬프는 백틱으로 묶습니다(``...``). 백틱은 모든 Ion 리터럴을 나타내는 데 사용할 수 있습니다.
+ 정수와 소수는 표시할 필요가 없는 리터럴 값입니다.

PartiQL의 구문 및 시맨틱에 대한 자세한 내용은 [Amazon QLDB에서 PartiQL을 사용한 Ion 쿼리](ql-reference.query.md) 섹션을 참조하세요.

`INSERT` 명령문은 버전 번호가 0인 문서의 초기 개정을 생성합니다. 각 문서를 고유하게 식별하기 위해 QLDB는 *문서 ID*를 메타데이터의 일부로 할당합니다. Insert 문은 삽입된 각 문서의 ID를 반환합니다.

**중요**  
QLDB는 스키마를 적용하지 않으므로 동일한 문서를 테이블에 여러 번 삽입할 수 있습니다. 각 Insert 명령문은 저널에 별도의 문서 항목을 커밋하고 QLDB는 각 문서에 고유한 ID를 할당합니다.

테이블에 삽입한 문서를 쿼리하는 방법을 알아보려면 [데이터 쿼리](working.userdata.md)로 이동합니다.

# 데이터 쿼리
<a name="working.userdata"></a>

**중요**  
지원 종료 알림: 기존 고객은 07/31/2025에 지원이 종료될 때까지 Amazon QLDB를 사용할 수 있습니다. 자세한 내용은 [Amazon QLDB 원장을 Amazon Aurora PostgreSQL로 마이그레이션](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)을 참조하세요.

*사용자 뷰*는 사용자 데이터의 삭제되지 않은 최신 버전만 반환합니다. 이는 Amazon QLDB의 기본 뷰입니다. 즉, 데이터만 쿼리하려는 경우에는 특별한 한정자가 필요하지 않습니다.

다음 쿼리 예제의 구문 및 파라미터에 대한 자세한 내용은 *Amazon QLDB PartiQL 참조*의 [SELECT](ql-reference.select.md) 섹션을 참조하세요.

**Topics**
+ [기본 쿼리](#working.userdata.basic)
+ [프로젝션 및 필터](#working.userdata.projections-filters)
+ [조인](#working.userdata.joins)
+ [중첩된 데이터](#working.userdata.nested)

## 기본 쿼리
<a name="working.userdata.basic"></a>

기본 `SELECT` 쿼리는 테이블에 삽입한 문서를 반환합니다.

**주의**  
인덱싱된 조회 없이 QLDB에서 쿼리를 실행하면 전체 테이블 스캔이 호출됩니다. PartiQL은 SQL과 호환되므로 이러한 쿼리를 지원합니다. 하지만 QLDB의 프로덕션 사용 사례에 대해서는 테이블 스캔을 실행하지 *마세요.*. 테이블 스캔은 동시성 충돌 및 트랜잭션 시간 초과를 포함하여 대규모 테이블에서 성능 문제를 일으킬 수 있습니다.  
인덱싱된 필드 또는 문서 ID(예: `WHERE indexedField = 123` 또는 `WHERE indexedField IN (456, 789)`)에서 동등 *연산자*를 사용하여 `WHERE` 조건자 절이 포함된 문을 실행하는 것이 좋습니다. 자세한 내용은 [쿼리 성능 최적화](working.optimize.md)을 참조하세요.

다음 쿼리는 이전에 [인덱스가 포함된 테이블 생성 및 문서 삽입](working.create.md)에 삽입한 차량 등록 문서에 대한 결과를 보여줍니다. 결과 순서는 구체적이지 않으며 각 `SELECT` 쿼리마다 다를 수 있습니다. QLDB의 모든 쿼리에 대해 결과 순서에 의존해서는 안 됩니다.

```
SELECT * FROM VehicleRegistration
WHERE LicensePlateNumber IN ('LEWISR261LL', 'CA762X')
```

```
{
    VIN: "1N4AL11D75C109151",
    LicensePlateNumber: "LEWISR261LL",
    State: "WA",
    City: "Seattle",
    PendingPenaltyTicketAmount: 90.25,
    ValidFromDate: 2017-08-21T,
    ValidToDate: 2020-05-11T,
    Owners: {
        PrimaryOwner: { PersonId: "294jJ3YUoH1IEEm8GSabOs" },
        SecondaryOwners: [{ PersonId: "5Ufgdlnj06gF5CWcOIu64s" }]
    }
},
{
    VIN: "KM8SRDHF6EU074761",
    LicensePlateNumber: "CA762X",
    State: "WA",
    City: "Kent",
    PendingPenaltyTicketAmount: 130.75,
    ValidFromDate: 2017-09-14T,
    ValidToDate: 2020-06-25T,
    Owners: {
        PrimaryOwner: { PersonId: "IN7MvYtUjkp1GMZu0F6CG9" },
        SecondaryOwners: []
    }
}
```

```
SELECT * FROM Vehicle
WHERE VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
```

```
{
    VIN: "1N4AL11D75C109151",
    Type: "Sedan",
    Year: 2011,
    Make: "Audi",
    Model: "A5",
    Color: "Silver"
},
{
    VIN: "KM8SRDHF6EU074761",
    Type: "Sedan",
    Year: 2015,
    Make: "Tesla",
    Model: "Model S",
    Color: "Blue"
}
```

**중요**  
PartiQL에서는 작은따옴표를 사용하여 DML(데이터 조작 언어) 또는 쿼리문의 문자열을 나타냅니다. 하지만 QLDB 콘솔과 QLDB 쉘은 Amazon Ion 텍스트 형식으로 쿼리 결과를 반환하므로 문자열이 큰따옴표로 묶여 있습니다.  
이 구문을 사용하면 PartiQL 쿼리 언어가 SQL 호환성을 유지하고 Amazon Ion 텍스트 형식이 JSON 호환성을 유지할 수 있습니다.

## 프로젝션 및 필터
<a name="working.userdata.projections-filters"></a>

프로젝션(대상 `SELECT`) 및 기타 표준 필터(`WHERE` 절)를 수행할 수 있습니다. 다음 쿼리는 `VehicleRegistration` 테이블에 있는 문서 필드의 하위 집합을 반환합니다. 이는 다음 기준에 따라 차량을 필터링합니다.
+ **문자열 필터** - 시애틀에 등록되어 있습니다.
+ **십진수 필터** - 보류 중인 페널티 티켓 금액이 `100.0`보다 적습니다.
+ **날짜 필터** - 2019년 9월 4일 이후에 유효한 등록일을 갖습니다.

```
SELECT r.VIN, r.PendingPenaltyTicketAmount, r.Owners
FROM VehicleRegistration AS r
WHERE r.VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
AND r.City = 'Seattle' --string
AND r.PendingPenaltyTicketAmount < 100.0 --decimal
AND r.ValidToDate >= `2019-09-04T` --timestamp with day precision
```

```
{
    VIN: "1N4AL11D75C109151",
    PendingPenaltyTicketAmount: 90.25,
    Owners: {
        PrimaryOwner: { PersonId: "294jJ3YUoH1IEEm8GSabOs" },
        SecondaryOwners: [{ PersonId: "5Ufgdlnj06gF5CWcOIu64s" }]
    }
}
```

## 조인
<a name="working.userdata.joins"></a>

내부 조인 쿼리를 작성할 수도 있습니다. 다음 예제는 등록된 차량의 속성과 함께 모든 등록 문서를 반환하는 암시적 내부 조인 쿼리를 보여줍니다.

```
SELECT * FROM VehicleRegistration AS r, Vehicle AS v
WHERE r.VIN = v.VIN
AND r.VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
```

```
{
    VIN: "1N4AL11D75C109151",
    LicensePlateNumber: "LEWISR261LL",
    State: "WA",
    City: "Seattle",
    PendingPenaltyTicketAmount: 90.25,
    ValidFromDate: 2017-08-21T,
    ValidToDate: 2020-05-11T,
    Owners: {
        PrimaryOwner: { PersonId: "294jJ3YUoH1IEEm8GSabOs" },
        SecondaryOwners: [{ PersonId: "5Ufgdlnj06gF5CWcOIu64s" }]
    },
    Type: "Sedan",
    Year: 2011,
    Make: "Audi",
    Model: "A5",
    Color: "Silver"
},
{
    VIN: "KM8SRDHF6EU074761",
    LicensePlateNumber: "CA762X",
    State: "WA",
    City: "Kent",
    PendingPenaltyTicketAmount: 130.75,
    ValidFromDate: 2017-09-14T,
    ValidToDate: 2020-06-25T,
    Owners: {
        PrimaryOwner: { PersonId: "IN7MvYtUjkp1GMZu0F6CG9" },
        SecondaryOwners: []
    },
    Type: "Sedan",
    Year: 2015,
    Make: "Tesla",
    Model: "Model S",
    Color: "Blue"
}
```

또는 다음과 같이 명시적 구문으로 동일한 내부 조인 쿼리를 작성할 수 있습니다.

```
SELECT * FROM VehicleRegistration AS r INNER JOIN Vehicle AS v
ON r.VIN = v.VIN
WHERE r.VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
```

## 중첩된 데이터
<a name="working.userdata.nested"></a>

QLDB의 PartiQL을 사용하여 문서의 중첩된 데이터를 쿼리할 수 있습니다. 다음 예제는 중첩된 데이터를 평면화하는 상관 하위 쿼리를 보여줍니다. 여기서 이 `@` 문자는 엄밀히 따지자면 선택 사항입니다. 하지만 이는 `Owners`이라는 다른 컬렉션(있는 경우)이 아니라 `VehicleRegistration` 안에 `Owners` 구조를 적용하고자 한다는 것을 명시적으로 나타냅니다.

```
SELECT 
    r.VIN, 
    o.SecondaryOwners
FROM
    VehicleRegistration AS r, @r.Owners AS o
WHERE
    r.VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
```

```
{
    VIN: "1N4AL11D75C109151",
    SecondaryOwners: [{ PersonId: "5Ufgdlnj06gF5CWcOIu64s" }]
},
{
    VIN: "KM8SRDHF6EU074761",
    SecondaryOwners: []
}
```

다음은 내부 조인 외에도 중첩된 데이터를 프로젝션하는 `SELECT` 목록의 하위 쿼리를 보여줍니다.

```
SELECT 
    v.Make, 
    v.Model, 
    (SELECT VALUE o.PrimaryOwner.PersonId FROM @r.Owners AS o) AS PrimaryOwner
FROM 
    VehicleRegistration AS r, Vehicle AS v
WHERE 
    r.VIN = v.VIN AND r.VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
```

```
{
    Make: "Audi",
    Model: "A5",
    PrimaryOwner: ["294jJ3YUoH1IEEm8GSabOs"]
},
{
    Make: "Tesla",
    Model: "Model S",
    PrimaryOwner: ["IN7MvYtUjkp1GMZu0F6CG9"]
}
```

다음 쿼리는 `VehicleRegistration` 문서에 대해 `Owners.SecondaryOwners` 목록에 있는 각 사람의 `PersonId` 및 인덱스(서수) 번호를 반환합니다.

```
SELECT s.PersonId, owner_idx
FROM VehicleRegistration AS r, @r.Owners.SecondaryOwners AS s AT owner_idx
WHERE r.VIN = '1N4AL11D75C109151'
```

```
{
    PersonId: "5Ufgdlnj06gF5CWcOIu64s",
    owner_idx: 0
}
```

문서 메타데이터를 쿼리하는 방법을 알아보려면 [문서 메타데이터 쿼리](working.metadata.md) 섹션으로 이동하세요.

# 문서 메타데이터 쿼리
<a name="working.metadata"></a>

**중요**  
지원 종료 알림: 기존 고객은 07/31/2025에 지원이 종료될 때까지 Amazon QLDB를 사용할 수 있습니다. 자세한 내용은 [Amazon QLDB 원장을 Amazon Aurora PostgreSQL로 마이그레이션](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)을 참조하세요.

`INSERT` 명령문은 버전 번호가 0인 문서의 초기 개정을 생성합니다. 각 문서를 고유하게 식별하기 위해 Amazon QLDB는 메타데이터의 일부로 *문서ID*를 할당합니다.

QLDB는 문서 ID 및 버전 번호 외에도 각 문서에 대해 시스템에서 생성한 다른 메타데이터를 테이블에 저장합니다. 이 메타데이터에는 트랜잭션 정보, 저널 속성 및 문서의 해시 값이 포함됩니다.

시스템에서 할당한 모든 ID는 Base62로 인코딩된 문자열로 각각 표시되는 범용 고유 식별자(UUID)입니다. 자세한 내용은 [Amazon QLDB의 고유 ID](working.unique-id.md)을 참조하세요.

**Topics**
+ [커밋된 뷰](#working.metadata.committed)
+ [커밋된 뷰와 사용자 뷰 조인](#working.metadata.committed-joins)

## 커밋된 뷰
<a name="working.metadata.committed"></a>

*커밋된 뷰*를 쿼리하여 문서 메타데이터에 액세스할 수 있습니다. 이 뷰는 사용자 테이블에 직접 해당하는 시스템 정의 테이블의 문서를 반환합니다. 여기에는 데이터와 시스템에서 생성한 메타데이터의 최신 커밋되고 삭제되지 않은 개정이 포함됩니다. 이 뷰를 쿼리하려면 쿼리의 테이블 이름에 접두사 `_ql_committed_`를 추가합니다. (접두사 `_ql_`는 QLDB에서 시스템 객체용으로 예약되어 있습니다.)

```
SELECT * FROM _ql_committed_VehicleRegistration AS r
WHERE r.data.VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
```

이전에 [인덱스가 포함된 테이블 생성 및 문서 삽입](working.create.md)에 삽입한 데이터를 사용하여 이 쿼리의 출력에는 삭제되지 않은 각 문서의 최신 개정 버전에 대한 시스템 내용이 표시됩니다. 시스템 문서의 메타데이터는 `metadata` 필드에 중첩되어 있고 사용자 데이터는 `data` 필드에 중첩되어 있습니다.

```
{
    blockAddress:{
        strandId:"JdxjkR9bSYB5jMHWcI464T",
        sequenceNo:14
    },
    hash:{{wCsmM6qD4STxz0WYmE+47nZvWtcCz9D6zNtCiM5GoWg=}},
    data:{
        VIN: "1N4AL11D75C109151",
        LicensePlateNumber: "LEWISR261LL",
        State: "WA",
        City: "Seattle",
        PendingPenaltyTicketAmount: 90.25,
        ValidFromDate: 2017-08-21T,
        ValidToDate: 2020-05-11T,
        Owners: {
            PrimaryOwner: { PersonId: "294jJ3YUoH1IEEm8GSabOs" },
            SecondaryOwners: [{ PersonId: "5Ufgdlnj06gF5CWcOIu64s" }]
        }
    },
    metadata:{
        id:"3Qv67yjXEwB9SjmvkuG6Cp",
        version:0,
        txTime:2019-06-05T20:53:321d-3Z,
        txId:"HgXAkLjAtV0HQ4lNYdzX60"
    }
},
{
    blockAddress:{
        strandId:"JdxjkR9bSYB5jMHWcI464T",
        sequenceNo:14
    },
    hash:{{wPuwH60TtcCvg/23BFp+redRXuCALkbDihkEvCX22Jk=}},
    data:{
        VIN: "KM8SRDHF6EU074761",
        LicensePlateNumber: "CA762X",
        State: "WA",
        City: "Kent",
        PendingPenaltyTicketAmount: 130.75,
        ValidFromDate: 2017-09-14T,
        ValidToDate: 2020-06-25T,
        Owners: {
            PrimaryOwner: { PersonId: "IN7MvYtUjkp1GMZu0F6CG9" },
            SecondaryOwners: []
        }
    },
    metadata:{
        id:"JOzfB3lWqGU727mpPeWyxg",
        version:0,
        txTime:2019-06-05T20:53:321d-3Z,
        txId:"HgXAkLjAtV0HQ4lNYdzX60"
    }
}
```

**커밋된 뷰 필드**
+ `blockAddress` - 문서 개정이 커밋된 원장 저널의 블록 위치입니다. 암호화 검증에 사용할 수 있는 주소에는 다음과 같은 두 개의 필드가 있습니다.
  + `strandId` - 블록을 포함하는 저널 스트랜드의 고유 ID입니다.
  + `sequenceNo` - 스트랜드 내 블록 위치를 지정하는 인덱스 번호입니다.
**참고**  
이 예제의 두 문서는 동일한 `sequenceNo`의 `blockAddress`를 가지고 있습니다. 이러한 문서는 단일 트랜잭션(이 경우에는 단일 명령문) 내에 삽입되었으므로 동일한 블록에서 커밋되었습니다.
+ `hash` - 문서 개정을 고유하게 나타내는 SHA-256 Ion 해시 값입니다. 해시는 개정판의 `data` 및 `metadata` 필드를 포함하며 [암호화 검증](verification.md)에 사용할 수 있습니다.
+ `data` - 문서의 사용자 데이터 속성입니다.

  개정을 수정하면 이 `data` 구조가 `dataHash` 필드로 대체되며 필드의 값은 제거된 `data` 구조의 Ion 해시입니다.
+ `metadata` - 문서의 메타데이터 속성입니다.
  + `id` - 시스템에서 할당한 문서의 고유 ID입니다.
  + `version` - 문서의 버전 번호입니다. 이는 0으로 시작하는 정수로, 문서가 개정될 때마다 증가합니다.
  + `txTime` - 문서 개정이 저널에 커밋된 시점의 타임스탬프입니다.
  + `txId` - 문서 개정을 커밋한 트랜잭션의 고유 ID입니다.

## 커밋된 뷰와 사용자 뷰 조인
<a name="working.metadata.committed-joins"></a>

커밋된 뷰의 테이블을 사용자 뷰의 테이블과 조인하는 쿼리를 작성할 수 있습니다. 예를 들어 한 테이블의 `id` 문서를 다른 테이블의 사용자 정의 필드와 조인하고자 할 수 있습니다.

다음 쿼리는 `PersonId` 및 문서 `id` 필드에서 각각 `DriversLicense` 및 `Person`라는 두 테이블을 조인하며, 후자에 대해 커밋된 뷰를 사용합니다.

```
SELECT * FROM DriversLicense AS d INNER JOIN _ql_committed_Person AS p
ON d.PersonId = p.metadata.id
WHERE p.metadata.id = '1CWScY2qHYI9G88C2SjvtH'
```

기본 사용자 뷰에서 문서 ID 필드를 쿼리하는 방법을 알아보려면 [BY 절을 사용하여 문서 ID 쿼리하기](working.metadata.by-clause.md)을 참조하세요.

# BY 절을 사용하여 문서 ID 쿼리하기
<a name="working.metadata.by-clause"></a>

**중요**  
지원 종료 알림: 기존 고객은 07/31/2025에 지원이 종료될 때까지 Amazon QLDB를 사용할 수 있습니다. 자세한 내용은 [Amazon QLDB 원장을 Amazon Aurora PostgreSQL로 마이그레이션](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)을 참조하세요.

고유 식별자(예: 차량의 VIN)로 의도된 필드를 정의할 수 있지만 문서의 진정한 고유 식별자는 [문서 삽입하기](working.create.md#working.create.insert)에 설명된 대로 `id` 메타데이터 필드입니다. 이러한 이유로 `id` 필드를 사용하여 테이블 간의 관계를 만들 수 있습니다.

문서 `id` 필드는 커밋된 뷰에서만 직접 액세스할 수 있지만, `BY` 절을 사용하여 기본 사용자 뷰로 프로젝션할 수도 있습니다. 예를 들어 다음 쿼리와 해당 결과를 참조하세요.

```
SELECT r_id, r.VIN, r.LicensePlateNumber, r.State, r.City, r.Owners
FROM VehicleRegistration AS r BY r_id
WHERE r_id = '3Qv67yjXEwB9SjmvkuG6Cp'
```

```
{
    r_id: "3Qv67yjXEwB9SjmvkuG6Cp",
    VIN: "1N4AL11D75C109151",
    LicensePlateNumber: "LEWISR261LL",
    State: "WA",
    City: "Seattle",
    Owners: {
        PrimaryOwner: { PersonId: "294jJ3YUoH1IEEm8GSabOs" },
        SecondaryOwners: [{ PersonId: "5Ufgdlnj06gF5CWcOIu64s" }]
    }
}
```

이 쿼리에서 `r_id`는 `FROM` 키워드를 사용하여 `BY` 절에 선언되는 사용자 정의 별칭입니다. 이 `r_id` 별칭은 쿼리 결과 집합에 있는 각 문서의 `id` 메타데이터 필드에 바인딩됩니다. 이 별칭은 `SELECT` 절에 사용할 수 있으며 *사용자 뷰*의 쿼리 `WHERE` 절에도 사용할 수 있습니다.

하지만 다른 메타데이터 속성에 액세스하려면 커밋된 뷰를 쿼리해야 합니다.

## 문서 ID에 조인하기
<a name="working.by-clause.joining-on-id"></a>

한 테이블의 문서 `id`를 다른 테이블의 사용자 정의 필드에서 외래 키로 사용하고 있다고 가정해 보겠습니다. 이 `BY` 절을 사용하여 이러한 필드에 있는 두 테이블의 내부 조인 쿼리를 작성할 수 있습니다(이전 항목의 [커밋된 뷰와 사용자 뷰 조인](working.metadata.md#working.metadata.committed-joins)과 유사).

다음 예제에서는 `PersonId` 및 문서 `id` 필드에서 각각 `DriversLicense` 및 `Person`라는 두 테이블을 조인하며, 후자에 대해 `BY` 절을 사용합니다.

```
SELECT * FROM DriversLicense AS d INNER JOIN Person AS p BY pid
ON d.PersonId = pid
WHERE pid = '1CWScY2qHYI9G88C2SjvtH'
```

테이블의 문서를 변경하는 방법을 알아보려면 [문서 업데이트 및 삭제](working.revisions.md) 섹션으로 이동하세요.

# 문서 업데이트 및 삭제
<a name="working.revisions"></a>

**중요**  
지원 종료 알림: 기존 고객은 07/31/2025에 지원이 종료될 때까지 Amazon QLDB를 사용할 수 있습니다. 자세한 내용은 [Amazon QLDB 원장을 Amazon Aurora PostgreSQL로 마이그레이션](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)을 참조하세요.

Amazon QLDB에서 *문서 개정*은 고유한 문서 ID로 식별되는 문서 시퀀스의 단일 버전을 나타내는 Amazon Ion 구조입니다. 모든 개정에는 사용자 데이터와 시스템 생성 메타데이터를 포함한 문서의 전체 데이터 세트가 포함됩니다. 각 개정은 문서 ID와 0으로 시작하는 버전 번호의 조합으로 고유하게 식별됩니다.

문서를 업데이트하면 QLDB는 동일한 문서 ID와 증가된 버전 번호를 사용하여 새 개정을 생성합니다. 테이블에서 문서를 삭제하면 문서 수명 주기가 종료됩니다. 즉, 동일한 문서 ID로 문서 개정본을 다시 만들 수 없습니다.

## 문서 개정하기
<a name="working.revisions.making"></a>

예를 들어, 다음 명령문은 새 차량 등록을 삽입하고 등록 도시를 업데이트한 다음 등록을 삭제합니다. 이로 인해 한 문서가 세 번 개정됩니다.

```
INSERT INTO VehicleRegistration
{
    'VIN' : '1HVBBAANXWH544237',
    'LicensePlateNumber' : 'LS477D',
    'State' : 'WA',
    'City' : 'Tacoma',
    'PendingPenaltyTicketAmount' : 42.20,
    'ValidFromDate' : `2011-10-26T`,
    'ValidToDate' : `2023-09-25T`,
    'Owners' : {
        'PrimaryOwner' : { 'PersonId': 'KmA3XPKKFqYCP2zhR3d0Ho' },
        'SecondaryOwners' : []
    }
}
```

**참고**  
Insert 명령문 및 기타 DML 명령문은 영향을 받는 각 문서의 ID를 반환합니다. 다음 항목의 기록 함수에 필요하므로 계속하기 전에 이 ID를 저장해 두세요. 다음 쿼리로도 문서 ID를 찾을 수 있습니다.  

```
SELECT r_id FROM VehicleRegistration AS r BY r_id
WHERE r.VIN = '1HVBBAANXWH544237'
```

```
UPDATE VehicleRegistration AS r 
SET r.City = 'Bellevue' 
WHERE r.VIN = '1HVBBAANXWH544237'
```

```
DELETE FROM VehicleRegistration AS r 
WHERE r.VIN = '1HVBBAANXWH544237'
```

이러한 DML 명령문의 구문에 대한 추가 예제와 정보는 *Amazon QLDB PartiQL 참조*의 [UPDATE](ql-reference.update.md) 및 [DELETE](ql-reference.delete.md)를 참조하세요.

문서에 특정 요소를 삽입하거나 제거하려면 `FROM` 키워드로 시작하는 `UPDATE` 명령문이나 기타 DML 문을 사용할 수 있습니다. 자세한 내용과 예제는 [FROM (INSERT, REMOVE, 또는 SET)](ql-reference.from.md) 참조를 참조하세요.

문서를 삭제한 후에는 커밋된 뷰나 사용자 뷰에서 문서를 더 이상 쿼리할 수 없습니다. 내장된 기록 함수를을 사용하여 이 문서의 수정 기록을 쿼리하는 방법을 알아보려면 [개정 기록 쿼리](working.history.md)을 참조하세요.

# 개정 기록 쿼리
<a name="working.history"></a>

**중요**  
지원 종료 알림: 기존 고객은 07/31/2025에 지원이 종료될 때까지 Amazon QLDB를 사용할 수 있습니다. 자세한 내용은 [Amazon QLDB 원장을 Amazon Aurora PostgreSQL로 마이그레이션](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)을 참조하세요.

Amazon QLDB는 모든 문서의 전체 기록을 테이블에 저장합니다. 내장된 기록 함수를 쿼리하면 [문서 업데이트 및 삭제](working.revisions.md)에서 이전에 삽입, 업데이트 및 삭제한 차량 등록 문서의 세 가지 개정본을 모두 볼 수 있습니다.

**Topics**
+ [기록 함수](#working.history.function)
+ [기록 쿼리 예제](#working.history.example)

## 기록 함수
<a name="working.history.function"></a>

QLDB의 기록 함수는 테이블의 시스템 정의 뷰에서 개정 내용을 반환하는 PartiQL 확장입니다. 따라서 커밋된 뷰와 동일한 스키마에 데이터와 관련 메타데이터가 모두 포함됩니다.

**구문**

```
SELECT * FROM history( table_name | 'table_id' [, `start-time` [, `end-time` ] ] ) AS h
[ WHERE h.metadata.id = 'id' ]
```인수

***table\$1name* \$1 '*table\$1id*'**  
테이블 이름 또는 테이블 ID입니다. 테이블 이름은 큰따옴표로 표시하거나 따옴표 없이 표시할 수 있는 PartiQL 식별자입니다. 테이블 ID는 작은따옴표로 묶여야 하는 문자열 리터럴입니다. 테이블 ID 사용에 대한 자세한 내용은 [비활성 테이블의 기록 쿼리](working.manage-tables.md#working.history.inactive-table) 섹션을 참조하세요.

**`*start-time*`, `*end-time*`**  
(선택 사항)모든 개정이 활성화된 시간 범위를 지정합니다. *이러한 파라미터는 개정이 트랜잭션에서 저널에 커밋된 시간 범위를 지정하지 않습니다.*  
시작 및 종료 시간은 백틱(``...``)으로 표시할 수 있는 Ion 타임스탬프 리터럴입니다. 자세한 내용은 [Amazon QLDB에서 PartiQL을 사용한 Ion 쿼리](ql-reference.query.md) 섹션을 참조하세요.  
이 시간 파라미터는 다음과 같은 동작을 합니다.  
+ *시작 시간*과 *종료 시간*이 모두 포함됩니다. 이는 [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) 날짜 및 시간 형식이어야 하며 협정 세계시(UTC)여야 합니다.
+ *시작 시간*은 *종료 시간*보다 작거나 같아야 하며 임의의 과거 날짜가 될 수 있습니다.
+ *종료 시간*은 현재 UTC 날짜 및 시간보다 작거나 같아야 합니다.
+ *시작 시간*은 지정하고 *종료 시간*은 지정하지 않는 경우 쿼리는 *종료 시간*을 현재 날짜 및 시간으로 기본 설정합니다. 둘 다 지정하지 않으면 쿼리는 전체 기록을 반환합니다.

**'*id*'**  
(선택 사항)수정 기록을 쿼리하려는 문서 ID로, 작은 따옴표로 표시됩니다.

**작은 정보**  
가장 좋은 방법은 날짜 범위(*시작 시간* 및 *종료 시간*)와 문서 ID(`metadata.id`)를 모두 사용하여 기록 쿼리를 한정하는 것입니다. QLDB에서는 모든 `SELECT` 쿼리가 트랜잭션에서 처리되며 [트랜잭션 시간 초과 제한](limits.md#limits.fixed)이 적용됩니다.  
기록 쿼리는 테이블에 만든 인덱스를 사용하지 않습니다. QLDB 기록은 문서 ID로만 인덱싱되며, 이번에는 추가 기록 인덱스를 만들 수 없습니다. 시작 시간과 종료 시간을 포함하는 기록 쿼리는 날짜 범위 한정이라는 이점을 갖습니다.

## 기록 쿼리 예제
<a name="working.history.example"></a>

차량 등록 문서의 내역을 쿼리하려면 이전에 [문서 업데이트 및 삭제](working.revisions.md)에 저장한 `id`을 사용하세요. 예를 들어, 다음 기록 쿼리는 `2019-06-05T00:00:00Z`와 `2019-06-05T23:59:59Z` 사이에 `ADR2Ll1fGsU4Jr4EqTdnQF` 활성화된 문서 ID의 모든 개정 내용을 반환합니다.

**참고**  
시작 및 종료 시간 파라미터는 개정이 트랜잭션에서 저널에 커밋된 시간 범위를 지정하지 *않는다는* 점을 기억하세요. 예를 들어 `2019-06-05T00:00:00Z` 이전에 개정이 커밋되었고 해당 시작 시간 이후에도 활성 상태를 유지한 경우 이 예제 쿼리는 결과에 해당 개정 내용을 반환합니다.

`id`, 시작 시간 및 종료 시간을 적절하게 사용자 고유의 값으로 바꾸세요.

```
SELECT * FROM history(VehicleRegistration, `2019-06-05T00:00:00Z`, `2019-06-05T23:59:59Z`) AS h
WHERE h.metadata.id = 'ADR2Ll1fGsU4Jr4EqTdnQF' --replace with your id
```

쿼리 결과는 다음과 비슷한 모습이어야 합니다.

```
{
    blockAddress:{
        strandId:"JdxjkR9bSYB5jMHWcI464T",
        sequenceNo:14
    },
    hash:{{B2wYwrHKOWsmIBmxUgPRrTx9lv36tMlod2xVvWNiTbo=}},
    data: {
        VIN: "1HVBBAANXWH544237",
        LicensePlateNumber: "LS477D",
        State: "WA",
        City: "Tacoma",
        PendingPenaltyTicketAmount: 42.20,
        ValidFromDate: 2011-10-26T,
        ValidToDate: 2023-09-25T,
        Owners: {
            PrimaryOwner: { PersonId: "KmA3XPKKFqYCP2zhR3d0Ho" },
            SecondaryOwners: []
        }
    },
    metadata:{
        id:"ADR2Ll1fGsU4Jr4EqTdnQF",
        version:0,
        txTime:2019-06-05T20:53:321d-3Z,
        txId:"HgXAkLjAtV0HQ4lNYdzX60"
    }
},
{
    blockAddress:{
        strandId:"JdxjkR9bSYB5jMHWcI464T",
        sequenceNo:17
    },
    hash:{{LGSFZ4iEYWZeMwmAqcxxNyT4wbCtuMOmFCj8pEd6Mp0=}},
    data: {
        VIN: "1HVBBAANXWH544237",
        LicensePlateNumber: "LS477D",
        State: "WA",
        PendingPenaltyTicketAmount: 42.20,
        ValidFromDate: 2011-10-26T,
        ValidToDate: 2023-09-25T,
        Owners: {
            PrimaryOwner: { PersonId: "KmA3XPKKFqYCP2zhR3d0Ho" },
            SecondaryOwners: []
        },
        City: "Bellevue"
    },
    metadata:{
        id:"ADR2Ll1fGsU4Jr4EqTdnQF",
        version:1,
        txTime:2019-06-05T21:01:442d-3Z,
        txId:"9cArhIQV5xf5Tf5vtsPwPq"
    }
},
{
    blockAddress:{
        strandId:"JdxjkR9bSYB5jMHWcI464T",
        sequenceNo:19
    },
    hash:{{7bm5DUwpqJFGrmZpb7h9wAxtvggYLPcXq+LAobi9fDg=}},
    metadata:{
        id:"ADR2Ll1fGsU4Jr4EqTdnQF",
        version:2,
        txTime:2019-06-05T21:03:76d-3Z,
        txId:"9GslbtDtpVHAgYghR5FXbZ"
    }
}
```

출력에는 각 항목이 수정된 시기와 어떤 트랜잭션에 의해 수정되었는지에 대한 세부 정보를 제공하는 메타데이터 속성이 포함됩니다. 이 데이터에서 다음을 확인할 수 있습니다.
+ 이 문서는 시스템이 할당한 `id`인 `ADR2Ll1fGsU4Jr4EqTdnQF`로 고유하게 식별됩니다 이는 Base62로 인코딩된 문자열로 표시되는 UUID입니다.
+ `INSERT` 명령문을 사용하면 문서의 초기 개정본(버전 `0`)이 만들어집니다.
+ 이후 업데이트될 때마다 동일한 문서 `id`와 증가된 버전 번호를 사용하여 새 개정을 생성합니다.
+ 이 `txId` 필드는 각 개정을 커밋한 트랜잭션을 나타내며 `txTime`은 각 개정이 커밋된 시기를 보여줍니다.
+ `DELETE` 명령문은 문서의 새로운 최종 개정본을 생성합니다. 이 최종 개정본에는 메타데이터만 있습니다.

개정 버전을 영구 삭제하는 방법을 알아보려면 [문서 개정본 수정하기](working.redaction.md)으로 이동하세요.

# 문서 개정본 수정하기
<a name="working.redaction"></a>

**중요**  
지원 종료 알림: 기존 고객은 07/31/2025에 지원이 종료될 때까지 Amazon QLDB를 사용할 수 있습니다. 자세한 내용은 [Amazon QLDB 원장을 Amazon Aurora PostgreSQL로 마이그레이션](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)을 참조하세요.

Amazon QLDB에서 `DELETE` 문은 문서를 삭제된 것으로 표시하는 새 개정본을 생성하여 논리적으로만 문서를 삭제합니다. QLDB는 테이블 기록에서 비활성 문서 개정을 영구적으로 삭제할 수 있는 *데이터 수정* 작업도 지원합니다.

**참고**  
2021년 7월 22일 이전에 생성된 원장은 현재 수정할 수 없습니다. Amazon QLDB 콘솔에서 원장 생성 시간을 볼 수 있습니다.

수정 작업을 수행하면 지정된 정 버전의 사용자 데이터만 삭제되고 저널 시퀀스와 문서 메타데이터는 변경되지 않은 상태로 둡니다. 이렇게 하면 원장의 전체 데이터 무결성이 유지됩니다.

QLDB에서 데이터 수정을 시작하기 전에 먼저 *Amazon QLDB PartiQL 참조*에서 [수정 고려 사항 및 제한](ql-stored-procedures.redact_revision.md#ql-stored-procedures.redact_revision.considerations)를 검토해야 합니다.

**Topics**
+ [저장된 프로시저로 수정](#working.redaction.stored-proc)
+ [수정 완료 여부 확인](#working.redaction.check-completion)
+ [수정 예제](#working.redaction.example)
+ [활성 개정본 삭제 및 수정](#working.redaction.active-revision)
+ [개정본 내 특정 필드 수정하기](#working.redaction.field)

## 저장된 프로시저로 수정
<a name="working.redaction.stored-proc"></a>

[REDACT\$1REVISION](ql-stored-procedures.redact_revision.md) 저장 프로시저를 사용하여 원장에서 비활성 상태인 개별 개정본을 영구적으로 삭제할 수 있습니다. 이 저장 프로시저는 인덱싱된 저장소와 저널 저장소 모두에서 지정된 개정본의 모든 사용자 데이터를 삭제합니다. 하지만 저널 시퀀스와 문서 ID 및 해시를 포함한 문서 메타데이터는 변경되지 않습니다. *이 작업은 되돌릴 수 없습니다.*

지정된 문서 수정본은 기록에서 비활성 상태여야 합니다. 문서의 최신 활성 수정본은 수정할 수 없습니다.

여러 수정본을 수정하려면 각 수정본마다 한 번씩 저장 프로시저를 실행해야 합니다. 트랜잭션당 하나의 수정본을 수정할 수 있습니다.

**구문**

```
EXEC REDACT_REVISION `block-address`, 'table-id', 'document-id'
```인수

`*block-address*`  
수정할 문서의 수정본의 저널 블록 위치입니다. 주소는 `strandId` 및 `sequenceNo`라는 두 개의 필드로 구성된 Amazon Ion 구조입니다.  
이 값은 백틱으로 표시되는 Ion 리터럴 값입니다. 예시:  

```
`{strandId:"JdxjkR9bSYB5jMHWcI464T", sequenceNo:17}`
```

'*table-id*'  
수정하고자 하는 문서 개정본이 있는 테이블의 고유 ID로, 작은 따옴표로 표시됩니다.

'*document-id*'  
수정할 개정본의 고유한 문서 ID로, 작은 따옴표로 표시됩니다.

## 수정 완료 여부 확인
<a name="working.redaction.check-completion"></a>

저장된 프로시저를 실행하여 수정 요청을 제출하면 QLDB는 데이터 수정을 비동기적으로 처리합니다. 완료되면 개정의 사용자 데이터(`data` 구조로 표시됨)가 영구적으로 제거됩니다. 수정 요청이 완료되었는지 확인하려면 다음 중 하나를 사용할 수 있습니다.
+ [저널 내보내기](export-journal.md)
+ [저널 스트림](streams.md)
+ [GetBlock API 작업](https://docs.aws.amazon.com/qldb/latest/developerguide/API_GetBlock.html)
+ [GetRevision API 작업](https://docs.aws.amazon.com/qldb/latest/developerguide/API_GetRevision.html)
+ [기록 함수](working.history.md#working.history.function)- **참고:** 저널에서 편집을 완료한 후 기록 쿼리에 편집 결과가 표시되기까지 다소 시간이 걸릴 수 있습니다. 비동기 편집이 완료되면 일부 개정 사항이 다른 개정보다 먼저 편집되는 것을 볼 수 있지만 기록 쿼리에는 결국 완료된 결과가 표시됩니다.

개정본 수정이 완료되면 개정본의 `data` 구조가 새 `dataHash` 필드로 대체됩니다. 이 필드의 값은 다음 예와 같이 제거된 `data` 구조의 Ion 해시입니다. 따라서 원장은 전반적인 데이터 무결성을 유지하고 기존 검증 API 작업을 통해 암호화 방식으로 검증 가능한 상태를 유지합니다. 검증에 대한 자세한 정보는 [Amazon QLDB에서의 데이터 확인](verification.md)을 참조하세요.

## 수정 예제
<a name="working.redaction.example"></a>

이전에 [개정 기록 쿼리](working.history.md)에서 검토한 차량 등록 문서를 생각해 보세요. 두 번째 개정(`version:1`)을 삭제하려 한다고 가정합니다. 다음 쿼리 예제는 수정 전의 이 개정본을 보여줍니다. 쿼리 결과에서, 수정될 `data` 구조는 *빨간색 기울임꼴*로 강조 표시됩니다.

```
SELECT * FROM history(VehicleRegistration) AS h
WHERE h.metadata.id = 'ADR2Ll1fGsU4Jr4EqTdnQF' --replace with your id
AND h.metadata.version = 1
```

```
{
    blockAddress:{
        strandId:"JdxjkR9bSYB5jMHWcI464T",
        sequenceNo:17
    },
    hash:{{LGSFZ4iEYWZeMwmAqcxxNyT4wbCtuMOmFCj8pEd6Mp0=}},
    data: {
        VIN: "1HVBBAANXWH544237",
        LicensePlateNumber: "LS477D",
        State: "WA",
        PendingPenaltyTicketAmount: 42.20,
        ValidFromDate: 2011-10-26T,
        ValidToDate: 2023-09-25T,
        Owners: {
            PrimaryOwner: { PersonId: "KmA3XPKKFqYCP2zhR3d0Ho" },
            SecondaryOwners: []
        },
        City: "Bellevue"
    },
    metadata:{
        id:"ADR2Ll1fGsU4Jr4EqTdnQF",
        version:1,
        txTime:2019-06-05T21:01:442d-3Z,
        txId:"9cArhIQV5xf5Tf5vtsPwPq"
    }
}
```

이 값을 `REDACT_REVISION` 저장 프로시저로 전달해야 하므로 쿼리 결과에서 `blockAddress`를 기록해 두세요. 그런 다음 다음과 같이 [시스템 카탈로그](working.catalog.md)를 쿼리하여 `VehicleRegistration` 테이블의 고유 ID를 찾습니다.

```
SELECT tableId FROM information_schema.user_tables
WHERE name = 'VehicleRegistration'
```

이 테이블 ID를 문서 ID 및 블록 주소와 함께 사용하여 `REDACT_REVISION`를 실행합니다. 테이블 ID와 문서 ID는 작은따옴표로 묶어야 하는 문자열 리터럴이고, 블록 주소는 백틱으로 묶인 Ion 리터럴입니다. 이 인수는 필요에 따라 사용자 고유의 값으로 바꿉니다.

```
EXEC REDACT_REVISION `{strandId:"JdxjkR9bSYB5jMHWcI464T", sequenceNo:17}`, '5PLf9SXwndd63lPaSIa0O6', 'ADR2Ll1fGsU4Jr4EqTdnQF'
```

**작은 정보**  
QLDB 콘솔 또는 QLDB 쉘을 사용하여 테이블 ID 또는 문서 ID(또는 문자열 리터럴 값)를 쿼리할 때 반환된 값은 *큰*따옴표로 묶입니다. 하지만 `REDACT_REVISION` 저장 프로시저의 테이블 ID 및 문서 ID 인수를 지정할 때는 값을 *작은* 따옴표로 묶어야 합니다.  
이는 명령문을 PartiQL 형식으로 작성하지만 QLDB는 결과를 Amazon Ion 형식으로 반환하기 때문입니다. QLDB에서 PartiQL의 구문 및 시맨틱에 대한 자세한 내용은 [PartiQL을 사용하여 Ion 쿼리하기](ql-reference.query.md) 섹션을 참조하세요.

유효한 수정 요청은 다음과 같이 수정 중인 문서 개정본을 나타내는 Ion 구조를 반환합니다.

```
{
  blockAddress: {
    strandId: "JdxjkR9bSYB5jMHWcI464T",
    sequenceNo: 17
  },
  tableId: "5PLf9SXwndd63lPaSIa0O6",
  documentId: "ADR2Ll1fGsU4Jr4EqTdnQF",
  version: 1
}
```

이 저장된 프로시저를 실행하면 QLDB는 수정 요청을 비동기적으로 처리합니다. 수정이 완료되면 `data` 구조가 영구적으로 제거되고 새 *`dataHash`* 필드로 대체됩니다. 이 필드의 값은 다음과 같이 제거된 `data` 구조의 Ion 해시입니다.

**참고**  
이 `dataHash` 예제는 정보 제공용으로만 제공되며 실제 계산된 해시 값이 아닙니다.

```
{
    blockAddress:{
        strandId:"JdxjkR9bSYB5jMHWcI464T",
        sequenceNo:17
    },
    hash:{{LGSFZ4iEYWZeMwmAqcxxNyT4wbCtuMOmFCj8pEd6Mp0=}},
    dataHash: {{s83jd7sfhsdfhksj7hskjdfjfpIPP/DP2hvionas2d4=}},
    metadata:{
        id:"ADR2Ll1fGsU4Jr4EqTdnQF",
        version:1,
        txTime:2019-06-05T21:01:442d-3Z,
        txId:"9cArhIQV5xf5Tf5vtsPwPq"
    }
}
```

## 활성 개정본 삭제 및 수정
<a name="working.redaction.active-revision"></a>

활성 문서 개정본(즉, 각 문서의 삭제되지 않은 최신 개정)은 데이터 수정 대상이 아닙니다. 활성 개정본을 수정하려면 먼저 이를 업데이트하거나 삭제해야 합니다. 이렇게 하면 이전에 활성 개정본이었던 것이 기록으로 이동하고 수정이 가능해집니다.

사용 사례에서 전체 문서를 삭제된 것으로 표시해야 하는 경우에는 먼저 [DELETE](ql-reference.delete.md) 명령문을 사용합니다. 예를 들어, 다음 명령문은 VIN이 `1HVBBAANXWH544237`인 `VehicleRegistration` 문서를 논리적으로 삭제합니다.

```
DELETE FROM VehicleRegistration AS r
WHERE r.VIN = '1HVBBAANXWH544237'
```

그런 다음 앞에서 설명한 대로 이 삭제 전에 이전 개정본을 수정합니다. 필요한 경우 이전 개정본을 개별적으로 수정할 수도 있습니다.

사용 사례에 따라 문서를 활성 상태로 유지해야 하는 경우 먼저 [UPDATE](ql-reference.update.md) 또는 [FROM](ql-reference.from.md) 문을 사용하여 수정하려는 필드를 모호하게 하거나 제거해야 합니다. 이 프로세스는 다음 섹션에서 설명합니다.

## 개정본 내 특정 필드 수정하기
<a name="working.redaction.field"></a>

QLDB는 문서 개정 내 특정 필드의 수정을 지원하지 않습니다. 이렇게 하려면 먼저 [UPDATE-REMOVE](ql-reference.update.md) 또는 [FROM-REMOVE](ql-reference.from.md) 문을 사용하여 개정본에서 기존 필드를 제거할 수 있습니다. 예를 들어, 다음 명령문은 VIN이 `1HVBBAANXWH544237`인 `VehicleRegistration` 문서에서 `LicensePlateNumber` 필드를 제거합니다.

```
UPDATE VehicleRegistration AS r
REMOVE r.LicensePlateNumber
WHERE r.VIN = '1HVBBAANXWH544237'
```

그런 다음 앞에서 설명한 대로 이 제거 전에 이전 개정본을 수정합니다. 필요한 경우 현재 제거된 이 필드를 포함하는 이전 개정본을 개별적으로 수정할 수도 있습니다.

쿼리를 최적화하는 방법을 알아보려면 [쿼리 성능 최적화](working.optimize.md)으로 이동하세요.

# 쿼리 성능 최적화
<a name="working.optimize"></a>

**중요**  
지원 종료 알림: 기존 고객은 07/31/2025에 지원이 종료될 때까지 Amazon QLDB를 사용할 수 있습니다. 자세한 내용은 [Amazon QLDB 원장을 Amazon Aurora PostgreSQL로 마이그레이션](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)을 참조하세요.

Amazon QLDB는 고성능 온라인 트랜잭션 처리(OLTP) 워크로드의 니즈를 해결하기 위한 것입니다. 즉, QLDB는 SQL과 유사한 쿼리 기능을 지원하지만 특정 쿼리 패턴 세트에 최적화되어 있습니다. 이러한 쿼리 패턴과 함께 작동하도록 애플리케이션과 해당 데이터 모델을 설계하는 것이 중요합니다. 그렇지 않으면 테이블이 커질수록 쿼리 지연 시간, 트랜잭션 시간 초과, 동시성 충돌 등 심각한 성능 문제가 발생할 수 있습니다.

이 섹션에서는 QLDB의 쿼리 제약 조건에 대해 설명하고 이러한 제약 조건을 고려하여 최적의 쿼리를 작성하기 위한 지침을 제공합니다.

**Topics**
+ [트랜잭션 시간 초과 제한](#working.optimize.txn-timeout)
+ [동시성 충돌](#working.optimize.occ)
+ [최적의 쿼리 패턴](#working.optimize.patterns)
+ [피해야 하는 쿼리 패턴](#working.optimize.avoid)
+ [모니터링 성능](#working.optimize.monitor)

## 트랜잭션 시간 초과 제한
<a name="working.optimize.txn-timeout"></a>

QLDB에서는 모든 PartiQL 문(모든 `SELECT` 쿼리 포함)이 트랜잭션에서 처리되며, [트랜잭션 시간 초과 제한](limits.md#limits.fixed)이 적용됩니다. 트랜잭션은 커밋되기 전까지 최대 **30초** 동안 실행될 수 있습니다. 이 제한을 초과하면 QLDB는 트랜잭션에 대한 모든 작업을 거부하고 트랜잭션을 실행한 [세션](concurrency.md#concurrency.sessions)을 삭제합니다. 이 제한은 트랜잭션을 시작하고 커밋하거나 취소하지 않음으로써 서비스 클라이언트의 세션 유출을 방지합니다.

## 동시성 충돌
<a name="working.optimize.occ"></a>

QLDB는 *낙관적 동시성 제어*(OCC)를 사용하여 동시성 제어를 구현합니다. 쿼리가 최적이 아닌 경우 OCC 충돌이 더 많이 발생할 수도 있습니다. OCC에 대한 자세한 내용은 [Amazon QLDB 동시성 모델](concurrency.md) 섹션을 참조하세요.

## 최적의 쿼리 패턴
<a name="working.optimize.patterns"></a>

인덱싱된 필드 또는 문서 ID를 기준으로 필터링하는 `WHERE` 조건자 절을 사용하여 명령문을 실행하는 것이 가장 좋습니다. QLDB에서 문서를 효율적으로 검색하려면 인덱싱된 필드에 *동등* 연산자(`=` 또는 `IN`)가 필요합니다.

다음은 [사용자 뷰](working.userdata.md)에서 가장 적합한 쿼리 패턴의 예입니다.

```
--Indexed field (VIN) lookup using the = operator
SELECT * FROM VehicleRegistration
WHERE VIN = '1N4AL11D75C109151'

--Indexed field (VIN) AND non-indexed field (City) lookup
SELECT * FROM VehicleRegistration
WHERE VIN = '1N4AL11D75C109151' AND City = 'Seattle'

--Indexed field (VIN) lookup using the IN operator
SELECT * FROM VehicleRegistration
WHERE VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')

--Document ID (r_id) lookup using the BY clause
SELECT * FROM VehicleRegistration BY r_id
WHERE r_id = '3Qv67yjXEwB9SjmvkuG6Cp'
```

이러한 패턴을 따르지 않는 모든 쿼리는 *전체 테이블 스캔*을 호출합니다. 테이블 스캔으로 인해 큰 테이블에 대한 쿼리 또는 큰 결과 집합을 반환하는 쿼리의 경우 트랜잭션 제한 시간이 초과될 수 있습니다. 또한 [이로 인해 경쟁 트랜잭션과 OCC 충돌이 발생](concurrency.md#concurrency.indexes)할 수 있습니다.

**하이 카디널리티 인덱스**

카디널리티 값이 높은 필드를 인덱싱하는 것이 좋습니다. 예를 들어, `VehicleRegistration` 테이블의 `VIN` 및 `LicensePlateNumber` 필드는 고유하도록 인덱싱된 필드입니다.

상태 코드, 주소 주 또는 지방, 우편 번호와 같이 카디널리티가 낮은 필드는 인덱싱하지 마세요. 이러한 필드를 인덱싱하면 쿼리에서 큰 결과 집합이 생성되어 *트랜잭션 시간 초과가 발생하거나 의도하지 않은 OCC 충돌이 발생할 가능성이 높습니다*.

**커밋된 뷰 쿼리**

[커밋된 뷰](working.metadata.md)에서 실행하는 쿼리는 사용자 뷰 쿼리와 동일한 최적화 가이드라인을 따릅니다. 테이블에 만든 인덱스는 커밋된 뷰의 쿼리에도 사용됩니다.

**기록 함수 쿼리**

[기록 함수](working.history.md) 쿼리는 테이블에 생성한 인덱스를 사용하지 않습니다. QLDB 기록은 문서 ID로만 인덱싱되며, 이번에는 추가 기록 인덱스를 만들 수 없습니다.

가장 좋은 방법은 날짜 범위(*시작 시간* 및 *종료 시간*)와 문서 ID(`metadata.id`)를 모두 사용하여 기록 쿼리를 한정하는 것입니다. 시작 시간과 종료 시간을 포함하는 기록 쿼리는 날짜 범위 한정이라는 이점을 갖습니다.

**내부 조인 쿼리**

내부 조인 쿼리의 경우 조인 오른쪽에 있는 테이블에 대해 최소한 하나의 인덱싱된 필드가 포함된 조인 기준을 사용합니다. 조인 인덱스가 없는 경우 조인 쿼리는 여러 테이블 스캔을 호출합니다. 즉, 조인의 왼쪽 테이블에 있는 모든 문서에 대해 쿼리는 오른쪽 테이블을 완전히 스캔합니다. 가장 좋은 방법은 하나 이상의 테이블에 `WHERE` 동등 조건을 지정하는 것 외에도 조인하는 각 테이블에 대해 인덱싱된 필드를 조인하는 것입니다.

예를 들어 다음 쿼리는 둘 다 인덱싱된 각 `VIN` 필드의 `VehicleRegistration` 및 `Vehicle` 테이블을 조인합니다. 이 쿼리에는 `VehicleRegistration.VIN`에 동등 조건자도 있습니다.

```
SELECT * FROM VehicleRegistration AS r INNER JOIN Vehicle AS v
ON r.VIN = v.VIN
WHERE r.VIN IN ('1N4AL11D75C109151', 'KM8SRDHF6EU074761')
```

조인 쿼리의 조인 기준과 동등 조건자 모두에 대해 카디널리티가 높은 인덱스를 선택하세요.

## 피해야 하는 쿼리 패턴
<a name="working.optimize.avoid"></a>

다음은 QLDB의 더 큰 테이블에 맞게 확장되지 않는 **차선책** 명령문의 몇 가지 예입니다. 쿼리로 인해 결국 트랜잭션 제한 시간이 초과되므로, 시간이 지남에 따라 증가하는 테이블에 대해서는 이러한 유형의 쿼리를 사용하지 않는 것이 좋습니다. 테이블에는 크기가 다양한 문서가 포함되어 있기 때문에 인덱싱되지 않은 쿼리에 대한 정확한 제한을 정의하기는 어렵습니다.

```
--No predicate clause
SELECT * FROM Vehicle

--COUNT() is not an optimized function
SELECT COUNT(*) FROM Vehicle

--Low-cardinality predicate
SELECT * FROM Vehicle WHERE Color = 'Silver'

--Inequality (>) does not qualify for indexed lookup
SELECT * FROM Vehicle WHERE "Year" > 2019

--Inequality (LIKE)
SELECT * FROM Vehicle WHERE VIN LIKE '1N4AL%'

--Inequality (BETWEEN)
SELECT SUM(PendingPenaltyTicketAmount) FROM VehicleRegistration
WHERE ValidToDate BETWEEN `2020-01-01T` AND `2020-07-01T`

--No predicate clause
DELETE FROM Vehicle

--No document id, and no date range for the history() function
SELECT * FROM history(Vehicle)
```

일반적으로 QLDB의 프로덕션 사용 사례에서는 다음과 같은 유형의 쿼리 패턴을 *실행하지 않는 것이 좋습니다*.
+ 온라인 분석 처리(OLAP) 쿼리
+ 조건자 절이 없는 탐색적 쿼리
+ 쿼리 보고
+ 텍스트 검색

대신 분석적인 사용 사례에 최적화된 목적별 데이터베이스 서비스로 데이터를 스트리밍하는 것이 좋습니다. 예를 들어 QLDB 데이터를 Amazon OpenSearch Service로 스트리밍하여 문서에 대한 전체 텍스트 검색 기능을 제공할 수 있습니다. 이 사용 사례를 보여주는 샘플 애플리케이션을 보려면 GitHub 리포지토리 [aws-samples/amazon-qldb-streaming-amazon-opensearch-service-sample-python](https://github.com/aws-samples/amazon-qldb-streaming-amazon-opensearch-service-sample-python)을 참조하세요. QLDB 스트림에 대한 자세한 내용은 [Amazon QLDB에서 저널 데이터 스트리밍](streams.md) 섹션을 참조하세요.

## 모니터링 성능
<a name="working.optimize.monitor"></a>

QLDB 드라이버는 사용된 I/O 사용량 및 타이밍 정보를 명령문의 결과 개체에 제공합니다. 이러한 지표를 사용하여 비효율적인 PartiQL 문을 식별할 수 있습니다. 자세히 알아보려면 [PartiQL 문 통계 가져오기](working.statement-stats.md) 섹션을 참조하세요.

Amazon CloudWatch를 사용하여 데이터 작업에 대한 원장의 성능을 추적할 수도 있습니다. 지정된 `LedgerName` 및 `CommandType`의 `CommandLatency` 지표를 모니터링합니다. 자세한 내용은 [Amazon CloudWatch를 사용한 모니터링](monitoring-cloudwatch.md)을 참조하세요. QLDB가 명령을 사용하여 데이터 작업을 관리하는 방법을 알아보려면 [드라이버를 사용한 세션 관리](driver-session-management.md) 섹션을 참조하세요.

# PartiQL 문 통계 가져오기
<a name="working.statement-stats"></a>

**중요**  
지원 종료 알림: 기존 고객은 07/31/2025에 지원이 종료될 때까지 Amazon QLDB를 사용할 수 있습니다. 자세한 내용은 [Amazon QLDB 원장을 Amazon Aurora PostgreSQL로 마이그레이션](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)을 참조하세요.

Amazon QLDB는 보다 효율적인 PartiQL 문을 실행하여 QLDB 사용을 최적화하는 데 도움이 되는 문 실행 통계를 제공합니다. QLDB는 명령문의 결과와 함께 이러한 통계를 반환합니다. 여기에는 소비된 I/O 사용량과 서버 측 처리 시간을 정량화하는 지표가 포함되며, 이를 통해 비효율적인 명령문을 식별할 수 있습니다.

이 기능은 현재 [QLDB 콘솔](console_QLDB.md)의 *PartiQL 편집기*, [QLDB 쉘](data-shell.md) 및 지원되는 모든 언어에 대한 최신 버전의 [QLDB 드라이버](getting-started-driver.md)에서 사용할 수 있습니다. 콘솔에서 쿼리 기록에 대한 명령문 통계를 볼 수도 있습니다.

**Topics**
+ [I/O 사용량](#statement-stats.io-usage)
+ [타이밍 정보](#statement-stats.timing-information)

## I/O 사용량
<a name="statement-stats.io-usage"></a>

I/O 사용량 지표는 읽기 I/O 요청 수를 설명합니다. 읽기 I/O 요청 수가 예상보다 많으면 인덱스 부족 등 명령문이 최적화되지 않았음을 나타냅니다. 이전 항목인 *쿼리 성능 최적화*에서 [최적의 쿼리 패턴](working.optimize.md#working.optimize.patterns)를 검토하는 것이 좋습니다.

**참고**  
비어 있지 않은 테이블에서 `CREATE INDEX` 명령문을 실행하면 I/O 사용량 지표에는 동기 인덱스 생성 호출에 대한 읽기 요청만 포함됩니다.  
QLDB는 테이블의 기존 문서에 대한 인덱스를 비동기적으로 빌드합니다. 이러한 비동기 읽기 요청은 명령문 결과의 I/O 사용량 지표에 포함되지 않습니다. 비동기 읽기 요청은 별도로 요금이 부과되며 인덱스 빌드가 완료된 후 총 읽기 I/O에 추가됩니다.

### QLDB 콘솔 사용
<a name="statement-stats.io-usage.con"></a>

QLDB 콘솔을 사용하여 명령문의 읽기 I/O 사용량을 확인하려면 다음 단계를 수행하세요.

1. [https://console.aws.amazon.com/qldb](https://console.aws.amazon.com/qldb)에서 Amazon QLDB 콘솔을 엽니다.

1. 탐색 창에서 **PartiQL 편집기**를 선택합니다.

1. 원장 드롭다운 목록에서 원장을 선택합니다.

1. 쿼리 편집기 창에서 선택한 문을 입력한 다음 **실행**을 선택합니다. 다음은 쿼리 예제입니다.

   ```
   SELECT * FROM testTable WHERE firstName = 'Jim'
   ```

   명령문을 실행하려면 키보드 단축키 (Windows의 경우 Ctrl\$1Enter, macOS의 경우 Cmd\$1Return)를 사용할 수도 있습니다. 키보드 단축키에 대한 자세한 내용은 [PartiQL 편집기 키보드 바로 가기](console_QLDB.md#console_QLDB.partiql-editor-shortcuts) 섹션을 참조하세요.

1. 쿼리 편집기 창 아래에 쿼리 결과에는 명령문에 의해 수행된 읽기 요청 횟수인 *읽기 I/O*가 포함됩니다.

다음 단계를 수행하여 쿼리 기록의 읽기 I/O를 볼 수도 있습니다.

1. 탐색 창의 **PartiQL 편집기**에서 **최근 쿼리**를 선택합니다.

1. **읽기 I/O** 열에는 각 명령문에서 이루어진 읽기 요청 수가 표시됩니다.

### QLDB 드라이버 사용
<a name="statement-stats.io-usage.driver"></a>

QLDB 드라이버를 사용하여 명령문의 I/O 사용량을 가져오려면 결과의 스트림 커서 또는 버퍼링된 커서의 `getConsumedIOs` 작업을 호출하세요.

다음 코드 예제는 문 결과의 *스트림 커서*에서 읽기 I/O를 가져오는 방법을 보여줍니다.

------
#### [ Java ]

```
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonValue;
import com.amazon.ion.system.IonSystemBuilder;
import software.amazon.qldb.IOUsage;
import software.amazon.qldb.Result;

IonSystem ionSystem = IonSystemBuilder.standard().build();
IonValue ionFirstName = ionSystem.newString("Jim");

driver.execute(txn -> {
    Result result = txn.execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);

    for (IonValue ionValue : result) {
        // User code here to handle results
    }

    IOUsage ioUsage = result.getConsumedIOs();
    long readIOs = ioUsage.getReadIOs();
});
```

------
#### [ .NET ]

```
using Amazon.IonDotnet.Builders;
using Amazon.IonDotnet.Tree;
using Amazon.QLDB.Driver;
using IAsyncResult = Amazon.QLDB.Driver.IAsyncResult;

// This is one way of creating Ion values. We can also use a ValueFactory.
// For more details, see: https://docs.aws.amazon.com/qldb/latest/developerguide/driver-cookbook-dotnet.html#cookbook-dotnet.ion
IIonValue ionFirstName = IonLoader.Default.Load("Jim");

await driver.Execute(async txn =>
{
    IAsyncResult result = await txn.Execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);

    // Iterate through stream cursor to accumulate read IOs.
    await foreach (IIonValue ionValue in result)
    {
        // User code here to handle results.
        // Warning: It is bad practice to rely on results within a lambda block, unless
        // it is to check the state of a result. This is because lambdas are retryable.
    }

    var ioUsage = result.GetConsumedIOs();
    var readIOs = ioUsage?.ReadIOs;
});
```

**참고**  
동기 코드로 변환하려면 `await` 및 `async` 키워드를 제거하고 `IAsyncResult` 유형을 `IResult`로 변경하세요.

------
#### [ Go ]

```
import (
    "context"
    "fmt"
    "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver"
)

driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    result, err := txn.Execute("SELECT * FROM testTable WHERE firstName = ?", "Jim")

    if err != nil {
        panic(err)
    }

    for result.Next(txn) {
        // User code here to handle results
    }

    ioUsage := result.GetConsumedIOs()
    readIOs := *ioUsage.GetReadIOs()
    fmt.Println(readIOs)
    return nil,nil
})
```

------
#### [ Node.js ]

```
import { IOUsage, ResultReadable, TransactionExecutor } from "amazon-qldb-driver-nodejs";

await driver.executeLambda(async (txn: TransactionExecutor) => {
    const result: ResultReadable = await txn.executeAndStreamResults("SELECT * FROM testTable WHERE firstName = ?", "Jim");

    for await (const chunk of result) {
        // User code here to handle results
    }

    const ioUsage: IOUsage = result.getConsumedIOs();
    const readIOs: number = ioUsage.getReadIOs();
});
```

------
#### [ Python ]

```
def get_read_ios(transaction_executor):
    cursor = transaction_executor.execute_statement("SELECT * FROM testTable WHERE firstName = ?", "Jim")

    for row in cursor:
        # User code here to handle results
        pass

    consumed_ios = cursor.get_consumed_ios()
    read_ios = consumed_ios.get('ReadIOs')

qldb_driver.execute_lambda(lambda txn: get_read_ios(txn))
```

------

다음 코드 예제는 문 결과의 *버퍼된 커서*에서 읽기 I/O를 가져오는 방법을 보여줍니다. 그러면 `ExecuteStatement` 및 `FetchPage` 요청의 총 읽기 I/O가 반환됩니다.

------
#### [ Java ]

```
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonValue;
import com.amazon.ion.system.IonSystemBuilder;
import software.amazon.qldb.IOUsage;
import software.amazon.qldb.Result;

IonSystem ionSystem = IonSystemBuilder.standard().build();
IonValue ionFirstName = ionSystem.newString("Jim");

Result result = driver.execute(txn -> {
    return txn.execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);
});

IOUsage ioUsage = result.getConsumedIOs();
long readIOs = ioUsage.getReadIOs();
```

------
#### [ .NET ]

```
using Amazon.IonDotnet.Builders;
using Amazon.IonDotnet.Tree;
using Amazon.QLDB.Driver;
using IAsyncResult = Amazon.QLDB.Driver.IAsyncResult;

IIonValue ionFirstName = IonLoader.Default.Load("Jim");

IAsyncResult result = await driver.Execute(async txn =>
{
    return await txn.Execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);
});

var ioUsage = result.GetConsumedIOs();
var readIOs = ioUsage?.ReadIOs;
```

**참고**  
동기 코드로 변환하려면 `await` 및 `async` 키워드를 제거하고 `IAsyncResult` 유형을 `IResult`로 변경하세요.

------
#### [ Go ]

```
import (
    "context"
    "fmt"
    "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver"
)

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    result, err :=  txn.Execute("SELECT * FROM testTable WHERE firstName = ?", "Jim")
    if err != nil {
        return nil, err
    }
    return txn.BufferResult(result)
})

if err != nil {
    panic(err)
}

qldbResult := result.(*qldbdriver.BufferedResult)
ioUsage := qldbResult.GetConsumedIOs()
readIOs := *ioUsage.GetReadIOs()
fmt.Println(readIOs)
```

------
#### [ Node.js ]

```
import { IOUsage, Result, TransactionExecutor } from "amazon-qldb-driver-nodejs";

const result: Result = await driver.executeLambda(async (txn: TransactionExecutor) => {
    return await txn.execute("SELECT * FROM testTable WHERE firstName = ?", "Jim");
});

const ioUsage: IOUsage = result.getConsumedIOs();
const readIOs: number = ioUsage.getReadIOs();
```

------
#### [ Python ]

```
cursor = qldb_driver.execute_lambda(
    lambda txn: txn.execute_statement("SELECT * FROM testTable WHERE firstName = ?", "Jim"))

consumed_ios = cursor.get_consumed_ios()
read_ios = consumed_ios.get('ReadIOs')
```

------

**참고**  
스트림 커서는 결과 집합의 페이지를 매기므로 상태를 추적할 수 있습니다. 따라서 `getConsumedIOs` 및 `getTimingInformation` 연산은 호출한 시점부터 누적된 지표를 반환합니다.  
버퍼링된 커서는 결과 집합을 메모리에 버퍼링하고 총 누적된 지표를 반환합니다.

## 타이밍 정보
<a name="statement-stats.timing-information"></a>

타이밍 정보 지표는 서버 측 처리 시간을 밀리초 단위로 설명합니다. 서버측 처리 시간은 QLDB가 명령문을 처리하는 데 소비하는 시간으로 정의됩니다. 네트워크 호출 또는 일시 중지에 소요된 시간은 여기에 포함되지 않습니다. 이 메트릭은 QLDB 서비스 측의 처리 시간과 클라이언트 측의 처리 시간을 구분합니다.

### QLDB 콘솔 사용
<a name="statement-stats.timing-information.con"></a>

QLDB 콘솔을 사용하여 명령문의 타이밍 정보를 가져오려면 다음 단계를 수행하세요.

1. [https://console.aws.amazon.com/qldb](https://console.aws.amazon.com/qldb)에서 Amazon QLDB 콘솔을 엽니다.

1. 탐색 창에서 **PartiQL 편집기**를 선택합니다.

1. 원장 드롭다운 목록에서 원장을 선택합니다.

1. 쿼리 편집기 창에서 선택한 문을 입력한 다음 **실행**을 선택합니다. 다음은 쿼리 예제입니다.

   ```
   SELECT * FROM testTable WHERE firstName = 'Jim'
   ```

   명령문을 실행하려면 키보드 단축키 (Windows의 경우 Ctrl\$1Enter, macOS의 경우 Cmd\$1Return)를 사용할 수도 있습니다. 키보드 단축키에 대한 자세한 내용은 [PartiQL 편집기 키보드 바로 가기](console_QLDB.md#console_QLDB.partiql-editor-shortcuts) 섹션을 참조하세요.

1. 쿼리 편집기 창 아래에 쿼리 결과에는 QLDB가 명령문 요청을 수신한 시점과 응답을 보낸 시점 사이의 시간인 *서버 측 지연 시간*이 포함됩니다. 이는 총 쿼리 기간의 하위 집합입니다.

다음 단계를 수행하여 쿼리 기록의 타이밍 정보를 볼 수도 있습니다.

1. 탐색 창의 **PartiQL 편집기**에서 **최근 쿼리**를 선택합니다.

1. **실행 시간(밀리초)** 열에는 각 명령문에 대한 이 타이밍 정보가 표시됩니다.

### QLDB 드라이버 사용
<a name="statement-stats.timing-information.driver"></a>

QLDB 드라이버를 사용하여 명령문의 타이밍 정보를 가져오려면 결과의 스트림 커서 또는 버퍼링된 커서의 `getTimingInformation` 작업을 호출하세요.

다음 코드 예제는 문 결과의 *스트림 커서*에서 처리 시간을 가져오는 방법을 보여줍니다.

------
#### [ Java ]

```
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonValue;
import com.amazon.ion.system.IonSystemBuilder;
import software.amazon.qldb.Result;
import software.amazon.qldb.TimingInformation;

IonSystem ionSystem = IonSystemBuilder.standard().build();
IonValue ionFirstName = ionSystem.newString("Jim");

driver.execute(txn -> {
    Result result = txn.execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);

    for (IonValue ionValue : result) {
        // User code here to handle results
    }

    TimingInformation timingInformation = result.getTimingInformation();
    long processingTimeMilliseconds = timingInformation.getProcessingTimeMilliseconds();
});
```

------
#### [ .NET ]

```
using Amazon.IonDotnet.Builders;
using Amazon.IonDotnet.Tree;
using Amazon.QLDB.Driver;
using IAsyncResult = Amazon.QLDB.Driver.IAsyncResult;

IIonValue ionFirstName = IonLoader.Default.Load("Jim");

await driver.Execute(async txn =>
{
    IAsyncResult result = await txn.Execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);

    // Iterate through stream cursor to accumulate processing time.
    await foreach(IIonValue ionValue in result)
    {
        // User code here to handle results.
        // Warning: It is bad practice to rely on results within a lambda block, unless
        // it is to check the state of a result. This is because lambdas are retryable.
    }

    var timingInformation = result.GetTimingInformation();
    var processingTimeMilliseconds = timingInformation?.ProcessingTimeMilliseconds;
});
```

**참고**  
동기 코드로 변환하려면 `await` 및 `async` 키워드를 제거하고 `IAsyncResult` 유형을 `IResult`로 변경하세요.

------
#### [ Go ]

```
import (
    "context"
    "fmt"
    "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver"
)

driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    result, err := txn.Execute("SELECT * FROM testTable WHERE firstName = ?", "Jim")

    if err != nil {
        panic(err)
    }

    for result.Next(txn) {
        // User code here to handle results
    }

    timingInformation := result.GetTimingInformation()
    processingTimeMilliseconds := *timingInformation.GetProcessingTimeMilliseconds()
    fmt.Println(processingTimeMilliseconds)
    return nil, nil
})
```

------
#### [ Node.js ]

```
import { ResultReadable, TimingInformation, TransactionExecutor } from "amazon-qldb-driver-nodejs";

await driver.executeLambda(async (txn: TransactionExecutor) => {
    const result: ResultReadable = await txn.executeAndStreamResults("SELECT * FROM testTable WHERE firstName = ?", "Jim");

    for await (const chunk of result) {
        // User code here to handle results
    }

    const timingInformation: TimingInformation = result.getTimingInformation();
    const processingTimeMilliseconds: number = timingInformation.getProcessingTimeMilliseconds();
});
```

------
#### [ Python ]

```
def get_processing_time_milliseconds(transaction_executor):
    cursor = transaction_executor.execute_statement("SELECT * FROM testTable WHERE firstName = ?", "Jim")

    for row in cursor:
        # User code here to handle results
        pass

    timing_information = cursor.get_timing_information()
    processing_time_milliseconds = timing_information.get('ProcessingTimeMilliseconds')

qldb_driver.execute_lambda(lambda txn: get_processing_time_milliseconds(txn))
```

------

다음 코드 예제는 문 결과의 *버퍼된 커서*에서 처리 시간을 가져오는 방법을 보여줍니다. 그러면 `ExecuteStatement` 및 `FetchPage` 요청의 총 처리 시간이 반환됩니다.

------
#### [ Java ]

```
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonValue;
import com.amazon.ion.system.IonSystemBuilder;
import software.amazon.qldb.Result;
import software.amazon.qldb.TimingInformation;

IonSystem ionSystem = IonSystemBuilder.standard().build();
IonValue ionFirstName = ionSystem.newString("Jim");

Result result = driver.execute(txn -> {
    return txn.execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);
});

TimingInformation timingInformation = result.getTimingInformation();
long processingTimeMilliseconds = timingInformation.getProcessingTimeMilliseconds();
```

------
#### [ .NET ]

```
using Amazon.IonDotnet.Builders;
using Amazon.IonDotnet.Tree;
using Amazon.QLDB.Driver;
using IAsyncResult = Amazon.QLDB.Driver.IAsyncResult;

IIonValue ionFirstName = IonLoader.Default.Load("Jim");

IAsyncResult result = await driver.Execute(async txn =>
{
    return await txn.Execute("SELECT * FROM testTable WHERE firstName = ?", ionFirstName);
});

var timingInformation = result.GetTimingInformation();
var processingTimeMilliseconds = timingInformation?.ProcessingTimeMilliseconds;
```

**참고**  
동기 코드로 변환하려면 `await` 및 `async` 키워드를 제거하고 `IAsyncResult` 유형을 `IResult`로 변경하세요.

------
#### [ Go ]

```
import (
    "context"
    "fmt"
    "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver"
)

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    result, err :=  txn.Execute("SELECT * FROM testTable WHERE firstName = ?", "Jim")
    if err != nil {
        return nil, err
    }
    return txn.BufferResult(result)
})

if err != nil {
    panic(err)
}

qldbResult := result.(*qldbdriver.BufferedResult)
timingInformation := qldbResult.GetTimingInformation()
processingTimeMilliseconds := *timingInformation.GetProcessingTimeMilliseconds()
fmt.Println(processingTimeMilliseconds)
```

------
#### [ Node.js ]

```
import { Result, TimingInformation, TransactionExecutor } from "amazon-qldb-driver-nodejs";

const result: Result = await driver.executeLambda(async (txn: TransactionExecutor) => {
    return await txn.execute("SELECT * FROM testTable WHERE firstName = ?", "Jim");
});

const timingInformation: TimingInformation = result.getTimingInformation();
const processingTimeMilliseconds: number = timingInformation.getProcessingTimeMilliseconds();
```

------
#### [ Python ]

```
cursor = qldb_driver.execute_lambda(
    lambda txn: txn.execute_statement("SELECT * FROM testTable WHERE firstName = ?", "Jim"))

timing_information = cursor.get_timing_information()
processing_time_milliseconds = timing_information.get('ProcessingTimeMilliseconds')
```

------

**참고**  
스트림 커서는 결과 집합의 페이지를 매기므로 상태를 추적할 수 있습니다. 따라서 `getConsumedIOs` 및 `getTimingInformation` 연산은 호출한 시점부터 누적된 지표를 반환합니다.  
버퍼링된 커서는 결과 집합을 메모리에 버퍼링하고 총 누적된 지표를 반환합니다.

시스템 카탈로그를 쿼리하는 방법을 알아보려면 [시스템 카탈로그 쿼리](working.catalog.md) 섹션을 참조하세요.

# 시스템 카탈로그 쿼리
<a name="working.catalog"></a>

**중요**  
지원 종료 알림: 기존 고객은 07/31/2025에 지원이 종료될 때까지 Amazon QLDB를 사용할 수 있습니다. 자세한 내용은 [Amazon QLDB 원장을 Amazon Aurora PostgreSQL로 마이그레이션](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)을 참조하세요.

Amazon QLDB 원장에 생성하는 각 테이블에는 시스템에서 할당한 고유 ID가 있습니다. 시스템 카탈로그 테이블 `information_schema.user_tables`을 쿼리하여 테이블 ID, 인덱스 목록 및 기타 메타데이터를 찾을 수 있습니다.

시스템에서 할당한 모든 ID는 Base62로 인코딩된 문자열로 각각 표시되는 범용 고유 식별자(UUID)입니다. 자세한 내용은 [Amazon QLDB의 고유 ID](working.unique-id.md)을 참조하세요.

다음 예제는 `VehicleRegistration` 테이블의 메타데이터 속성을 반환하는 쿼리 결과를 보여줍니다.

```
SELECT * FROM information_schema.user_tables
WHERE name = 'VehicleRegistration'
```

```
{
    tableId: "5PLf9SXwndd63lPaSIa0O6",
    name: "VehicleRegistration",
    indexes: [
        { indexId: "Djg2nt0yIs2GY0T29Kud1z", expr: "[VIN]", status: "ONLINE" },
        { indexId: "4tPW3fUhaVhDinRgKRLhGU", expr: "[LicensePlateNumber]", status: "BUILDING" }
    ],
    status: "ACTIVE"
}
```

**테이블 메타데이터 필드**
+ `tableId` - 테이블의 고유 ID입니다.
+ `name` - 테이블 이름입니다.
+ `indexes` - 테이블의 인덱스 목록입니다.
  + `indexId` - 인덱스의 고유 ID입니다.
  + `expr` - 인덱싱된 문서 경로입니다. 이 필드는 `[fieldName]` 형식의 문자열입니다.
  + `status` - 인덱스의 현재 상태(`BUILDING`, `FINALIZING`, `ONLINE`, `FAILED`, 또는`DELETING`)입니다. QLDB는 상태가 `ONLINE`이면 쿼리에 인덱스를 사용하지 않습니다.
  + `message` - 인덱스에 `FAILED` 상태가 있는 이유를 설명하는 오류 메시지입니다. 이 필드는 실패한 인덱스에만 포함됩니다.
+ `status` - 테이블의 현재 상태(`ACTIVE` 또는`INACTIVE`)입니다. 테이블을 `DROP` 할 경우 `INACTIVE`이 됩니다.

`DROP TABLE` 및 `UNDROP TABLE` 문을 사용하여 테이블을 관리하는 방법을 알아보려면 [테이블 관리](working.manage-tables.md)으로 이동하세요.

# 테이블 관리
<a name="working.manage-tables"></a>

**중요**  
지원 종료 알림: 기존 고객은 07/31/2025에 지원이 종료될 때까지 Amazon QLDB를 사용할 수 있습니다. 자세한 내용은 [Amazon QLDB 원장을 Amazon Aurora PostgreSQL로 마이그레이션](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)을 참조하세요.

이 단원에서는 Amazon QLDB에서 `DROP TABLE` 및 `UNDROP TABLE` 문을 사용하여 테이블을 관리하는 방법을 설명합니다. 또한 테이블을 생성할 때 태그를 지정하는 방법도 설명합니다. 생성할 수 있는 활성 테이블 수와 총 테이블 수에 대한 할당량은 [Amazon QLDB 할당량 및 제한](limits.md#limits.fixed)에 정의되어 있습니다.

**Topics**
+ [테이블 생성 시 태그 지정](#working.manage-tables.tags)
+ [테이블 삭제](#working.manage-tables.drop)
+ [비활성 테이블의 기록 쿼리](#working.history.inactive-table)
+ [테이블 재활성화](#working.history.undrop-table)

## 테이블 생성 시 태그 지정
<a name="working.manage-tables.tags"></a>

**참고**  
 테이블 생성 시 태그 지정은 현재 ‭`STANDARD` 권한 모드의 원장에만 지원됩니다.

테이블 리소스에 태그를 지정할 수 있습니다. 기존 테이블의 태그를 관리하려면 AWS Management Console 또는 API 작업 `TagResource`, `UntagResource`, `ListTagsForResource`을 사용하세요. 자세한 내용은 [Amazon QLDB 리소스 태그 지정](tagging.md)을 참조하세요.

테이블을 생성하는 동안 QLDB 콘솔을 사용하거나 `CREATE TABLE` PartiQL 문에 테이블 태그를 지정하여 테이블 태그를 정의할 수도 있습니다. 다음 예제에서는 `environment=production` 태그가 있는 `Vehicle`이라는 테이블을 생성합니다.

```
CREATE TABLE Vehicle WITH (aws_tags = `{'environment': 'production'}`)
```

리소스를 생성하는 동안 태그를 지정하면 리소스 생성 후 사용자 지정 태그 지정 스크립트를 실행할 필요가 없습니다. 테이블에 태그가 지정된 후 해당 태그를 기반으로 테이블에 대한 액세스를 제어할 수 있습니다. 예를 들어 특정 태그가 있는 테이블에만 전체 액세스 권한을 부여할 수 있습니다. JSON 정책의 예는 [테이블 태그를 기반으로 하는 모든 작업에 대한 전체 액세스 권한](security_iam_id-based-policy-examples.md#security_iam_id-based-policy-examples-full-tags) 섹션을 참조하세요.

## 테이블 삭제
<a name="working.manage-tables.drop"></a>

테이블을 삭제하려면 기본 [DROP TABLE](ql-reference.drop-table.md) 명령문을 사용하세요. QLDB에 테이블을 삭제하면 해당 테이블이 비활성화됩니다.

예를 들어 다음 명령문은 `VehicleRegistration` 테이블을 비활성화합니다.

```
DROP TABLE VehicleRegistration
```

`DROP TABLE` 명령문은 테이블의 시스템 할당 ID를 반환합니다. 이제 `VehicleRegistration`의 상태는 시스템 카탈로그 테이블 [information\$1schema.user\$1tables](working.catalog.md)에서 `INACTIVE`여야 합니다.

```
SELECT status FROM information_schema.user_tables
WHERE name = 'VehicleRegistration'
```

## 비활성 테이블의 기록 쿼리
<a name="working.history.inactive-table"></a>

테이블 이름 외에도 테이블 ID를 첫 번째 입력 인수로 사용하여 QLDB [기록 함수](working.history.md#working.history.function)를 쿼리할 수도 있습니다. 비활성 테이블의 기록을 쿼리하려면 테이블 ID를 사용해야 합니다. 테이블이 비활성화되면 더 이상 테이블 이름으로 테이블 기록을 쿼리할 수 없습니다.

먼저 시스템 카탈로그 테이블을 쿼리하여 테이블 ID를 찾으세요. 예를 들어 다음 쿼리는 `VehicleRegistration` 테이블의 `tableId`를 반환합니다.

```
SELECT tableId FROM information_schema.user_tables
WHERE name = 'VehicleRegistration'
```

그러면 이 ID를 사용하여 [개정 기록 쿼리](working.history.md)에서 동일한 기록 쿼리를 실행할 수 있습니다. 다음은 테이블 ID `5PLf9SXwndd63lPaSIa0O6`에서 문서 ID `ADR2Ll1fGsU4Jr4EqTdnQF` 기록을 쿼리하는 예제입니다. 테이블 ID는 작은 따옴표로 묶여야 하는 문자열 리터럴입니다.

```
--replace both the table and document IDs with your values
SELECT * FROM history('5PLf9SXwndd63lPaSIa0O6', `2000T`, `2019-06-05T23:59:59Z`) AS h
WHERE h.metadata.id = 'ADR2Ll1fGsU4Jr4EqTdnQF'
```

## 테이블 재활성화
<a name="working.history.undrop-table"></a>

QLDB에서 테이블을 비활성화한 후 [UNDROP TABLE](ql-reference.undrop-table.md) 명령문을 사용하여 테이블을 다시 활성화할 수 있습니다.

먼저 `information_schema.user_tables`에서 테이블 ID를 찾으세요. 예를 들어 다음 쿼리는 `VehicleRegistration` 테이블의 `tableId`를 반환합니다. 상태는 `INACTIVE`과 같아야 합니다.

```
SELECT tableId FROM information_schema.user_tables
WHERE name = 'VehicleRegistration'
```

그런 다음 이 ID를 사용하여 테이블을 다시 활성화하세요. 다음은 테이블 ID `5PLf9SXwndd63lPaSIa0O6`의 *삭제를 취소하는* 예제입니다. 이 경우 테이블 ID는 큰따옴표로 묶는 고유 식별자입니다.

```
UNDROP TABLE "5PLf9SXwndd63lPaSIa0O6"
```

이제 `VehicleRegistration`의 상태가 `ACTIVE`여야 합니다.

인덱스를 생성하고, 설명하고, 삭제하는 방법을 알아보려면 [인덱스 관리](working.manage-indexes.md)으로 이동하세요.

# 인덱스 관리
<a name="working.manage-indexes"></a>

**중요**  
지원 종료 알림: 기존 고객은 07/31/2025에 지원이 종료될 때까지 Amazon QLDB를 사용할 수 있습니다. 자세한 내용은 [Amazon QLDB 원장을 Amazon Aurora PostgreSQL로 마이그레이션](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)을 참조하세요.

이 섹션에서는 Amazon QLDB에서 인덱스를 생성, 설명 및 삭제하는 방법을 설명합니다. 생성할 수 있는 테이블당 인덱스 수에 대한 할당량은 [Amazon QLDB 할당량 및 제한](limits.md#limits.fixed)에 정의되어 있습니다.

**Topics**
+ [인덱스 생성](#working.manage-indexes.create)
+ [인덱스 설명](#working.manage-indexes.describe)
+ [인덱스 삭제](#working.manage-indexes.drop)
+ [일반적인 오류](#working.manage-indexes.errors)

## 인덱스 생성
<a name="working.manage-indexes.create"></a>

[테이블 및 인덱스 생성](working.create.md#working.create.tables-indexes)에도 설명된 대로 [CREATE INDEX](ql-reference.create-index.md) 문을 사용하여 다음과 같이 지정된 최상위 필드의 인덱스를 테이블에 생성할 수 있습니다. 테이블 이름과 인덱싱된 필드 이름은 모두 대소문자를 구분합니다.

```
CREATE INDEX ON VehicleRegistration (VIN)
```

```
CREATE INDEX ON VehicleRegistration (LicensePlateNumber)
```

테이블에 생성하는 각 인덱스에는 시스템에서 할당한 고유 ID가 있습니다. 이 인덱스 ID를 찾으려면 다음 [인덱스 설명](#working.manage-indexes.describe) 섹션을 참조하세요.

**중요**  
QLDB는 문서를 효율적으로 조회하기 위한 인덱스가 필요합니다. 인덱스가 없으면 QLDB는 문서를 읽을 때 전체 테이블 스캔을 수행해야 합니다. 이로 인해 동시성 충돌 및 트랜잭션 시간 초과를 포함하여 대규모 테이블에서 성능 문제가 발생할 수 있습니다.  
인덱싱된 필드 또는 문서 ID(예: `=` 또는 `IN`)에서 동등 *연산자*를 사용하여 `WHERE` 조건자 절이 포함된 문을 실행하는 것이 좋습니다. 자세한 내용은 [쿼리 성능 최적화](working.optimize.md)을 참조하세요.

인덱스를 생성할 때는 다음 제약 조건에 유의하세요.
+ 인덱스는 단일 최상위 필드에만 생성할 수 있습니다. 복합, 중첩, 고유 및 함수 기반 인덱스는 지원되지 않습니다.
+ `list` 및 `struct`를 비롯한 모든 [Ion 데이터 유형](ql-reference.data-types.md)에 대해 인덱스를 생성할 수 있습니다. 그러나 Ion 유형에 관계없이 전체 Ion 값이 같아야만 인덱스 조회를 수행할 수 있습니다. 예를 들어 `list` 형식을 인덱스로 사용하는 경우 목록 내에서 한 항목씩 인덱스 검색을 수행할 수 없습니다.
+ 동등 조건자(예: `WHERE indexedField = 123` 또는 `WHERE indexedField IN (456, 789)`)를 사용할 때만 쿼리 성능이 향상됩니다.

  QLDB는 쿼리 조건자의 부등을 인정하지 않습니다. 따라서 범위 필터링된 스캔은 구현되지 않습니다.
+ 인덱싱된 필드 이름은 대소문자를 구분하며 최대 128자입니다.
+ QLDB에서의 인덱스 생성은 비동기적으로 이루어집니다. 비어 있지 않은 테이블에서 인덱스 빌드를 완료하는 데 걸리는 시간은 테이블 크기에 따라 다릅니다. 자세한 내용은 [인덱스 관리](#working.manage-indexes)을 참조하세요.

## 인덱스 설명
<a name="working.manage-indexes.describe"></a>

QLDB에서의 인덱스 생성은 비동기적으로 이루어집니다. 비어 있지 않은 테이블에서 인덱스 빌드를 완료하는 데 걸리는 시간은 테이블 크기에 따라 다릅니다. 인덱스 빌드 상태를 확인하려면 시스템 카탈로그 테이블 [information\$1schema.user\$1tables](working.catalog.md)을 쿼리하면 됩니다.

예를 들어, 다음 명령문은 시스템 카탈로그에서 `VehicleRegistration` 테이블의 모든 인덱스를 쿼리합니다.

```
SELECT VALUE indexes
FROM information_schema.user_tables info, info.indexes indexes
WHERE info.name = 'VehicleRegistration'
```

```
{
    indexId: "Djg2nt0yIs2GY0T29Kud1z",
    expr: "[VIN]",
    status: "ONLINE"
},
{
    indexId: "4tPW3fUhaVhDinRgKRLhGU",
    expr: "[LicensePlateNumber]",
    status: "FAILED",
    message: "aws.ledger.errors.InvalidEntityError: Document contains multiple values for indexed field: LicensePlateNumber"
}
```

**인덱스 필드**
+ `indexId` - 인덱스의 고유 ID입니다.
+ `expr` - 인덱싱된 문서 경로입니다. 이 필드는 `[fieldName]` 형식의 문자열입니다.
+ `status` - 인덱스의 현재 상태입니다. 인덱스의 상태는 다음 값 중 하나일 수 있습니다.
  + `BUILDING` - 테이블의 인덱스를 적극적으로 빌드하고 있습니다.
  + `FINALIZING` - 인덱스 빌드를 완료하고 활성화하여 사용하기 시작합니다.
  + `ONLINE` - 활성 상태이며 쿼리에 사용할 준비가 되었습니다. QLDB는 상태가 온라인 상태가 될 때까지 쿼리에 인덱스를 사용하지 않습니다.
  + `FAILED` - 복구할 수 없는 오류로 인해 인덱스를 빌드할 수 없습니다. 이 상태의 인덱스는 여전히 테이블당 인덱스 할당량에 포함됩니다. 자세한 내용은 [일반적인 오류](#working.manage-indexes.errors)을 참조하세요.
  + `DELETING` - 사용자가 인덱스를 삭제한 후 인덱스를 적극적으로 삭제하고 있습니다.
+ `message` - 인덱스에 `FAILED` 상태가 있는 이유를 설명하는 오류 메시지입니다. 이 필드는 실패한 인덱스에만 포함됩니다.

### 콘솔 사용
<a name="working.manage-indexes.describe.con"></a>

 AWS Management Console 를 사용하여 인덱스의 상태를 확인할 수도 있습니다.

**인덱스의 상태를 확인하려면(콘솔)**

1. 에 로그인 AWS Management Console하고 [https://console.aws.amazon.com/qldb](https://console.aws.amazon.com/qldb) Amazon QLDB 콘솔을 엽니다.

1. 탐색 창에서 **원장**을 선택합니다.

1. **원장** 목록에서 관리하려는 인덱스가 있는 원장 이름을 선택합니다.

1. 원장 세부정보 페이지의 **테이블** 탭에서 인덱스를 확인할 테이블 이름을 선택합니다.

1. 테이블 세부정보 페이지에서 **인덱싱된 필드** 카드를 찾습니다. **인덱스 상태** 열은 테이블의 각 인덱스의 현재 상태를 표시합니다.

## 인덱스 삭제
<a name="working.manage-indexes.drop"></a>

[DROP INDEX](ql-reference.drop-index.md) 명령문을 사용하여 인덱스를 삭제합니다. 인덱스를 삭제하면 테이블에서 영구적으로 삭제됩니다.

먼저 인덱스 ID를 `information_schema.user_tables`에서 찾으세요. 예를 들어, 다음 쿼리는 `VehicleRegistration` 테이블에서 인덱싱된 `LicensePlateNumber` 필드의 `indexId`을 반환합니다.

```
SELECT indexes.indexId
FROM information_schema.user_tables info, info.indexes indexes
WHERE info.name = 'VehicleRegistration' and indexes.expr = '[LicensePlateNumber]'
```

그런 다음 이 ID를 사용하여 인덱스를 삭제하세요. 다음은 인덱스 ID `4tPW3fUhaVhDinRgKRLhGU`를 삭제하는 예제입니다. 인덱스 ID는 큰따옴표로 묶어야 하는 고유 식별자입니다.

```
DROP INDEX "4tPW3fUhaVhDinRgKRLhGU" ON VehicleRegistration WITH (purge = true)
```

**참고**  
이 `WITH (purge = true)` 절은 모든 `DROP INDEX` 명령문에 필수이며 `true`는 현재 지원되는 유일한 값입니다.  
`purge` 키워드는 대소문자를 구분하며 모두 소문자여야 합니다.

### 콘솔 사용
<a name="working.manage-indexes.drop.con"></a>

 AWS Management Console 를 사용하여 인덱스를 삭제할 수도 있습니다.

**인덱스를 삭제하려면(콘솔)**

1. 에 로그인 AWS Management Console하고 [https://console.aws.amazon.com/qldb](https://console.aws.amazon.com/qldb) Amazon QLDB 콘솔을 엽니다.

1. 탐색 창에서 **원장**을 선택합니다.

1. **원장** 목록에서 관리하려는 인덱스가 있는 원장 이름을 선택합니다.

1. 원장 세부정보 페이지의 **테이블** 탭에서 인덱스를 삭제할 테이블 이름을 선택합니다.

1. 테이블 세부정보 페이지에서 **인덱싱된 필드** 카드를 찾습니다. 삭제하려는 인덱스를 선택한 다음 **인덱스 삭제**를 선택합니다.

## 일반적인 오류
<a name="working.manage-indexes.errors"></a>

이 섹션에서는 인덱스를 만들 때 발생할 수 있는 일반적인 오류를 설명하고 가능한 해결 방법을 제안합니다.

**참고**  
`FAILED` 상태의 인덱스는 여전히 테이블당 인덱스 할당량에 포함됩니다. 또한 실패한 인덱스는 테이블에서 인덱스 생성 실패를 초래한 문서를 수정하거나 삭제하는 것을 방지합니다.  
할당량에서 인덱스를 제거하려면 명시적으로 인덱스를 [제거](#working.manage-indexes.drop)해야 합니다.

**문서에 인덱싱된 필드 *fieldName*에 대한 여러 값이 포함되어 있습니다.**  
테이블에 동일한 필드에 대해 여러 값(즉, 중복된 필드 이름)이 있는 문서가 포함되어 있기 때문에 QLDB는 지정된 필드 이름에 대한 인덱스를 생성할 수 없습니다.  
먼저 실패한 인덱스를 삭제해야 합니다. 그런 다음 인덱스 생성을 다시 시도하기 전에 테이블의 모든 문서에 각 필드 이름에 대해 하나의 값만 있는지 확인하세요. 중복이 없는 다른 필드에 대한 인덱스를 생성할 수도 있습니다.  
또한 테이블에 이미 인덱싱된 필드에 대해 여러 값이 포함된 문서를 삽입하려고 할 경우 QLDB는 이 오류를 반환합니다.

**인덱스 제한 초과: 테이블 *TableName에는* 이미 *n개*의 인덱스가 있으며 더 만들 수 없습니다.**  
QLDB는 실패한 인덱스를 포함하여 테이블당 인덱스를 5개로 제한합니다. 새 인덱스를 생성하기 전에 기존 인덱스를 삭제해야 합니다.

**식별자가 *indexId*인 정의된 인덱스가 없습니다.**  
지정된 테이블과 인덱스 ID 조합에 대해 존재하지 않는 인덱스를 삭제하려고 했습니다. 기존 인덱스를 확인하는 방법을 알아보려면 [인덱스 설명](#working.manage-indexes.describe)을 참조하세요.

# Amazon QLDB의 고유 ID
<a name="working.unique-id"></a>

**중요**  
지원 종료 알림: 기존 고객은 07/31/2025에 지원이 종료될 때까지 Amazon QLDB를 사용할 수 있습니다. 자세한 내용은 [Amazon QLDB 원장을 Amazon Aurora PostgreSQL로 마이그레이션](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)을 참조하세요.

이 섹션에서는 Amazon QLDB에 있는 시스템 할당 고유 식별자의 속성 및 사용 지침을 설명합니다. 또한 QLDB 고유 ID의 몇 가지 예를 제공합니다.

**Topics**
+ [속성](#working.unique-id.properties)
+ [사용법](#working.unique-id.usage)
+ [예시](#working.unique-id.examples)

## 속성
<a name="working.unique-id.properties"></a>

QLDB의 모든 시스템 할당 ID는 범용 고유 식별자(UUID)입니다. 각 ID에는 다음 속성이 있습니다.
+ 128비트 UUID 숫자
+ Base62로 인코딩된 텍스트로 표시됨
+ 22자의 고정 길이 영숫자 문자열(예:`3Qv67yjXEwB9SjmvkuG6Cp`)

## 사용법
<a name="working.unique-id.usage"></a>

애플리케이션에서 QLDB 고유 ID를 사용하는 경우 다음 지침에 유의하세요.

**해야 할 일**
+ ID를 문자열로 취급합니다.

**하지 않아야 할 일**
+ 문자열을 디코딩하려고 시도합니다.
+ 문자열에 의미론적 의미를 부여합니다(예: 시간 구성 요소 도출).
+ 문자열을 의미론적 순서로 정렬합니다.

## 예시
<a name="working.unique-id.examples"></a>

다음 속성은 QLDB의 고유 ID의 몇 가지 예입니다.
+ 문서 ID
+ 인덱스 ID
+ 스트랜드 ID
+ 테이블 ID
+ 트랜잭션 ID