Wednesday, September 29, 2010

Mendebug Database Production

Suatu aplikasi, walaupun sudah go-live di environment production, tetap bisa saja mengalami error dan bug. Bug ini seringkali tidak ditemukan di environment development karena berbagai hal, misalnya variasi data, jumlah data, dan sebagainya.

Langkah pertama ketika kita mengetahui ada bug tentunya adalah melokalisir masalah. Pada kondisi mana saja bug tersebut muncul. Setelah itu, kita dapat memfokuskan pencarian masalah di lokasi tersebut. Ini lebih efisien daripada kita harus menelusuri keseluruhan sistem.

Misalnya kita sudah berhasil melokalisir masalah, yaitu transaksi di bulan tertentu. Langkah selanjutnya adalah memindahkan data production di lokasi tersebut ke environment development. Ini kita lakukan supaya kita bebas bereksperimen dengan data tersebut tanpa khawatir membahayakan data production.

Masalahnya, tools backup database yang tersedia biasanya tidak bisa digunakan untuk mengambil sebagian data. Walaupun bisa (mysqldump menyediakan opsi where untuk membatasi record yang diambil), biasanya terbatas hanya di satu tabel saja. Sedangkan untuk bisa merestore-nya di development, kita butuh semua relasinya.

Sebagai contoh, coba lihat skema berikut.

Untuk mengambil data payment, tentunya kita juga harus menarik data lain yang berelasi dengannya, yaitu di tabel grup loket, loket, payment value, payment info dan fee loket. Ini sangat sulit dilakukan, apalagi kalau data payment tersebut jumlahnya ratusan ribu record.

Untunglah ada tools untuk mengatasi masalah ini, namanya Jailer. Dengan menggunakan Jailer, kita dapat menentukan tabel mana yang akan diambil datanya (payment), kriteria pengambilan (bulan tertentu saja), dan relasi mana saja yang ingin kita ambil. Hasilnya adalah satu set data lengkap dengan dependensinya yang bisa kita restore di development.

Persiapan Jailer

Pertama, tentunya kita unduh dulu Jailer di websitenya. Jangan lupa teriakkan, “Hidup Open Source !!!”, karena aplikasi ini tersedia secara gratis berkat gerakan open source.

Setelah berhasil diunduh, extract ke folder tertentu. Jailer tidak menyertakan driver untuk koneksi ke database, sehingga kita harus sediakan sendiri. Karena saya menggunakan MySQL, saya masukkan file mysql-connector.jar ke dalam folder lib. Kita mengikutkan driver database ke folder Jailer karena nantinya folder ini akan kita pack dan upload ke server production.

Jailer ini akan kita jalankan di mesin development yang sudah terisi skema database. Kita akan coba dulu ambil data di development, kalau sudah sukses baru kita jalankan di production.

Ada dua script untuk menjalankan jailer, yaitu jailerGUI dan jailer. jailerGUI digunakan untuk mendesain pengambilan data, sedangkan jailer adalah antarmuka command line untuk menjalankan pengambilan data. Karena kita ingin mendesain proses pengambilannya, kita gunakan jailerGUI.

  1. $ sh jailerGUI.sh

Berikut adalah tampilan awal Jailer.



Jailer memberitahu kita bahwa belum ada data model yang bisa dikerjakan, dan menyarankan kita untuk menganalisa database. Klik Analyze Database. Selanjutnya Jailer akan meminta informasi cara koneksi ke database.


Isikan informasi koneksi database dan driver yang digunakan. Driver yang kita gunakan adalah yang tadi sudah kita copy ke folder lib.


Klik OK untuk menganalisa database.


Setelah itu, Jailer akan menghubungi database untuk mengambil informasi. Lognya akan ditampilkan di log output. Jailer akan memberi tahu kita tabel-tabel yang tidak ada primary keynya. Jailer tidak dapat memproses tabel tanpa primary key.


