Bagaimana kami menambahkan pintu masuk ke peta dan mengurangi ukuran pangkalan sebesar 10%



Pada akhir bulan lalu, 2GIS mulai menampilkan beranda. Kami telah menunjukkan pintu masuk ke organisasi sejak 2013, dan pintu masuk tampaknya merupakan pintu masuk yang sama. Jadi mengapa sekarang? Semua produk dan proses internal siap, yang perlu Anda lakukan adalah menambahkan sedikit lebih banyak dan memperbaiki tampilan di UI.

Selain jawaban standar "Ada prioritas lain," ada juga yang tidak cukup standar: "Tidak begitu sederhana." Artikel ini adalah tentang kesulitan apa dan bagaimana kami menyelesaikannya.

Pertama, pintu masuk bukan pintu masuk. Jadi, dalam satu pintu masuk bisa mengarah beberapa pintu masuk, biasanya dari sisi bangunan yang berbeda. Definisi "blok apartemen (multi-level) dengan pintu masuk bersama" juga salah. Kebetulan bahwa satu pintu masuk mengarah ke lantai pertama dari pintu masuk, dan yang lainnya - ke lantai kedua dan selanjutnya.

Masuk dengan dua pintu masuk

Kedua, saya ingin mencari pintu masuk.

Pintu masuk dalam hasil pencarian

Ini adalah kesempatan yang cukup dicari, karena pintu masuknya jauh dari selalu terletak di jalan yang jelas.

Rumah dengan penomoran masuk bukan yang paling jelas

Selain nomor masuk, ada juga nama (biasanya dari satu huruf).

Surat-surat atas nama beranda

Atau bahkan seperti di Kaliningrad - alamat terpisah diberikan tepat ke tangga.

Penomoran pintu masuk independenced di Kaliningrad

Ketiga, kami memutuskan bahwa jika kami ingin melakukan pencarian pintu masuk, maka mengapa tidak segera mendukung pencarian dengan nomor apartemen. Mereka memutuskan - dan mengumpulkan rentang apartemen, sejauh ini tanpa lantai mengikat. Seperti halnya pintu masuk, apartemen tidak hanya memiliki nama numerik (paling sering ada varian dengan huruf "a"), dan rentangnya jauh dari selalu kontinu. Di rumah-rumah tua St. Petersburg, penomorannya agak aneh: apartemen 1 dan 3 bisa berada di pintu masuk yang sama, dan apartemen 2 bisa berada di seberang gedung.

Pintu masuk khas Petersburg tua

Penyimpangan liris tentang validasi data
Jangan mencoba membuat validasi data yang dikumpulkan dengan sangat cerdas - di ibu kota utara ada kasus-kasus ketika Anda dapat masuk ke satu apartemen dari beberapa pintu masuk, dan di beberapa pemukiman Eropa, alamat itu berisi di samping nama pintu masuk dan nomor apartemen di pintu masuk ini. Peter juga senang dengan sebuah bangunan dengan dua pintu masuk kedelapan.

Keempat, saya ingin selalu menunjukkan pintu masuk di peta, dan tidak hanya ketika kita mempelajari informasi rumah atau pintu masuk tertentu.

Dan, akhirnya, ada banyak pintu masuk - menurut perkiraan saat ini, ada sekitar seratus ribu di antaranya di Moskow saja. Penilaian pertama "di jari" memang memberikan beberapa angka astronomi - kami melakukan kesalahan setiap enam kali ke sisi yang lebih besar.

Kesimpulan:

  • Kami membutuhkan entitas tambahan, yang memiliki namanya sendiri, berisi daftar rentang apartemen dan yang dapat dilampirkan di pintu masuk gedung.
  • Kami akan mencari entitas ini dan akan menunjukkannya kartu informasi terpisah.
  • Kami dipaksa untuk sekali lagi meningkatkan ukuran data yang diunduh oleh aplikasi seluler, atau menunjukkan data ini hanya jika ada koneksi jaringan, atau untuk membuat sesuatu dengan format.

