

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

# Praktik terbaik untuk menangani pembaruan bersamaan di DynamoDB
<a name="BestPractices_ImplementingVersionControl"></a>

Dalam sistem terdistribusi, beberapa proses atau pengguna dapat mencoba memodifikasi data yang sama pada saat yang sama. Tanpa kontrol konkurensi, penulisan bersamaan ini dapat menyebabkan pembaruan yang hilang, data yang tidak konsisten, atau kondisi balapan. DynamoDB menyediakan beberapa mekanisme untuk membantu Anda mengelola akses bersamaan dan menjaga integritas data.

**catatan**  
Operasi penulisan individual seperti `UpdateItem` atom dan selalu beroperasi pada versi item terbaru, terlepas dari konkurensi. Strategi penguncian diperlukan ketika aplikasi Anda harus membaca item dan kemudian menulisnya kembali berdasarkan nilai baca ( read-modify-writesiklus), karena proses lain dapat memodifikasi item antara baca dan tulis.

Ada dua strategi utama untuk menangani pembaruan bersamaan:
+ **Penguncian optimis — Mengasumsikan** konflik jarang terjadi. Ini memungkinkan akses bersamaan dan mendeteksi konflik pada waktu penulisan menggunakan penulisan bersyarat. Jika konflik terdeteksi, penulisan gagal dan aplikasi dapat mencoba lagi.
+ **Penguncian pesimis — Mengasumsikan** konflik mungkin terjadi. Ini mencegah akses bersamaan dengan memperoleh akses eksklusif ke sumber daya sebelum memodifikasinya. Proses lain harus menunggu sampai kunci dilepaskan.

Tabel berikut merangkum pendekatan yang tersedia di DynamoDB:


| Pendekatan | Mekanisme | Terbaik untuk | 
| --- | --- | --- | 
| Penguncian optimis | Atribut versi\$1penulisan bersyarat | Pertikaian rendah, percobaan ulang yang murah | 
| Penguncian pesimis (transaksi) | TransactWriteItems | Atomisitas multi-item, pertengkaran sedang | 
| Penguncian pesimis (klien kunci) | Meja kunci khusus dengan sewa dan detak jantung | Alur kerja yang berjalan lama, koordinasi terdistribusi | 

# Penguncian Optimis dengan nomor versi
<a name="BestPractices_OptimisticLocking"></a>

Penguncian optimis adalah strategi yang mendeteksi konflik pada waktu tulis daripada mencegahnya. Setiap item menyertakan atribut versi yang bertambah dengan setiap pembaruan. Saat memperbarui item, Anda menyertakan [ekspresi kondisi](Expressions.ConditionExpressions.md) yang memeriksa apakah nomor versi cocok dengan nilai yang terakhir dibaca aplikasi Anda. Jika proses lain memodifikasi item sementara itu, kondisi gagal dan DynamoDB mengembalikan a. `ConditionalCheckFailedException`

## Kapan menggunakan penguncian optimis
<a name="BestPractices_OptimisticLocking_WhenToUse"></a>

Penguncian optimis sangat cocok ketika:
+ Beberapa pengguna atau proses dapat memperbarui item yang sama, tetapi konflik jarang terjadi.
+ Mencoba kembali penulisan yang gagal tidak mahal untuk aplikasi Anda.
+ Anda ingin menghindari overhead dan kompleksitas mengelola kunci terdistribusi.

Contoh umum termasuk pembaruan inventaris e-commerce, platform pengeditan kolaboratif, dan catatan transaksi keuangan.

## Pengorbanan
<a name="BestPractices_OptimisticLocking_Tradeoffs"></a>

**Coba lagi overhead dalam pertengkaran tinggi**  
Dalam lingkungan konkurensi tinggi, kemungkinan konflik meningkat, berpotensi menyebabkan percobaan ulang dan biaya penulisan yang lebih tinggi.

