Rabu, 11 Maret 2009

8 Hal yang Harus Diperhatikan Tentang Logging ke MySQL


Hong Kong STock exchange recently has a incident that stopped their trading system for a hour because 2 records have the same key values. I don't know whether they use GUID to generate keys but it makes me worried that even large organization have these problems.—A comment on news://borland.public.delphi.oleautomation.

Bayangkan situasi berikut. Anda sudah membuat dan memasang aplikasi web untuk klien. Aplikasi telah berjalan selama beberapa minggu. User-user telah dibuat, login, membuat dan mengupdate data mereka. Lalu suatu hari klien Anda bertanya, “Berapa user yang login dan logout setiap harinya?” Anda menjawab, “Maaf, tidak tahu.” Klien Anda bertanya lagi, “Saya curiga dengan user X. Bagaimana cara saya memantau kapan si user login dan apa saja yang dikerjakannya saat login?” Anda menjawab, “Maaf, tidak bisa.” Lalu keesokan harinya terjadilah bencana. Semua data user hilang. Kemungkinan ada user yang berhasil meng-crack aplikasi Anda dan memperoleh akses administrator aplikasi lalu menghapus data-data yang ada. Anda perlu melacak siapa, dari mana, dan bagaimana ini terjadi. Sambil meringis Anda berkata pada diri sendiri, “Maaf, tidak ada log sama sekali.”

Praktis semua aplikasi multiuser, dan nyaris semua aplikasi yang sudah cukup kompleks, membutuhkan logging. Logging adalah catatan apa-apa saja yang terjadi selama aplikasi berjalan. Jadi, di sepanjang jalur eksekusinya aplikasi Anda meninggalkan jejak-jejak yang bisa amat membantu dalam berbagai hal: 1) untuk security records dan auditing (siapa saja yang melakukan apa saja dan kapan); 2) untuk debugging (mengapa program kita menghasilkan output yang salah, kapan dan di bagian mana kira-kira penyebabnya); 3) untuk lebih mengerti user (bagaimana jalur masuk dan keluar si user, halaman-halaman mana saja yang diakses, kapan user paling banyak mengakses dan ke mana, kesalahan-kesalahan apa yang sering dilakukan user, dsb); 4) dll. Dengan kata lain, logging itu amat sangat penting. Tidak terbayang kalau program-program seperti daemon (Apache, FTP server, dsb) atau bahkan OS sendiri tidak memiliki fasilitas logging.

Logging bisa dilakukan dengan berbagai cara dan ke berbagai medium. Yang paling sederhana adalah dengan perintah-perintah print atau echo, baik ke layar ataupun ke file. Logging pun bisa dilakukan ke tabel database atau ke daemon lain di network (“dioper ke mesin lain”). Dan tersedia pula framework-framework untuk melakukan logging secara fleksibel, seperti log4perl, log4php, log4py, dan Log4r (keempatnya terinspirasi dari paket log4j yang amat popular di komunitas Java). Framework-framework ini memungkinkan kita melakukan logging sambil nantinya mengatur output logging ke mana saja, statement mana saja yang masuk ke output (pengaturan “debug level” atau “verbosity”), dan bagian program mana saja yang loggingnya masuk output.

Logging ke database adalah salah satu praktik yang popular. Dan ini yang menjadi topik bahasan kita kali ini.

1. Database atau filesystem?