Klik tabel yang berwarna merah, dan definisikan primary keynya. Primary key yang kita definisikan di sini hanya digunakan Jailer, sehingga tidak perlu khawatir skema aslinya akan berubah.


Setelah itu, klik OK. Jailer akan menampilkan screen Extraction Model Editor. Pilih tabel payment, di dropdown subject, karena inilah tabel yang akan kita gunakan sebagai pusat extraction.


Jailer mendeteksi relasi antar tabel berdasarkan constraint foreign key yang kita pasang di database. Kadangkala ada tabel-tabel yang berelasi, namun tidak ada constraintnya. Entah karena malas mendefinisikan, atau memang sengaja tidak dikaitkan. Kita bisa mendaftarkan relasi tanpa constraint ini dengan membuka lagi Data Model Editor, kemudian klik Add di kotak Association sebelah kanan.

Relasi non Foreign Key

Setelah diklik OK, maka skema relasi di Extraction Model Editor akan berubah sesuai relasi yang ditambahkan. Sama dengan definisi primary key di atas, relasi ini hanya disimpan oleh Jailer dan tidak diaplikasikan ke skema database.


Relasi Payment - Loket

Kita perlu mendefinisikan batasan record payment yang akan diambil, yaitu yang terjadi di bulan Juni 2010. Dalam bentuk SQL, berikut adalah query yang digunakan

  1. select * from payment where date_format(paid_date, '%Y-%m') = '2010-06'

Kita ambil expression setelah where dan pasang di textfield where dalam Extraction Model Editor.



Restriction

Simpan dulu extraction modelnya.



Save Extraction

Beri nama yang representatif, misalnya payment-201006. Jailer akan menyimpan extraction model ini dalam format csv. Kalau sudah memahami formatnya, kita bisa membuatnya dengan text editor tanpa GUI (kalau mau).

Setelah tersimpan, kita bisa klik Export Data sehingga memunculkan dialog berikut.



Export Data

Di screen tersebut kita bisa mengatur konfigurasi pengambilan data. Bagi saya, nilai defaultnya sudah memadai sehingga tidak ada yang diubah.

Di box paling bawah ada command line yang bisa kita copy-paste untuk dijalankan tanpa GUI. Copy saja isinya ke text file untuk digunakan nanti.

Yang harus kita isi di screen ini adalah textfield Into. Ini adalah nama file yang akan menampung script SQL berisi data yang diinginkan. Isi saja dengan nama payment-201006.sql.



Export Into File

Setelah itu, klik Export Data. Jailer akan segera bekerja dan menampilkan hasilnya dalam bentuk tree.


Hasil Export

Di situ kita bisa lihat berapa row yang akan diambil dari masing-masing tabel.
Seperti kita lihat, cukup signifikan, yaitu 2000an record. Ini disebabkan karena jailer mengambil record secara rekursif tanpa ada batasan.

Setelah dianalisa, kita hanya ingin mengambil tabel-tabel yang berkaitan langsung, yaitu payment, payment_info, payment_value, dan fee_loket. Sedangkan tabel sisanya dapat diabaikan karena bersifat pelengkap atau master data yang sudah ada di database development.

Dengan melihat ke tree-nya, kita bisa memutus relasi fee_loket ke loket, karena dari situlah semua data lain akan ikut terbawa.

Tutup screennya, dan kembali ke Extraction Model Editor.




Membatasi Relasi

Di kotak Association, expand node yang ingin kita putuskan, yaitu fee loket. Klik relasi loket, dan centang checkbox disabled di pojok kiri bawah. Setelah itu, jalankan lagi Export Data.


Warning Restricted Dependency

Jailer akan mengingatkan bahwa dengan membatasi dependensi, referential integrity akan rusak, karena relasi foreign key dari fee_loket ke loket akan terputus. Klik saja Yes, karena di database development kita tabel loket sudah terisi lengkap.

Inilah hasilnya

Hasil Export setelah dibatasi