**Kompleksitas implementasi**  
Menambahkan kontrol versi ke item dan menangani pemeriksaan bersyarat menambah kompleksitas logika aplikasi. AWS SDK for Java v2 Enhanced Client menyediakan dukungan bawaan melalui [https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE)anotasi, yang secara otomatis mengelola nomor versi untuk Anda.

## Desain pola
<a name="BestPractices_OptimisticLocking_PatternDesign"></a>

Sertakan atribut versi di setiap item. Berikut adalah desain skema sederhana:
+ Kunci partisi — Pengidentifikasi unik untuk setiap item (misalnya,`ItemId`).
+ Atribut:
  + `ItemId`— Pengidentifikasi unik untuk item tersebut.
  + `Version`— Bilangan bulat yang mewakili nomor versi item.
  + `QuantityLeft`— Inventaris item yang tersisa.

Ketika item pertama kali dibuat, `Version` atribut diatur ke 1. Dengan setiap pembaruan, nomor versi bertambah 1.


| ItemId (kunci partisi) | Versi | QuantityLeft | 
| --- | --- | --- | 
| Pisang | 1 | 10 | 
| Apel | 1 | 5 | 
| Jeruk | 1 | 7 | 

## Implementasi
<a name="BestPractices_OptimisticLocking_Implementation"></a>

Untuk menerapkan penguncian optimis, ikuti langkah-langkah ini:

1. Baca versi item saat ini.

   ```
   def get_item(item_id):
       response = table.get_item(Key={'ItemID': item_id})
       return response['Item']
   
   item = get_item('Bananas')
   current_version = item['Version']
   ```

1. Perbarui item menggunakan ekspresi kondisi yang memeriksa nomor versi.

   ```
   def update_item(item_id, qty_bought, current_version):
       try:
           response = table.update_item(
               Key={'ItemID': item_id},
               UpdateExpression="SET QuantityLeft = QuantityLeft - :qty, Version = :new_v",
               ConditionExpression="Version = :expected_v",
               ExpressionAttributeValues={
                   ':qty': qty_bought,
                   ':new_v': current_version + 1,
                   ':expected_v': current_version
               },
               ReturnValues="UPDATED_NEW"
           )
           return response
       except ClientError as e:
           if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
               print("Version conflict: another process updated this item.")
           raise
   ```

1. Tangani konflik dengan mencoba kembali dengan bacaan baru.

   Setiap percobaan lagi membutuhkan pembacaan tambahan, jadi batasi jumlah total percobaan ulang.

   ```
   def update_with_retry(item_id, qty_bought, max_retries=3):
       for attempt in range(max_retries):
           item = get_item(item_id)
           try:
               return update_item(item_id, qty_bought, item['Version'])
           except ClientError as e:
               if e.response['Error']['Code'] != 'ConditionalCheckFailedException':
                   raise
               print(f"Retry {attempt + 1}/{max_retries}")
       raise Exception("Update failed after maximum retries.")
   ```

Untuk aplikasi Java, AWS SDK for Java v2 Enhanced Client menyediakan dukungan penguncian optimis bawaan melalui [https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE)anotasi, yang secara otomatis mengelola nomor versi untuk Anda.

Untuk informasi selengkapnya tentang ekspresi kondisi, lihat[Contoh CLI ekspresi kondisi DynamoDB](Expressions.ConditionExpressions.md).

# Penguncian pesimis dengan transaksi DynamoDB
<a name="BestPractices_PessimisticLocking"></a>

Transaksi [DynamoDB](transactions.md) memberikan pendekatan untuk operasi all-or-nothing yang dikelompokkan. Saat Anda menggunakan`TransactWriteItems`, DynamoDB memantau semua item dalam transaksi. Jika ada item yang dimodifikasi oleh operasi lain selama transaksi, seluruh transaksi dibatalkan dan DynamoDB mengembalikan a. `TransactionCanceledException` Perilaku ini memberikan bentuk kontrol konkurensi pesimis karena modifikasi bersamaan yang bertentangan dicegah daripada terdeteksi setelah fakta.