Sebelum memutuskan melakukan logging ke database, terlebih dahulu Anda perlu meyakinkan diri akan alasan-alasan apa yang membuat database tepat sebagai medium target logging. Seperti kita ketahui, di Unix praktik yang umum adalah melakukan logging ke file (umumnya dalam format teks polos). Selain sederhanafopen(“file”,”a”) diikuti fputs() diikuti fclose()), tidak butuh komponen software tambahan (tidak ada mysqld atau database server lain tidak masalah, toh semua OS mampu melakukan read/write ke file), juga tidak ada overhead (tinggal operasi network atau parsing SQL, dsb.

Beberapa alasan utama orang melakukan logging ke server database RDBMS adalah: 1) ingin bisa melakukan realtime analysis atau query terhadap log; 2) server database memungkinkan logging ke mesin lain; 3) server database memungkinkan sharing data log; 4) server database memungkinkan replikasi/load balancing. Poin 1 adalah yang terpenting. Logging ke tabel database memungkinkan analisis yang lebih mudah karena data log yang dimasukkan sudah terstruktur (dalam bentuk row dan columns) dan database menyediakan indeks dan bahasa SQL untuk menggali data ini. Poin 2, 3, dan 4 sebetulnya bisa juga dilakukan oleh filesystem (filesystem bisa dishare ke network dan bisa diload balance dengan RAID misalnya) namun keberadaan server database nampaknya seringkali dipandang lebih afdol dalam memanage data dalam jumlah besar.

Jadi pertimbangkan dulu apakah Anda memang membutuhkan fitur-fitur yang ditawarkan database sehingga merelakan kode logging Anda lebih kompleks dan aplikasi Anda bergantung pada database. Jika ya, mari lanjut.

2. MySQL?

Pertanyaan kedua adalah: apakah MySQL cocok sebagai database target? Jawaban sebagian besar pembaca mungkin ya, karena selain MySQL memang ringkas, tidak rewel/bertele-tele dalam instalasi, dan cukup cepat dalam melakukan INSERT/SELECT, sebagian besar pembaca juga telah familiar dengan produk yang satu ini. Tapi Anda mungkin ingin bereksperimen dengan database lain.

Jika membutuhkan kecepatan INSERT yang lebih tinggi misalnya, Anda bisa mencoba SQLite. SQLite juga memiliki keuntungan yaitu ukurannya yang kecil, mudah diembed ke dalam aplikasi (meskipun MySQL 4.x pun sekarang dapat diembed), dan seluruh database dapat disimpan dalam satu file saja. Ada database embedded lain seperti BerkeleyDB, namun tidak menyediakan interface SQL.

Anda juga bisa mencoba PostgreSQL yang menawarkan tipe-tipe data “antik” seperti alamat IP atau tipe-tipe data geometrik (titik, garis, dsb) yang mungkin kelop dengan aplikasi-aplikasi Anda.

Dan terakhir, jika aplikasi Anda benar-benar berskala besar dan tingkat logging amat tinggi (mis: ratusan-ribuan statement per detik) maka database yang mungkin lebih cocok dalam kondisi clustering dan skalabel hingga mesin-mesin berspek tinggi adalah database komersial seperti Oracle dan DB2.

3. Database Sama/Berbeda?

Rata-rata aplikasi hanya menggunakan satu database saja. Database ini berisi data aplikasi dan juga data logging (dalam tabel-tabel terpisah tentu saja). Tapi ada kalanya logging ke database berbeda menjadi pilihan yang menarik dengan alasan-alasan berikut: 1) Database logging dapat ditaruh di mesin yang lebih secure/terfirewall (penting jika ingin melakukan logging untuk auditing); 2) Database logging dapat melayani beberapa aplikasi sekaligus secara terpusat; 3) Database logging dapat dituning secara terpisah (logging adalah operasi yang mayoritasnya INSERT, sementara pola akses database untuk aplikasi mungkin seimbang antara INSERT, UPDATE, DELETE, dan SELECT).

Namun perlu diperhatikan bahwa jika Anda menggunakan 2 database berbeda, apa yang harus Anda lakukan seandainya salah satu database sedang tidak available? Misalnya mesin logging sedang mati atau terputus koneksinya. Karena logging adalah operasi yang kritikal, maka jika database logging tidak bisa dipakai, hentikan aplikasi dengan pesan kesalahan fatal.

4. Tipe Data

