Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Zabbix adalah sistem pemantauan. Seperti sistem lainnya, sistem ini menghadapi tiga masalah utama dari semua sistem pemantauan: pengumpulan dan pemrosesan data, penyimpanan riwayat, dan pembersihannya.

Tahapan penerimaan, pengolahan, dan pencatatan data memerlukan waktu. Tidak banyak, namun untuk sistem yang besar hal ini dapat mengakibatkan penundaan yang besar. Masalah penyimpanan adalah masalah akses data. Mereka digunakan untuk laporan, pemeriksaan dan pemicu. Latensi dalam akses data juga memengaruhi kinerja. Ketika basis data berkembang, data yang tidak relevan harus dihapus. Menghapus adalah operasi sulit yang juga memakan sejumlah sumber daya.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Masalah penundaan selama pengumpulan dan penyimpanan di Zabbix diselesaikan dengan caching: beberapa jenis cache, caching dalam database. Untuk mengatasi masalah ketiga, caching tidak cocok, sehingga Zabbix menggunakan TimescaleDB. Dia akan memberitahumu tentang hal itu Andrey Gushchin - insinyur dukungan teknis Zabbix SIA. Andrey telah mendukung Zabbix selama lebih dari 6 tahun dan memiliki pengalaman langsung dengan kinerja.

Bagaimana cara kerja TimescaleDB, kinerja apa yang dapat diberikannya dibandingkan dengan PostgreSQL biasa? Peran apa yang dimainkan Zabbix untuk database TimescaleDB? Bagaimana memulai dari awal dan cara bermigrasi dari PostgreSQL dan konfigurasi mana yang memiliki kinerja lebih baik? Tentang semua ini di bawah potongan.

Tantangan Produktivitas

Setiap sistem pemantauan menghadapi tantangan kinerja yang spesifik. Saya akan membahas tiga di antaranya: pengumpulan dan pemrosesan data, penyimpanan, dan pembersihan riwayat.

Pengumpulan dan pemrosesan data yang cepat. Sistem pemantauan yang baik harus dengan cepat menerima semua data dan memprosesnya sesuai dengan ekspresi pemicu – sesuai dengan kriterianya. Setelah diproses, sistem juga harus segera menyimpan data ini ke database untuk digunakan nanti.

Penyimpanan sejarah. Sistem pemantauan yang baik harus menyimpan riwayat dalam database dan menyediakan akses mudah ke metrik. Riwayat diperlukan untuk digunakan dalam laporan, grafik, pemicu, ambang batas, dan item data peringatan yang dihitung.

Menghapus riwayat. Terkadang ada saatnya Anda tidak perlu menyimpan metrik. Mengapa Anda memerlukan data yang dikumpulkan 5 tahun yang lalu, satu atau dua bulan: beberapa node telah dihapus, beberapa host atau metrik tidak lagi diperlukan karena sudah usang dan tidak lagi dikumpulkan. Sistem pemantauan yang baik harus menyimpan data historis dan menghapusnya dari waktu ke waktu agar database tidak bertambah.

Membersihkan data lama adalah masalah penting yang sangat mempengaruhi kinerja database.

Menyimpan cache di Zabbix

Di Zabbix, panggilan pertama dan kedua diselesaikan menggunakan caching. RAM digunakan untuk mengumpulkan dan memproses data. Untuk penyimpanan - riwayat dalam pemicu, grafik, dan elemen data terhitung. Di sisi database terdapat beberapa cache untuk pilihan dasar, misalnya grafik.

Caching pada sisi server Zabbix sendiri adalah :

  • KonfigurasiCache;
  • NilaiCache;
  • RiwayatCache;
  • TrenCache.

Pertimbangkan mereka dengan lebih detail.

KonfigurasiCache

Ini adalah cache utama tempat kami menyimpan metrik, host, item data, pemicu - semua yang kami perlukan untuk Pra-Pemrosesan dan pengumpulan data.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Semua ini disimpan di ConfigurationCache agar tidak membuat kueri yang tidak perlu di database. Setelah server dimulai, kami memperbarui cache ini, membuat dan memperbarui konfigurasi secara berkala.