Mendapatkan solusi


Dua poin pertama memerlukan perubahan pada produk internal dan eksternal, tetapi ini adalah rutin. Yang terakhir adalah masalah yang sama sekali berbeda. Karena kami tidak suka mengganggu pengguna, kami menetapkan batasan: data harus tersedia secara offline, dan menambahkannya tidak boleh menambah ukuran database.

Bagaimana? Di satu sisi, Anda perlu mengurangi ukuran data saat ini, di sisi lain, Anda dapat membuat format untuk menyimpan informasi tentang pintu masuk, sehingga jumlah data tambahan minimal.

Kurangi volume data


Ada dua cara untuk menguranginya:

  • Perhatikan dengan cermat data yang disimpan dan cobalah untuk menemukan sesuatu yang bisa kita lakukan tanpa.
  • Pikirkan apakah mungkin menyimpan data dengan lebih efisien daripada yang kita lakukan sekarang.

Evolusi format penyimpanan data sebelum era beranda

Opsi pertama dan termudah


Cara tradisional untuk menyimpan data yang kompleks adalah dengan menormalkannya , menempatkannya dalam database tabel, dan membuat indeks tambahan. Sekali, 2GIS melakukan hal itu, kecuali untuk mengurangi ukuran, isi setiap tabel diurutkan sehingga sesering mungkin nilai sel bertepatan dengan nilai-nilai dari baris sebelumnya. Kami menyimpan kolom secara independen, dan menciutkan urutan nilai yang identik.

Contoh yang sangat disederhanakan untuk mengoptimalkan penyimpanan meja dengan bangunan:

Contoh penyimpanan tabel yang dioptimalkan

Normalisasi memungkinkan Anda untuk mengurangi redundansi, tetapi ada juga sisi negatif - untuk membentuk elemen UI untuk beberapa objek, Anda harus membuat beberapa kueri yang mengakses sejumlah besar tabel. Namun, pada saat itu, tabel ini digunakan tidak hanya untuk mendapatkan data untuk ditampilkan, tetapi untuk hampir semua tugas, termasuk pencarian teks dan berbagai jenis pencarian perjalanan. Untuk semua tugas ini, data yang dinormalisasi hanya memungkinkan kami untuk mengurangi jumlah informasi yang diproses.

Data untuk rendering peta sudah memiliki format biner sendiri dan disimpan di blok yang terpisah. Secara bertahap, kami membuat format biner terpisah untuk mempercepat pencarian arah dan pencarian teks. Hanya informasi yang tersisa di database, yang digunakan untuk menampilkan kartu objek, serta untuk tautan dari beberapa objek dengan yang lain.

Kartu dan komunikasi

Sederhanakan pekerjaan


Bagaimana Anda bisa menyederhanakan bekerja dengan data? Terima semua data yang diperlukan untuk menggambar kartu sekaligus oleh pengenal objek. Karena versi online sudah menerima semua data dari API untuk satu permintaan dalam format JSON , Anda dapat sekaligus menggabungkan format yang digunakan oleh semua produk kami.

Baik json's untuk menampilkan kartu, dan komunikasi harus diterima untuk sejumlah objek, dari kekuatan beberapa puluh sekaligus. Tetapi ada skenario di mana atribut individu perlu diperoleh sekaligus untuk sampel besar, hingga ratusan ribu. Lebih logis untuk memisahkan atribut tersebut menjadi entitas yang terpisah dengan format penyimpanan yang terpisah - properti. Properti dapat bertipe dan disimpan lebih efisien dalam format biner.

Pendekatan naif untuk menyimpan json adalah menggunakan database nilai kunci. Ambil Moskow sebagai contoh, sebagai yang terbesar dari proyek kami. Bahkan dalam bentuk sesederhana mungkin - untuk setiap objek json itu sendiri disimpan, 8 byte pengidentifikasi dan karakter pembatas - direktori akan mengambil 1,9 GiB. Ukuran yang dihasilkan hampir enam kali lebih besar dari opsi yang dijelaskan sebelumnya, dan ini masih tanpa ikatan dan properti.