Pemilihan tipe data yang sekompak mungkin menjadi penting karena logging berpotensi menghasilkan banyak sekali record. Contohnya, dalam sebuah aplikasi saya melakukan logging header Referer dan User-Agent klien, saya memilih menggunakan CHAR(80) dan CHAR(40) saja untuk tujuan ini. Meskipun cukup banyak string yang terpotong karena melebihi 80 dan 40 karakter, ini tak mengapa dalam kasus yang saya hadapi dan saya memilih menghemat ruang space karena setiap minggunya bisa ada jutaan record yang dilog.

Menjaga sebuah row agar tetap fixed length juga bisa jadi cukup penting dalam menjaga kinerja, apalagi jika tabel database akan banyak di-SELECT/di-scan. Ini artinya, hindari VARCHAR() dan TEXT/BLOB dan gunakan CHAR(). Lebih jelasnya mengenai hal ini bisa dilihat pada manual MySQL bagian 7.1.2.1 Static (fixed-length) table characteristics.

Ada pelajaran lain yang saya petik dari pengalaman me-log ke database. Pertama, hati-hati jika memberi tipe data kolom waktu sebagai TIMESTAMP. Kolom TIMESTAMP akan selalu diupdate oleh MySQL jika sebuah UPDATE ke record tidak menyertakan nilai kolom itu secara spesifik. Meskipun teorinya tabel logging hanya di-INSERT dan record yang sudah ada tidak pernah dimodifikasi, kecelakaan bisa saja terjadi. Perhatikan jangan sampai catatan waktu di tabel log Anda berubah semua gara-gara UPDATE Anda salah. Dalam meng-update, jangan lupa melakukan seperti ini:

UPDATE tablename SET ...,kolomwaktu=kolomwaktu WHERE ...

Agar si kolomwaktu yang bertipe TIMESTAMP tidak berubah.

5. Indeks

Banyak tabel logging struktur datanya seperti ini:

CREATE TABLE tbl_log (

time DATETIME NOT NULL, index(waktu),

ipaddress INT UNSIGNED NOT NULL, index(ipaddress),

userid INT UNSIGNED NOT NULL, index(userid),

taskname CHAR(20) NOT NULL, index(taskname)

);

Struktur seperti ini mirip dengan fact table, istilah yang sering dipakai di data warehousing. Fact table adalah tabel yang memuat data namun field-fieldnya adalah foreign key ke tabel lain (kecuali field yang mengandung data itu sendiri, dalam kasus ini taskname). Pada contoh di atas, userid adalah foreign key untuk tbl_users misalnya. Pada umumnya, untuk mempercepat logging, tidak semua field berupa foreign key (mis: time).

Bentuk tabel seperti ini amat praktis dalam mengizinkan kita menyortir/menyeleksi berdasarkan field-field foreign key (field-field dimension) karena field-field ini terindeks. Pada contoh di atas, kita dengan mudah dapat melakukan WHERE dan ORDER BY terhadap kolom-kolom time, ipaddress, dan userid. Untuk melihat username, useremail, dan field-field tambahan lainnya, kita tinggal melakukan join dengan tabel-tabel dimensi ybs (tbl_users, dsb).

Kadangkala, kita juga menginginkan full-text searching untuk kolom-kolom yang berisi teks:

CREATE TABLE tbl_log2 (

time DATETIME NOT NULL, index(waktu),

ipaddress INT UNSIGNED NOT NULL, index(ipaddress),

userid INT UNSIGNED NOT NULL, index(userid),

taskname CHAR(20) NOT NULL, index(taskname),

note VARCHAR(255) NOT NULL, fulltext(note)

);

6. Denormalisasi

Dalam database relasional kita sering bicara normalisasi. Tapi dalam auditing kadangkala kita harus melakukan denormalisasi. Apa itu denormalisasi? Yaitu mencatat data-data yang tidak bergantung pada kolom lain, meskipun pada akhirnya mengakibatkan banyak terjadi duplikasi data. Tujuannya adalah agar semua informasi yang Anda perlukan harus terkandung di tabel log itu sendiri dan tidak bergantung pada tabel lain.