Pengumpulan data

Diagramnya cukup besar, tetapi hal utama di dalamnya adalah kolektor. Ini adalah berbagai "poller" - proses perakitan. Mereka bertanggung jawab atas berbagai jenis perakitan: mereka mengumpulkan data melalui SNMP, IPMI, dan mentransfer semuanya ke PreProcessing.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDBKolektor diberi garis oranye.

Zabbix telah menghitung item agregasi yang diperlukan untuk menggabungkan pemeriksaan. Jika kami memilikinya, kami mengambil datanya langsung dari ValueCache.

Cache Riwayat Pra-Pemrosesan

Semua kolektor menggunakan ConfigurationCache untuk menerima pekerjaan. Kemudian mereka mentransfernya ke PreProcessing.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

PreProcessing menggunakan ConfigurationCache untuk menerima langkah-langkah PreProcessing. Ini memproses data ini dengan berbagai cara.

Setelah data diolah menggunakan PreProcessing, kita simpan di HistoryCache untuk diproses. Ini mengakhiri pengumpulan data dan kita beralih ke proses utama di Zabbix - sinkronisasi sejarah, karena ini adalah arsitektur monolitik.

Catatan: Pra-Pemrosesan adalah operasi yang cukup sulit. Dengan v 4.2 telah dipindahkan ke proxy. Jika Anda memiliki Zabbix yang sangat besar dengan jumlah elemen data dan frekuensi pengumpulan yang banyak, maka ini akan membuat pekerjaan menjadi lebih mudah.

ValueCache, cache riwayat & tren

Sinkronisasi riwayat adalah proses utama yang memproses secara atom setiap elemen data, yaitu setiap nilai.

Sinkronisasi riwayat mengambil nilai dari HistoryCache dan memeriksa Konfigurasi untuk mengetahui adanya pemicu penghitungan. Jika ada, itu akan dihitung.

Sinkronisasi riwayat membuat peristiwa, eskalasi untuk membuat peringatan jika diperlukan oleh konfigurasi, dan mencatat. Jika ada pemicu untuk pemrosesan selanjutnya, maka nilai ini disimpan di ValueCache agar tidak mengakses tabel riwayat. Beginilah cara ValueCache diisi dengan data yang diperlukan untuk menghitung pemicu dan elemen terhitung.

Sinkronisasi riwayat menulis semua data ke database, dan menulis ke disk. Proses pemrosesan berakhir di sini.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Caching di database

Di sisi database terdapat berbagai cache ketika Anda ingin melihat grafik atau laporan kejadian:

  • Innodb_buffer_pool di sisi MySQL;
  • shared_buffers di sisi PostgreSQL;
  • effective_cache_size di pihak Oracle;
  • shared_pool di sisi DB2.

Ada banyak cache lainnya, tapi ini adalah cache utama untuk semua database. Mereka memungkinkan Anda menyimpan data dalam RAM yang sering kali diperlukan untuk kueri. Mereka punya teknologi sendiri untuk ini.

Kinerja basis data sangat penting

Server Zabbix terus-menerus mengumpulkan data dan menulisnya. Saat dimulai ulang, ia juga membaca riwayat untuk mengisi ValueCache. Menggunakan skrip dan laporan API Zabbix, yang dibangun pada antarmuka Web. Zabbix API mengakses database dan mengambil data yang diperlukan untuk grafik, laporan, daftar acara, dan terbitan terbaru.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Untuk visualisasi - grafana. Ini adalah solusi populer di kalangan pengguna kami. Itu dapat langsung mengirim permintaan melalui Zabbix API dan ke database, dan menciptakan persaingan tertentu untuk menerima data. Oleh karena itu, penyesuaian database yang lebih baik dan lebih baik diperlukan agar dapat mengimbangi pengiriman hasil dan pengujian yang cepat.

Pengurus rumah tangga

