Flazzo memiliki fokus utama untuk menambah nilai bisnis Anda.

Blog

Pesimis dan Penguncian Optimis dengan MySQL, jOOQ dan Kotlin

16832215-thumb.jpg
Blog

Pesimis dan Penguncian Optimis dengan MySQL, jOOQ dan Kotlin


Mengelola akses bersamaan ke data bersama dapat menjadi tantangan, tetapi dengan menggunakan strategi penguncian yang tepat, Anda dapat memastikan bahwa aplikasi Anda berjalan dengan lancar dan menghindari konflik yang dapat mengakibatkan kerusakan data atau hasil yang tidak konsisten.

Pada artikel ini, kita akan melihat bagaimana menerapkan penggunaan penguncian pesimis dan optimis Kotlin, ktorDan jOOQdan berikan contoh praktis untuk membantu Anda memahami kapan harus menggunakan setiap pendekatan.

Apakah Anda seorang pemula atau pengembang berpengalaman, idenya adalah untuk mendapatkan ikhtisar tentang prinsip kontrol konkurensi dan cara menerapkannya dalam praktik.

Model data

Katakanlah kita memiliki tabel bernama users di database MySQL kami dengan skema berikut:

CREATE TABLE users (
  id INT NOT NULL AUTO_INCREMENT,
  name VARCHAR(255) NOT NULL,
  age INT NOT NULL,
  PRIMARY KEY (id)
);

Kunci pesimis

Kami ingin menerapkan penguncian pesimistis saat memperbarui usia pengguna, artinya kami ingin mengunci baris pengguna tersebut saat kami membacanya dari database dan menahan kunci hingga akhir pembaruan. Ini memastikan bahwa tidak ada transaksi lain yang dapat memperbarui baris yang sama saat kami sedang mengerjakannya.

Pertama, kita perlu memberi tahu jOOQ untuk menggunakan penguncian pesimistis saat melakukan kueri users lukisan.

Kita bisa melakukan ini dengan mengatur forUpdate() bendera di SELECT mempertanyakan:

val user = dslContext.selectFrom(USERS)
                     .where(USERS.ID.eq(id))
                     .forUpdate()
                     .fetchOne()

Ini akan mengunci baris untuk pengguna dengan id yang ditentukan saat kami menjalankan kueri.

Selanjutnya, kita dapat memperbarui usia pengguna dan memvalidasi transaksi:

dslContext.update(USERS)
         .set(USERS.AGE, newAge)
         .where(USERS.ID.eq(id))
         .execute()
transaction.commit()

Perhatikan bahwa kita perlu melakukan pembaruan dalam transaksi yang sama dengan yang kita gunakan untuk membaca baris pengguna dan menguncinya. Ini memastikan bahwa kunci dilepaskan saat transaksi dilakukan. Anda dapat melihat bagaimana ini dilakukan di bagian selanjutnya.

titik akhir Ktor

Terakhir, berikut adalah contoh titik akhir Ktor yang menunjukkan cara menggunakan kode ini untuk memperbarui usia pengguna:

post("/users/{id}/age") {
    val id = call.parameters["id"]?.toInt() ?: throw BadRequestException("Invalid ID")
    val newAge = call.receive<Int>()

    dslContext.transaction { transaction ->
        val user = dslContext.selectFrom(USERS)
                             .where(USERS.ID.eq(id))
                             .forUpdate()
                             .fetchOne()

        if (user == null) {
            throw NotFoundException("User not found")
        }

        user.age = newAge
        dslContext.update(USERS)
                 .set(USERS.AGE, newAge)
                 .where(USERS.ID.eq(id))
                 .execute()
        transaction.commit()
    }

    call.respond(HttpStatusCode.OK)
}

Seperti yang Anda lihat, pertama-tama kita membaca baris pengguna dan menguncinya menggunakan jOOQ forUpdate() metode. Kemudian kami memeriksa apakah pengguna itu ada, memperbarui usianya, dan melakukan transaksi. Terakhir, kami merespons dengan kode status HTTP 200 OK untuk menunjukkan keberhasilan.

Versi optimis

Penguncian optimis adalah teknik di mana kita tidak mengunci baris saat kita membacanya, melainkan menambahkan nomor versi ke baris dan memeriksanya saat kita memperbaruinya. Jika nomor versi telah berubah sejak kami membaca baris, itu berarti bahwa orang lain telah memperbaruinya sementara itu, dan kami harus mencoba lagi operasi dengan baris yang diperbarui.

Untuk menerapkan penguncian optimis, kita perlu menambahkan a version kolom ke kami users lukisan:

CREATE TABLE users (
  id INT NOT NULL AUTO_INCREMENT,
  name VARCHAR(255) NOT NULL,
  age INT NOT NULL,
  version INT NOT NULL DEFAULT 0,
  PRIMARY KEY (id)
);

Kami akan menggunakan version kolom untuk melacak versi setiap baris.