Kami sengaja menggembungkan objek dengan mengisinya dengan informasi tentang segala hal yang mungkin diperlukan untuk menampilkan kartu mereka, tetapi Anda masih perlu menyimpan nama bidang, tanda kutip, koma, dan tanda kurung.

Kompres data


Normalisasi bukan satu-satunya cara untuk menghilangkan redundansi. Cara populer kedua adalah kompresi. Arsip lzma dari file kami yang sangat tebal hanya membutuhkan 55 MiB.

Tentu saja, kita tidak dapat menggunakan format ini secara langsung, karena untuk mengakses objek yang berubah-ubah kita perlu membongkar semua data yang disimpan sebelumnya, dan arsip lzma tidak dibongkar dengan sangat cepat.

Bagaimana kita bisa mendapatkan akses acak cepat di satu sisi, dan di sisi lain, dengan mengompresi data, mengurangi ukuran ruang yang diperlukan? Jawabannya adalah menggunakan pagination.

Format penyimpanan biner

Sekarang setelah data diberi nomor halaman, kita dapat memampatkan masing-masing secara individual. Untuk mengakses tempat sewenang-wenang, kita perlu membuka ritsleting dan memindai halaman, tetapi ini jauh lebih cepat daripada membongkar dan memindai seluruh arsip.

Dalam format ini, semua data disimpan - json'y, hubungan dan properti. Tetap mengaitkan data ini dengan pengidentifikasi objek. Untuk setiap pengidentifikasi, kita perlu menyimpan satu atau lebih pasangan <nomor penyimpanan, nomor data dalam penyimpanan> .

Format hubungan yang disederhanakan antara pengidentifikasi objek dan data dalam penyimpanan
Semua nomor seri, offset dan ukuran disimpan dalam format terkompresi mirip dengan UTF-8 , di mana nilai kecil hanya memakan satu byte. Ini memungkinkan kita untuk mengurangi ukuran tautan dengan hanya menyortir isi dari repositori biner untuk mengurangi frekuensi kemunculannya.

Sortir dan berhemat

Beberapa properti memiliki banyak nilai frekuensi, dan karenanya penyortiran memberikan keuntungan besar dalam ukuran. Di sisi lain, karena itu, nomor seri data tidak dapat bertepatan untuk semua penyimpanan.

Juga, jauh dari semua objek memiliki data di semua penyimpanan, dan karenanya menyimpan nomor penyimpanan lebih efisien daripada merujuk objek kosong.

Mempercepat pengambilan data


Format yang dijelaskan memiliki satu masalah. Untuk menemukan jumlah objek yang menyimpan indeks untuk pengidentifikasi yang ditentukan, kita perlu menjalankan pencarian biner di dalam data objek pertama. Untuk melakukan ini, Anda harus memuat 10,9 MiB ke dalam memori, atau melakukan 21 pembacaan tambahan. Kedua solusi tidak cocok untuk kami, dan oleh karena itu kami mengurangi jumlah bacaan dengan menyimpan data di pohon.

Format pohon untuk mencari data cepat dengan pengidentifikasi

Kami mengelompokkan data pada 32 objek, dan di tingkat menengah kami menyimpan 128 pengidentifikasi pertama dari node bersarang. Kami menambahkan tiga level pohon dan mengurangi jumlah bacaan tambahan menjadi empat (tetapi pada kenyataannya, dengan memperhitungkan caching node pohon yang sebelumnya dibaca, bahkan menjadi 1-3). Harga - sedikit kurang dari 400 KiB.

Pada tahap ini, kita cukup efisien dalam menyimpan hubungan dan properti, tetapi json membutuhkan banyak ruang. Ini bisa dimengerti. Semakin besar ukuran halaman, semakin banyak objek di sana dan semakin baik algoritma kompresi dapat menentukan informasi apa yang berlebihan. Tetapi, di sisi lain, semakin banyak pekerjaan yang perlu kita lakukan untuk membaca satu objek. Json's adalah objek yang cukup besar, dan oleh karena itu ada sangat sedikit dari mereka dalam satu halaman. Karena itu, Anda perlu membantu algoritma melakukan tugasnya dengan lebih baik.

