Bagaimana Kami Membangun Pipeline Sub-30ms tracio.ai
Dari pengumpulan sinyal hingga visitor ID dalam waktu kurang dari 30ms: arsitektur kami menggunakan Go, ClickHouse, Redis, dan pemrosesan terdistribusi.
Ketika kami mulai membangun mesin identifikasi perangkat tracio.ai, kami punya satu persyaratan yang tidak bisa ditawar: seluruh pipeline — dari menerima sinyal terenkripsi hingga mengembalikan visitor ID — harus selesai dalam waktu kurang dari 30 milidetik pada persentil ke-95. Artikel ini adalah penjelasan mendetail tentang arsitektur yang kami bangun untuk mencapai target tersebut.
Gambaran Umum Pipeline
Pipeline identifikasi memiliki lima tahap: dekripsi sinyal, normalisasi sinyal, komputasi hash, resolusi identitas, dan serialisasi respons. Setiap tahap dioptimalkan secara independen, dan tahap-tahap yang bisa berjalan paralel memang dijalankan paralel. Total anggaran adalah 30ms, dialokasikan kira-kira sebagai: dekripsi 2ms, normalisasi 3ms, hashing 2ms, resolusi identitas 20ms, serialisasi 1ms. Sisa 2ms adalah cadangan.
Dekripsi sinyal membalik transport terenkripsi di sisi klien. Kami menggunakan paket kripto Go dengan akselerasi perangkat keras, yang menyelesaikan dekripsi payload 4KB umum dalam waktu kurang dari 1ms. Normalisasi mem-parsing JSON sinyal, memvalidasi tipe, dan menerapkan transformasi spesifik platform — misalnya, menormalkan string user agent untuk menghilangkan noise yang spesifik versi.
Resolusi Identitas Terdistribusi
Resolusi identitas — menentukan apakah perangkat ini pernah terlihat sebelumnya — adalah tahap yang paling sensitif terhadap latensi. Kami menyimpan profil perangkat di Redis, di-shard pada sebuah cluster menggunakan lapisan routing key terdistribusi. Routing mendistribusikan key berdasarkan fingerprint tingkat perangkat keras, yang memastikan bahwa pencarian untuk perangkat yang sama selalu mengenai node Redis yang sama.
Implementasi sharding kami menggunakan virtual node (150 per node fisik) untuk memastikan distribusi yang merata. Ketika sebuah node ditambahkan atau dihapus, hanya 1/N key yang perlu dipetakan ulang, di mana N adalah jumlah node. Kami mengimplementasikan lapisan routing dalam Go dengan waktu pencarian O(log n) dan tanpa alokasi.
Redis sebagai Penyimpan Identitas
Kami memilih Redis dibandingkan alternatif lain (Memcached, ScyllaDB, DynamoDB) karena waktu responsnya yang konsisten di bawah satu milidetik dan dukungannya terhadap struktur data yang kompleks. Setiap profil perangkat disimpan sebagai hash Redis dengan field untuk hash tiap tingkatan sinyal, visitor ID, timestamp terakhir terlihat, dan metadata confidence.
Kueri resolusi identitas adalah satu panggilan HGETALL yang diikuti perbandingan hash sinyal yang masuk terhadap hash yang tersimpan. Jika tingkatan perangkat keras cocok, kami mengembalikan visitor ID yang sudah ada dengan confidence tinggi. Jika hanya tingkatan perangkat lunak yang cocok, kami melakukan perbandingan kemiripan pada data tingkat sinyal untuk menentukan apakah ini perangkat yang sama dengan browser yang telah diperbarui. Jika tidak ada yang cocok, kami menghasilkan visitor ID baru.
ClickHouse untuk Penyimpanan Event
Setiap event identifikasi ditulis ke ClickHouse secara asinkron. Kami menggunakan penulis ber-buffer yang mem-batch insert — mengumpulkan event selama 100ms atau hingga terkumpul 1.000 event, mana yang lebih dulu. Batching ini krusial karena ClickHouse bekerja paling optimal dengan insert besar (ribuan baris sekaligus) alih-alih insert baris satu per satu.
Skema ClickHouse kami dioptimalkan untuk dua pola kueri yang paling umum: mencari semua event untuk suatu visitor ID tertentu, dan mengagregasi event sepanjang periode waktu. Kami menggunakan engine MergeTree dengan primary key (visitor_id, timestamp), yang memberikan point lookup cepat dan range scan yang efisien. Materialized view memelihara metrik harian dan per jam yang telah diagregasi sebelumnya.
Mencapai Sub-30ms dalam Skala Besar
Tiga keputusan arsitektur sangat krusial untuk memenuhi target latensi kami. Pertama, pipeline sepenuhnya streaming — kami mulai memproses sinyal sebelum seluruh body request HTTP diterima. Kedua, pencarian Redis menggunakan connection pooling dengan koneksi persisten, menghilangkan overhead TCP handshake. Ketiga, penulisan ClickHouse sepenuhnya asinkron dan tidak pernah memblokir jalur respons.
Dalam uji beban dengan 50K request/detik, latensi p50 kami adalah 12ms, p95 adalah 24ms, dan p99 adalah 38ms. p99 kadang melampaui target 30ms kami selama rebalancing cluster Redis, tetapi p95 tetap konsisten di bawah 30ms. Untuk pelanggan dengan kebutuhan latensi yang lebih ketat, kami menawarkan cluster Redis khusus yang menghilangkan kontensi multi-tenant.