## Kapan menggunakan transaksi untuk mengunci
<a name="BestPractices_PessimisticLocking_WhenToUse"></a>

Transaksi sangat cocok ketika:
+ Anda perlu memperbarui beberapa item secara atom, baik dalam tabel yang sama atau di seluruh tabel.
+ Logika bisnis Anda membutuhkan all-or-nothing semantik — baik semua perubahan berhasil atau tidak ada yang diterapkan.

Contoh umum termasuk mentransfer dana antar akun, menempatkan pesanan yang memperbarui inventaris dan tabel pesanan, dan bertukar item antar pemain dalam permainan.

## Pengorbanan
<a name="BestPractices_PessimisticLocking_Tradeoffs"></a>

**Biaya tulis lebih tinggi**  
Untuk item hingga 1 KB, transaksi mengkonsumsi 2 WCUs per item (satu untuk mempersiapkan, satu untuk melakukan), dibandingkan dengan 1 WCU untuk penulisan standar.

**Batas barang**  
Satu transaksi dapat mencakup hingga 100 tindakan di satu atau lebih tabel.

**Sensitivitas konflik**  
Jika ada item dalam transaksi yang dimodifikasi oleh operasi lain, seluruh transaksi gagal. Dalam skenario pertengkaran tinggi, ini dapat menyebabkan pembatalan yang sering terjadi.

## Implementasi
<a name="BestPractices_PessimisticLocking_Implementation"></a>

Contoh berikut digunakan `TransactWriteItems` untuk mentransfer inventaris antara dua item secara atom. Jika proses lain memodifikasi salah satu item selama transaksi, seluruh operasi dibatalkan.

```
import boto3

client = boto3.client('dynamodb')

def transfer_inventory(source_id, target_id, quantity):
    try:
        client.transact_write_items(
            TransactItems=[
                {
                    'Update': {
                        'TableName': 'Inventory',
                        'Key': {'ItemID': {'S': source_id}},
                        'UpdateExpression': 'SET QuantityLeft = QuantityLeft - :qty',
                        'ConditionExpression': 'QuantityLeft >= :qty',
                        'ExpressionAttributeValues': {
                            ':qty': {'N': str(quantity)}
                        }
                    }
                },
                {
                    'Update': {
                        'TableName': 'Inventory',
                        'Key': {'ItemID': {'S': target_id}},
                        'UpdateExpression': 'SET QuantityLeft = QuantityLeft + :qty',
                        'ExpressionAttributeValues': {
                            ':qty': {'N': str(quantity)}
                        }
                    }
                }
            ]
        )
        return True
    except client.exceptions.TransactionCanceledException as e:
        print(f"Transaction canceled: {e}")
        return False
```

Dalam contoh ini, ekspresi kondisi memeriksa apakah inventaris yang cukup ada, tetapi tidak ada atribut versi yang diperlukan. DynamoDB secara otomatis membatalkan transaksi jika ada item dalam transaksi yang dimodifikasi oleh operasi lain antara fase persiapan dan komit. Inilah yang memberikan kontrol konkurensi pesimistis — modifikasi bersamaan yang bertentangan dicegah oleh transaksi itu sendiri.

**catatan**  
Anda dapat menggabungkan transaksi dengan penguncian optimis dengan menambahkan pemeriksaan versi sebagai ekspresi kondisi tambahan. Ini memberikan lapisan perlindungan tambahan tetapi tidak diperlukan untuk transaksi untuk mendeteksi konflik.

Untuk informasi selengkapnya, lihat [Mengelola alur kerja kompleks dengan DynamoDB Transactions](transactions.md).

# Penguncian terdistribusi dengan DynamoDB Lock Client
<a name="BestPractices_DistributedLocking"></a>

Untuk aplikasi yang memerlukan lock-acquire-release semantik tradisional, DynamoDB Lock Client adalah pustaka sumber terbuka yang mengimplementasikan penguncian terdistribusi menggunakan tabel DynamoDB sebagai toko kunci. Pendekatan ini berguna ketika Anda perlu mengoordinasikan akses ke sumber daya eksternal (seperti objek S3 atau konfigurasi bersama) di beberapa instance aplikasi.