Tantangan performa ketiga di Zabbix adalah pembersihan riwayat menggunakan Housekeeper. Ini mengikuti semua pengaturan - elemen data menunjukkan berapa lama untuk menyimpan dinamika perubahan (tren) dalam beberapa hari.

Kami menghitung TrendsCache dengan cepat. Ketika data tiba, kami menggabungkannya selama satu jam dan mencatatnya dalam tabel untuk mengetahui dinamika perubahan tren.

Pengurus rumah tangga memulai dan menghapus informasi dari database menggunakan “pilihan” yang biasa. Hal ini tidak selalu efektif, seperti terlihat dari grafik kinerja proses internal.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Grafik merah menunjukkan bahwa sinkronisasi Riwayat selalu sibuk. Grafik oranye di atas adalah Housekeeper, yang terus berjalan. Dia menunggu database menghapus semua baris yang dia tentukan.

Kapan Anda harus menonaktifkan Housekeeper? Misalnya ada “Item ID” dan Anda perlu menghapus 5 ribu baris terakhir dalam waktu tertentu. Tentu saja, hal ini terjadi berdasarkan indeks. Namun biasanya datasetnya sangat besar, dan database masih membaca dari disk dan menyimpannya ke dalam cache. Ini selalu merupakan operasi yang sangat mahal untuk database dan, tergantung pada ukuran database, dapat menyebabkan masalah kinerja.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Pengurus rumah tangga mudah dinonaktifkan. Di antarmuka Web terdapat pengaturan di “Administrasi umum” untuk Pengurus Rumah Tangga. Kami menonaktifkan Housekeeping internal untuk riwayat tren internal dan tidak lagi mengelolanya.

Pengurus rumah tangga dimatikan, grafik menjadi rata - masalah apa yang mungkin timbul dalam kasus ini dan apa yang dapat membantu memecahkan tantangan kinerja ketiga?

Partisi - mempartisi atau mempartisi

Biasanya, partisi dikonfigurasi dengan cara berbeda pada setiap database relasional yang saya daftarkan. Masing-masing memiliki teknologinya sendiri, namun secara umum serupa. Membuat partisi baru seringkali menimbulkan masalah tertentu.

Biasanya, partisi dikonfigurasikan bergantung pada "pengaturan" - jumlah data yang dibuat dalam satu hari. Biasanya, Partisi dikeluarkan dalam satu hari, ini adalah jumlah minimum. Untuk tren batch baru - 1 bulan.

Nilainya bisa berubah jika “setup”-nya sangat besar. Jika “setup” kecil hingga 5 nvps (nilai baru per detik), yang sedang dari 000 hingga 5, lalu yang besar di atas 000 nvps. Ini adalah instalasi besar dan sangat besar yang memerlukan konfigurasi database yang cermat.

Pada instalasi yang sangat besar, jangka waktu satu hari mungkin tidak optimal. Saya telah melihat partisi MySQL sebesar 40 GB atau lebih per hari. Jumlah data yang sangat besar ini dapat menimbulkan masalah dan perlu dikurangi.

Apa yang diberikan Partisi?

Tabel partisi. Seringkali ini adalah file terpisah di disk. Paket kueri memilih satu partisi dengan lebih optimal. Biasanya partisi digunakan berdasarkan rentang - ini juga berlaku untuk Zabbix. Kami menggunakan "cap waktu" di sana - waktu sejak awal era. Ini adalah angka-angka biasa bagi kami. Anda mengatur awal dan akhir hari - ini adalah partisi.

Penghapusan cepat - DELETE. Satu file/subtabel dipilih, bukan pemilihan baris untuk dihapus.

Secara signifikan mempercepat pengambilan data SELECT - menggunakan satu atau lebih partisi, bukan seluruh tabel. Jika Anda mengakses data yang berumur dua hari, data tersebut diambil dari database lebih cepat karena Anda hanya perlu memuat satu file ke dalam cache dan mengembalikannya, bukan tabel besar.

Seringkali banyak database juga dipercepat INSERT — penyisipan ke dalam tabel anak.