Seperti kita lihat di atas, kita cuma mendapatkan 84 record dan pengambilan data berhenti di tabel fee_loket.
Periksa output payment-201006.sql di folder Jailer untuk memastikan hasilnya sudah benar.

Setelah sukses dijalankan di database development, compress lagi jailer yang sudah dimodifikasi barusan dan upload ke server production. Setibanya di server production, extract, kemudian jalankan script yang tadi kita copy-paste.

Kalau baru pertama kali dijalankan, script ini akan menimbulkan error sebagai berikut :

  1. $ ./export-payment-201006.sh
  2. 2010-06-28 14:15:08,114 [main] INFO - Jailer 3.4.5
  3. 2010-06-28 14:15:08,117 [main] INFO - added 'lib/mysql-connector-java-5.1.6-bin.jar' to classpath
  4. 2010-06-28 14:15:08,119 [main] INFO - exporting 'extractionmodel/payment-201006.csv' to 'payment-201006.sql'
  5. 2010-06-28 14:15:08,700 [main] INFO - begin guessing SQL dialect
  6. 2010-06-28 14:15:08,711 [main] INFO - end guessing SQL dialect
  7. 2010-06-28 14:15:08,718 [main] ERROR - Can't find working tables! Run 'bin/jailer.sh create-ddl' and execute the DDL-script first!
  8. java.lang.RuntimeException: Can't find working tables! Run 'bin/jailer.sh create-ddl' and execute the DDL-script first!
  9. at net.sf.jailer.entitygraph.EntityGraph.create(EntityGraph.java:122)
  10. at net.sf.jailer.Jailer.export(Jailer.java:1142)
  11. at net.sf.jailer.Jailer.jailerMain(Jailer.java:1064)
  12. at net.sf.jailer.Jailer.jailerMain(Jailer.java:989)
  13. at net.sf.jailer.Jailer.main(Jailer.java:967)
  14. Caused by: java.sql.SQLException: "Table 'ppobgsp_test.JAILER_GRAPH' doesn't exist" in statement "Insert into JAILER_GRAPH(id, age) values (2104021762, 1)"
  15. at net.sf.jailer.database.Session.executeUpdate(Session.java:470)
  16. at net.sf.jailer.entitygraph.EntityGraph.create(EntityGraph.java:120)
  17. ... 4 more
  18. Error: java.lang.RuntimeException: Can't find working tables! Run 'bin/jailer.sh create-ddl' and execute the DDL-script first!
  19. 2010-06-28 14:15:08,724 [main] ERROR - working directory is /opt/downloads/java/tools/test/integration-test/jailer

Ini disebabkan karena Jailer ternyata membuat beberapa tabel di database untuk kebutuhan internalnya. Ini dapat dilihat pada database development kita.






Tabel Internal Jailer