Klien kunci tersedia sebagai [pustaka Java](https://github.com/awslabs/amazon-dynamodb-lock-client) open-source.

## Cara kerjanya
<a name="BestPractices_DistributedLocking_HowItWorks"></a>

Klien kunci menggunakan tabel DynamoDB khusus untuk melacak kunci. Setiap kunci direpresentasikan sebagai item dengan atribut kunci berikut:
+ Kunci partisi yang mengidentifikasi sumber daya yang dikunci.
+ Durasi sewa yang menentukan berapa lama kunci valid. Jika pemegang kunci mogok atau menjadi tidak responsif, kunci secara otomatis kedaluwarsa setelah durasi sewa.
+ Detak jantung yang dikirim pemegang kunci secara berkala untuk memperpanjang sewa. Ini mencegah kunci kedaluwarsa saat dudukan masih aktif diproses.

Klien kunci menggunakan penulisan bersyarat untuk memastikan bahwa hanya satu proses yang dapat memperoleh kunci pada satu waktu. Jika kunci sudah ditahan, penelepon dapat memilih untuk menunggu dan mencoba lagi atau gagal segera.

## Kapan menggunakan klien kunci
<a name="BestPractices_DistributedLocking_WhenToUse"></a>

Klien kunci sangat cocok ketika:
+ Anda perlu mengoordinasikan akses ke sumber daya bersama di beberapa instance aplikasi atau layanan mikro.
+ Bagian kritis berjalan lama (detik hingga menit) dan mencoba kembali seluruh operasi pada konflik akan mahal.
+ Anda memerlukan kedaluwarsa kunci otomatis untuk menangani kegagalan proses dengan anggun.

Contoh umum termasuk mengatur alur kerja terdistribusi, mengoordinasikan pekerjaan cron di beberapa instance, dan mengelola akses ke sumber daya eksternal bersama.

## Pengorbanan
<a name="BestPractices_DistributedLocking_Tradeoffs"></a>

**Infrastruktur tambahan**  
Membutuhkan tabel DynamoDB khusus untuk manajemen kunci, dengan kapasitas baca dan tulis tambahan untuk operasi kunci dan detak jantung.

**Ketergantungan jam**  
Kedaluwarsa kunci bergantung pada stempel waktu. Kemiringan jam yang signifikan antara klien dapat menyebabkan perilaku yang tidak terduga, terutama untuk jangka waktu sewa yang pendek.

**Risiko kebuntuan**  
Jika aplikasi Anda memperoleh kunci pada beberapa sumber daya, Anda harus mendapatkannya dalam urutan yang konsisten untuk menghindari kebuntuan. Durasi sewa menyediakan jaring pengaman dengan secara otomatis melepaskan kunci dari pemegang yang tidak responsif.

## Implementasi
<a name="BestPractices_DistributedLocking_Implementation"></a>

Contoh berikut menunjukkan cara menggunakan DynamoDB Lock Client untuk memperoleh dan melepaskan kunci:

```
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;

final DynamoDbClient dynamoDB = DynamoDbClient.builder()
    .region(Region.US_WEST_2)
    .build();

final AmazonDynamoDBLockClient lockClient = new AmazonDynamoDBLockClient(
    AmazonDynamoDBLockClientOptions.builder(dynamoDB, "Locks")
        .withTimeUnit(TimeUnit.SECONDS)
        .withLeaseDuration(10L)
        .withHeartbeatPeriod(3L)
        .withCreateHeartbeatBackgroundThread(true)
        .build());

// Try to acquire a lock on a resource
final Optional<LockItem> lock =
    lockClient.tryAcquireLock(AcquireLockOptions.builder("my-shared-resource").build());

if (lock.isPresent()) {
    try {
        // Perform operations that require exclusive access
        processSharedResource();
    } finally {
        // Always release the lock when done
        lockClient.releaseLock(lock.get());
    }
} else {
    System.out.println("Failed to acquire lock.");
}

lockClient.close();
```

**penting**  
Selalu lepaskan kunci di `finally` blok untuk memastikan kunci dilepaskan meskipun logika pemrosesan Anda mengeluarkan pengecualian. Kunci yang belum dirilis memblokir proses lain sampai masa sewa berakhir.

Anda juga dapat menerapkan mekanisme penguncian sederhana tanpa pustaka klien kunci dengan menggunakan penulisan bersyarat secara langsung. Contoh berikut menggunakan `UpdateItem` dengan ekspresi kondisi untuk memperoleh kunci, dan `DeleteItem` untuk melepaskannya:

```
from datetime import datetime, timedelta
from boto3.dynamodb.conditions import Attr

def acquire_lock(table, resource_name, owner_id, ttl_seconds):
    """Attempt to acquire a lock. Returns True if successful."""
    expiry = (datetime.now() + timedelta(seconds=ttl_seconds)).isoformat()
    now = datetime.now().isoformat()
    try:
        table.update_item(
            Key={'LockID': resource_name},
            UpdateExpression='SET #owner = :owner, #expiry = :expiry',
            ConditionExpression=Attr('LockID').not_exists() | Attr('ExpiresAt').lt(now),
            ExpressionAttributeNames={'#owner': 'OwnerID', '#expiry': 'ExpiresAt'},
            ExpressionAttributeValues={':owner': owner_id, ':expiry': expiry}
        )
        return True
    except table.meta.client.exceptions.ConditionalCheckFailedException:
        return False

def release_lock(table, resource_name, owner_id):
    """Release a lock. Only succeeds if the caller is the lock owner."""
    try:
        table.delete_item(
            Key={'LockID': resource_name},
            ConditionExpression=Attr('OwnerID').eq(owner_id)
        )
        return True
    except table.meta.client.exceptions.ConditionalCheckFailedException:
        return False
```

Pendekatan ini menggunakan ekspresi kondisi untuk memastikan bahwa kunci hanya dapat diperoleh jika tidak ada atau telah kedaluwarsa, dan hanya dapat dilepaskan oleh proses yang memperolehnya. Pertimbangkan untuk mengaktifkan [Time to Live (TTL)](TTL.md) di meja kunci untuk secara otomatis membersihkan item kunci yang kedaluwarsa.

## Memilih strategi kontrol konkurensi
<a name="BestPractices_ChoosingLockingStrategy"></a>

Gunakan panduan berikut untuk memilih pendekatan yang tepat untuk beban kerja Anda:

**Gunakan penguncian optimis** saat:  
+ Konflik jarang terjadi.
+ Mencoba kembali menulis yang gagal tidak mahal.
+ Anda memperbarui satu item pada satu waktu.

**Gunakan transaksi** saat:  
+ Anda perlu memperbarui beberapa item secara atom.
+ Anda memerlukan all-or-nothing semantik di seluruh item atau tabel.
+ Anda perlu menggabungkan pemeriksaan kondisi dengan menulis dalam satu operasi.

**Gunakan klien kunci** saat:  
+ Anda perlu mengoordinasikan akses ke sumber daya eksternal di seluruh proses terdistribusi.
+ Bagian kritis sudah berjalan lama dan mencoba kembali konflik itu mahal.
+ Anda memerlukan kedaluwarsa kunci otomatis untuk menangani kegagalan proses.

**catatan**  
Jika Anda menggunakan tabel global [DynamoDB, ketahuilah bahwa tabel global](GlobalTables.md) menggunakan strategi rekonsiliasi “penulis terakhir menang” untuk pembaruan bersamaan. Penguncian optimis dengan nomor versi tidak berfungsi seperti yang diharapkan di seluruh Wilayah karena penulisan di satu Wilayah dapat menimpa penulisan bersamaan di Wilayah lain tanpa pemeriksaan versi. Rancang aplikasi Anda untuk menangani konflik di tingkat aplikasi saat menggunakan tabel global.