Break json menjadi beberapa bagian


Pertama, banyak objek memiliki skema data yang cocok, hanya nilai atribut yang berbeda. Kedua, banyak nilai atribut sama bahkan untuk objek dengan skema berbeda. Kami akan memisahkan skema dan nilai atribut ke dalam penyimpanan terpisah, dan kami akan menyimpan json dalam bentuk: tautan ke skema + tautan ke nilai atribut.

Pemisahan Json menjadi pengaturan dan data

Dalam skema data kami, jumlah nama atribut terbatas. Jadi kita bisa meletakkannya di file terpisah dan menyimpan nomor sebagai gantinya. Kami juga akan membuat beberapa perubahan lagi, dengan mempertimbangkan bahwa json selalu menjadi objek.

Format nyata untuk menyimpan manfaat objek json

Ya, kami pada dasarnya memeras data kami sendiri, mengurangi ruang lingkup agar algoritma berfungsi. Namun di sisi lain, kami sedikit memperlambat akses ke data, dan algoritme masih membantu, mengompresi nilai yang sama yang disimpan di dekatnya.

Kami mengatur ukuran halaman menjadi 1 KiB dan ternyata saat kami mengoptimalkan format sehingga, di satu sisi, pembacaannya tidak terlalu lambat, dan di sisi lain, data dikemas sebaik mungkin, kami ... sudah memintas “tabel yang dioptimalkan” baik dalam ukuran maupun untuk kemudahan penggunaan. Tapi tidak sia-sia semua ini! Keuntungan maksimum harus berasal dari kompresi nilai atribut, properti, dan skema. Kami menghasut zlib dan memverifikasi bahwa, dengan latar belakang sisa pekerjaan, membaca data dari database membutuhkan sedikit waktu. Puas dengan hasilnya, kami beralih ke tugas lain.

Singkirkan yang tidak perlu


Kami mulai mengurangi dengan mencari data yang dapat Anda singkirkan. Ternyata selama keberadaan format, kita telah belajar untuk melakukannya tanpa koneksi terbesar. Ini 10% dari ukuran!

Kode untuk data ini masih terikat, tetapi perubahan yang diperlukan cukup sepele. Namun aplikasi yang sudah dirilis tidak dapat dengan mudah diubah. Kami berusaha untuk mempertahankan selama mungkin tidak hanya mundur, tetapi juga kompatibilitas langsung. Dan jika yang pertama akrab bagi semua orang, maka banyak orang mungkin dengan senang hati tidak memikirkan yang kedua. Kami terpaksa mendukungnya karena ekor pengguna yang agak panjang, karena berbagai alasan, telah mematikan pembaruan otomatis dan tidak terburu-buru untuk beralih ke versi aplikasi yang baru.

Distribusi pengguna menurut versi
Distribusi pengguna menurut versi

Di bagian paling atas adalah distribusi pengguna oleh versi terbaru dari aplikasi Android. Bawah adalah iOS.

Sangat mudah untuk memperhatikan bahwa pengguna perangkat iOS diperbarui jauh lebih mudah, tetapi bahkan di antara mereka ada banyak pengguna versi yang lebih lama.

Kami juga masih merilis data baru untuk versi beku untuk Windows Phone. Dan meskipun pengguna WP8 hanya membuat sebagian kecil dari pemirsa kami, dalam jumlah absolut ini hampir 200.000 per bulan.

Kami telah lama memiliki mekanisme yang memungkinkan kami menghasilkan beberapa format data, dan secara otomatis menentukan versi mana yang harus mendapatkan apa. Peluang itu bagus, tetapi Anda masih perlu belajar cara membongkar format ini. Tugas besar pertama adalah mengimplementasikan layanan yang akan menerima semua data dan menyaring yang baru untuk format database lama dan yang lama untuk yang baru.