Untuk menggenerate tabel di atas, kita jalankan jailer dengan opsi create-ddl. Ini akan menghasilkan SQL di layar. SQL ini harus kita jalankan di database production supaya tabelnya terbentuk.

  1. $ sh jailer.sh create-ddl
  2. DROP TABLE JAILER_ENTITY;
  3. DROP TABLE JAILER_DEPENDENCY;
  4. DROP TABLE JAILER_SET;
  5. DROP TABLE JAILER_GRAPH;
  6. DROP TABLE JAILER_CONFIG;
  7. DROP TABLE JAILER_TMP;

  8. CREATE TABLE JAILER_CONFIG
  9. (
  10. jversion VARCHAR(20),
  11. jkey VARCHAR(200),
  12. jvalue VARCHAR(200)
  13. ) ;

  14. INSERT INTO JAILER_CONFIG(jversion, jkey, jvalue) values('3.4.5', 'magic', '837065098274756382534403654245288');

  15. CREATE TABLE JAILER_GRAPH
  16. (
  17. id INTEGER NOT NULL,
  18. age INTEGER NOT NULL

  19. -- ,CONSTRAINT jlr_pk_graph PRIMARY KEY(id)
  20. ) ;

  21. CREATE TABLE JAILER_ENTITY
  22. (
  23. r_entitygraph INTEGER NOT NULL,

  24. PK0 BIGINT , PK1 VARCHAR(255) , PK2 VARCHAR(255) , PK3 INT , PK4 VARCHAR(255) , PK5 BIGINT ,
  25. birthday INTEGER NOT NULL,
  26. type VARCHAR(120) NOT NULL,

  27. PRE_PK0 BIGINT , PRE_PK1 VARCHAR(255) , PRE_PK2 VARCHAR(255) , PRE_PK3 INT , PRE_PK4 VARCHAR(255) , PRE_PK5 BIGINT ,
  28. PRE_TYPE VARCHAR(120),
  29. orig_birthday INTEGER,
  30. association INTEGER

  31. -- , CONSTRAINT jlr_fk_graph_e FOREIGN KEY (r_entitygraph) REFERENCES JAILER_GRAPH(id)
  32. ) ;

  33. CREATE INDEX jlr_enty_brthdy ON JAILER_ENTITY (r_entitygraph, birthday, type) ;
  34. CREATE INDEX jlr_enty_upk1 ON JAILER_ENTITY (r_entitygraph , PK0, PK1, PK2, PK3, PK4, PK5, type) ;

  35. CREATE TABLE JAILER_SET
  36. (
  37. set_id INTEGER NOT NULL,
  38. type VARCHAR(120) NOT NULL,
  39. PK0 BIGINT , PK1 VARCHAR(255) , PK2 VARCHAR(255) , PK3 INT , PK4 VARCHAR(255) , PK5 BIGINT
  40. ) ;

  41. CREATE INDEX jlr_pk_set1 ON JAILER_SET (set_id , PK0, PK1, PK2, PK3, PK4, PK5, type) ;

  42. CREATE TABLE JAILER_DEPENDENCY
  43. (
  44. r_entitygraph INTEGER NOT NULL,
  45. assoc INTEGER NOT NULL,
  46. depend_id INTEGER NOT NULL,
  47. traversed INTEGER,
  48. from_type VARCHAR(120) NOT NULL,
  49. to_type VARCHAR(120) NOT NULL,
  50. FROM_PK0 BIGINT , FROM_PK1 VARCHAR(255) , FROM_PK2 VARCHAR(255) , FROM_PK3 INT , FROM_PK4 VARCHAR(255) , FROM_PK5 BIGINT ,
  51. TO_PK0 BIGINT , TO_PK1 VARCHAR(255) , TO_PK2 VARCHAR(255) , TO_PK3 INT , TO_PK4 VARCHAR(255) , TO_PK5 BIGINT

  52. -- , CONSTRAINT jlr_fk_graph_d FOREIGN KEY (r_entitygraph) REFERENCES JAILER_GRAPH(id)
  53. ) ;

  54. CREATE INDEX jlr_dep_from1 ON JAILER_DEPENDENCY (r_entitygraph, assoc , FROM_PK0, FROM_PK1, FROM_PK2, FROM_PK3, FROM_PK4, FROM_PK5) ;

  55. CREATE INDEX jlr_dep_to1 ON JAILER_DEPENDENCY (r_entitygraph , TO_PK0, TO_PK1, TO_PK2, TO_PK3, TO_PK4, TO_PK5) ;

  56. CREATE TABLE JAILER_TMP
  57. (
  58. c1 INTEGER,
  59. c2 INTEGER
  60. ) ;

  61. INSERT INTO JAILER_CONFIG(jversion, jkey, jvalue) values('3.4.5', 'upk', '679547784');

