Redis University RU102J - Minggu Ke-2

Redis University RU102J - Minggu Ke-2

Ini adalah catatan minggu ke-2 saya di kelas RU102J-nya Redis University. Jika pada minggu pertama hanya sebatas setup java environment, java redis client dan perkenalan DAO pattern, pada minggu ini kita mulai membuat aplikasi RediSolar.

Spesifiknya, minggu ini topik pembahasannya adalah:

  1. Pengenalan Data Model
  2. Metrics with Sorted Sets
  3. Making Lua Script
  4. Pipelining with Jedis
  5. Transactions with Jedis

1. Pengenalan Data Model

Data model ini sebetulnya sudah sedikit disinggung pada minggu pertama pada diagram DAO. Cuma disini dicontohkan membuat data model untuk MeterReading (meteran).

Jadi untuk membuat aplikasi RediSolar ini, kita membutuhkan data model class MeterReading dengan properti seperti dibawah ini:

public class MeterReading {
    private Long siteId;            // ID lokasi (rumah)
    private ZonedDateTime dateTime; // waktu
    private Double whUsed;          // daya digunakan
    private Double whGenerated;     // daya digenerate
    private Double tempC;           // suhu dalam celcius
}

2. Metrics with Sorted Sets

Kemudian untuk menyimpan metric, struktur data yang digunakan adalah sorted sets dengan format key dan value seperti dibawah ini:

Format key:

metrics:wHg:2020-01-01:1

Dimana:

  • metrics: prefix
  • wHg: unit (satuan)
  • 2020-01-01: tanggal
  • 1: site ID

Sedangkan untuk struktur sorted setnya adalah sebagai berikut:

  • score: menit
  • value: nilai:menit

Contoh, perintah ZADD dibawah ini maksudnya adalah:

ZADD metrics:wHg:2020-01-01:1 30 18.5:30
  • Unit: wHg (watt hours generated)
  • Tanggal: 2020-01-01
  • Site ID: 1
  • Menit: 30
  • Nilai: 18.5

Struktur seperti diatas digunakan karena:

  1. Pengukuran (metrics) akan secara otomatis disortir.
  2. Efisien untuk di fetch: O((log n) + m).
  3. Efisien untuk insert: O(log n).
  4. Hemat memori.

3. Making Lua Script

Topik pembahasan selanjutnya adalah tentang tata cara menjalankan script Lua pada Java untuk melakukan atomic transaction di Redis. Kalau di MySQL atau RDBMS lain mungkin kalian familiar dengan istilah stored procedure, nah si Lua disini perannya seperti itu.

Sebagai contoh, kita ingin membuat script untuk mengupdate sebuah key, hanya jika key belum ada, atau nilai yang baru lebih rendah dari nilainya saat ini.

Pertama kita harus persiapkan class UpdateIfLowerScript sebagai berikut (pahami sendiri):

public class UpdateIfLowerScript {

    private final Jedis jedis;
    private final String sha;

    private final static String script = 
        "local key = KEYS[1]" +  
        "local new = ARGS[1]" +  
        "local current = redis.call('GET', key)" +  
        "if (current == false or (tonumber(new) < tonumber(current))) then" +
        "    redis.call('SET', key, new)" + 
        "    return 1" +  
        "else" + 
        "    return 0" + 
        "end" +

    public UpdateIfLowerScript(Jedis jedis) {
        this.jedis = jedis;
        this.sha = jedis.scriptLoad(script);
    }

    public boolean updateIfLower(String key, Integer newValue) {
        List<String> keys = Collections.singletonList(key);
        List<String> args = Collections.singletonList(newValue);
        Object response = jedis.evalsha(sha, keys, args);

        return (Long) response == 1;
    }
}

Selanjutnya, cara panggilnya ya tinggal buat aja instancenya, lalu panggil methodnya seperti dibawah ini:

UpdateIfLowerScript script = new UpdateIfLowerScript(jedis);
boolean result = script.updateIfLower('keynya', 100);

Oia katanya biasanya bakal banyak script Lua di project Java kita, jadi supaya rapih sebisa mungkin dibuat 1 script 1 class, dan disimpan dalam 1 folder yang sama.

