Pertanyaan dan Ruang Lingkup
Rancang papan peringkat musiman untuk game kompetitif. Satu musim memiliki 50 juta pemain aktif, menerima hingga 200.000 pembaruan skor per detik, dan melayani hingga 1 juta pembacaan per detik. Pemain membutuhkan 100 teratas global, peringkat pasti mereka, dan 10 pemain di sisi atas maupun bawah mereka. Hasil yang dikirimkan harus muncul dalam 5 detik, dan p99 pembacaan harus tetap di bawah 100 milidetik. Skor berupa bilangan bulat dalam 0..1,000,000, dan setiap pemain hanya menyimpan skor terbaik mereka untuk musim tersebut. Skor seri berbagi peringkat, sehingga peringkat 1, 2, 2 diikuti oleh 4. Pemain dengan skor seri memiliki urutan tampilan yang stabil berdasarkan player_id, tetapi urutan tersebut tidak mengubah peringkat mereka.
Skala, latensi, dan rentang skor adalah asumsi wawancara. Klien tidak dapat menyatakan skor tepercaya secara sepihak. Layanan pertandingan menyelesaikan validasi hasil dan pemeriksaan anti-cheat sebelum menghasilkan event yang dapat dikonsumsi oleh papan peringkat. Masalah ini menguji pengindeksan terurut, pemisahan pembacaan dan penulisan, sharding hot-key, konsistensi, kemampuan membangun ulang (rebuildability), dan siklus hidup musim. Masalah ini tidak meminta model anti-cheat.
Panduan wawancara teknis publik mencantumkan “merancang papan peringkat untuk game” sebagai contoh desain sistem dan menyatakan bahwa ini menguji logika pemeringkatan, performa baca/tulis, dan pembaruan real-time. Tutorial papan peringkat Redis yang diterbitkan pada tahun 2026 juga mendemonstrasikan jalur dasar Sorted Set untuk memperbarui skor, mengambil Top-N, dan mencari peringkat. Bukti publik tidak menetapkan atribusi perusahaan yang andal, sehingga companyName tetap null.
Hal yang Dinilai Pewawancara
Sinyal pertama adalah apakah kandidat mendefinisikan "peringkat". Skor lebih tinggi lebih dulu itu mudah; memutuskan apakah skor seri berbagi peringkat, mengutamakan pencapai paling awal, atau memecah seri berdasarkan ID pemain secara langsung mengubah model data. Tanpa kontrak ini, dua layanan dapat menghasilkan peringkat yang berbeda untuk pemain yang sama.
Sinyal kedua adalah mengenali bahwa satu Sorted Set menyelesaikan satu koleksi terurut. Redis mendokumentasikan pembaruan ZADD dan pencarian peringkat ZREVRANK sebagai O(log N), yang berguna untuk papan peringkat skala moderat. Namun, Redis Cluster menetapkan slot hash berdasarkan kunci (key). Menyimpan papan peringkat global dalam satu kunci tidak membagi 50 juta anggotanya hanya karena lebih banyak node klaster ditambahkan. Jawaban yang kuat mempertahankan jalur sederhana terlebih dahulu, kemudian menambahkan sharding tingkat aplikasi hanya ketika satu kunci melebihi anggaran memori, penulisan, atau pemulihan.
Sinyal ketiga adalah mengubah "tepat dalam 5 detik" menjadi versi baca yang konsisten. Memindahkan pemain dari satu bucket skor ke bucket lain menciptakan penghilangan sementara dengan remove-then-add, atau duplikasi dengan add-then-remove. Membaca jumlah bucket dari satu saat dan urutan bucket dari saat lain juga menghasilkan peringkat yang salah. Desain yang dapat diuji membuat satu permintaan membaca satu versi yang diterbitkan secara penuh.
Terakhir, desain harus dapat dioperasikan. Event skor memerlukan idempoten, koreksi harus dapat menurunkan skor, penutupan musim harus membekukan snapshot yang konsisten, dan cache yang hilang harus dapat dibangun kembali dari buku besar tepercaya. Diagram yang hanya berisi "game service → Redis" tidak mengatasi duplikasi, hotspot, kehilangan data, skor kecurangan yang dicabut, atau perselisihan penyelesaian musim.
Pertanyaan Klarifikasi Sebelum Menjawab
- Apakah skor bersifat kumulatif, terbaik, atau terbaru? Prompt ini menyimpan yang terbaik di musim tersebut. Skor kumulatif
membuat identitas event dan penambahan atomik menjadi kritis karena event duplikat akan bertambah dua kali. Jika penilai dapat mengoreksi skor, API memerlukan nilai absolut berversi daripada hanya ZINCRBY.
- Bagaimana peringkat pemain yang seri ditentukan? Prompt ini menggunakan peringkat kompetisi: hanya skor yang benar-benar lebih tinggi yang dihitung, dan
pemain yang seri berbagi peringkat. "Pencapai paling awal menang" memerlukan waktu pencapaian tepercaya dalam kunci pengurutan; urutan seri leksikografis default dari ZSET tidak menyediakan aturan tersebut secara otomatis.
- Apa persyaratan ketepatan dan kesegaran data (freshness)? Versi yang diterbitkan harus tepat, dengan keusangan hingga
5 detik. Visibilitas langsung yang linearizable akan menempatkan koordinasi lintas-shard pada jalur penulisan. Peringkat perkiraan akan memungkinkan sampel atau quantile sketches dan desain yang jauh lebih sederhana.
- Papan peringkat mana saja yang diperlukan? Jalur utamanya adalah papan peringkat musiman global. Sejumlah kecil wilayah tetap
dapat memiliki materialized view terpisah. Papan teman biasanya mengambil skor teman dalam satu batch dan mengurutkannya per permintaan alih-alih mengelola satu papan per pemain.
- Operasi baca mana saja yang didukung? Top 100, peringkat pribadi, dan lingkungan 20 pemain. Paginasi mendalam tanpa batas
menciptakan pemindaian yang mahal, sehingga jendela harus dibatasi dan menggunakan kursor berversi. Versi yang kedaluwarsa memerlukan relokasi.
- Siapa yang mengonfirmasi skor? Hanya layanan hasil pertandingan tepercaya yang menulis. Pengiriman dari klien adalah input
game, bukan skor yang dapat dimasukkan langsung ke dalam indeks berurutan.
- Kapan musim ditutup? Gunakan waktu event server dan batas waktu (cutoff) yang eksplisit. Kebijakan produk harus menyatakan
apakah hasil yang terlambat ditolak, ditinjau, atau dialokasikan ke tempat lain; pekerjaan latar belakang tidak boleh secara diam-diam mengubah snapshot yang telah diberi hadiah.
- Apa yang tahan lama (durable)? Tampilan peringkat mungkin basi sebentar atau dibangun kembali. Buku besar skor tepercaya dan snapshot
akhir musim tidak boleh hilang akibat kegagalan cache.
Kerangka Jawaban 30 Detik
“Saya akan mendefinisikan peringkat sebagai 1 + jumlah pemain dengan skor yang benar-benar lebih tinggi, sehingga pemain yang seri berbagi peringkat. Layanan pertandingan tepercaya menulis skor terbaik absolut dengan event_id dan versi pemain. Tabel skor yang tahan lama adalah sumber kebenaran (source of truth), dan change stream-nya mematerialisasi papan peringkat. Pada skala yang lebih kecil, satu Redis Sorted Set mendukung ZADD, Top-N, dan peringkat terbalik. Begitu hot key global untuk 50 juta pemain melebihi anggaran satu shard, saya akan mempartisinya berdasarkan rentang skor tetap. Peringkat pribadi adalah jumlah pemain di semua bucket yang lebih tinggi, ditambah jumlah skor yang lebih tinggi di bucket pemain itu sendiri, ditambah satu.
“Perpindahan lintas-bucket tidak dapat diekspos di tengah proses. Oleh karena itu, materializer melakukan commit versi setiap beberapa detik. Setelah semua bucket, prefiks hitungan, dan Top 100 siap, koordinator secara atomik menukar manifes saat ini. Respons mencakup leaderboard_version dan as_of. Penulisan bersifat idempoten berdasarkan event dan versi, cache dapat dibangun kembali dari buku besar skor, dan penutupan musim membekukan serta merekonsiliasi satu versi lengkap sebelum hadiah dibagikan.”
Pembahasan Mendalam Langkah demi Langkah
Langkah 1: Tetapkan API dan kontrak pengurutan sebelum memilih penyimpanan.
Pisahkan jalur penulisan internal dari pembacaan publik:
POST /internal/v1/seasons/{season_id}/scores:apply
GET /v1/seasons/{season_id}/leaderboard/top?limit=100
GET /v1/seasons/{season_id}/players/{player_id}/rank
GET /v1/seasons/{season_id}/players/{player_id}/neighbors?radius=10&version=...Penulisan berisi event_id, match_id, player_id, best_score absolut, score_version, dan waktu penyelesaian tepercaya. Hanya identitas hasil pertandingan yang diotorisasi. Pembacaan mengembalikan leaderboard_version, as_of, skor, peringkat, dan anggota. Permintaan lingkungan sekitar harus mempertahankan versi dari respons pertama; jika tidak, perubahan peringkat saat melakukan paging dapat menduplikasi atau melewatkan pemain.
Empat invarian mendorong desain ini: setiap (season_id, player_id) terjadi sekali dalam satu versi; skor pemain yang diterbitkan cocok dengan indeks berurutan; peringkat selalu sama dengan 1 + count(score > my_score); dan sebuah manifes hanya mereferensikan versi shard yang semuanya telah selesai.
Langkah 2: Jadikan tabel skor tepercaya sebagai sumber kebenaran.
Layanan penyelesaian pertandingan memvalidasi otorisasi, status pertandingan, dan hasil anti-cheat sebelum menulis buku besar hasil yang tidak dapat diubah (immutable). Dalam satu transaksi basis data, penulis papan peringkat mendeduplikasi event_id dan memperbarui baris pemain secara kondisional. Ini menolak versi yang lebih lama, mengembalikan hasil sebelumnya untuk percobaan ulang yang identik, dan mengubah skor terbaik normal hanya ketika new_score > best_score. Mencabut skor kecurangan menulis score_version yang lebih tinggi dan nilai absolut yang dikoreksi, sehingga penurunan juga konvergen.
Setelah commit, sebuah outbox atau change stream tahan lama yang setara memancarkan:
ScoreChanged {
season_id, player_id, old_score, new_score,
score_version, event_id, committed_at
}Event dipartisi berdasarkan pemain, dan materializer hanya menerapkan perubahan yang lebih baru dari versi pemain saat ini. Pengiriman duplikat tidak dapat menambahkan poin dua kali, dan event lama yang terlambat tidak dapat menimpa skor baru. Papan peringkat adalah materialized view sekali pakai. Buku besar dan tabel skor saat ini adalah sumber untuk membangun kembali.
Langkah 3: Tampilkan desain satu Sorted Set terlebih dahulu.
Ketika keanggotaan, penulisan puncak, memori, dan waktu pemulihan sesuai dengan satu shard, satu ZSET per musim adalah titik awal yang tepat:
key = leaderboard:{season_id}
member = player_id
score = best_scoreZADD menetapkan skor baru untuk anggota yang sudah ada dan memposisikannya kembali. Rentang menurun mengembalikan Top-N, dan ZREVRANK mengembalikan posisi. Redis mendokumentasikan pembaruan sebagai O(log N), rentang ukuran tetap sebagai O(log N + M), dan pencarian peringkat sebagai O(log N). Rentang bilangan bulat dalam prompt 0..1,000,000 jauh di bawah batas 2^53 di mana tipe double mewakili bilangan bulat secara tepat.
Anggota dengan skor sama diurutkan berdasarkan urutan leksikografis biner dari nilai anggota. Itu hanya cocok dengan kontrak di mana skor seri berbagi peringkat dan ID mengontrol urutan tampilan. Jangan mengemas skor sembarang dan stempel waktu milidetik ke dalam satu nilai floating-point dan berasumsi pengurutan komposit tetap benar. ZREVRANK + 1 juga bukan peringkat bisnis bersama karena anggota yang seri menerima posisi yang berbeda. Rumus bisnisnya adalah 1 + count(score > my_score). Di dalam satu ZSET, ZCOUNT key (my_score +inf menghitung jumlah tersebut; ( membuat batas bawah bersifat eksklusif. Pisahkan posisi tampilan dari peringkat bisnis.
Langkah 4: Buktikan mengapa skala yang lebih besar memerlukan sharding tingkat aplikasi.
Redis Cluster memetakan kunci ke slot hash, dan slot yang stabil dilayani oleh satu primary. Kunci leaderboard:{season} tetap menjadi satu kunci, sehingga anggotanya, penulisan, dan beban pemulihan tetap terkonsentrasi pada primary dari satu slot tersebut. Melakukan sharding berdasarkan hash(player_id) menyeimbangkan penulisan tetapi membuat peringkat global menjadi mahal karena setiap permintaan harus menggabungkan atau menghitung di semua shard pemain.
Prompt ini memiliki rentang skor yang terbatas, sehingga bucket rentang skor tetap sangat berguna. Dengan satu rentang 10.000 poin per bucket, ada 101 bucket:
bucket_id = floor(score / 10,000)
rank(player) = 1
+ count(all buckets with a higher bucket_id)
+ count(score > player_score inside the player's bucket)Setiap bucket tetap terurut, dan kunci bucket dapat menempati slot yang berbeda. Untuk setiap versi, simpan jumlah bucket dan jumlah prefiks dari tinggi ke rendah. Peringkat pribadi kemudian membutuhkan pencarian pemain, satu nilai prefiks, dan satu hitungan yang benar-benar lebih tinggi di dalam bucket. Top 100 hanya memindai bucket tertinggi yang tidak kosong dan dimaterialisasi secara terpisah. Rentang tetap dapat menciptakan bucket skor tinggi yang panas (hot bucket). Bagi rentang tersebut lebih lanjut jika terukur, tetapi tempatkan batas-batasnya dalam manifes berversi sehingga pembaca dan penulis menggunakan tata letak yang sama.
Langkah 5: Gunakan publikasi berversi untuk memecahkan atomisitas lintas-bucket.
Ketika seorang pemain berpindah dari 39.000 ke 51.000, sistem menghapus mereka dari bucket 3, menambahkan mereka ke bucket 5, dan mengubah dua hitungan. Penghapusan, penambahan, dan perubahan penghitung di seluruh slot Redis bukanlah satu operasi atomik biasa. Mengekspos status perantara menghasilkan duplikasi, penghilangan, atau peringkat global yang meleset satu angka (off-by-one).
Oleh karena itu, materializer melakukan commit snapshot logis dalam epoch singkat. Setiap shard peringkat dimulai dari versi terpublikasi saat ini, menerapkan batch idempoten, dan menyiapkan indeks berurutan serta hitungan berikutnya. Setelah setiap shard melaporkan penyelesaian, koordinator memeriksa total keanggotaan, jumlah perubahan, dan checksum shard, lalu secara atomik memindahkan current_manifest dari v ke v+1. Operasi baca mengambil manifes terlebih dahulu dan membawa versi tersebut melalui semua subquery. Versi yang belum selesai tidak terlihat, shard yang gagal dapat mencoba lagi, dan versi sebelumnya tetap ada sampai pembacaan yang sedang berjalan selesai.
Snapshot logis dapat menggunakan kembali data lama melalui MVCC, halaman copy-on-write, atau basis ditambah delta, menghindari penyalinan penuh 50 juta anggota setiap beberapa detik sambil mempertahankan versi eksternal yang tidak dapat diubah. Durasi epoch, lag penerapan, dan lag publikasi harus tetap di bawah 5 detik secara bersamaan. Jika tidak, laporkan keusangan data dan berhenti mengklaim layanan real-time daripada menerbitkan versi yang baru selesai setengahnya.
Langkah 6: Pisahkan papan peringkat global, regional, dan teman.
Papan peringkat global menggunakan indeks berbasis bucket. Sejumlah kecil wilayah tetap dapat mempertahankan tampilan (season, region) independen dari event skor yang sama. Wilayah berasal dari versi profil pemain yang dikelola server sehingga klien tidak dapat berpindah wilayah selama permintaan. Memfilter hanya Top-N global akan melewatkan pemain regional yang kuat di luar halaman global; gunakan indeks regional atau beri label hasilnya sebagai perkiraan.
Papan peringkat teman biasanya kecil. Ambil ID teman berversi dari graf sosial, baca skor mereka secara batch pada versi papan peringkat yang sama, lalu urutkan berdasarkan score DESC, player_id ASC dan hitung seri pada layanan aplikasi. Mempertahankan satu ZSET teman per pemain menyebabkan amplifikasi penulisan yang ekstrem: satu perubahan skor menyebar (fan-out) ke papan setiap teman, dan perubahan relasi pertemanan memerlukan backfill.
Langkah 7: Perkirakan lalu lintas, lalu tentukan ukuran shard dari hasil pengukuran.
Jika satu event skor tahan lama termasuk amplopnya berukuran 128 byte, batas bawah penulisan logis puncaknya adalah:
200,000 events/s × 128 bytes = 25.6 MB/s
25.6 MB/s × 86,400 s = 2.21184 TB/day
three-replica log lower bound = 6.63552 TB/dayBatas bawah yang tidak dikompresi ini belum termasuk indeks, overhead protokol, batch, percobaan ulang, dan pemulihan replika. Hanya event yang meningkatkan skor terbaik atau mengoreksinya yang mengubah peringkat, tetapi setiap event tepercaya tetap memerlukan deduplikasi dan audit di sisi buku besar. Satu juta pembacaan per detik tidak semuanya dapat mencapai shard peringkat. Simpan Top 100 dalam cache berdasarkan versi. Hasil pribadi dapat menggunakan TTL singkat, tetapi kuncinya mencakup pemain dan versi; shard kemudian membaca jendela lingkungan sekitar secara batch.
Jangan menyalin jumlah shard yang tetap dari sebuah artikel. Lakukan benchmark memori anggota, ZADD, hitungan yang benar-benar lebih tinggi, pembacaan rentang, dan konstruksi snapshot dengan 50 juta baris berbentuk data produksi. Catat p50/p95/p99, CPU, memori, lag replikasi, dan waktu pemulihan, lalu turunkan pemisahan bucket dan jumlah node dari penulisan puncak dan headroom kegagalan. Jika satu ZSET tetap berada di dalam setiap batas anggaran, itu lebih andal daripada koordinator bucket kustom.
Langkah 8: Tutup musim dan jaga agar tampilan dapat dibangun kembali.
Setelah musim memasuki CLOSING, hasil sebelum batas waktu yang sudah diterima akan terus diproses melalui stream, sementara hasil baru yang tidak memenuhi syarat akan ditolak. Catat input high watermark. Setelah setiap materializer mencapainya, buat kandidat versi final. Penyelesaian membandingkan total pemain, jumlah hitungan bucket, Top 100, peringkat sampel acak, jumlah pemain duplikat, dan setiap checksum shard. Jika berhasil, tandai manifes sebagai FINAL; layanan hadiah hanya membaca versi yang tidak dapat diubah tersebut. Perselisihan di kemudian hari masuk ke alur koreksi yang diaudit alih-alih menulis ulang papan yang telah diberi hadiah secara diam-diam.
Jika cache pemeringkatan atau seluruh klaster hilang, putar ulang (replay) tabel skor saat ini atau buku besar yang tidak dapat diubah berdasarkan score_version ke dalam namespace baru. Bangun dan rekonsiliasi kandidat yang lengkap, lalu tukar manifes secara atomik. Versi lama tetap hanya-baca (read-only) selama proses pembangunan kembali. Jika tidak ada yang tersedia, kembalikan status tidak tersedia sementara atau basi secara eksplisit daripada menyajikan papan yang tidak lengkap sebagai data yang utuh.
Matriks kegagalan mencakup event duplikat dan di luar urutan; peningkatan lintas-bucket dan koreksi ke bawah; shard crash di tengah epoch; koordinator crash sebelum dan sesudah penukaran manifes; hilangnya node cache; banyaknya skor seri pada batas Top-100; 1 juta QPS pembacaan; hasil yang terlambat di sekitar batas waktu; dan pembangunan ulang penuh dengan perbandingan shadow. Uji keempat invarian secara terus-menerus dan pantau p99 dari event-ke-publikasi, usia versi, kemiringan bucket (bucket skew), event yang ditolak, progres pembangunan kembali, dan kegagalan checksum.
Contoh Jawaban yang Kuat
“Saya akan mendefinisikan peringkat terlebih dahulu. Pemain yang seri berbagi peringkat di sini, jadi peringkat pemain adalah 1 + jumlah pemain dengan skor yang benar-benar
lebih tinggi; posisi tampilan Top-100 dan peringkat bisnis dipisahkan. Klien tidak dapat menulis skor tepercaya. Setelah validasi pertandingan, penulis papan peringkat mendeduplikasi ID event dan secara kondisional menetapkan skor terbaik absolut berdasarkan versi skor pemain. Tabel skor saat ini adalah sumber kebenaran, dan change stream yang telah di-commit menggerakkan tampilan peringkat.
“Jika papan peringkat muat dalam satu shard Redis, saya akan mulai dengan satu Sorted Set per musim. Menetapkan skor, membaca Top-N, dan menghitung berdasarkan skor dilakukan secara langsung. Namun papan peringkat global untuk 50 juta pemain adalah satu hot key, dan Redis Cluster melakukan sharding berdasarkan kunci alih-alih berdasarkan anggota di dalam kunci. Karena rentang skor dibatasi pada 0..1,000,000, saya akan meningkatkannya menjadi 101 bucket rentang skor tetap. Peringkat pribadi adalah prefiks hitungan bucket yang lebih tinggi ditambah pemain yang benar-benar lebih tinggi di bucket lokal ditambah satu. Top 100 berasal dari bucket tertinggi yang tidak kosong.
“Perpindahan lintas-bucket mengubah dua kunci dan hitungan. Memperbarui langsung di tempat akan mengekspos status perantara, jadi saya akan membangun indeks berikutnya dalam epoch paling lama beberapa detik. Hanya setelah setiap shard menerapkan perubahan idempoten dan rekonsiliasi keanggotaan/checksum lolos, koordinator akan menukar manifes saat ini secara atomik. Setiap respons mencakup versi dan as-of, dan pembacaan lingkungan sekitar mempertahankan versi tersebut. Versi yang diterbitkan tepat dengan konsekuensi keusangan hingga 5 detik.
“Pada 128 byte per event, penulisan puncak adalah 25,6 MB/s lalu lintas logis, sekitar 2,21 TB per hari, dan batas bawah log tiga replika sekitar 6,64 TB. Node indeks dan jumlah bucket tetap ditentukan dari benchmark pada data berbentuk produksi. Simpan Top 100 dalam cache berdasarkan versi. Bangun papan peringkat teman dengan mengambil skor teman secara batch dan mengurutkannya secara lokal, menghindari penyebaran (fan-out) pada setiap perubahan skor.
“Pada penutupan musim, saya akan mencatat input high watermark, menunggu semua shard mengejar ketinggalan, dan membekukan kandidat versi final. Saya akan merekonsiliasi keanggotaan, jumlah bucket, Top 100, sampel peringkat, dan checksum shard sebelum layanan hadiah membaca manifes FINAL. Cache yang hilang dibangun kembali dari tabel skor atau buku besar tepercaya. Pengujian kegagalan mencakup event duplikat/di luar urutan, koreksi lintas-bucket, crash pada shard dan koordinator, banyaknya skor seri, race condition saat batas waktu, dan pembangunan kembali secara penuh.”
Kesalahan Umum
- Menerima skor langsung dari klien → penyimpanan pengurutan tidak dapat menentukan apakah skor tersebut valid → hanya konsumsi hasil yang diaudit dan dikonfirmasi oleh layanan pertandingan tepercaya.
- Menggunakan
ZREVRANK + 1untuk peringkat yang seri → ZSET memberikan posisi leksikografis yang berbeda untuk skor yang sama, melanggar kontrak peringkat bersama → hitung1 + count(score > my_score). - Mengemas stempel waktu sembarangan ke dalam skor floating-point → tipe double memiliki batas presisi dan pengkodean komposit dapat membalikkan prioritas kunci → definisikan aturan seri terlebih dahulu, lalu gunakan pengkodean bilangan bulat yang terbukti atau indeks urutan komposit.
- Mengasumsikan lebih banyak node Redis Cluster membagi satu kunci papan peringkat global → Klaster menetapkan slot hash untuk satu kunci, dan kunci tunggal tersebut tetap berada pada primary dari satu slot → ukur batas kemampuan kunci tunggal, lalu lakukan sharding berdasarkan dimensi bisnis yang dapat digabungkan.
- Melakukan hash-sharding pada pemain dan menyebarkan (fan-out) setiap kueri peringkat → biaya kueri tumbuh seiring jumlah shard dan melonjak tajam pada 1 juta QPS pembacaan → gunakan hitungan pada rentang skor terbatas, atau kembalikan perkiraan peringkat jika diizinkan.
- Menghapus lalu menambah, atau menambah lalu menghapus, lintas-bucket → pembaca melihat penghilangan data, duplikasi, atau hitungan yang salah → bangun versi lengkap dan terbitkan secara atomik melalui manifes.
- Menggunakan
ZINCRBYuntuk setiap hasil → event duplikat bertambah dua kali dan pencabutan skor kecurangan tidak dapat menurunkannya dengan aman → tetapkan skor absolut secara idempoten berdasarkan event dan versi pemain. - Membagikan hadiah dari cache langsung saat batas waktu (cutoff) → event sebelum batas waktu yang telah diterima mungkin masih dalam proses, dan cache bukanlah kebenaran yang tahan lama → catat high watermark, kejar ketertinggalan, rekonsiliasi, dan bekukan versi FINAL.
- Hanya memvalidasi bahwa Top 100 terlihat benar → kesalahan hitungan bucket atau pemain yang terduplikasi dapat menggeser setiap peringkat ekor panjang (long-tail) → verifikasi konservasi keanggotaan, keunikan, rumus peringkat sampel, checksum shard, dan pembangunan kembali secara penuh.
Pertanyaan Lanjutan dan Jawaban
Pertanyaan Lanjutan 1: Bisakah kita mempertahankan snapshot lima detik jika pemain harus langsung membaca apa yang baru saja mereka tulis?
Pisahkan pengalaman read-your-writes dari penulis dengan peringkat yang diterbitkan secara global. Respons penulisan dapat mengembalikan skor baru yang telah di-commit dan versi yang tertunda. Refresh langsung dapat menyatakan bahwa skor telah dikonfirmasi dan peringkat global sedang diperbarui, atau menampilkan skor pribadi yang baru sementara peringkat global masih mereferensikan manifes lengkap terbaru. Jika produk mewajibkan skor baru dan peringkat global yang tepat dalam respons sinkron yang sama, pembaruan harus memasuki jalur pengurutan yang dikoordinasikan secara global. Latensi penulisan dan ketergantungan kegagalan akan meningkat, sehingga target throughput awal harus dievaluasi ulang.
Pertanyaan Lanjutan 2: Bagaimana jika 10 juta pemain menumpuk di bucket skor tertinggi?
Tata letak bucket adalah metadata berversi. Pertama buktikan adanya hotspot melalui QPS penulisan, keanggotaan, CPU, dan p99, lalu bagi rentang skor tersebut menjadi sub-bucket yang lebih sempit. Karena peringkat bersama bergantung pada skor yang benar-benar lebih tinggi, satu skor pasti tidak dapat di-shard secara acak berdasarkan pemain lalu peringkat lokalnya dijumlahkan; jumlah totalnya harus tetap diagregasi. Bangun tata letak baru secara paralel, bandingkan keanggotaan dan peringkat sampel pada input high watermark yang sama, lalu terbitkan manifes yang mereferensikan tata letak baru tersebut.
Pertanyaan Lanjutan 3: Apa yang berubah jika pemain pertama yang mencapai skor seri dinyatakan menang?
Peringkat bukan lagi hitungan berdasarkan skor saja. Kunci pengurutan menjadi (score DESC, achieved_at ASC, player_id ASC), dengan achieved_at berasal dari penyelesaian pertandingan tepercaya. ZSET normal hanya memecah seri berdasarkan urutan leksikografis anggota. Skor bilangan bulat dengan lebar tetap dan pengkodean anggota terbalik dapat berfungsi jika presisi dan urutannya terbukti, tetapi ini rapuh. Pada skala yang lebih besar, saya akan memilih shard terurut yang mendukung kunci komposit dan statistik urutan, dengan pengujian untuk milidetik yang sama, percobaan ulang, dan waktu pencapaian yang dikoreksi.
Pertanyaan Lanjutan 4: Apa yang terjadi jika anti-cheat mencabut status juara setelah hadiah dibagikan?
Sistem teknis tidak dapat memutuskan apakah hadiah ditarik kembali secara operasional. Sistem akan menerima koreksi negatif dengan score_version yang lebih tinggi, mempertahankan event asli, bukti, penyetuju, dan waktu, serta menghasilkan manifes baru yang terkoreksi tanpa menulis ulang snapshot FINAL yang asli. Layanan hadiah menerapkan kebijakan operasional untuk membekukan, memulihkan, atau mempromosikan hadiah dan mengaitkan tindakannya dengan versi papan peringkat. Ini mempertahankan penjelasan mengenai pemberian hadiah awal maupun koreksi di kemudian hari.
Pertanyaan Lanjutan 5: Bagaimana Anda membuktikan bahwa pembangunan kembali dari buku besar cocok dengan papan peringkat online?
Putar ulang versi event hingga input high watermark yang sama di dalam namespace yang terisolasi. Bandingkan jumlah pemain, hitungan per bucket skor, jumlah dan hash skor, Top 100, banyak pemain sampel bertingkat menggunakan 1 + count(higher score), dan checksum deterministik untuk setiap shard. Lakukan shadow pada kedua jalur pembacaan dan hitung setiap perbedaan skor, peringkat, atau jendela tetangga. Tukar manifes hanya setelah setiap ambang batas terpenuhi. Pekerjaan pembangunan kembali yang selesai dengan sukses bukanlah bukti bahwa hasilnya benar.