Skala waktuDB

Untuk v 4.2, kami mengalihkan perhatian kami ke TimescaleDB. Ini adalah ekstensi untuk PostgreSQL dengan antarmuka asli. Ekstensi ini bekerja secara efektif dengan data deret waktu, tanpa kehilangan manfaat database relasional. TimescaleDB juga mempartisi secara otomatis.

TimescaleDB memiliki konsep hipertable (hipertabel) yang Anda buat. Itu mengandung potongan - partisi. Potongan adalah fragmen hypertable yang dikelola secara otomatis dan tidak memengaruhi fragmen lainnya. Setiap potongan memiliki rentang waktunya sendiri.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Skala WaktuDB vs PostgreSQL

TimescaleDB bekerja sangat efisien. Produsen ekstensi mengklaim bahwa mereka menggunakan algoritme pemrosesan kueri yang lebih tepat, khususnya inserts . Seiring bertambahnya ukuran penyisipan kumpulan data, algoritme mempertahankan kinerja yang konstan.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Setelah 200 juta baris, PostgreSQL biasanya mulai melorot secara signifikan dan kehilangan kinerja hingga 0. TimescaleDB memungkinkan Anda memasukkan "sisipan" secara efisien untuk sejumlah data berapa pun.

Instalasi

Menginstal TimescaleDB cukup mudah untuk paket apa pun. DI DALAM dokumentasi semuanya dijelaskan secara rinci - itu tergantung pada paket resmi PostgreSQL. TimescaleDB juga dapat dibangun dan dikompilasi secara manual.

Untuk database Zabbix kita cukup mengaktifkan ekstensinya:

echo "CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;" | sudo -u postgres psql zabbix

Anda mengaktifkan extension dan buat untuk database Zabbix. Langkah terakhir adalah membuat hypertable.

Memigrasikan tabel riwayat ke TimescaleDB

Ada fungsi khusus untuk ini create_hypertable:

SELECT create_hypertable(‘history’, ‘clock’, chunk_time_interval => 86400, migrate_data => true);
SELECT create_hypertable(‘history_unit’, ‘clock’, chunk_time_interval => 86400, migrate_data => true);
SELECT create_hypertable(‘history_log’, ‘clock’, chunk_time_interval => 86400, migrate_data => true);
SELECT create_hypertable(‘history_text’, ‘clock’, chunk_time_interval => 86400, migrate_data => true);
SELECT create_hypertable(‘history_str’, ‘clock’, chunk_time_interval => 86400, migrate_data => true);
SELECT create_hypertable(‘trends’, ‘clock’, chunk_time_interval => 86400, migrate_data => true);
SELECT create_hypertable(‘trends_unit’, ‘clock’, chunk_time_interval => 86400, migrate_data => true);
UPDATE config SET db_extension=’timescaledb’, hk_history_global=1, hk_trends_global=1

Fungsi ini memiliki tiga parameter. Pertama - tabel dalam database, untuk itu Anda perlu membuat hypertable. Kedua - bidang, sesuai dengan yang perlu Anda buat chunk_time_interval — interval potongan partisi yang akan digunakan. Dalam kasus saya, intervalnya adalah satu hari - 86.

Parameter ketiga - migrate_data. Jika Anda mengatur true, lalu semua data saat ini ditransfer ke potongan yang telah dibuat sebelumnya. Saya sendiri yang menggunakannya migrate_data. Saya menderita sekitar 1 TB, yang memakan waktu lebih dari satu jam. Bahkan dalam beberapa kasus, selama pengujian, saya menghapus data historis tipe karakter yang tidak diperlukan untuk penyimpanan, agar tidak ditransfer.

Langkah terakhir - UPDATE: db_extension taruh timescaledbsehingga database memahami bahwa ekstensi ini ada. Zabbix mengaktifkannya dan menggunakan sintaksis dan kueri ke database dengan benar - fitur-fitur yang diperlukan untuk TimescaleDB.

Konfigurasi perangkat keras