Dan kenapa nggak pakai script Java aja langsung? seperti yang disinggung diatas, script Lua ini bersifat atomic. Yang artinya script akan membloking eksekusi dari Redis Client lain sampai script Lua ini selesai dijalankan. Jadi tidak ada transaksi dari Redis Client lain yang terselip ditengah-tengah Lua script ini.

4. Pipelining with Jedis

Pembahasan selanjutnya adalah tentang penggunaan pipeline. Pada DBMS lain, istilah yang mirip-mirip dengan pipeline adalah transaction. Dengan pipeline kita bisa mengirim banyak perintah sekaligus sehingga Redis Client nggak perlu bolak-balik request-response ke Redis Server.

Bayangin kamu ke warung mau beli mie instan dan saos. Jika kamu adalah Redis Client, secara default kamu akan bolak-balik, beli mie instan dulu, sampai rumah, kemudian balik lagi untuk beli saos, terus balik lagi ke rumah. Dengan pipeline, kamu (si Redis Client) akan membeli mie instan dan saos secara bersamaan. Efeknya? tentu saja mempercepat waktu eksekusi dan latency.

Di Jedis sendiri, cara menggunakan pipeline adalah sebagai berikut:

Long siteId = 1L;
Pipeline p = jedis.pipelined();  // inisiasi pipeline

Response<Long> hsetResponse = p.hset(statusKey, "available", "true"); // push hset command to pipeline
Response<Long> expireResponse = p.expire(statusKey, 1000); // push expire command to pipeline
Response<Long> saddResponse = p.sadd(availableKey, String.valueOf(siteId)); // push sadd command to pipeline

p.sync(); // run pipeline to execute all commands

Long hsetResult = hsetResponse.get(); // get result from p.hset
Long expireResult = expireResponse.get(); // get result from p.expire
Long saddResult = saddResponse.get(); // get result from p.sadd

Dengan pipeine seperti diatas, 3 perintah akan dikirim ke Redis Server secara bersamaan. Hanya saja, sifat pipeline tidak seperti transaksi, dimana jika salah satunya gagal, Redis tidak akan merollback command sebelumnya. Dan juga saat pipeline berjalan, katakanlah kita mengirim 100 perintah, kemudian pada perintah ke 70 ada Redis Client mengirim perintah juga, maka perintah dari Redis Client lain akan berjalan ditengah-tengah pipeline.

5. Transactions with Jedis

Pembahasan selanjutnya adalah tentang transaction. Transaction di Redis ini mirip-mirip dengan pipeline, hanya saja transaction mendukung atomic transaction. Jika kita menjalankan 5 perintah menggunakan transaction, maka jika ada Redis Client lain mengirim perintah ke Redis Server yang sama, maka Redis Client lain tersebut harus menunggu sampai 5 perintah kita selesai dijalankan.

Contoh menerapkan transaction menggunakan Jedis:

Long siteId = 1L;
Transaction t = jedis.multi();  // inisiasi transaction

Response<Long> hsetResponse = t.hset(statusKey, "available", "true"); // push hset command to transaction
Response<Long> expireResponse = t.expire(statusKey, 1000); // push expire command to transaction
Response<Long> saddResponse = t.sadd(availableKey, String.valueOf(siteId)); // push sadd command to transaction

t.exec(); // run transaction to execute all commands

Long hsetResult = hsetResponse.get(); // get result from t.hset
Long expireResult = expireResponse.get(); // get result from t.expire
Long saddResult = saddResponse.get(); // get result from t.sadd

Kapan Harus Menggunakan Lua Script, Pipeline, dan Transaction?

  1. Jika kamu membolehkan ada query dari client lain berjalan di tengah-tengah multiple query, gunakan Pipeline.
  2. Jika kamu tidak ingin ada query dari client lain berjalan di tengah-tengah multiple query, gunakan Transaction.
  3. Jika kamu ingin menggunakan conditional statement di tengah-tengah eksekusi multiple query, gunakan Lua Script.

Begitulah topik pembahasan di kelas RU102J di minggu ke-2. Yaudah, sampai jumpa lagi di minggu selanjutnya.

Suka artikel ini?

Saya biasanya share artikel-artikel terbaru via facebook atau fanpage foobarology saya. Kalau mau dapat updatenya, di add friend/like/follow aja link-link dibawah ini 😃

Facebook Foobarology