Mengatasi Deadlock Detected pada Transaksi Paralel PostgreSQL

Deadlock adalah situasi di mana dua atau lebih transaksi menunggu satu sama lain untuk melepaskan kunci pada sumber daya tertentu, sehingga tidak ada yang dapat melanjutkan. Dalam PostgreSQL, deadlock dapat terjadi jika transaksi paralel tidak dirancang atau dikelola dengan hati-hati.



Contoh Masalah Deadlock

Skema Tabel

Misalkan kita memiliki dua tabel:

CREATE TABLE accounts (

    account_id SERIAL PRIMARY KEY,

    balance NUMERIC(10, 2) NOT NULL

);

Table lainnya adalah

CREATE TABLE transactions (

    transaction_id SERIAL PRIMARY KEY,

    account_id INT REFERENCES accounts(account_id),

    amount NUMERIC(10, 2) NOT NULL,

    transaction_date TIMESTAMP DEFAULT now()

);


Contoh Skenario Deadlock

Transaksi 1:


BEGIN;

UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;

UPDATE transactions SET amount = 100 WHERE transaction_id = 10;

-- Menunggu Transaksi 2 selesai


Transaksi 2:


BEGIN;

UPDATE transactions SET amount = 200 WHERE transaction_id = 10;

UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;

-- Menunggu Transaksi 1 selesai


Karena kedua transaksi saling menunggu kunci yang dipegang oleh transaksi lain, PostgreSQL akan mendeteksi deadlock dan membatalkan salah satu transaksi.


Pesan Kesalahan:


ERROR: deadlock detected

DETAIL: Process 12345 waits for ShareLock on transaction 67890; blocked by process 67890.

HINT: See server log for query details.


Langkah-Langkah Mengatasi Deadlock


1. Pahami Pola Akses Sumber Daya

Deadlock terjadi karena transaksi mengakses sumber daya dalam urutan yang berbeda.

Solusi: Pastikan semua transaksi mengakses sumber daya dalam urutan yang sama.

Contoh:

-- Urutan konsisten untuk semua transaksi

BEGIN;

UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;

UPDATE transactions SET amount = 100 WHERE transaction_id = 10;

COMMIT;


2. Gunakan FOR UPDATE untuk Kunci Eksklusif

Jika Anda akan memperbarui data, gunakan kunci eksklusif untuk menghindari deadlock.

Contoh:

-- Transaksi 1

BEGIN;

SELECT * FROM accounts WHERE account_id = 1 FOR UPDATE;

UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;

COMMIT;


-- Transaksi 2

BEGIN;

SELECT * FROM accounts WHERE account_id = 1 FOR UPDATE;

UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;

COMMIT;


3. Gunakan Waktu Tunggu Kunci (lock_timeout)

Batasi waktu tunggu untuk memperoleh kunci, sehingga transaksi tidak menggantung terlalu lama.

Tambahkan pengaturan berikut di PostgreSQL:

SET lock_timeout = '2s';


4. Kurangi Waktu Transaksi Berlangsung

Transaksi panjang meningkatkan risiko deadlock.


Tips:

Hindari operasi yang memakan waktu lama dalam transaksi, seperti membaca file atau menunggu input pengguna.

Pastikan transaksi hanya mencakup operasi database yang penting.


5. Gunakan Tingkat Isolasi yang Sesuai

Gunakan tingkat isolasi transaksi yang sesuai untuk mengurangi konflik kunci.

Default: READ COMMITTED

Jika deadlock sering terjadi, pertimbangkan untuk menggunakan SERIALIZABLE.

Contoh:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN;

UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;

COMMIT;


6. Monitor dan Identifikasi Deadlock

Gunakan log PostgreSQL untuk menganalisis deadlock.

Aktifkan logging dengan menambahkan konfigurasi berikut di postgresql.conf:

log_min_messages = ERROR

log_lock_waits = on

Setelah itu, restart PostgreSQL dan analisis log untuk menemukan pola akses yang menyebabkan deadlock.


Contoh Penanganan Ulang Transaksi

Saat transaksi dibatalkan karena deadlock, aplikasi harus menangkap error dan mencoba ulang transaksi.

Contoh Pseudocode:

import psycopg2

from psycopg2 import OperationalError


connection = psycopg2.connect("dbname=test user=postgres password=secret")

retry_count = 3


for attempt in range(retry_count):

    try:

        with connection:

            with connection.cursor() as cursor:

                cursor.execute("BEGIN")

                cursor.execute("UPDATE accounts SET balance = balance - 100 WHERE account_id = 1")

                cursor.execute("UPDATE transactions SET amount = 100 WHERE transaction_id = 10")

                cursor.execute("COMMIT")

        break

    except OperationalError as e:

        if "deadlock detected" in str(e):

            print(f"Deadlock detected, retrying ({attempt + 1}/{retry_count})...")

        else:

            raise e


Deadlock adalah masalah yang dapat menyebabkan kegagalan transaksi pada PostgreSQL. Dengan memastikan urutan akses yang konsisten, menggunakan kunci eksklusif, dan membatasi waktu transaksi, Anda dapat meminimalkan kemungkinan deadlock. Pastikan aplikasi Anda juga dirancang untuk menangani deadlock dengan mencoba ulang transaksi jika diperlukan. Semoga bermanfaat dan mohon maaf jika ada informasi yang tidak sesuai.

Comments

Popular posts from this blog

Integrating PHP with Message Queues RabbitMQ Kafka

FastAPI and UVLoop: The Perfect Pair for Asynchronous API Development

Konfigurasi dan Instalasi PostgreSQL Secara Lengkap di Windows Linux dan MacOS