MaxCompute, taranan veri miktarına ve tüketilen hesaplama kaynaklarına göre faturalandırma yapar. Beklenmedik fatura artışlarının çoğundan iki kalıp sorumludur: Gereğinden fazla veri tarayan SQL işleri ve iş yükünün ihtiyaç duyduğundan daha fazla kaynak ayıran MapReduce işleri. Bu konu, her iki durumun da nasıl teşhis edileceğini ve düzeltileceğini açıklar.
İşleri çalıştırmadan önce maliyetleri tahmin edin
İşleri göndermeden önce hesaplama maliyetlerini tahmin etmek için TCO araçlarını kullanın. SQL işleri için, bir sorgu çalışmadan önce maliyetini önizlemek üzere CostSQL'i kullanın. Beklenmedik maliyet artışlarını erken aşamada yakalamak için kaynak tüketimi uyarılarını yapılandırın.
SQL hesaplama maliyetlerini azaltın
Tam tablo taramalarından kaçının
Tam tablo taramaları, yüksek SQL hesaplama maliyetlerinin başlıca nedenidir. MaxCompute taranan verilere göre ücretlendirir; yalnızca bir alt kümeye ihtiyacınız olduğunda tüm tabloyu taramak faturanızı katlar.
Tam tablo taramalarını oturum veya proje düzeyinde devre dışı bırakın:
-- Disable for the current session
set odps.sql.allow.fullscan=false;
-- Disable for the entire project
SetProject odps.sql.allow.fullscan=false;
Sütun budaması kullanın — SELECT * kullanımından kaçının:
SELECT * her zaman tam tablo taramasını tetikler. Yalnızca ihtiyacınız olan sütunları seçin:
-- Table T has columns a, b, c, d, e.
-- This query reads only a, b, and e — skipping c and d entirely.
SELECT a, b FROM T WHERE e < 10;
Bölüm budaması kullanın — bölüm anahtarı sütunlarında filtreleme yapın:
WHERE yan tümcesinde bir bölüm anahtarı belirtmek, MaxCompute'in ilgisiz bölümleri atlamasını ve yalnızca eşleşen verileri taramasını sağlar:
SELECT a, b FROM T WHERE partitiondate = '2017-10-01';
Bölüm filtresi olmadan, bölümlenmiş bir tabloda JOIN veya SELECT işlemi tam tablo taramasına geri döner. JOIN işlemlerini gerçekleştirmeden önce her zaman bölümleri budayın. Bölüm budamanın etkili olmadığı durumlar için bkz. Bölüm budamanın etkili olmadığı senaryolar.
Maliyetli SQL kalıplarını yeniden yazın
Bazı SQL anahtar kelimeleri, ek karıştırma ve sıralama işlemlerini tetikleyerek ek hesaplama kaynakları tüketir. Kök neden veri hareketidir; motorun bir sorguyu karşılamak için verileri düğümler arasında yeniden düzenlemesi gerektiğinde, taşınan veri hacmiyle orantılı olarak CPU ve G/Ç tüketir. Bu kalıpları yeniden yazmak, bu hareketi azaltır veya ortadan kaldırır.
FULL OUTER JOIN yerine UNION ALL kullanın
FULL OUTER JOIN, motorun her iki tablodaki her satırı eşleştirmesini gerektirir ve bu durum büyük bir ara karıştırmaya yol açar. UNION ALL, eksik değerleri sıfırlarla doldurarak join işlemini tamamen ortadan kaldırır:
-- Original: 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);
-- Optimized: UNION ALL (no join, no data movement overhead)
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;
GROUP BY ifadesini UNION ALL dışına taşıyın
GROUP BY ifadesini UNION ALL'ın her dalının içine yerleştirmek, motorun iki kez toplama yapmasına neden olur. Birleşimden sonra yalnızca bir kez toplama yapın:
-- Original: GROUP BY inside each branch (double aggregation)
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;
-- Optimized: single aggregation after 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;
DISTINCT yerine GROUP BY kullanın
Büyük veri setlerinde DISTINCT kullanımı tam sıralama gerektirir. GROUP BY aynı sonucu daha düşük ek yükle elde eder:
-- Original: DISTINCT (full sort)
SELECT COUNT(DISTINCT id) AS cnt FROM table1;
-- Optimized: GROUP BY (more efficient deduplication)
SELECT COUNT(1) AS cnt
FROM (
SELECT id FROM table1 GROUP BY id
) t;
Kaçınılması gereken diğer kalıplar:
Bölümleme olmadan ekleme yapmak yerine, bir bölüm alanıyla
INSERT INTOkullanın. Bu yaklaşım SQL karmaşıklığını azaltır ve maliyeti düşürür.Geçici olarak dışa aktarılan verileri ORDER BY yerine Excel gibi harici araçlarla sıralayın. MaxCompute'de ORDER BY, tüm veri seti üzerinde genel bir sıralama tetikler.
Zamanlama sıklığını kontrol edin
MaxCompute, gerçek zamanlı sorgular için değil, büyük toplu işler için tasarlanmıştır. SQL işlerini kısa aralıklarla (her birkaç saniye veya dakikada bir) zamanlamak, kullandıkça öde faturalaması altında iş kuyruklarının birikmesine neden olur ve ertesi günkü fatura beklenmedik şekilde artabilir.
Sıklıkla zamanlanan işlerin cadansını belirlemeden önce maliyetlerini tahmin etmek için CostSQL'i çalıştırın. Neredeyse gerçek zamanlı sonuçlara ihtiyaç duyan iş yükleri için MaxCompute yerine özel bir gerçek zamanlı hesaplama hizmeti kullanın.
SQL çalıştırmadan tablo verilerini önizleyin
SELECT * FROM table LIMIT 10 çalıştırmak hesaplama kaynakları tüketir. Bunun yerine yerleşik tablo önizleme özelliğini kullanın; bu özellik, bir hesaplama işi tetiklemeden verileri doğrudan depolamadan okur:
DataWorks: Data Map sayfasını açın, tabloyu bulun ve önizleme sekmesini kullanın. Bkz. Tablonun ayrıntılarını görüntüleme.
MaxCompute Studio: Verilerinin önizlemesini yapmak için nesne ağacındaki bir tabloya çift tıklayın.
Aracı iş yüküne göre seçin
MaxCompute sonuçları milisaniyeler içinde değil, dakikalar içinde döndürür. Büyük toplu analizler için doğru araçtır ancak anlık yanıtlar gerektiren ön uç sorguları için uygun değildir.
Ön uç sorguları (panolar, arama sonuçları, satır aramaları) için ApsaraDB RDS gibi bir ilişkisel veritabanı kullanın. Toplanmış MaxCompute sonuçlarını burada saklayın ve sorguları veritabanından sunun. WHERE yan tümcesi olmayan, toplama yapmayan ve sözlüklerle join işlemi gerçekleştirmeyen ön uç sorguları MaxCompute'de her zaman yavaş olacaktır.
MapReduce hesaplama maliyetlerini azaltın
Bölme boyutunu ve reducer sayısını yapılandırın
İki yapılandırma ayarı, MapReduce kaynak tüketimi üzerinde en büyük etkiye sahiptir.
Bölme boyutu, kaç adet mapper oluşturulacağını kontrol eder. Varsayılan değer bölme başına 256 MB'dır. Daha küçük bir bölme boyutu daha fazla mapper oluşturur; bu durum paralelliği artırırken kaynak kullanımını da yükseltir. Haritalama mantığınızın hesaplama maliyetine bağlı olarak bu değeri ayarlamak için JobConf#setSplitSize kullanın; her kayıt işlenmesi pahalıysa, işi dağıtmak için daha fazla mapper oluşturmak amacıyla bölme boyutunu azaltın.
Reducer sayısı varsayılan olarak mapper sayısının dörtte biridir ve 0 ile 2.000 arasında herhangi bir değere ayarlanabilir. Daha fazla reducer daha fazla kaynak tüketir. Yalnızca toplama iş yükünüzün gerektirdiği kadar reducer ayarlayın ve sayıyı açıkça yapılandırmak için jobconf.setNumReduceTasks(num) kullanın.
İşlem hattı modunu kullanarak seri işleri birleştirin
Birden fazla MapReduce işi zincirlendiğinde (bir işin çıktısı sonrakine beslediğinde), her ara iş sonuçları diske yazar ve tekrar okur. Bu disk G/Ç'si zincir boyunca birikir.
İşlem hattı modu, seri MapReduce işlerini tek bir işte birleştirerek ara disk okuma ve yazma işlemlerini ortadan kaldırır. Bu hem maliyeti hem de zamanlama ek yükünü azaltır. Uygulama örnekleri için bkz. İşlem hattı örnekleri.
Giriş tablolarında sütunları budayın
Bir mapper bir giriş tablosunu okurken yalnızca birkaç sütuna ihtiyaç duyuyorsa, tüm satırı okumak G/Ç israfına yol açar. Bir giriş tablosu eklerken gerekli sütunları belirtin:
InputUtils.addTable(TableInfo.builder().tableName("wc_in").cols(new String[]{"c1","c2"}).build(), job);
Bu yapılandırmadan sonra, mapper yalnızca c1 ve c2 sütunlarını okur. Sütun adına göre erişilen veriler etkilenmez; dizin alt simgesine göre erişilen veriler farklı davranabilir.
Kaynakları kurulum aşamasında okuyun
Bir kaynağı okumak için yapılan her çağrı ek yüke neden olur ve kaynakları en fazla 64 kez okuyabilirsiniz. Aynı kaynağı bir map veya reduce işlevi içinde okumak, her kayıtta yeniden okunmasına neden olur. Bunun yerine kaynakları setup aşamasında bir kez okuyun. Kullanım örnekleri için bkz. Kaynak kullanım örneği.
Map veya reduce işlevinde nesne oluşturmayın
Bir map veya reduce işlevi içinde oluşturulan Java nesneleri, her kayıt çağrısında yeniden oluşturulur. Nesne oluşturmayı setup aşamasına taşıyın:
Record word;
Record one;
public void setup(TaskContext context) throws IOException {
// Construct once in setup — not on every map call
word = context.createMapOutputKeyRecord();
one = context.createMapOutputValueRecord();
one.set(new Object[]{1L});
}
Map çıktısında yinelenen anahtarlar varsa birleştirici (combiner) kullanın
Bir birleştirici, reducer'lara gönderilmeden önce bir map görevinin çıktısını önceden toplar ve karıştırma aşamasında ağ üzerinden aktarılan veri hacmini azaltır.
Bir birleştiriciyi yalnızca map çıktısı aynı anahtara sahip birden fazla kayıt içerdiğinde kullanın; örneğin, kelime sayma işinde. Map çıktı anahtarları benzersizse, bir birleştirici fayda sağlamadan ek yük ekler.
Aşağıdaki birleştirici, eşleşen anahtarlar için değerleri toplar:
/**
* A combiner class that combines map output by summing values.
*/
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);
}
}
Bölüm sütunları veya özel bir partitioner kullanarak veri eğrilmesini önleyin
Varsayılan olarak, reducer'lar tam anahtar şemasının hash değerine göre veri alır. Bazı anahtar değerleri diğerlerinden çok daha yaygınsa, belirli reducer'lar önemli ölçüde daha fazla veri alır; bu, bazı reducer'ların diğerlerinden çok daha sonra bitmesine ve tüm işi bekletmesine neden olan uzun kuyruk sorunudur.
Verileri reducer'lara daha eşit dağıtmak için JobConf#setPartitionColumns kullanarak bölüm anahtarı sütunlarını belirtin. Veriler, tam anahtar yerine bu sütunların hash değerine göre reducer'lara yönlendirilir:
jobconf.setPartitionerClass(MyPartitioner.class)
jobconf.setNumReduceTasks(num)
Daha hassas kontrol için özel bir partitioner uygulayın:
import com.aliyun.odps.mapred.Partitioner;
public static class MyPartitioner extends Partitioner {
@Override
public int getPartition(Record key, Record value, int numPartitions) {
// numPartitions is the total number of reducers.
// Route each key to a reducer based on key length.
String k = key.get(0).toString();
return k.length() % numPartitions;
}
}
JVM belleğini 1:4 CPU-bellek oranı içinde tutun
Standart yapılandırma 1 CPU çekirdeği ve 4 GB bellektir; odps.stage.reducer.jvm.mem 4006 olarak ayarlanır. Belleği CPU çekirdeklerine göre 1:4 oranının üzerine çıkacak şekilde ayırmak faturalandırmayı artırır. odps.stage.reducer.jvm.mem değerini aşırı tahsis etmek yerine gerçek iş yükünüzle eşleşecek şekilde ayarlayın.
Sırada ne var
Depolama maliyetlerini optimize etmek için bkz. Depolama maliyetlerini optimize etme.
Veri yükleme ve indirme maliyetlerini azaltmak için bkz. Veri yükleme ve indirme maliyetlerini optimize etme.
Faturalandırma anomalilerini analiz etmek ve çözmek için bkz. Maliyetleri yönetme.
Bir kaynak optimizasyon planı oluşturmak için bkz. Hesaplama kaynağı optimizasyon planı oluşturma.