Saya menggunakan dua server. Pertama - mesin VMware. Ukurannya cukup kecil: 20 prosesor Intel® Xeon® CPU E5-2630 v 4 @ 2.20GHz, RAM 16 GB, dan SSD 200 GB.

Saya menginstal PostgreSQL 10.8 dengan OS Debian 10.8-1.pgdg90+1 dan sistem file xfs. Saya mengkonfigurasi semuanya secara minimal untuk menggunakan database khusus ini, kecuali apa yang akan digunakan Zabbix sendiri.

Di mesin yang sama ada server Zabbix, PostgreSQL dan agen beban. Saya memiliki 50 agen aktif yang menggunakan LoadableModuleuntuk dengan cepat menghasilkan hasil yang berbeda: angka, string. Saya mengisi database dengan banyak data.

Awalnya konfigurasinya berisi 5 elemen data per host. Hampir setiap elemen mengandung pemicu agar mirip dengan instalasi sebenarnya. Dalam beberapa kasus, terdapat lebih dari satu pemicu. Untuk satu node jaringan ada 3-000 pemicu.

Interval Pembaruan Item Data - detik 4-7. Saya mengatur bebannya sendiri dengan menggunakan tidak hanya 50 agen, tetapi menambahkan lebih banyak. Selain itu, dengan menggunakan elemen data, saya menyesuaikan beban secara dinamis dan mengurangi interval pembaruan menjadi 4 detik.

PostgreSQL. 35 nvps

Pengoperasian pertama saya pada perangkat keras ini menggunakan PostgreSQL murni - 35 ribu nilai per detik. Seperti yang Anda lihat, memasukkan data membutuhkan waktu sepersekian detik - semuanya baik dan cepat. Satu-satunya hal adalah disk SSD 200 GB terisi dengan cepat.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Ini adalah dasbor kinerja server Zabbix standar.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Grafik biru pertama adalah jumlah nilai per detik. Grafik kedua di sebelah kanan adalah pemuatan proses pembangunan. Yang ketiga adalah memuat proses pembangunan internal: sinkronisasi riwayat dan Pengurus Rumah Tangga, yang telah berjalan di sini selama beberapa waktu.

Grafik keempat menunjukkan penggunaan HistoryCache. Ini semacam buffer sebelum dimasukkan ke database. Grafik kelima berwarna hijau menunjukkan penggunaan ValueCache, yaitu berapa banyak ValueCache yang terkena pemicu - ini adalah beberapa ribu nilai per detik.

PostgreSQL. 50 nvps

Kemudian saya meningkatkan beban menjadi 50 ribu nilai per detik pada perangkat keras yang sama.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Saat memuat dari Housekeeper, memasukkan 10 ribu nilai membutuhkan waktu 2-3 detik.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB
Pengurus rumah tangga sudah mulai mengganggu pekerjaan.

Grafik ketiga menunjukkan bahwa secara umum beban trapper dan history syncher masih sebesar 60%. Pada grafik keempat, HistoryCache sudah mulai terisi cukup aktif selama pengoperasian Housekeeper. Penuh 20%, yaitu sekitar 0,5 GB.

PostgreSQL. 80 nvps

Lalu saya tingkatkan bebannya menjadi 80 ribu nilai per detik. Ini kira-kira 400 ribu elemen data dan 280 ribu pemicu.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB
Biaya pemuatan tiga puluh sinkronisasi sejarah sudah cukup tinggi.

Saya juga meningkatkan berbagai parameter: sinkronisasi riwayat, cache.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Di perangkat keras saya, pemuatan sinkronisasi riwayat meningkat secara maksimal. HistoryCache dengan cepat terisi dengan data - data untuk pemrosesan telah terakumulasi di buffer.

Selama ini saya mengamati bagaimana prosesor, RAM dan parameter sistem lainnya digunakan, dan menemukan bahwa pemanfaatan disk sudah maksimal.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Saya telah mencapai penggunaannya kemampuan disk maksimum pada perangkat keras ini dan pada mesin virtual ini. Dengan intensitas seperti itu, PostgreSQL mulai melakukan flush data dengan cukup aktif, dan disk tidak lagi memiliki waktu untuk menulis dan membaca.

