Selasa, 02 April 2019

Terasi (bagian 4): Migrasi ke C++

Di tengah-tengah pengerjaan front end, tiba-tiba saya ada mendapatkan komitmen pekerjaan baru yang membuat proyek Terasi perlu ditunda dulu. Jadi sekitar bulan Juni 2016, saya berhenti untuk sementara.


Lanjutan Pengerjaan

Kini sudah bulan Juni 2017. Sekitar setahun setelah Terasi ditinggal.

Saya sudah beres urusan pekerjaan lainnya dan dapat kembali melanjutkan proyek Terasi. Sebelum langsung melanjutkan, saya periksa dulu apakah tiba-tiba sudah muncul aplikasi yang serupa.

Dari teman, saya tahu kalau Google Maps sudah mengintegrasikan jalur Transjakarta. Jadi saya lihat-lihat. Ternyata implementasinya masih sangat kasar. Jadi ketibaan bus di setiap halte di-hardcode "setiap 15 menit". Ditulisnya data didapatkan dari Transportasi Jakarta. Mungkin karena mereka belum memiliki data yang akurat, jadinya digunakan estimasi kasar 15 menit.

Dari teman lagi, ada sebuah aplikasi yang bernama "Trafi". Saya periksa, dan aplikasinya bagus. Saat itu kita bisa melihat posisi bus secara live. Sepertinya API yang digunakan sama dengan yang saya pakai. Melihat estimasi waktu tunggu, lagi-lagi waktunya di-hardcode per halte.

Kesimpulannya, saya bisa lanjut mengerjakan Terasi.

Oke lalu saya lihat data yang sudah dipanen. Secara mengejutkan ukuran data 1 hari menjadi besar. Setelah dibandingkan dengan data tahun lalu, ini yang saya lihat:

gyosh@gyosh ~/workspace/terasi/data $ ls -l --block-size=M | grep 01-01.tar.gz
-rw-r--r-- 1 gyosh gyosh 12M Jan  1  2016 2016-01-01.tar.gz
-rw-r--r-- 1 gyosh gyosh 25M Mar 18  2017 2017-01-01.tar.gz

Perhatikan kolom ke-5 (1 based). Terlihat bahwa dulunya 12 MB, kini 25 MB. Jadi ukurannya menjadi 2 kali lebih besar!
Pastinya ini hal yang baik untuk Jakarta, karena banyaknya bus bertambah. Lalu tiba-tiba terpikir, atau jangan-jangan ada koridor baru?

Jadi saya periksa peta Transjakarta terbaru, dan ternyata benar. Muncul lebih banyak subkoridor yang namanya berupa angka + huruf, seperti 5A, 5B, 5C, 5D, 5E. Koridor seperti itu biasanya menggunakan sebagian besar koridor 5, tetapi halte awal dan akhirnya mungkin saja berbeda. Selain itu, ada juga koridor baru dengan kode huruf + angka, seperti S11, S21, T11, dan sebagainya. Koridor itu sepertinya mencapai luar Jakarta, seperti daerah "-bodetabek".

Selanjutnya, saya coba menonton rekam jejak terbaru di aplikasi visualisasi saya. Ternyata data bus baru itu tidak terekam. Bus-busnya masih menggunakan informasi koridor yang lama. Artinya tidak ada bus yang mengaku di koridor 5A, 5B, s/d 5E. Semuanya mengaku di koridor 5. Lalu muncul juga banyak bus dengan kode koridor "NBRT".


Saya menduga kalau NBRT ini adalah bus pengumpan (Feeder Bus) Transjakarta. Merekalah yang saya sebut subkoridor, seperti 1A, 1B, 5A, 5B, dsb.



Semua perubahan ini tentunya bagus untuk Jakarta. Namun tidak bagus bagi saya karena lagi-lagi saya harus mengumpulkan polyline setiap rutenya. Selain itu, program untuk ekstraksi dan agregasi yang saya buat berjalan cukup lama. Untuk memproses data 1 hari tahun 2016, dibutuhkan waktu 5 menit. Kalau sekarang datanya berlipat ganda, berarti 10 menit.

Meningkatnya waktu eksekusi cukup merepotkan. Mungkin pertumbuhan 2x tidak seberapa besar. Namun, kalau dalam 1 tahun saja mereka dapat meningkatkan layanannya sebesar 2x, saya tidak tahu apakah tahun depan datanya akan lebih banyak.