Sekarang mari perbarui endpoint Ktor kita untuk menggunakan penguncian optimis. Pertama, kita akan membaca baris dari pengguna dan memeriksa versinya:

post("/users/{id}/age") {
    val id = call.parameters["id"]?.toInt() ?: throw BadRequestException("Invalid ID")
    val newAge = call.receive<Int>()

    var updated = false
    while (!updated) {
        val user = dslContext.selectFrom(USERS)
                             .where(USERS.ID.eq(id))
                             .fetchOne()

        if (user == null) {
            throw NotFoundException("User not found")
        }

        val oldVersion = user.version
        user.age = newAge
        user.version += 1

        val rowsUpdated = dslContext.update(USERS)
                                    .set(USERS.AGE, newAge)
                                    .set(USERS.VERSION, user.version)
                                    .where(USERS.ID.eq(id))
                                    .and(USERS.VERSION.eq(oldVersion))
                                    .execute()

        if (rowsUpdated == 1) {
            updated = true
        }
    }

    call.respond(HttpStatusCode.OK)
}

Dalam contoh ini, kami menggunakan a while loop untuk mencoba lagi pembaruan hingga kami berhasil memperbarui baris dengan nomor versi yang benar. Pertama, kita membaca baris pengguna dan mendapatkan nomor versi mereka saat ini. Selanjutnya, kami memperbarui usia pengguna dan menambah nomor versi. Terakhir, kami menjalankan kueri pembaruan dan memeriksa berapa banyak baris yang telah diperbarui.

Jika pembaruan berhasil (yaitu, satu baris telah diperbarui), kami menetapkan updated Untuk true dan keluar dari lingkaran. Jika pembaruan gagal (mis. tidak ada baris yang diperbarui karena nomor versi berubah), kami mengulangi pengulangan dan mencoba lagi.

Perhatikan bahwa kami menggunakan and(USERS.VERSION.eq(oldVersion)) negara bagian di WHERE klausa untuk memastikan bahwa kami hanya memperbarui baris jika nomor versinya masih sama dengan yang kami baca sebelumnya.

Kompromi

Penguncian optimis dan pesimis adalah dua teknik penting yang digunakan dalam kontrol konkurensi untuk memastikan konsistensi dan kebenaran data di lingkungan multi-pengguna.

Penguncian pesimis mencegah pengguna lain mengakses catatan saat sedang diedit, sementara penguncian optimis memungkinkan banyak pengguna mengakses dan mengedit data secara bersamaan.

Aplikasi perbankan yang menangani transfer uang antar rekening adalah contoh yang baik dari skenario di mana penguncian pesimis adalah pilihan yang lebih baik. Dalam skenario ini, saat pengguna melakukan transfer, sistem harus memastikan bahwa dana di akun tersedia dan tidak ada pengguna lain yang secara bersamaan mengubah saldo akun yang sama.

Dalam hal ini, penting untuk mencegah pengguna lain mengakses akun saat transaksi sedang berlangsung. Aplikasi dapat menggunakan kunci pesimistis untuk memastikan akses eksklusif ke akun selama proses transfer, mencegah pembaruan bersamaan dan memastikan konsistensi data.

Aplikasi belanja online yang mengelola inventaris produk adalah contoh skenario di mana penguncian optimis adalah pilihan yang lebih baik.

Dalam skenario ini, banyak pengguna dapat mengakses halaman produk yang sama dan melakukan pembelian secara bersamaan. Saat pengguna menambahkan produk ke keranjang dan melanjutkan ke pembayaran, sistem harus memastikan bahwa ketersediaan produk selalu terbaru dan tidak ada pengguna lain yang membeli produk yang sama.

Pendaftaran produk tidak perlu dikunci karena sistem dapat menangani perselisihan selama proses checkout. Aplikasi dapat menggunakan penguncian optimis, memungkinkan akses bersamaan ke catatan produk dan menyelesaikan konflik selama transaksi dengan memeriksa ketersediaan produk dan memperbarui inventaris yang sesuai.

Kesimpulan

Saat merancang dan mengimplementasikan sistem basis data, penting untuk mengetahui kelebihan dan keterbatasan strategi penguncian pesimistis dan optimistis.

Meskipun penguncian pesimis adalah cara yang andal untuk memastikan konsistensi data, hal ini dapat menyebabkan penurunan performa dan skalabilitas. Di sisi lain, penguncian optimis memberikan kinerja dan skalabilitas yang lebih baik, tetapi membutuhkan perhatian yang cermat terhadap masalah konkurensi dan penanganan kesalahan.

Pada akhirnya, memilih strategi penguncian yang tepat bergantung pada kasus penggunaan tertentu dan kompromi antara konsistensi data dan performa. Pengetahuan tentang kedua strategi penguncian sangat penting untuk pengambilan keputusan yang baik dan untuk membangun sistem backend yang kuat dan andal.