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:
- Pengenalan Data Model
- Metrics with Sorted Sets
- Making Lua Script
- Pipelining with Jedis
- 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
: prefixwHg
: unit (satuan)2020-01-01
: tanggal1
: 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:
- Pengukuran (metrics) akan secara otomatis disortir.
- Efisien untuk di fetch: O((log n) + m).
- Efisien untuk insert: O(log n).
- 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?
- Jika kamu membolehkan ada query dari client lain berjalan di tengah-tengah multiple query, gunakan Pipeline.
- Jika kamu tidak ingin ada query dari client lain berjalan di tengah-tengah multiple query, gunakan Transaction.
- 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.