FoundationDB menyimpan seluruh data dalam bentuk ordered key-value store global. Setiap key diurutkan secara leksikografis menggunakan byte order standar. Karakteristik ini membuat operasi range query sangat efisien jika struktur byte dirancang dengan benar, namun rentan menimbulkan read/write bottleneck jika distribusi key terkonsentrasi pada satu storage server (hot-spotting).
1. Desain Key-Space untuk Range Query
Prinsip utama optimasi range query FoundationDB adalah memanfaatkan pengurutan leksikografis secara deterministik. Lapisan abstraksi tuple bawaan FoundationDB mengonversi tipe data primitif ke representasi byte yang mempertahankan urutan tipe asli (misalnya integer, UUID, string).
Untuk data berbasis multi-tenant atau hierarkis, tempatkan entitas pembatas lingkup di byte paling depan (prefix) guna membatasi jangkauan range scan:
// Format skema key data:
// (prefix_tabel, tenant_id, timestamp, entity_id) -> JSON / Protobuf Payload
(1, "tenant_abc", 1711929600, "ord_901") = { ... }
(1, "tenant_abc", 1711929660, "ord_902") = { ... }
Dengan struktur ini, query seluruh pesanan milik tenant_abc dalam rentang waktu tertentu dapat dieksekusi menggunakan batas awal dan batas akhir tuple tanpa menyentuh data tenant lain.
2. Mitigasi Hot-Spotting Monotonic Key
Pola kunci yang naik secara monoton (misalnya auto-increment ID atau raw timestamp di prefix utama) mengarahkan seluruh operasi write ke shard terakhir di storage server. FoundationDB membagi shard berdasarkan ukuran data (umumnya setiap ~100MB), bukan hash otomatis.
Strategi Prefix Hashing / Bucket Splitting
Untuk mendistribusikan beban penulisan ke berbagai storage server secara merata, gunakan salt bucket terhitung sebelum timestamp:
// Format: (prefix, bucket_id, timestamp, entity_id)
bucket_id = hash(entity_id) % TOTAL_BUCKETS
Kelemahan: Range query temporal tidak lagi dapat dibaca dengan satu kali scan linier. Pembacaan harus menggunakan teknik scatter-gather secara paralel ke semua bucket yang ada.
3. Pola Secondary Index dan Pagination Leksikografis
Secondary index di FoundationDB dibuat manual sebagai pasangan key terpisah di dalam transaksi yang sama guna menjamin konsistensi ACID.
// Primary Key:
("data", "user_123") = { "status": "ACTIVE", "name": "Alice" }
// Secondary Index:
("idx_status", "ACTIVE", "user_123") = ""
Untuk membaca data berurutan (pagination), hindari penggunaan offset numerik. Gunakan key terakhir yang dibaca sebagai continuation token.
4. Batasan Teknis FoundationDB
- Batas Transaksi 5 Detik: Transaksi FoundationDB otomatis dibatalkan (
transaction_too_old) jika berlangsung lebih dari 5 detik. Range query bervolume besar harus dipecah menjadi batch-batch kecil. - Ukuran Data: Ukuran key maksimal 10.000 byte (disarankan < 1 KB). Ukuran value maksimal 100.000 byte. Data besar harus dipecah menjadi chunk terpisah.
- Batas Transaksi Write: Total write dalam satu transaksi tidak boleh melebihi 10 MB.
5. Implementasi Operasional (Python)
Contoh berikut mendemonstrasikan implementasi indexing sekunder, range query dengan batching, dan pencegahan batas waktu 5 detik:
import fdb
import fdb.tuple
fdb.api_version(710)
db = fdb.open()
# Namespace prefix
DATA_PREFIX = 1
INDEX_PREFIX = 2
@fdb.transactional
def insert_record(tr, entity_id: str, status: str, payload: bytes):
data_key = fdb.tuple.pack((DATA_PREFIX, entity_id))
index_key = fdb.tuple.pack((INDEX_PREFIX, status, entity_id))
# Simpan data utama dan index secara atomik
tr[data_key] = payload
tr[index_key] = b""
def query_by_status(db, status: str, batch_size: int = 100):
start_key = fdb.tuple.pack((INDEX_PREFIX, status))
stop_key = fdb.tuple.range((INDEX_PREFIX, status)).stop
more = True
while more:
# Jalankan pembacaan dalam batas transaksi independen
records, last_key = _read_batch(db, start_key, stop_key, batch_size)
for key, _ in records:
unpacked = fdb.tuple.unpack(key)
entity_id = unpacked[2]
yield entity_id
if len(records) < batch_size:
more = False
else:
# Lanjutkan pagination berbasis leksikografis (exclusive)
start_key = fdb.KeySelector.first_greater_than(last_key)
@fdb.transactional
def _read_batch(tr, start, stop, limit):
# Mengambil range tuple dengan batas jumlah record
r = tr.get_range(start, stop, limit=limit)
items = list(r)
last_key = items[-1].key if items else None
return items, last_key
6. Best Practice Operasional
- Gunakan
StreamingModeyang sesuai: gunakanfdb.StreamingMode.want_alluntuk fetch data penuh, ataufdb.StreamingMode.iteratoruntuk streaming bertahap. - Jangan menaruh indeks dinamis pada key yang sering berubah (write-heavy update) tanpa mempertimbangkan overhead konsensus.
- Jika pagination memerlukan filter multi-kolom, urutkan elemen tuple dari kardinalitas terendah ke tertinggi.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!