Ternyata memang benar. Data terakhir yang dipanen tanggal 23 Agustus 2017 memiliki ukuran 55 MB. Dalam waktu 8 bulan, mereka menggandakan layanan busnya...


Penyusunan Ulang Komponen

Karena saya akan sering menjalankan proses ekstraksi dan agregasi untuk keperluan tuning, rasanya tidak menyenangkan kalau sekali mengolah data 1 hari membutuhkan waktu puluhan menit.

Melihat apa yang sudah saya kerjakan, sebenarnya ada banyak kekurangan:
  1. Ekstraksi dan agregasi merupakan kegiatan yang banyak hitung-hitungan. Rasanya menulisnya dengan Javascript tidaklah bijak. Untuk skala data yang besar, lebih baik menggunakan bahasa pemrograman dengan tipe data statis seperti Java atau C++.
  2. Ekstraksi dan agregasi hanya bisa berjalan di 1 core CPU. Komputer saya memiliki 4 core, jadi sebaiknya dimanfaatkan juga untuk memparalelisasi hitung-hitungannya.

Jadi saya berpikir, apakah lebih baik saya menuliskan kembali programnya dalam C++? Lalu saya teringat bahwa kita dapat "mengawinkan" Node.js dengan C++ seperti yang dijelaskan di sini: http://benfarrell.com/2013/01/03/c-and-node-js-an-unholy-combination-but-oh-so-right/

Konsepnya adalah kita menulis request routing dalam Node.js, tetapi modul utamanya menggunakan C++. Jadi saya bisa menulis program C++ yang membaca data, menyimpannya dalam array, dan mengembalikan data sesuai permintaan API dari Node.js. Dengan array bertipe data statis, saya bisa menggunakan in memory database dan mengkompres ukuran memorinya (misalnya menggunakan tipe data shortint daripada int 32 bit).

Karena keuntungan C++ cukup besar dalam hal pengolahan dan penyajian data, akhirnya saya memutuskan untuk menulis ulang programnya. Tidak ada perubahan besar dalam struktur aliran data, hanya saja bagian database kini diganti menjadi file biasa.



Coding Trance

Selama beberapa minggu selanjutnya, saya coding terus-terusan. Hasil terakhir yang dicapai adalah seluruh modul selesai ditulis ulang ke C++ dan performanya menjadi jauh lebih cepat. Pengolahan data untuk satu hari kini berlangsung dalam 30 detik.

Saya juga menambahkan fitur baru, yaitu mencari informasi perjalanan dari suatu halte ke halte lainnya. Algoritma yang digunakan adalah A* Search, dengan heuristik berupa aproksimasi Great Circle Distance antara posisi saat ini dengan posisi tujuan (sebenarnya saya hanya menghitung jarak Euclidean antara longitude/latitude posisi awal dan akhir). Jadi saya precompute dulu untuk seluruh kemungkinan pasangan halte, jalankan A*, lalu simpan hasilnya dalam bentuk berkas .txt. Seperti biasa, berkas ini akan disimpan dalam in memory database.

Selain memberikan informasi halte mana saja yang dikunjungi, durasi menunggu atau perjalanan juga diberikan. Untuk saat ini saya hanya menggunakan waktu median menunggu dan perjalanan, lalu menjumlahkannya.

