Migrasi database dari Non-Relational ke Relational

Tujuan menulis artikel ini kayaknya lebih ke curhat, bukan tutorial XD

Posisi saya saat itu sedang menjalani probation sebagai junior di suatu tech company, ekspektasi diawal paling dikasih task ringan. Awalnya sih iya, pas di bulan terakhir probation, situasi negara si client lagi bermasalah. jadinya tasknya ditunda dulu dan diganti task lain dengan rayuan "Mas, saya mau ngasih tantangan. Bisa nggak migrasiin sistem berbasis mongodb ke sistem yang berbasis mysql?". Dengan alasan servernya yang ngejalanin ini app selalu out of memory (beberapa kali udah vertical scaling, kalo kata server adminnya gegara mongodb-nya, cuman saya lebih curiganya logic di app sebelumnya boros memory)

Dikarenakan pilihan saya nggak banyak, ya saya iyain aja. Paling kan nanti bisa tanya senior-senior dikantor. Ternyata nggak ada yang pernah migrasi database, haaaaaaaaaaaaa~. (Ternyata task ini pernah ditawarin ke 2 senior sebelumnya, dan nggak ada yang selesai dan nolak task ini, baru dikasi tahu pas udah selesai migrasi data -__-)

Bermodal tiap hari ngulik-ngulik nyari artikel, awalnya dimulai dari apa sih buat migrasi data ini? akhirnya sampai pada kesimpulan.

  1. Pahami model data yang lama
  2. Bikin skema relational buat database SQL yang baru
  3. Modifikasi data lama agar sesuai dengan skema SQL yang baru
  4. Import ke database SQL yang baru

Untuk poin pertama dan kedua kayaknya nggak perlu diceritakan terlalu banyak ya. Pada intinya penyimpanan data di mongo kan nggak relational, kalo udah biasa dengan relational database design, nanti keliatan field-field mana yang mau dibikin relasi, yang mana perlu di normalisasi.

Masalah yang saya temukan :

  • Susah memastikan field-field apa saja yang dibutuhkan, karena data terbaru ada tambahan beberapa field yang nggak diupdate ke data sebelumnya. Misal di data yang baru mengandung field orang_baru, di data yang lama nggak ada field tersebut. Jadi harus tahu field-field apa saja yang ada di collection tersebut.
  • Di collection tertentu, ntah kenapa ada data yang duplicate. Ini harus dibersihkan buat migrasi ke SQL nanti.

Langsung ke poin ke - 3.
Nah ini yang lumayan lama prosesnya, soalnya sambil trial & error dengan poin 4, gimana modifikasinya? format apa yang mau saya jadikan output buat diimport ke sql nanti? datanya ada jutaan, pas di hari migrasi nanti, migrasinya mau secara live atau mau matikan server? kalau mau matikan server, prosesnya berapa jam?

Diawal-awal saya nyoba bikin dummy data aja, performa kalo nginput data satu-satu pake script dengan node js habis berapa lama.
Hasilnya data 100rb rows makan waktu 30-40 menit coy, gimana ntar yg jutaan. (saya lupa pas make ini saya ngirim requestnya secara paralel atau synchronous, soalnya bener-bener baru juga di nodejs)

Opsi lain yang saya temukan adalah, mysql bisa nampung rows value banyak dalam sekali input. Wogh okenih pas dicoba 100rb rows, jauh lebih cepet. Permasalahan lain muncul ketika data yang dicoba adalah 1jt. Memory nodenya nggak kuat nampung itu data ~_~. Mungkin hal tersebut nggak jadi masalah kalau kalian mengolahnya dengan bahasa yang lain.

Lalu saya menemukan sebuah artikel bahwa salah satu cara cepat (raw performance) buat import ke sql ya dengan import dengan format csv.
*tkp => Article 1, Article 2

Akhirnya saya cobalah format data2 mongo yang lama ke format csv.

  • Step pertama saya export dulu data dari mongonya ke bentuk csv.
  • Di step pertama itu saya menggunakan 2 cara.
  • Cara pertama adalah untuk data-data yang valuenya tidak mengandung koma. Langsung saja jalanin command
mongoexport --db=db_name --collection=collection_name --type=csv --fields=field1,field2,fieldn --noHeaderLine --out=D:\output_path_csvmu.csv
  • Cara kedua ini saya pakai kalau data yang mau di export, valuenya mengandung koma. misal di field alamat, valuenya "Jl. x no. x, RT X/RWX". Saya bikinkan script buat konekin dengan mongoclient dan valuenya nanti dipisahkan dengan semicolon. (bisa sedikit lebih lama dibanding cara yang pertama).
const fs = require("fs");
const writeStream = fs.createWriteStream(
  "./output_path_csvmu.csv"
);
const MongoClient = require("mongodb").MongoClient;

(async () => {
  const connect = await MongoClient.connect(
    "mongodb://username:password@host:port/?authSource=admin",
    {
      useUnifiedTopology: true,
    }
  );
  const db = await connect.db("db_name");
  const cur = await db
    .collection("collection_name")
    .find({});
  let obj;
  while (await cur.hasNext()) {
    obj = await cur.next();
    const newobj = `${obj.id};${obj.name};${obj.address}`;
    writeStream.write(newobj + "\r\n");
  }
})();
  • Nah kekurangan pakai cara yang pertama tadi adalah karena format datanya masih seperti mongo, saya bikin script lagi biar formatnya bisa sesuai dengan database yang sudah saya design. Kalau mau pakai langsung cara yang kedua juga bisa, dari select datanya ke db secara langsung sekalian formatin sesuai design database yang sudah dibuat.
  • Setelah semua data diformat sesuai kebutuhan masing-masing, saatnya import ke SQL nya dengan LOAD DATA LOCAL INFILE!!!!!
  • Cukup gampang sih, kalau main terminal, tinggal masuk ke SQL nya
- mysql -u root -p
- use db_name
- LOAD DATA LOCAL INFILE '/path_ke_file_csvmu_berada.csv' 
INTO TABLE table_name 
FIELDS TERMINATED BY ',' 
ENCLOSED BY '"' 
LINES TERMINATED BY '\r\n';
  • Untuk format yang valuenya sudah kalian pisahkan dengan semicolon, FIELDS TERMINATED BY ',' bisa diganti menjadi FIELDS TERMINATED BY ';'
  • Dari segi performanya, nginsert data 2 jutaan memakan waktu dibawah 1 menit

Semoga membantu.
Cara diatas bisa saja bukan merupakan cara yang paling optimal, barangkali ada developer lain yang ingin memberi masukan tentunya akan sangat membantu

31