Setelah tabelnya siap, jalankan kembali script yang error di atas. Berikut outputnya.


  1. $ ./export-payment-201006.sh
  2. 2010-06-28 14:12:31,175 [main] INFO - Jailer 3.4.5
  3. 2010-06-28 14:12:31,190 [main] INFO - added 'lib/mysql-connector-java-5.1.6-bin.jar' to classpath
  4. 2010-06-28 14:12:31,191 [main] INFO - exporting 'extractionmodel/payment-201006.csv' to 'payment-201006.sql'
  5. 2010-06-28 14:12:32,850 [main] INFO - SQL dialect is MYSQL
  6. 2010-06-28 14:12:32,925 [main] INFO - gather statistics after 0 inserted rows...
  7. 2010-06-28 14:12:32,966 [main] INFO - reading file 'renew.sql'
  8. 2010-06-28 14:12:32,966 [main] INFO - 0 statements (100%)
  9. 2010-06-28 14:12:32,967 [main] INFO - successfully read file 'renew.sql'
  10. 2010-06-28 14:12:32,977 [main] INFO - exporting payment Where date_format(paid_date,'%Y-%m') = '2010-06'
  11. 2010-06-28 14:12:33,028 [main] INFO - day 1, progress: payment
  12. 2010-06-28 14:12:33,039 [main] INFO - starting 4 jobs
  13. 2010-06-28 14:12:33,040 [main] INFO - gather statistics after 3 inserted rows...
  14. 2010-06-28 14:12:33,041 [main] INFO - reading file 'renew.sql'
  15. 2010-06-28 14:12:33,042 [main] INFO - 0 statements (100%)
  16. 2010-06-28 14:12:33,042 [main] INFO - successfully read file 'renew.sql'
  17. 2010-06-28 14:12:33,047 [main] INFO - resolving payment -> payment_info (inverse-FKE25C3F47AB64A229) 1:n on B.id_payment=A.id...
  18. 2010-06-28 14:12:33,105 [main] INFO - 66 entities found resolving payment -> payment_info (inverse-FKE25C3F47AB64A229) 1:n on B.id_payment=A.id
  19. 2010-06-28 14:12:33,105 [main] INFO - resolving payment -> cetak_ulang (inverse-FK45B985E0AB64A229) 1:n on B.id_payment=A.id...
  20. 2010-06-28 14:12:33,126 [main] INFO - 0 entities found resolving payment -> cetak_ulang (inverse-FK45B985E0AB64A229) 1:n on B.id_payment=A.id
  21. 2010-06-28 14:12:33,126 [main] INFO - resolving payment -> fee_loket (inverse-FK9632AFFEAB64A229) 1:n on B.id_payment=A.id...
  22. 2010-06-28 14:12:33,129 [main] INFO - 3 entities found resolving payment -> fee_loket (inverse-FK9632AFFEAB64A229) 1:n on B.id_payment=A.id
  23. 2010-06-28 14:12:33,131 [main] INFO - resolving payment -> payment_value (inverse-FK69DD09F8AB64A229) 1:n on B.id_payment=A.id...
  24. 2010-06-28 14:12:33,142 [main] INFO - 12 entities found resolving payment -> payment_value (inverse-FK69DD09F8AB64A229) 1:n on B.id_payment=A.id
  25. 2010-06-28 14:12:33,143 [main] INFO - executed 4 jobs
  26. 2010-06-28 14:12:33,143 [main] INFO - day 2, progress: payment_info, fee_loket, payment_value
  27. 2010-06-28 14:12:33,144 [main] INFO - skip reversal association payment_info -> payment
  28. 2010-06-28 14:12:33,144 [main] INFO - skip reversal association fee_loket -> payment
  29. 2010-06-28 14:12:33,147 [main] INFO - skip reversal association payment_value -> payment
  30. 2010-06-28 14:12:33,147 [main] INFO - starting 1 jobs
  31. 2010-06-28 14:12:33,148 [main] INFO - executed 1 jobs
  32. 2010-06-28 14:12:33,149 [main] INFO - exported payment Where date_format(paid_date,'%Y-%m') = '2010-06'
  33. 2010-06-28 14:12:33,149 [main] INFO - total progress: payment_info, payment, fee_loket, payment_value
  34. 2010-06-28 14:12:33,149 [main] INFO - export statistic:
  35. 2010-06-28 14:12:33,169 [main] INFO - Exported Rows: 84
  36. 2010-06-28 14:12:33,169 [main] INFO - fee_loket 3
  37. 2010-06-28 14:12:33,169 [main] INFO - payment 3
  38. 2010-06-28 14:12:33,172 [main] INFO - payment_info 66
  39. 2010-06-28 14:12:33,172 [main] INFO - payment_value 12
  40. 2010-06-28 14:12:33,173 [main] INFO - writing file 'payment-201006.sql'...
  41. 2010-06-28 14:12:33,178 [main] INFO - independent tables: payment
  42. 2010-06-28 14:12:33,179 [main] INFO - starting 1 jobs
  43. 2010-06-28 14:12:33,380 [main] INFO - executed 1 jobs
  44. 2010-06-28 14:12:33,380 [main] INFO - independent tables: payment_info, fee_loket, payment_value
  45. 2010-06-28 14:12:33,384 [main] INFO - starting 3 jobs
  46. 2010-06-28 14:12:33,447 [main] INFO - executed 3 jobs
  47. 2010-06-28 14:12:33,447 [main] INFO - cyclic dependencies for:
  48. 2010-06-28 14:12:33,447 [main] INFO - starting 0 jobs
  49. 2010-06-28 14:12:33,448 [main] INFO - executed 0 jobs
  50. 2010-06-28 14:12:33,448 [main] INFO - gather statistics after 84 inserted rows...
  51. 2010-06-28 14:12:33,450 [main] INFO - reading file 'renew.sql'
  52. 2010-06-28 14:12:33,450 [main] INFO - 0 statements (100%)
  53. 2010-06-28 14:12:33,454 [main] INFO - successfully read file 'renew.sql'
  54. 2010-06-28 14:12:33,456 [main] INFO - starting 0 jobs
  55. 2010-06-28 14:12:33,467 [main] INFO - executed 0 jobs
  56. 2010-06-28 14:12:33,486 [main] INFO - file 'payment-201006.sql' written.
