MaxCompute menagih berdasarkan jumlah data yang dipindai dan sumber daya komputasi yang dikonsumsi. Dua pola menyebabkan sebagian besar lonjakan tagihan tak terduga: pekerjaan SQL yang memindai lebih banyak data daripada yang diperlukan, dan pekerjaan MapReduce yang mengalokasikan lebih banyak sumber daya daripada yang dibutuhkan oleh beban kerja. Topik ini menjelaskan cara mendiagnosis dan memperbaiki keduanya.
Perkirakan biaya sebelum menjalankan pekerjaan
Gunakan TCO tools untuk memperkirakan biaya komputasi sebelum mengirimkan pekerjaan. Untuk pekerjaan SQL, gunakan CostSQL guna melihat pratinjau biaya kueri sebelum dijalankan. Konfigurasikan alert konsumsi sumber daya untuk mendeteksi pertumbuhan biaya tak terduga sedini mungkin.
Kurangi biaya komputasi SQL
Hindari full table scan
Full table scan merupakan penyebab utama tingginya biaya komputasi SQL. MaxCompute menagih berdasarkan data yang dipindai — memindai seluruh tabel saat Anda hanya membutuhkan subset akan melipatgandakan tagihan Anda.
Nonaktifkan full table scan di tingkat session atau project:
-- Nonaktifkan untuk session saat ini
set odps.sql.allow.fullscan=false;
-- Nonaktifkan untuk seluruh project
SetProject odps.sql.allow.fullscan=false;Gunakan column pruning — hindari SELECT *:
SELECT * selalu memicu full table scan. Pilih hanya kolom yang Anda butuhkan:
-- Tabel T memiliki kolom a, b, c, d, e.
-- Kueri ini hanya membaca a, b, dan e — melewati c dan d sepenuhnya.
SELECT a, b FROM T WHERE e < 10;Gunakan partition pruning — filter pada kolom kunci partisi:
Menentukan kunci partisi dalam klausa WHERE memberi tahu MaxCompute untuk melewati partisi yang tidak relevan dan hanya memindai data yang sesuai:
SELECT a, b FROM T WHERE partitiondate = '2017-10-01';Tanpa filter partisi, operasi JOIN atau SELECT pada tabel partisi akan kembali ke full table scan. Selalu lakukan pemangkasan partisi sebelum melakukan JOIN. Untuk kasus di mana partition pruning tidak berlaku, lihat Skenario di mana partition pruning tidak berlaku.
Tulis ulang pola SQL yang mahal
Beberapa kata kunci SQL memicu pengacakan (shuffling) dan pengurutan tambahan, yang mengonsumsi sumber daya komputasi ekstra. Penyebab utamanya adalah perpindahan data — ketika engine harus mengatur ulang data di antara node untuk memenuhi kueri, CPU dan I/O yang dikonsumsi sebanding dengan volume data yang dipindahkan. Menulis ulang pola-pola ini dapat mengurangi atau menghilangkan perpindahan tersebut.
Ganti FULL OUTER JOIN dengan UNION ALL
FULL OUTER JOIN mengharuskan engine mencocokkan setiap baris di kedua tabel, menghasilkan pengacakan antara (intermediate shuffle) yang besar. UNION ALL menghilangkan join sepenuhnya dengan mengisi nilai yang hilang menggunakan nol:
-- Asli: FULL OUTER JOIN
SELECT COALESCE(t1.id, t2.id) AS id, SUM(t1.col1) AS col1, SUM(t2.col2) AS col2
FROM (
SELECT id, col1 FROM table1
) t1
FULL OUTER JOIN (
SELECT id, col2 FROM table2
) t2
ON t1.id = t2.id
GROUP BY COALESCE(t1.id, t2.id);
-- Dioptimalkan: UNION ALL (tanpa join, tanpa overhead perpindahan data)
SELECT t.id, SUM(t.col1) AS col1, SUM(t.col2) AS col2
FROM (
SELECT id, col1, 0 AS col2 FROM table1
UNION ALL
SELECT id, 0 AS col1, col2 FROM table2
) t
GROUP BY t.id;Pindahkan GROUP BY ke luar UNION ALL
Menempatkan GROUP BY di dalam setiap cabang UNION ALL menyebabkan engine melakukan agregasi dua kali. Lakukan agregasi sekali setelah union:
-- Asli: GROUP BY di dalam setiap cabang (agregasi ganda)
SELECT t.id, SUM(t.val) AS val
FROM (
SELECT id, SUM(col3) AS val FROM table3 GROUP BY id
UNION ALL
SELECT id, SUM(col4) AS val FROM table4 GROUP BY id
) t
GROUP BY t.id;
-- Dioptimalkan: agregasi tunggal setelah union
SELECT t.id, SUM(t.val) AS val
FROM (
SELECT id, col3 AS val FROM table3
UNION ALL
SELECT id, col4 AS val FROM table4
) t
GROUP BY t.id;Ganti DISTINCT dengan GROUP BY
DISTINCT pada dataset besar memerlukan pengurutan penuh. GROUP BY memberikan hasil yang sama dengan overhead lebih rendah:
-- Asli: DISTINCT (pengurutan penuh)
SELECT COUNT(DISTINCT id) AS cnt FROM table1;
-- Dioptimalkan: GROUP BY (deduplikasi lebih efisien)
SELECT COUNT(1) AS cnt
FROM (
SELECT id FROM table1 GROUP BY id
) t;Pola lain yang harus dihindari:
Gunakan
INSERT INTOdengan bidang partisi, bukan memasukkan data tanpa partisi. Ini mengurangi kompleksitas SQL dan menurunkan biaya.Urutkan data yang diekspor sementara menggunakan tool eksternal seperti Excel, bukan ORDER BY. ORDER BY di MaxCompute memicu pengurutan global di seluruh dataset.
Kontrol frekuensi penjadwalan
MaxCompute dirancang untuk pemrosesan batch besar, bukan kueri real-time. Menjadwalkan pekerjaan SQL dalam interval pendek — setiap beberapa detik atau menit — mengakumulasi antrian pekerjaan dalam skema penagihan pay-as-you-go, dan tagihan hari berikutnya bisa melonjak secara tak terduga.
Jalankan CostSQL untuk memperkirakan biaya pekerjaan yang dijadwalkan secara berkala sebelum menentukan frekuensinya. Untuk beban kerja yang membutuhkan hasil near-real-time, gunakan layanan komputasi real-time khusus, bukan MaxCompute.
Pratinjau data tabel tanpa menjalankan SQL
Menjalankan SELECT * FROM table LIMIT 10 mengonsumsi sumber daya komputasi. Gunakan fitur pratinjau tabel bawaan sebagai gantinya — fitur ini membaca data langsung dari penyimpanan tanpa memicu pekerjaan komputasi:
DataWorks: Buka halaman Data Map, temukan tabelnya, lalu gunakan tab pratinjau. Lihat Lihat detail tabel.
MaxCompute Studio: Klik ganda tabel di pohon objek untuk melihat pratinjau datanya.
Sesuaikan tool dengan workload
MaxCompute mengembalikan hasil dalam hitungan menit, bukan milidetik. Tool ini cocok untuk analitik batch besar, tetapi tidak tepat untuk kueri frontend yang memerlukan respons instan.
Untuk kueri frontend — dasbor, hasil pencarian, pencarian baris — gunakan database relasional seperti ApsaraDB for RDS. Simpan hasil agregasi MaxCompute di sana dan layani kueri dari database tersebut. Kueri frontend yang tidak memiliki klausa WHERE, tidak melakukan agregasi, dan tidak melakukan join dengan kamus akan selalu lambat di MaxCompute.
Kurangi biaya komputasi MapReduce
Konfigurasikan ukuran split dan jumlah reducer
Dua pengaturan konfigurasi memiliki dampak terbesar terhadap konsumsi sumber daya MapReduce.
Ukuran split mengontrol berapa banyak mapper yang dibuat. Nilai default-nya adalah 256 MB per split. Ukuran split yang lebih kecil membuat lebih banyak mapper, meningkatkan paralelisme tetapi juga penggunaan sumber daya. Gunakan JobConf#setSplitSize untuk menyesuaikan nilai ini berdasarkan biaya komputasi logika map Anda — jika setiap record mahal untuk diproses, kurangi ukuran split agar membuat lebih banyak mapper dan mendistribusikan beban kerja.
Jumlah reducer secara default bernilai seperempat dari jumlah mapper dan dapat diatur ke nilai apa pun antara 0 hingga 2.000. Lebih banyak reducer mengonsumsi lebih banyak sumber daya. Tetapkan hanya jumlah reducer yang dibutuhkan oleh beban kerja agregasi Anda, dan gunakan jobconf.setNumReduceTasks(num) untuk mengonfigurasi jumlah tersebut secara eksplisit.
Gabungkan pekerjaan serial menggunakan mode pipeline
Ketika beberapa pekerjaan MapReduce dirangkai — di mana output satu pekerjaan menjadi input pekerjaan berikutnya — setiap pekerjaan antara menulis hasil ke disk dan membacanya kembali. I/O disk ini bertambah sepanjang rantai.
Mode pipeline menggabungkan pekerjaan MapReduce serial menjadi satu pekerjaan, menghilangkan pembacaan dan penulisan disk antara. Hal ini mengurangi biaya sekaligus overhead penjadwalan. Untuk contoh implementasi, lihat Contoh pipeline.
Lakukan pemangkasan kolom pada tabel input
Ketika mapper membaca tabel input tetapi hanya membutuhkan beberapa kolomnya, membaca seluruh baris merupakan pemborosan I/O. Tentukan kolom yang diperlukan saat menambahkan tabel input:
InputUtils.addTable(TableInfo.builder().tableName("wc_in").cols(new String[]{"c1","c2"}).build(), job);Setelah konfigurasi ini, mapper hanya membaca kolom c1 dan c2. Data yang diakses berdasarkan nama kolom tidak terpengaruh; data yang diakses berdasarkan indeks subscript mungkin berperilaku berbeda.
Baca resource pada tahap setup
Setiap pemanggilan untuk membaca resource menimbulkan overhead, dan Anda dapat membaca resource hingga 64 kali. Membaca resource yang sama di dalam fungsi map atau reduce menyebabkan resource tersebut dibaca ulang pada setiap record. Bacalah resource sekali saja pada tahap setup. Untuk contoh penggunaan, lihat Contoh penggunaan resource.
Hindari pembuatan objek di dalam fungsi map atau reduce
Objek Java yang dibuat di dalam fungsi map atau reduce dibangun ulang pada setiap pemanggilan record. Pindahkan pembuatan objek ke tahap setup:
Record word;
Record one;
public void setup(TaskContext context) throws IOException {
// Bangun sekali di setup — bukan pada setiap pemanggilan map
word = context.createMapOutputKeyRecord();
one = context.createMapOutputValueRecord();
one.set(new Object[]{1L});
}Gunakan combiner ketika output map memiliki kunci duplikat
Combiner melakukan pra-agregasi output tugas map sebelum dikirim ke reducer, sehingga mengurangi volume data yang ditransfer melalui jaringan selama fase shuffle.
Gunakan combiner hanya ketika output map berisi beberapa record dengan kunci yang sama — misalnya, dalam pekerjaan penghitungan kata (word count). Jika kunci output map bersifat unik, combiner justru menambah overhead tanpa manfaat.
Combiner berikut menjumlahkan nilai untuk kunci yang cocok:
/**
* Kelas combiner yang menggabungkan output map dengan menjumlahkan nilai.
*/
public static class SumCombiner extends ReducerBase {
private Record count;
@Override
public void setup(TaskContext context) throws IOException {
count = context.createMapOutputValueRecord();
}
@Override
public void reduce(Record key, Iterator<Record> values, TaskContext context)
throws IOException {
long c = 0;
while (values.hasNext()) {
Record val = values.next();
c += (Long) val.get(0);
}
count.set(0, c);
context.write(key, count);
}
}Cegah data skew dengan kolom partisi atau partitioner kustom
Secara default, reducer menerima data berdasarkan hash dari skema kunci lengkap. Jika beberapa nilai kunci jauh lebih umum daripada yang lain, reducer tertentu menerima data jauh lebih banyak — masalah long-tail di mana beberapa reducer selesai jauh lebih lambat daripada yang lain, sehingga menunda seluruh pekerjaan.
Untuk mendistribusikan data lebih merata ke reducer, tentukan kolom kunci partisi menggunakan JobConf#setPartitionColumns. Data diarahkan ke reducer berdasarkan hash kolom-kolom tersebut, bukan kunci lengkap:
jobconf.setPartitionerClass(MyPartitioner.class)
jobconf.setNumReduceTasks(num)Untuk kontrol lebih rinci, implementasikan partitioner kustom:
import com.aliyun.odps.mapred.Partitioner;
public static class MyPartitioner extends Partitioner {
@Override
public int getPartition(Record key, Record value, int numPartitions) {
// numPartitions adalah jumlah total reducer.
// Arahkan setiap kunci ke reducer berdasarkan panjang kunci.
String k = key.get(0).toString();
return k.length() % numPartitions;
}
}Pertahankan memori JVM dalam rasio CPU-to-memory 1:4
Konfigurasi standar adalah 1 core CPU dan 4 GB memori, dengan odps.stage.reducer.jvm.mem diatur ke 4006. Mengalokasikan memori melebihi rasio 1:4 relatif terhadap core CPU akan meningkatkan penagihan. Sesuaikan odps.stage.reducer.jvm.mem agar sesuai dengan beban kerja aktual Anda, bukan over-provisioning.
Langkah selanjutnya
Untuk mengoptimalkan biaya penyimpanan, lihat Optimalkan biaya penyimpanan.
Untuk mengurangi biaya unggah dan unduh data, lihat Optimalkan biaya unggah dan unduh data.
Untuk menganalisis dan menyelesaikan anomali penagihan, lihat Kelola biaya.
Untuk menghasilkan rencana optimasi sumber daya, lihat Hasilkan rencana optimasi sumber daya komputasi.