Berikut salah satu hasil untuk perjalanan dari RS. Harapan Kita menuju ke Blok M dengan asumsi sampai di halte RS. Harapan Kita pukul 06:00:
{
  "duration": 25,
  "transit": 1,
  "summary": "9/9A - 1",
  "path": [
    {
      "busStop": "RS Harapan Kita",
      "action": "wait for bus",
      "corridors": [
        [
          "9",
          "Pinang Ranti"
        ],
        [
          "9A",
          "PGC 2"
        ]
      ],
      "time": "06:00",
      "duration": 3
    },
    {
      "busStop": "Slipi Kemanggisan",
      "action": "inside bus",
      "corridors": [],
      "time": "06:03",
      "duration": 1
    },
    {
      "busStop": "Slipi Petamburan",
      "action": "inside bus",
      "corridors": [],
      "time": "06:04",
      "duration": 3
    },
    {
      "busStop": "Senayan JCC",
      "action": "inside bus",
      "corridors": [],
      "time": "06:07",
      "duration": 1
    },
    {
      "busStop": "Semanggi",
      "action": "inside bus",
      "corridors": [],
      "time": "06:08",
      "duration": 1
    },
    {
      "busStop": "Semanggi",
      "action": "get out of bus",
      "corridors": [],
      "time": "06:09",
      "duration": 0
    },
    {
      "busStop": "Bendungan Hilir",
      "action": "walk in inter station bridge",
      "corridors": [],
      "time": "06:09",
      "duration": 6
    },
    {
      "busStop": "Bendungan Hilir",
      "action": "wait for bus",
      "corridors": [
        [
          "1",
          "Blok M"
        ]
      ],
      "time": "06:15",
      "duration": 3
    },
    {
      "busStop": "Polda Metro Jaya",
      "action": "inside bus",
      "corridors": [],
      "time": "06:18",
      "duration": 1
    },
    {
      "busStop": "Gelora Bung Karno",
      "action": "inside bus",
      "corridors": [],
      "time": "06:19",
      "duration": 1
    },
    {
      "busStop": "Bundaran Senayan",
      "action": "inside bus",
      "corridors": [],
      "time": "06:20",
      "duration": 1
    },
    {
      "busStop": "Masjid Agung",
      "action": "inside bus",
      "corridors": [],
      "time": "06:21",
      "duration": 1
    },
    {
      "busStop": "Blok M",
      "action": "inside bus",
      "corridors": [],
      "time": "06:22",
      "duration": 3
    },
    {
      "busStop": "Blok M",
      "action": "get out of bus",
      "corridors": [],
      "time": "06:25",
      "duration": 0
    }
  ]
}
Berikut posisinya di peta:
Peta dari http://transjakarta.co.id/

Nilai "action" menyatakan apa yang perlu dilakukan. Nilai aslinya adalah angka, seperti 0 (tunggu bus), 1 (di dalam bus), 2 (turun bus), dsb. Namun untuk keperluan penjelasan, saya menggantinya menjadi teks saja.

Terdapat beberapa alternatif jalur yang diberikan. Kadang-kadang tidak masuk akal, seperti perlu memutar terlebih dahulu. Kalaupun tidak memutar, jalur bus yang diberikan adalah jalur yang jarang ada busnya (misalnya subkoridor yang sudah tidak aktif). Saya perlu sedikit menambahkan penyaringan hasil seperti membuang jalur yang waktu tempuhnya 1,5x lebih lama dari yang terbaik, atau yang transitnya lebih dari 3x.


Akhir Proyek

Suatu hari, saya iseng melihat kembali aplikasi Trafi.

Secara mengagetkan, mereka memperbaharui aplikasi mereka. Kini data mereka lengkap mencakup seluruh koridor baru, dapat menunjukkan informasi secara live, dan terlihat cantik. 

Reaksi saya ya kaget. Entah bagaimana mereka bisa mendapatkan datanya. Langsung deh saya mengaku kalah dan tidak lagi melanjutkan proyek ini :")

Memang akhir cerita ini anti klimaks. Tapi saya rasa tidak ada yang sia-sia. Saya belajar banyak hal dari pengerjaan proyek ini, seperti:
  1. Membuat sistem logging yang tepat, terutama pada pemanen data dan melaporkan adanya error.
  2. Pembuatan "internal tool" seperti visualisasi data sangat penting. Meskipun pada akhirnya tidak digunakan oleh pengguna umum, visualisasi ini sangat membantu programmer mengetahui kondisi data yang sedang dihadapi. Anggaplah seperti suatu alat debugging.
  3. Ketika berurusan dengan data di dunia nyata, hampir dipastikan datanya ada kekotoran. Sudah tugas kita untuk membersihkan datanya sebelum langsung digunakan.
  4. Tulislah program yang "CPU intensive" dalam bahasa pemrograman yang cocok!
Membuat aplikasi seperti ini menghabiskan banyak waktu. Namun semua itu menyenangkan untuk dilakukan, dan saya tidak menyesal ketika akhirnya tidak berhasil.

Pengalaman ini saya ceritakan sekedar untuk berbagai cerita saja tentang bagaimana data yang dihasilkan setiap hari dapat diolah dengan teknologi dan algoritma menjadi informasi yang berharga. Saya menganggap konsep itu sangat menarik.


Mungkin suatu saat nanti, kalau ada kesempatan saya dapat melakukan hal serupa untuk data lainnya. Barangkali data submission di TLX?

2 komentar :

  1. URI untuk trnsjakarta apa ya? di endpoint api jakarta tidak ada

    BalasHapus
    Balasan
    1. Iya memang sekarang uda ga ada. Sejak pertengahan 2017 uda musnah.

      Hapus