Selesai sudah, data yang kita inginkan ada di file payment-201006.sql, siap diunduh dan dijalankan di database development.

Semoga bermanfaat, kalau ada yang kurang jelas, silahkan baca tutorial resminya.


sumber : Blog Endy


Tuesday, September 21, 2010

Danau cantik dari Bencana

Tak lengkap rasanya jika Anda berkunjung ke Sumatera Utara tidak mampir sejenak ke Danau Toba, danau vulkanik yang merupakan danau terbesar di Indonesia, bahkan Asia Tenggara. Pesona eksotisnya berupa hamparan danau luas laksana lautan dengan pepohonan rindang dan perbukitan yang menawan. Danau ini berukuran 1700 meter persegi dengan kedalaman kurang lebih 450 meter dan terletak 906 meter di atas permukaan laut, di tengah danau terdapat Pulau Samosir yang tak kalah menariknya menjadi objek kunjungan wisata.

Dalam kunjungannya pada 1996, Pangeran Bernard dari Belanda bahkan menyatakan kekagumannya pada panorama indah danau ini. “Juallah nama saya untuk danau ini. Saya tak dapat melukiskan betapa indahnya Danau Toba,” katanya antusias.

Ada tujuh kabupaten di sekeliling danau, yakni Simalungun, Toba Samosir, Tapanuli Utara, Humbang Hasundutan, Dairi, Karo, dan Samosir yang memiliki panorama alam indah dan menjadi lokasi tujuan wisata. Umumnya wisatawan menikmati keelokan Danau Toba dari Parapat di Simalungun dan Tuktuk Siadong di Pulau Samosir.

