Impalad Query Coordinator
Query Coordinator, fragment’leri node kullanılabilirliğine ve verinin konumuna göre dağıtır. Her node’un kullanılabilirliği Statestore servisi tarafından iletilir ve verinin konumu ile ilgili bilgiler ise HDFS Name Node’dan gelir. Fragment’ler dağıtıldıktan sonra Query Coordinator, uzak Impala Daemon’larında execute eder. Tek bir fragment başarısız olursa, Coordinator tüm sorguyu iptal eder ve kullanıcıya bir hata döndürür.
Sorgunun nihai sonuçlarını kullanıcıya geri gönderen Query Coordinator’dır. Sorgu nihai bir aggregation içermiyorsa, gelen sonuçları doğrudan kullanıcıya aktarabilir. Sorgu, Top-N gibi son bir aggregation içeriyorsa Query Coordinator, nihai sonucu kullanıcıya göndermeden önce son plan fragment’ini execute eder.
Impalad Query Executer
Query Executer, bir plan fragment’ini çalıştırır. Bir fragment’in çalıştırılması (execute), lokal verilerin taranması veya büyük veri setlerinin pre-aggregation’ı gibi işlerdir. Bu işlemler esnasında milyarlaca satır veri işlenir ve bu işlemin büyük bir hız ile yapılabiliyor olması çok önemlidir.
Scan node üzerinde yaygın olarak yapılan işlemlerden biri, virgülle ayrılmış bir dosyayı okumak ve bunları sütunlara bölmektir. Bu işlem esnasında her bir satırda virgül karakterinin bulunması gerekir. Bu derece büyükbir operasyonu hızlıca yapabilmek için impala birkaç yöntem kullanır:
- Ek yükü önlemek için C++ kullanır
- Sorunsuz bir CPU Pipeline için veri hizalama (Data Alignment)
- Geleneksel interpretive metotların aksine run-time ‘da code generation
Query Planner, Hadoop ortamıyla kolay entegre olabilmesi için Java dili ile geliştirilmiş olmasına karşın Query Executer, C++ dili ile geliştirilmiştir. C++’ın kullanımasının temelde bazı nedenleri vardır:
Deterministik Bellek Yönetimi: C ++, belleğin ne zaman serbest bırakılacağı üzerinde tam kontrol sağlarken, Java Garbage Collector beklenmedik anlarda devreye girerek başka yerlerde daha iyi kullanılabilen CPU döngülerini tüketebilir.
Meta Programming Template: Java’dan farklı olarak C++ meta programlama desteği sağlar. Bu gelişmiş teknik, derleme (compile) süresi boyunca optimizasyonlar gerçekleştirir; bazı işlemler (döngü başlatma veya statik polimorfizm gibi), her execution yerine, derlemeler sırasında yalnızca bir kez yapılır.
No Bound-Check: Java’nın oluşturmasının motivasyonlarından biri daha güvenli bir C/C++ oluşturmaktı. Bound-Check ile Java, bir değişkenin sınırlarını denetleyen bir metot sundu. En iyi bilinen örnek, bir index’in array’ın sınırları içinde olup olmadığını denetlemektir. Bound-check execution sırasında buffer overflow ve segmentation exception durumlarını önlese de, bir performans kaybına neden lur. C/C++ hızını artırmak için bu otomatik kontrole sahip değildir.
LLVM: C++ ile yazıldığından, Impala performansları artırmak için bir dizi derleyici aracından yararlanabilir. Doğru C++ kodu yazmak daha büyük bir zorluk olsa da, CPU’dan en iyi şekilde verim elde etmek için düşük seviyeli kod yazmak daha iyi bir seçenektir.
Data Alignment (Veri Hizalama)
Data Alignment, verilerin bellekte düzenlenme şeklidir. Veriler CPU’nun özelliklerine göre düzenlendiğinde, verilere daha hızlı erişilebilir. Daha hızlı erişim, sorgunun daha hızlı yürütülmesine neden olur.
CU’lar, chunk’lardaki memory’ye erişir. Örneğin, 64 bitlik bir CPU’da chunk boyutu 8 byte’tır (64 bit’tir). CPU, verileri 8’in katı olan offset’lerde memory’den okur. Bir uygulama, offset 14’te bulunan verilere ihtiyaç duyarsa, CPU, chunk 8’i offset 8’de okuyacak ve istenen verileri çıkarmak için bazı aritmetik işlemleri yapacaktır.
Bu verimsiz erişimi önlemek için, her veri kaydını bir chunk’ın başlangıcında hizalamak mümkündür. Padding olarak da bilinen bu uygulama, boş alanlar eklediği için bellek boyutu açısından daha az etkilidir. Veri kaydını offset 14’te konumlandırmak yerine, offset 16’ya taşınacak ve konum 14 ve 15 boş kalacaktır. Avantajı, CPU’nun istenen verilere herhangi bir ek çıkarma aritmetiği olmadan erişebilmesidir. Hizalanmış veriler, bir kare içinde paketlenmiş bloklara kıyasla, aşağıdaki şekilde gösterildiği gibi, LEGO bloklarının tek bir satırda dizilmiş gibi görünür.
SIMD Komutları
Impala’da kullanılan bir başka optimizasyonda modern işlemcilerle ilgili bazı özel komutlar, özellikle de SIMD komutlarını kullanmaktır. Bu “Tek Komut, Çoklu Veri” fonksiyonları, birden fazla veri kaydı üzerinde aynı anda tek bir işlemin (karşılaştırma veya çarpma gibi) gerçekleştirilmesine izin verir. Veriler paralel olarak işlendiğinden, uygulamalara performans artışı sağlar. Impala’nın text parse (Metin Ayrıştırma) performansını artırmak için bu özel fonksiyonları kullanır.
Text parse, muhtemelen Impala’da en sık kullanılan fonksiyonlardan biridir. Bir csv dosyasındaki her satırın farklı sütunlara bölünmesi gerekir, ilgili sütunlar virgül gibi belirli bir karakterle ayrılırlar. Parser’ın her separatörün (ayırma işlemi için kullanılan karakter) konumunu bulması gerekir.
SIMD komutlarından biri, birkaç karakterin paralel olarak karşılaştırılmasına izin verir. Ortaya çıkan bit mask, her input karakteri için: karakterler eşleşiyorsa “1” ve karakterler farklıysa “0” içerir, yani true veya false durumunu belirtir.
SIMD komutları, işlem başına birkaç clock cycle kazanır. Bu çok önemli değil giib görünüyorsa da bir milyar veya daha fazla satırda tekrarlandığını hesaba katarsak, oldukça önemli bir performans kazanımı sağlayacağını rahatlıkla söyleyebiliriz.
Code Generation (Kod Oluşturma)
SQL engine’ler interpreter olarak çalışmaktadır. Bunun en önemli sorguda kullanılan sütunun veri tipini bilmediği gibi matematiksel işlemlerin ilgili sütun üzerindeki etkisini de bilmemektedir. Bu yüzden veriler üzerinde hareket ederken engine’ler büyük switch’ler kullanmaktadır.
Bu switch’ler modern CPU mimarisi için optimal değildirler; fakat dallanma çok fazla değilse maliyeti çok fazla değildir. Bu sebeple sorgu üzerindeki derin dallanmalar yapacak işlemler gerçekleştiğinde sıkıntılar oluşması muhtemeldir.
Bu sorunlarla baş edebilmek için Impala, çalışma zamanında (on the fly) JIT (Just In Time) compiler kullanarak code generation yapar. Üretilen kod, el ile yazılmış Assembly kodu kadar efektiftir.
LLVM: Optimizasyon İçin Open Source Compiler Toolkit
Impala’nın birçok optimizasyonu, LLVM (Low Level Virtual Machine) adı verilen open source (açık kaynak) bir compiler (derleyici) toolkit sayesinde mümkün olmaktadır. Query Executer, run time (çalışma zamanı) sırasında kod oluşturmak ve optimize etmek için LLVM component’lerini kullanır. Oluşturulan kod, Query Executer’ın çalıştığı node’un spesifik lokal donanımı için özel olarak tasarlanmıştır. LLVM toolkit’in merkezi, LLVM IR adı verilen assembler benzeri dildir. LLVM IR, Java byte koduna çok benzeyen, low level (alt seviye) komut setidir.
Resimde görüleceği gibi LLVM şunları içerir:
- Frontend, giren kodu parse edip LLVM IR’ye dönüştürür.
- LLVM Optimizer, LLVM IR’yi okur, kodu geliştirir ve LLVM IR olarak yazar.
- Backend, spesifik ortam için uygun machine code’u (Makine Kodu) üretir.
Query Executer, bir plan fragment’ini aldığında, örneğin SUM fonksiyonu çalıştırılan bir fragment olsun, bunun için anında (on the fly) LLVM IR kodunu oluşturacaktır. Bu IR kodu, daha sonra üzerinde koşacağı spesifik donanım için uygun olacak şeklinde optimize edilir ve derlenir.
LLVM JIT, Java JIT ile birçok avantajını paylaşır. Örneğin fonksiyon çağrılarının silinmesi, arama ağacının budanması (dalların kaldırılması) gibi. Data alignment ve simd komutları kullanımı da yine LLVM sayesinde mümkün olmuştur.