Di modul sebelumnya, kami menentukan pola akses aplikasi game. Dalam modul ini, kami merancang kunci primer untuk tabel DynamoDB dan memungkinkan pola akses inti.

Waktu untuk Menyelesaikan Modul: 20 Menit


Ketika merancang kunci primer untuk tabel DynamoDB, ingat praktik terbaik berikut:

  • Mulai dengan entitas berbeda di tabel Anda. Jika Anda menyimpan beberapa tipe data berbeda di dalam satu tabel—seperti karyawan, departemen, pelanggan, dan pesanan—pastikan kunci primer memiliki cara untuk mengidentikasi secara jelas setiap entitas dan memungkinkan tindakan inti pada item individu.
  • Gunakan prefiks untuk membedakan antara tipe entitas. Menggunakan prefiks untuk membedakan di antara tipe entitas dapat mencegah tabrakan dan membantu dalam membuat kueri. Misalnya, jika Anda memiliki pelanggan dan karyawan dalam satu tabel yang sama, kunci primer untuk pelanggan akan tampak seperti CUSTOMER#<CUSTOMERID>, dan kunci primer untuk karyawan adalah EMPLOYEE#<EMPLOYEEID>.
  • Fokus pada tindakan satu item terlebih dulu, kemudian menambah beberapa item tindakan jika memungkinkan. Untuk kunci primer, penting bahwa Anda dapat memenuhi opsi baca dan tulis pada satu item menggunakan API item tunggal: GetItem, PutItem, UpdateItem, dan DeleteItem. Anda juga dapat memenuhi pola baca beberapa item dengan kunci primer menggunakan Query. Jika tidak, Anda dapat menambahkan indeks sekunder untuk menangani kasus penggunaan Query.