Diperkirakan Danau Toba terjadi saat ledakan sekitar 73 ribu-75 ribu tahun lalu dan merupakan letusan super volcano (gunung berapi super) yang paling baru. Bill Rose dan Craig Chesner dari Michigan Technological University memperkirakan bahwa bahan-bahan vulkanik yang dimuntahkan gunung itu sebanyak 2.800 km³, dengan 800 km³ batuan ignimbrit dan 2.000 km³ abu vulkanik yang diperkirakan tertiup angin ke barat selama dua minggu.


Debu vulkanik yang ditiup angin telah menyebar ke separuh bumi, dari Cina sampai ke Afrika Selatan. Letusannya terjadi selama satu minggu dan lontaran debunya mencapai 10 km di atas permukaan laut.

Kejadian ini menyebabkan kematian massal dan, pada beberapa spesies, juga diikuti kepunahan. Menurut beberapa bukti DNA, letusan ini juga menyusutkan jumlah manusia sampai sekitar 60% dari jumlah populasi manusia bumi saat itu, yaitu sekitar 60 juta manusia. Letusan itu juga ikut menyebabkan terjadinya zaman es, walaupun para ahli masih memperdebatkannya.

Setelah letusan tersebut, terbentuk kaldera yang kemudian terisi oleh air dan menjadi yang sekarang dikenal sebagai Danau Toba. Tekanan ke atas oleh magma yang belum keluar menyebabkan munculnya Pulau Samosir. Ketika menikmati keindahan danau ini, Anda mungkin tak membayangkan bahwa pesona yang terjadi berasal dari bencana dahsyat letusan gunung berapi yang mendatangkan ketakutan dan kengerian ketika itu.
Perjalanan darat ke Danau Toba, tepatnya ke Parapat, memakan waktu empat sampai lima jam dari Medan. Tersedia bus atau travel yang langsung menuju Parapat. Rutenya melewati Lubuk Pakam, Tebing Tinggi, dan belok ke arah Pematang Siantar. Sepanjang perjalanan, kita disuguhi panorama perkebunan kelapa sawit dan karet.

Apabila menggunakan kereta api, dari Medan pilih rute menuju Pematang Siantar. Dari sini perjalanan dilanjutkan menggunakan bus ke Parapat. Waktu tempuhnya satu jam.


Untuk tempat menginap dan tinggal lebih lama menikmati keindahan Danau Toba, tersedia banyak hotel dan penginapan. Di Parapat, sedikitnya ada 900 kamar hotel berbagai jenis, mulai dari bintang empat hingga homestay, di Tuktuk juga tak berbeda. Baik di Parapat maupun Tuktuk, wisatawan dapat langsung menikmati danau dari pinggirannya. Tarif hotel di Tuktuk dan Parapat bervariasi, sesuai tipikal turis yang datang. Mulai dari Rp 30 ribu hingga Rp 500 ribu per malam tergantung tipe hotel.

Sebuah perusahaan travel bahkan menawarkan menikmati keindahan Danau Toba dari udara, yakni menggunakan paralayang. Setiap wisatawan diberi kesempatan terbang menggunakan paralayang dari kawasan pegunungan Tongging, Kabupaten Tanah Karo, Sumatera Utara. Bagi para wisatawan yang ingin mencoba paralayang akan ditemani seorang instruktur berpengalaman, namun tentunya penentuan bisa terbang atau tidak tergantung pada kondisi cuaca dan angin.

Tidak hanya itu, menikmati keindahan matahari terbit dan terbenam bisa Anda nikmati dari pesisir danau. Dari dataran tinggi Karo di sebelah utara, keelokan danau terlihat memanjang dipandang dari Sikodonkodon. Namun, hanya ada satu resor di sini. Di sisi barat, pemandangan danau dan Pulau Samosir dapat dengan sempurna disaksikan dari Tele. Ada gardu pandang di ketinggian sekitar 1.000 meter dari permukaan laut untuk menikmati senja di Danau Toba.

sumber