Server kedua

Saya mengambil server lain yang sudah memiliki 48 prosesor dan RAM 128 GB. Saya menyetelnya - menyetelnya ke 60 sinkronisasi riwayat, dan mencapai kinerja yang dapat diterima.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Padahal, ini sudah menjadi batas produktivitas yang perlu dilakukan sesuatu.

Skala waktuDB. 80 nvps

Tugas utama saya adalah menguji kemampuan TimescaleDB terhadap beban Zabbix. 80 ribu nilai per detik itu banyak, frekuensi pengumpulan metrik (kecuali Yandex, tentu saja) dan “pengaturan” yang cukup besar.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Ada penurunan di setiap grafik - inilah tepatnya migrasi data. Setelah kegagalan di server Zabbix, profil pemuatan sinkronisasi riwayat banyak berubah - turun tiga kali.

TimescaleDB memungkinkan Anda memasukkan data hampir 3 kali lebih cepat dan menggunakan lebih sedikit HistoryCache.

Oleh karena itu, Anda akan menerima data tepat waktu.

Skala waktuDB. 120 nvps

Kemudian saya menambah jumlah elemen data menjadi 500 ribu Tugas utamanya adalah menguji kemampuan TimescaleDB - saya menerima nilai terhitung 125 ribu nilai per detik.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Ini adalah “pengaturan” yang berfungsi dan dapat berfungsi untuk waktu yang lama. Namun karena disk saya hanya berukuran 1,5 TB, saya mengisinya dalam beberapa hari.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Yang paling penting adalah pada saat yang sama partisi TimescaleDB baru dibuat.

Ini sama sekali tidak terlihat dalam hal kinerja. Ketika partisi dibuat di MySQL, misalnya, semuanya berbeda. Hal ini biasanya terjadi pada malam hari karena menghalangi penyisipan umum, bekerja dengan tabel, dan dapat menyebabkan penurunan layanan. Hal ini tidak terjadi pada TimescaleDB.

Sebagai contoh, saya akan menunjukkan satu grafik dari banyak grafik di komunitas. Dalam gambar, TimescaleDB diaktifkan, sehingga beban penggunaan io.weight pada prosesor telah berkurang. Penggunaan elemen proses internal juga berkurang. Selain itu, ini adalah mesin virtual biasa pada disk pancake biasa, bukan SSD.

Performa tinggi dan partisi asli: Zabbix dengan dukungan TimescaleDB

Temuan

TimescaleDB adalah solusi yang baik untuk "pengaturan" kecil, yang memengaruhi kinerja disk. Ini akan memungkinkan Anda untuk terus bekerja dengan baik hingga database dimigrasikan ke perangkat keras secepat mungkin.

TimescaleDB mudah dikonfigurasi, memberikan peningkatan kinerja, bekerja dengan baik dengan Zabbix dan memiliki keunggulan dibandingkan PostgreSQL.

Jika Anda menggunakan PostgreSQL dan tidak berencana mengubahnya, saya sarankan gunakan PostgreSQL dengan ekstensi TimescaleDB bersama dengan Zabbix. Solusi ini bekerja secara efektif hingga "pengaturan" sedang.

Yang kami maksud saat kami mengatakan “kinerja tinggi”. HighLoad ++. Anda tidak perlu menunggu lama untuk mempelajari teknologi dan praktik yang memungkinkan layanan melayani jutaan pengguna. Daftar laporan untuk tanggal 7 dan 8 November sudah kami kompilasi, namun disini pertemuan lebih banyak lagi yang bisa disarankan.

Berlangganan kami ассылку и Telegram, di mana kami mengungkapkan fitur-fitur konferensi yang akan datang, dan mencari tahu cara memanfaatkannya semaksimal mungkin.

Sumber: www.habr.com

Tambah komentar