Bonus bagus dari pekerjaan yang dilakukan adalah mengurangi ukuran pembaruan bulanan, karena koneksi jarak jauh telah sangat berubah dari bulan ke bulan, menggembungkan ukuran perbedaan.

Jika Anda melihat data yang tersisa, maka secara total Anda dapat menekan 10% yang sama, namun, harga akan jauh lebih tinggi. Sejauh ini kami memutuskan untuk tidak menyentuh.

Optimalkan format penyimpanan saat ini


Seperti yang tertulis di atas, kami membuat 1 halaman KiB dan mengemas tidak semua repositori biner.

Hal pertama yang kami lakukan adalah mencoba mengemas juga halaman-halaman dengan tautan, dan memeriksa apakah perbedaan dalam kecepatan menerima data ada di wilayah kesalahan.

Item berikutnya adalah memilih ukuran halaman optimal. Semakin besar ukuran halaman, semakin efisien data dikompresi, tetapi semakin lambat data diambil. Dan jika dengan meningkatnya ukuran halaman, waktu dan biaya memori tumbuh secara linear, maka keuntungannya menjadi semakin tidak terlihat. Setelah tes, kami memutuskan untuk menambah ukuran menjadi 8 KiB.

Pengaruh ukuran halaman pada pilihan besar
Jika pembesaran halaman diharapkan memperlambat pilihan kecil, maka yang besar - dari ratusan elemen - bahkan dipercepat. Ini berarti bahwa dengan cara yang baik Anda harus memilih ukuran yang berbeda untuk penyimpanan tergantung pada kasus penggunaan data yang disimpan di dalamnya.

Secara total, hanya dua poin ini yang dapat mengurangi basis sebesar 18%!

Ubah format kompresi


zlib, tentu saja, adalah klasik, tetapi zstd memberikan kecepatan dekompresi yang lebih tinggi dengan rasio kompresi yang lebih besar. Selain itu, zstd memungkinkan Anda untuk membuat kamus tunggal untuk semua data yang tersedia, dan kemudian menyimpannya sekali dan mengompresnya dengan semua halaman. Jadi, kami memperlambat proses pembuatan file dengan database, tetapi kami memeras tambahan 8%.

Tambahkan beranda


Cara mudah


Cara termudah adalah dengan membuat setiap pintu masuk objek yang terpisah, indeks secara terpisah (sesuai dengan perkiraan kasar + 10% dari ukuran indeks), dan secara terpisah menyimpan geometri mereka dalam data untuk menggambar peta.

Metode ini akan mengembang basis dengan total 3%. Pada tahap sebelumnya, kami menang lebih dari cukup untuk menenangkan diri dan bekerja dengan pintu masuk sesuai dengan skema yang biasa, tetapi ... pada saat mulai bekerja, kami tidak yakin tentang hal ini, dan mengerjakan format alternatif secara paralel.

Penilaian terperinci, untuk mereka yang tertarik
Mari kita coba mengevaluasi peningkatan ukuran paket dengan database untuk setiap objek:

8 byte - pengidentifikasi,
6 byte - jumlah penyimpanan yang digunakan (data + lima properti; properti diekstraksi dari data utama dan disimpan dalam bentuk biner, karena sering diperlukan untuk sejumlah besar objek sekaligus),
3 byte - indeks di gudang data,
2 byte - mengimbangi data objek,
5 byte - nilai data - 2 byte per sirkuit, rata-rata 3 byte untuk informasi apartemen (kami percaya bahwa akan ada banyak duplikat dan data itu sendiri disimpan satu kali),
6 byte - mengimbangi koordinat data (properti lain memiliki banyak nilai duplikat dan akan runtuh),
8 byte - mengoordinasikan nilai.

Total 38 byte per objek. Dalam kasus Moskow, ini adalah 4,5 MiB untuk lebih dari 124 ribu input yang dikumpulkan.