Contoh, Anda ingin selalu mencatat alamat email user pada waktu tertentu, karena alamat email user berubah-ubah. Maka, kolom userid pada contoh sebelumnya harus diganti dengan useremail. Jika tetap userid, ini berarti Anda harus melakukan JOIN untuk mengetahui apa alamat email si user dengan id userid, dan tabel tbl_users hanya akan mencatat alamat email terbaru user. Alamat emailnya mungkin tidak sama dengan alamat email pada waktu kegiatan/perbuatan si user tercatat di log audit. Atau bagaimana jika record user tersebut dihapus dari tbl_users (berhubung MySQL tidak menjaga foreign key integrity) atau userid dengan id tersebu telah direcycle untuk user lain? Maka catatan audit Anda akan berubah. Padahal log audit harus merupakan rekaman kejadian sewaktu terjadi.

Jika Anda ingin melakukan analisis, umumnya pun tabel yang terdenormalisasi paling cepat, karena tidak ada operasi JOIN antartabel sama sekali, hanya INNER JOIN (jika memang perlu).

7. Konkurensi/Locking

MySQL menyediakan beberapa table handler seperti MyISAM dan InnoDB. Jika tingkat konkurensi aplikasi tinggi (mis: aplikasi web Anda bisa diakses oleh puluhan klien pada waktu bersamaan) maka ada baiknya Anda mempertimbangkan menggunakan InnoDB ketimbang MyISAM karena InnoDB menawarkan konkurensi yang jauh lebih baik daripada MyISAM. Apalagi jika Anda melakukan UPDATE/DELETE massal terhadap tabel (untuk tujuan rotasi misalnya). Dalam satu aplikasi yang saya maintain, sebuah tabel log dapat berisi 1 hingga 3 juta row pada suatu saat. INSERT log secara umum masih cukup cepat meskipun ada banyak klien konkuren, karena saya menggunakan INSERT DELAYED. Namun pada waktu rotasi sekali sehari (di mana saya melakukan DELETE pada ratusan-ribuan row pada tabel tersebut) maka klien-klien lain langsung terblokir aksesnya ke tabel tersebut. Selama operasi DELETE yang makan waktu hingga bermenit-menit, klien pun bertumpuk karena menunggu akses terhadap tabel log. Dengan multiversioning yang dimiliki InnoDB ini bisa dihindari, karena sewaktu melakukan DELETE klien-klien lain masih dapat membaca/tulis ke tabel yang sama.

Namun yang perlu diperhatikan, tabel InnoDB jauh lebih lambat dari MyISAM dalam operasi-operasi tertentu, seperti SELECT COUNT(*).

8. Rotasi

Untuk menghindari penumpukan tanpa akhir, tabel database harus dirotate. Ada berbagai cara yang bisa dilakukan. Pertama, berpindah-pindah tabel (mis: Anda membuat tabel bulanan: tbl_log_Jan, tbl_log_Feb, …) dan me-log ke tabel bulan pada bulan ybs dan mengosongkan tabel 12 bulan yang lalu (mis: di awal Jan 2004 Anda akan kembali ke tbl_log_Jan dan mengosongkan data Jan 2003). Kedua, melakukan DELETE row-row tua secara berkala (mudah dilakukan, mengingat praktis semua tabel log memiliki kolom waktu). Namun, ingat isu konkurensi selama melakukan DELETE pada tabel yang berukuran besar.

Anda juga bisa menumpahkan data di tabel yang lama ke dalam file lalu mengkompresinya. Data log lama kemungkinan besar tidak pernah di-SELECT lagi sehingga bisa dibuang dari database. Namun, Anda tetap menyimpannya dalam arsip jika suatu saat diperlukan.

Penutup

http://www.master.web.id/scriptingworld/02-8_hal_mysql/02-8_hal_mysql.html

Tidak ada komentar:

Posting Komentar