Dengan pemikiran praktik terbaik ini, mari merancang kunci primer untuk tabel aplikasi game dan menjalankan beberapa tindakan dasar.


  • Langkah 1. Rancang kunci primer

    Mari pertimbangkan entitas berbeda, seperti yang disarankan dalam pendahuluan di awal. Di dalam game, kami memiliki beberapa entitas:

    • User
    • Game
    • UserGameMapping

    UserGameMapping adalah catatan yang menunjukkan pengguna bergabung dalam game. Terdapat hubungan banyak ke banyak antara User dan Game.

    Memiliki pemetaan banyak ke banyak biasanya menunjukkan bahwa Anda ingin memenuhi dua pola Query, dan game ini tidak terkecuali. Kami memiliki pola akses yang harus menemukan semua pengguna yang telah bergabung ke game, serta pola lain untuk menemukan semua game yang dimainkan pengguna.

    Jika model data Anda memiliki beberapa entitas dengan hubungan di antaranya, Anda secara umum dapat menggunakan kunci primer komposit dengan kedua nilai HASH dan RANGE. Kunci primer komposit memberikan kita kemampuan Query pada kunci HASH untuk memenuhi pola kueri yang kita butuhkan. Dalam dokumentasi DynamoDB, kunci partisinya disebut HASH dan kunci sortir disebut RANGE, dan dalam panduan ini kita menggunakan terminologi API secara bergantian dan khususnya ketika kita mendiskusikan kode atau format protokol kabel JSON DynamoDB.

    Kedua entitas data lainnya—User dan Game—tidak memiliki properti alami untuk nilai RANGE karena pola akses pada User atau Game adalah pencarian nilai kunci. Karena nilai RANGE diperlukan, kami dapat memberikan nilai filter untuk kunci RANGE.

    Dengan pemikiran ini, mari gunakan pola berikut untuk nilai HASH dan RANGE untuk setiap tipe entitas.

    Entitas HASH RANGE
    User USER#<USERNAME> #METADATA#<USERNAME>
    Game GAME#<GAME_ID> #METADATA#<GAME_ID>
    UserGameMapping GAME#<GAME_ID> USER#<USERNAME>

    Mari lihat tabel sebelumnya.

    Untuk entitas User , nilai HASH adalah USER#<USERNAME>. Perhatikan bahwa kami menggunakan prefiks untuk mengidentifikasi entitas dan mencegah segala kemungkinan benturan di seluruh tipe entitas.

    Untuk nilai RANGE pada entitas User , kami menggunakan prefiks statis #METADATA# yang diikuti dengan nilai USERNAME. Untuk nilai RANGE, penting bahwa kita memiliki nilai yang diketahui, seperti USERNAME. Hal ini memungkinkan tindakan item tunggal seperti GetItem, PutItem, dan DeleteItem.

    Namun, kami juga ingin nilai RANGE dengan nilai berbeda di seluruh entitas User untuk memungkinkan pembuatan partisi genap jika kita ingin menggunakan kolom ini sebagai kunci HASH untuk indeks. Untuk alasan tersebut, kami menambahkan USERNAME.

    Entitas Game memiliki rancangan kunci primer yang serupa dengan rancangan entitas User. Entitas ini menggunakan prefiks berbeda (GAME#) dan GAME_ID bukannya USERNAME, tetapi prinsipnya tetap sama.

    Terakhir, UserGameMapping menggunakan kunci HASH yang sama seperti entitas Game. Hal ini memungkinkan kita untuk mengambil tidak hanya metadata Game, tetapi juga semua pengguna di Game dalam kueri tunggal. Kami kemudian menggunakan entitas User untuk kunci RANGE pada UserGameMapping guna mengidentifikasi pengguna mana yang telah bergabung dalam game tertentu.

    Pada langkah berikutnya, kami membuat tabel dengan rancangan kunci primer ini. 

  • Langkah 2: Membuat tabel

    Sekarang kami telah merancang kunci primer, mari kita membuat tabel.

    Kode yang Anda unduh di Langkah 3 Modul 1 menyertakan skrip Python di direktori scripts/ yang diberi nama create_table.py. Skrip konten Python berikut.

    import boto3
    
    dynamodb = boto3.client('dynamodb')
    
    try:
        dynamodb.create_table(
            TableName='battle-royale',
            AttributeDefinitions=[
                {
                    "AttributeName": "PK",
                    "AttributeType": "S"
                },
                {
                    "AttributeName": "SK",
                    "AttributeType": "S"
                }
            ],
            KeySchema=[
                {
                    "AttributeName": "PK",
                    "KeyType": "HASH"
                },
                {
                    "AttributeName": "SK",
                    "KeyType": "RANGE"
                }
            ],
            ProvisionedThroughput={
                "ReadCapacityUnits": 1,
                "WriteCapacityUnits": 1
            }
        )
        print("Table created successfully.")
    except Exception as e:
        print("Could not create table. Error:")
        print(e)
    

    Skrip sebelumnya menggunakan operasi CreateTable menggunakan Boto 3, AWS SDK untuk Python. Operasi tersebut menjelaskan dua definisi atribut yang mengetikkan atribut untuk digunakan di dalam kunci primer. Meskipun DynamoDB tanpa skema, Anda harus menjelaskan nama dan tipe atribut yang digunakan untuk kunci primer. Atribut harus disertakan di setiap item yang tertulis ke tabel dan oleh karena itu harus ditentukan saat Anda membuat tabel.

    Karena kami menyimpan entitas berbeda dalam satu tabel, kami tidak dapat menggunakan nama atribut kunci primer seperti UserId. Atribut berarti sesuatu yang berbeda berdasarkan tipe entitas yang disimpan. Misalnya, kunci primer untuk pengguna dapat berupa USERNAME mereka, dan kunci primer untuk game dapat GAMEID mereka. Oleh karena itu, kami menggunakan nama generik utnuk atribut, seperti PK (untuk kunci partisi) dan SK (untuk kunci pengurutan).

    Setelah mengonfigurasi dalam skema kunci, kami menentukan throughput terprovisi untuk tabel. DynamoDB memiliki dua mode kapasitas: terprovisi dan sesuai permintaan. Dalam mode kapasitas terprovisi, Anda menentukan jumlah throughput baca dan tulis dengan tepat sesuai yang diinginkan. Anda membayar kapasitas ini kapan saja Anda ingin menggunakannya atau tidak.

    Dalam mode kapasitas sesuai permintaan DynamoDB, Anda dapat membayar sesuai permintaan. Biaya sesuai permintaan sedikit lebih tinggi dibanding jika Anda menggunakan throughput terprovisi sepenuhnya, tetapi Anda tidak perlu menghabiskan waktu melakukan perencanaan kapasitas atau khawatir akan dibatasi. Mode sesuai permintaan berfungsi sangat baik untuk beban kerja yang naik-turun atau tidak dapat diprediksi. Kami menggunakan mode kapasitas terprovisi dalam lab ini karena sesuai dengan tingkat gratis DynamoDB.

    Untuk membuat tabel, jalankan skrip Python dengan perintah berikut.

    python scripts/create_table.py

    Skrip akan menghasilkan pesan ini: “Tabel berhasil dibuat.”

    Pada langkah berikutnya, kami memuat beberapa data contoh ke dalam tabel secara massal. 

  • Langkah 3: Memuat data secara massal ke dalam tabel

    Dalam langkah ini, kami memuat beberapa data secara massal ke DynamoDB yang dibuat di langkah sebelumnya. Hal ini berarti bahwa dalam langkah-langkah sukses, kita akan memiliki data sampel untuk digunakan.

    Pada direktori scripts/, Anda akan menemukan file yang bernama items.json. File ini berisi 835 item contoh yang dihasilkan secara acak untuk lab ini. Item ini mencakup entitas User, Game, dan UserGameMapping. Buka file jika Anda ingin melihat beberapa item contoh.

    Direktori scripts/ juga memiliki file bernama bulk_load_table.py yang membaca item dalam file items.json dan menulisnya secara massal ke tabel DynamoDB. Konten file tersebut antara lain.

    import json
    
    import boto3
    
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('battle-royale')
    
    items = []
    
    with open('scripts/items.json', 'r') as f:
        for row in f:
            items.append(json.loads(row))
    
    with table.batch_writer() as batch:
        for item in items:
            batch.put_item(Item=item)
    

    Dalam skrip ini, dibanding menggunakan klien level rendah di Boto 3, kami menggunakan objek Resource yang lebih tinggi. Objek Resource menyediakan antarmuka yang lebih mudah untuk menggunakan API AWS. Objek Resource berguna dalam situasi ini karina objek ini mengumpulkan permintaan kita. Operasi BatchWriteItem menerima sebanyak 25 item dalam satu permintaan. Objek Resource menangani pengumpulan tersebut untuk kita dibanding membuat kita membagi data menjadi permintaan berisi 25 item atau lebih.

    Jalankan skrip bulk_load_table.py dan muat tabel Anda dengan data dengan menjalankan perintah di terminal berikut.

    python scripts/bulk_load_table.py

    Anda dapat memastikan bahwa semua data Anda dimuat ke dalam tabel dengan menjalankan operasi Scan dan menghitung.

    aws dynamodb scan \
     --table-name battle-royale \
     --select COUNT

    Hal ini akan menampilkan hasil berikut.

    {
        "Count": 835, 
        "ScannedCount": 835, 
        "ConsumedCapacity": null
    }
    

    Anda harus melihat Count berisi 835, menunjukkan bahwa item Anda berhasil dimuat.

    Dalam langkah berikutnya, kami menunjukkan cara mengambil beberapa tipe entitas dalam satu permintaan, yang dapat mengurangi total permintaan jaringan yang Anda buat di aplikasi dan meningkatkan kinerja aplikasi.

  • Langkah 4: Mengambil beberapa tipe entitas dalam satu permintaan

    Seperti yang kami sebutkan di modul sebelumnya, Anda harus mengoptimalkan tabel DynamoDB untuk jumlah permintaan yang diterima. Kami juga menyebutkan bahwa DynamoDB tidak memiliki join seperti yang dimiliki database relasional. Sebagai gantinya, Anda merancang tabel untuk memungkinkan perilaku mirip join dalam permintaan Anda.

    Dalam langkah ini, kami mengambil beberapa tipe entitas dalam satu permintaan. Pada game ini, kami mungkin ingin mengambil detail tentang sesi game. Detail ini menyertakan informasi tentang game itu sendiri, misalnya waktu dimulai, waktu berakhir, orang yang menjalankan, dan detail tentang pengguna yang bermain di game.

    Permintaan ini menjangkau dua tipe entitas: entitas Game dan entitas UserGameMapping. Namun, hal ini tidak berarti kita perlu membuat beberapa permintaan.

    Dalam kode yang Anda unduh, skrip fetch_game_and_players.py ada dalam direktori application/. Skrip ini menunjukkan cara Anda dapat menyusun kode untuk mengambil kedua entitas Game dan entitas UserGameMapping game dalam satu permintaan.

    Kode berikut menyusun skrip fetch_game_and_players.py.

    import boto3
    
    from entities import Game, UserGameMapping
    
    dynamodb = boto3.client('dynamodb')
    
    GAME_ID = "3d4285f0-e52b-401a-a59b-112b38c4a26b"
    
    
    def fetch_game_and_users(game_id):
        resp = dynamodb.query(
            TableName='battle-royale',
            KeyConditionExpression="PK = :pk AND SK BETWEEN :metadata AND :users",
            ExpressionAttributeValues={
                ":pk": { "S": "GAME#{}".format(game_id) },
                ":metadata": { "S": "#METADATA#{}".format(game_id) },
                ":users": { "S": "USER$" },
            },
            ScanIndexForward=True
        )
    
        game = Game(resp['Items'][0])
        game.users = [UserGameMapping(item) for item in resp['Items'][1:]]
    
        return game
    
    
    game = fetch_game_and_users(GAME_ID)
    
    print(game)
    for user in game.users:
        print(user)
    

    Pada awal skrip ini, kami mengimpor perpustakaan Boto 3 dan beberapa kelas sederhana untuk merepresentasikan objek di kode aplikasi kami. Anda dapat melihat definisi entitas tersebut di file application/entities.py.

    Pekerjaan skrip yang nyata terjadi dalam fungsi fetch_game_and_users yang didefinisikan dalam modul tersebut. Hal ini serupa dengan fungsi yang Anda ingin tetapkan di aplikasi yang akan digunakan oleh titik akhir mana pun yang memerlukan data ini.

    Fungsi fetch_game_and_users melakukan beberapa hal. Pertama, fungsi ini membuat permintaan Query ke DynamoDB. Query ini menggunakan PK GAME#<GameId>. Kemudian, Query meminta entitas apa pun tempat kunci pengurutan di antara #METADATA#<GameId> dan USER$. Hal ini mencakup entitas Game, yang kunci pengurutannya berupa #METADATA#<GameId>, dan semua UserGameMappings, entitas yang kuncinya dimulai dengan USER#. Kunci pengurutan tipe string diurutkan berdasarkan kode karakter ASCII. Tanda dolar ($) berada tepat setelah tanda pagar (#) dalam ASCII, sehingga hal ini memastikan bahwa kita akan mendapatkan semua pemetaan dalam entitas UserGameMapping.

    Ketika kami menerima respons, kami merakit entitas data kami menjadi objek yang dikenal oleh aplikasi kami. Kami tahu bahwa entitas pertama menghasilkan entitas Game, sehingga kami membuat objek Game dari entitas. Untuk entitas lainnya, kami membuat objek UserGameMapping untuk setiap entitas dan melampirkan barisan pengguna ke objek Game.

    Pada akhir skrip menunjukkan penggunaan fungsi dan hasil objek yang dicetak. Anda dapat menjalankan skrip di terminal Anda dengan perintah berikut.

    python application/fetch_game_and_players.py

    Skrip akan mencetak objek Game dan semua objek UserGameMapping ke konsol.

    Game<3d4285f0-e52b-401a-a59b-112b38c4a26b -- Green Grasslands>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- branchmichael>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- deanmcclure>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- emccoy>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- emma83>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- iherrera>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- jeremyjohnson>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- lisabaker>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- maryharris>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- mayrebecca>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- meghanhernandez>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- nruiz>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- pboyd>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- richardbowman>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- roberthill>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- robertwood>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- victoriapatrick>
    UserGameMapping<3d4285f0-e52b-401a-a59b-112b38c4a26b -- waltervargas>
    

    Skrip ini menunjukkan bagaimana Anda dapat memodelkan tabel dan menulis kueri untuk mengambil beberapa tipe entitas dalam permintaan DynamoDB tunggal. Dalam database relasional, Anda menggunakan join untuk mengambil beberapa tipe entitas dari tabel berbeda dalam satu permintaan. Dengan DynamoDB, Anda memodelkan data secara spesifik, sehingga entitas yang harus Anda akses bersama terletak saling bersebelahan dalam satu tabel. Pendekatan ini menggantikan kebutuhan join di dalam database relasional dan menjaga aplikasi Anda tetap berkinerja tinggi saat Anda menaikkan skala.


    Dalam modul ini, kami merancang kunci primer dan membuat tabel. Kemudian, kami memuat data secara massal ke dalam tabel dan melihat cara membuat kueri beberapa tipe entitas dalam satu permintaan.

    Dengan rancangan kunci primer saat ini, kami bisa memenuhi pola akses berikut:

    • Membuat profil pengguna (Tulis)
    • Memperbarui profil pengguna (Tulis)
    • Mendapatkan profil pengguna (Baca)
    • Membuat game (Tulis)
    • Melihat game (Baca)
    • Bergabung ke game untuk pengguna (Tulis)
    • Memulai game (Tulis)
    • Memperbarui game untuk pengguna (Tulis)
    • Memperbarui game (Tulis)

    Dalam modul berikutnya, kami menambahkan indeks sekunder dan mempelajari tentang teknik indeks yang jarang. Indeks sekunder memungkinkan Anda mendukung pola akses tambahan di tabel DynamoDB Anda.