Selanjutnya, kita perlu menyimpan juga koneksi antara rumah dan pintu masuk, itu adalah 2,5 byte untuk setiap bangunan apartemen dan 8 byte untuk setiap pintu masuk. 1 lagi MiB.

Sekarang kami mempertimbangkan berapa banyak ini akan terjadi pada peta.

Pertama, kita harus menyimpan geometri. Untuk polyline, titik pertama selalu membutuhkan 8 byte, dan semua yang berikutnya disimpan sebagai perbedaan akurasi yang diperlukan. Di sini kami puas dengan ketepatan desimetres. Sebagian besar input terdiri dari dua titik yang tidak terlalu jauh satu sama lain, dan oleh karena itu dapat diasumsikan bahwa geometri itu sendiri akan menempati 10 byte. Total 1,2 MiB.

Kita juga perlu mengasosiasikan pengidentifikasi input dan objek dengan geometri. Indeks adalah penyimpanan biner yang sama dengan yang lainnya, hanya tujuan komunikasi (1 byte), nomor lapisan (1 byte) dan nomor objek (3 byte) yang disimpan. Ditambah 8 byte per pengidentifikasi, serta pohon pencarian cepat. Total 1,5 MiB lagi.

Seperti yang dikatakan di awal artikel, kami ingin terus-menerus menampilkan pintu masuk di peta, dan cara termudah untuk melakukan ini adalah membongkar lapisan lain dengan titik-titik, tapi ... Anda juga dapat menggunakan kembali lapisan dengan geometri, membuat simbol baru yang akan menampilkan gambar yang kita butuhkan pada titik terakhir dari polyline.

10% dari indeks pencarian adalah 5,9 MiB. Meringkas semuanya, kita mendapatkan 14,2 MiB, yang hanya sedikit lebih dari 3%.

Opsi saat ini


Alih-alih mengindeks pintu masuk dan menggelembungkan indeks pencarian, kami mencari rumah, tetapi juga menandai kata-kata permintaan dan mengekstrak alamat dari itu (relevan untuk mencari pintu masuk di Kaliningrad), pintu masuk dan / atau apartemen. Jadi, di pintu keluar kita memiliki pengidentifikasi rumah dan bidang teks yang diketik, dimana kita harus menemukan tangga yang kita butuhkan.

Catatan
Ini adalah poin yang kontroversial. Di satu sisi, ini memungkinkan kami untuk mengurangi ukuran basis data, dan di sisi lain, ia membatasi format input - pintu masuk tidak akan ada pada banyak permintaan yang akan diproses dengan benar menggunakan pencarian jujur.

Selanjutnya, alih-alih membongkar objek individual, kami menyertakan semua informasi tentang pintu masuk langsung ke data bangunan.
Dan akhirnya, kami mentransfer sebagian geometri ke direktori.

Yang terakhir layak diungkapkan secara lebih rinci.

Pertama, kami memperhatikan bahwa sebagian besar input adalah dua titik dan memiliki panjang yang sama. Input tersebut dapat disimpan dalam bentuk titik + arah, mis. simpan 1 byte per input.

Kedua, kami berasumsi bahwa sebagian besar rumah di kota-kota modern adalah tipikal, oleh karena itu, perpindahan titik-titik pintu masuk mereka relatif ke titik pusat rumah akan bertepatan hingga belokan.

Kami sudah memiliki titik pusat bangunan. , — «», — ? , json' , .

— , .

— . « - », json' . . json' , . — , , , json', , , .

Catatan
. , , 0,2% (972 ).

, . , .

? 3% 0,5%. , ( ), .

Hasil


, 0,5% 26,6% . - , — «» , — 10,1%.

Ubah ukuran pangkalan Moskow seiring waktu

. ( 0,4%), , , .

?


, , . , ( ).

, , .

, , , .

One more thought: JSON


, JSON. Ini tidak sepenuhnya benar. , . rapidjson, .

JSON C++ UI, .

-, .

-, , UI-, , .

JSON' , UI , .

Source: https://habr.com/ru/post/id415263/


All Articles