33 KiB
machine_translated | machine_translated_rev | toc_priority | toc_title |
---|---|---|---|
true | e8cd92bba3 |
62 | ClickHouse mimarisine genel bakış |
ClickHouse mimarisine genel bakış
ClickHouse gerçek bir sütun yönelimli DBMS olduğunu. Veriler sütunlar tarafından ve dizilerin yürütülmesi sırasında (vektörler veya sütun parçaları) saklanır. Mümkün olduğunda, işlemler tek tek değerler yerine dizilere gönderilir. Buna denir “vectorized query execution,” ve gerçek veri işleme maliyetini düşürmeye yardımcı olur.
Bu fikir yeni bir şey değil. Bu kadar uzanır
APL
programlama dili ve Tor andunları:A +
,J
,K
, veQ
. Dizi programlama bilimsel veri işlemede kullanılır. Bu fikir ilişkisel veritabanlarında yeni bir şey değildir: örneğin,Vectorwise
sistem.
Sorgu işlemeyi hızlandırmak için iki farklı yaklaşım vardır: vektörize sorgu yürütme ve çalışma zamanı kodu oluşturma. İkincisi, tüm Yönlendirme ve dinamik gönderimi kaldırır. Bu yaklaşımların hiçbiri diğerinden kesinlikle daha iyi değildir. Çalışma zamanı kodu üretimi, birçok işlemi birleştirdiğinde daha iyi olabilir, böylece CPU yürütme birimlerini ve boru hattını tam olarak kullanır. Vectorized sorgu yürütme daha az pratik olabilir, çünkü önbelleğe yazılması ve geri okunması gereken geçici vektörler içerir. Geçici veri L2 önbelleğinde uymuyorsa, bu bir sorun haline gelir. Ancak vektörize sorgu yürütme, CPU'nun SIMD yeteneklerini daha kolay kullanır. Bir araştırma öd paperevi arkadaşlarımız tarafından yazıldı, her iki yaklaşımı birleştirmenin daha iyi olduğunu gösteriyor. ClickHouse vectorized sorgu yürütme kullanır ve çalışma zamanı kodu üretimi için başlangıç desteği sınırlıdır.
Sütun
IColumn
arabirim, bellekteki sütunları temsil etmek için kullanılır (aslında, sütunların parçaları). Bu arayüz, çeşitli ilişkisel operatörlerin uygulanması için yardımcı yöntemler sağlar. Hemen hemen tüm işlemler değişmez: orijinal sütunu değiştirmezler, ancak yeni bir değiştirilmiş bir tane oluştururlar. Örneğin, IColumn :: filter
yöntem bir filtre bayt maskesi kabul eder. Bu için kullanılır WHERE
ve HAVING
ilişkisel operatörler. Ek örnekler: IColumn :: permute
desteklemek için yöntem ORDER BY
, bu IColumn :: cut
desteklemek için yöntem LIMIT
.
Çeşitli IColumn
uygulanışlar (ColumnUInt8
, ColumnString
ve benzeri) sütunların bellek düzeninden sorumludur. Bellek düzeni genellikle bitişik bir dizidir. Sütunların tamsayı türü için, sadece bir bitişik dizidir, örneğin std :: vector
. İçin String
ve Array
sütunlar, iki vektördür: biri bitişik olarak yerleştirilmiş tüm dizi elemanları için ve her dizinin başlangıcına ait ofsetler için ikinci bir tane. Ayrıca var ColumnConst
bu bellekte sadece bir değer depolar, ancak bir sütuna benziyor.
Alan
Bununla birlikte, bireysel değerlerle de çalışmak mümkündür. Bireysel bir değeri temsil etmek için, Field
kullanılır. Field
sadece ayrımcılığa uğramış bir birlik mi UInt64
, Int64
, Float64
, String
ve Array
. IColumn
has the operator[]
n - inci değerini bir olarak alma yöntemi Field
ve... insert
bir ekleme yöntemi Field
bir sütunun sonuna. Bu yöntemler çok verimli değildir, çünkü geçici olarak uğraşmayı gerektirirler Field
tek bir değeri temsil eden nesneler. Daha etkili yöntemleri vardır, mesela: insertFrom
, insertRangeFrom
ve bu yüzden.
Field
bir tablo için belirli bir veri türü hakkında yeterli bilgiye sahip değildir. Mesela, UInt8
, UInt16
, UInt32
, ve UInt64
hepsi olarak temsil edilir UInt64
in a Field
.
Sızdıran Soyutlamalar
IColumn
verilerin ortak ilişkisel dönüşümleri için yöntemler vardır, ancak tüm ihtiyaçları karşılamazlar. Mesela, ColumnUInt64
iki sütunun toplamını hesaplamak için bir yöntem yoktur ve ColumnString
bir alt dize araması çalıştırmak için bir yöntem yok. Bu sayısız rutinleri dışında uygulanmaktadır IColumn
.
Sütunlar üzerinde çeşitli işlevler kullanarak genel, verimli olmayan bir şekilde uygulanabilir IColumn
çıkarma yöntemleri Field
değerleri veya belirli bir veri iç bellek düzeni bilgisini kullanarak özel bir şekilde IColumn
uygulanış. Döküm fonksiyonları ile belirli bir IColumn
yazın ve doğrudan iç temsil ile anlaşma. Mesela, ColumnUInt64
has the getData
bir iç diziye başvuru döndüren yöntem, daha sonra ayrı bir yordam okur veya bu diziyi doğrudan doldurur. Sahibiz “leaky abstractions” çeşitli rutinlerin verimli uzmanlıklarına izin vermek.
Veri Türleri
IDataType
seri hale getirme ve serileştirmeden sorumludur: ikili veya metin biçiminde sütunların veya bireysel değerlerin parçalarını okumak ve yazmak için. IDataType
tablolardaki veri türlerine doğrudan karşılık gelir. Örneğin, DataTypeUInt32
, DataTypeDateTime
, DataTypeString
ve böyle devam eder.
IDataType
ve IColumn
sadece gevşek birbirleriyle ilişkilidir. Farklı veri türleri bellekte aynı tarafından temsil edilebilir IColumn
uygulanışlar. Mesela, DataTypeUInt32
ve DataTypeDateTime
her ikisi de tarafından temsil edilir ColumnUInt32
veya ColumnConstUInt32
. Buna ek olarak, aynı veri türü farklı tarafından temsil edilebilir IColumn
uygulanışlar. Mesela, DataTypeUInt8
tarafından temsil edilebilir ColumnUInt8
veya ColumnConstUInt8
.
IDataType
yalnızca meta verileri depolar. Mesela, DataTypeUInt8
hiçbir şey saklamıyor (vptr hariç) ve DataTypeFixedString
mağazalar sadece N
(sabit boyutlu dizelerin boyutu).
IDataType
çeşitli veri formatları için yardımcı yöntemlere sahiptir. Örnekler, Olası Alıntı ile bir değeri serileştirmek, json için bir değeri serileştirmek ve XML formatının bir parçası olarak bir değeri serileştirmek için kullanılan yöntemlerdir. Veri formatlarına doğrudan yazışma yoktur. Örneğin, farklı veri biçimleri Pretty
ve TabSeparated
aynı kullanabilirsiniz serializeTextEscaped
hel methodper yöntemi IDataType
Arabirim.
Blok
A Block
bellekteki bir tablonun bir alt kümesini (yığın) temsil eden bir kapsayıcıdır. Bu sadece üçlü bir dizi: (IColumn, IDataType, column name)
. Sorgu yürütme sırasında veri tarafından işlenir Block
s. Eğer bir Block
(bu yaptığımız verileri IColumn
nesne), biz onun türü hakkında bilgi var (içinde IDataType
) bu bize bu sütunla nasıl başa çıkacağımızı söyler ve sütun adına sahibiz. Tablodan orijinal sütun adı veya hesaplamaların geçici sonuçlarını almak için atanan bazı yapay ad olabilir.
Bir bloktaki sütunlar üzerinde bazı işlevleri hesapladığımızda, bloğa sonucu olan başka bir sütun ekleriz ve işlemler değişmez olduğu için işlevin argümanları için sütunlara dokunmayız. Daha sonra, gereksiz sütunlar bloktan kaldırılabilir, ancak değiştirilemez. Ortak alt ifadelerin ortadan kaldırılması için uygundur.
İşlenen her veri yığını için bloklar oluşturulur. Aynı hesaplama türü için, sütun adları ve türleri farklı bloklar için aynı kalır ve yalnızca sütun verileri değişir unutmayın. Küçük blok boyutları shared_ptrs ve sütun adlarını kopyalamak için geçici dizeleri yüksek bir ek yükü olduğundan blok üstbilgisinden blok verileri bölmek daha iyidir.
Blok Akışları
Blok akışları veri işleme içindir. Bir yerden veri okumak, veri dönüşümleri gerçekleştirmek veya bir yere veri yazmak için blok akışları kullanıyoruz. IBlockInputStream
has the read
mevcut iken bir sonraki bloğu getirme yöntemi. IBlockOutputStream
has the write
bloğu bir yere itmek için yöntem.
Akar responsibles areular sorumludur:
- Bir tabloya okuma veya yazma. Tablo sadece okuma veya yazma blokları için bir akış döndürür.
- Veri formatlarının uygulanması. Örneğin, bir terminale veri çıkışı yapmak istiyorsanız
Pretty
biçim, blokları ittiğiniz bir blok çıkış akışı oluşturursunuz ve bunları biçimlendirir. - Veri dönüşümleri gerçekleştirme. Diyelim ki var
IBlockInputStream
ve filtrelenmiş bir akış oluşturmak istiyorum. Yarat createtığınızFilterBlockInputStream
ve akışı ile başlatın. Sonra bir blok çektiğinizdeFilterBlockInputStream
, akışınızdan bir blok çeker, filtreler ve filtrelenmiş bloğu size döndürür. Sorgu yürütme boru hatları bu şekilde temsil edilir.
Daha sofistike dönüşümler var. Örneğin, çektiğiniz zaman AggregatingBlockInputStream
, kaynağındaki tüm verileri okur, toplar ve sizin için toplanmış bir veri akışı döndürür. Başka bir örnek: UnionBlockInputStream
yapıcıdaki birçok giriş kaynağını ve ayrıca bir dizi iş parçacığını kabul eder. Birden çok iş parçacığı başlatır ve paralel olarak birden fazla kaynaktan okur.
Blok akışları “pull” akışı kontrol etme yaklaşımı: ilk akıştan bir blok çektiğinizde, gerekli blokları iç içe geçmiş akışlardan çeker ve tüm yürütme boru hattı çalışır. Ne “pull” ne “push” en iyi çözümdür, çünkü kontrol akışı örtükdür ve bu, birden fazla sorgunun eşzamanlı yürütülmesi (birçok boru hattının birlikte birleştirilmesi) gibi çeşitli özelliklerin uygulanmasını sınırlar. Bu sınırlama, coroutines ile veya sadece birbirlerini bekleyen ekstra iş parçacıkları çalıştırarak aşılabilir. Kontrol akışını açık hale getirirsek daha fazla olasılığa sahip olabiliriz: verileri bir hesaplama biriminden diğerine bu hesaplama birimlerinin dışında geçirme mantığını bulursak. Re thisad this makale daha fazla düşünce için.
Sorgu yürütme boru hattının her adımda geçici veri oluşturduğuna dikkat etmeliyiz. Blok boyutunu yeterince küçük tutmaya çalışıyoruz, böylece geçici veriler CPU önbelleğine sığıyor. Bu varsayımla, geçici veri yazmak ve okumak, diğer hesaplamalarla karşılaştırıldığında neredeyse ücretsizdir. Boru hattındaki birçok operasyonu bir araya getirmek için bir alternatif düşünebiliriz. Boru hattını mümkün olduğunca kısa hale getirebilir ve geçici verilerin çoğunu kaldırabilir, bu da bir avantaj olabilir, ancak dezavantajları da vardır. Örneğin, bölünmüş bir boru hattı, Ara verileri önbelleğe almayı, aynı anda çalışan benzer sorgulardan Ara verileri çalmayı ve benzer sorgular için boru hatlarını birleştirmeyi kolaylaştırır.
Biçimliler
Veri formatları blok akışları ile uygulanır. Var “presentational” sadece müşteriye veri çıkışı için uygun biçimler, örneğin Pretty
sadece sağlayan biçim IBlockOutputStream
. Ve gibi giriş / çıkış biçimleri vardır TabSeparated
veya JSONEachRow
.
Satır akışları da vardır: IRowInputStream
ve IRowOutputStream
. Verileri bloklarla değil, tek tek satırlarla çekmenize/itmenize izin verirler. Ve sadece satır yönelimli formatların uygulanmasını basitleştirmek için gereklidir. Sarıcı BlockInputStreamFromRowInputStream
ve BlockOutputStreamFromRowOutputStream
satır yönelimli akışları normal blok yönelimli akışlara dönüştürmenize izin verin.
I/O
Bayt yönelimli giriş / çıkış için, ReadBuffer
ve WriteBuffer
soyut sınıflar. C++yerine kullanılırlar iostream
s. merak etmeyin: her olgun C++ projesi başka bir şey kullanıyor iostream
s iyi nedenlerden dolayı.
ReadBuffer
ve WriteBuffer
sadece bitişik bir tampon ve bu tampondaki konuma işaret eden bir imleç. Uygulamalar, arabellek belleğine sahip olabilir veya sahip olmayabilir. Arabelleği aşağıdaki verilerle doldurmak için sanal bir yöntem vardır (for ReadBuffer
) veya tamponu bir yere yıkamak için (için WriteBuffer
). Sanal yöntemler nadiren denir.
Uygulamaları ReadBuffer
/WriteBuffer
sıkıştırma uygulamak için dosyalar ve dosya tanımlayıcıları ve ağ soketleri ile çalışmak için kullanılır (CompressedWriteBuffer
is initialized with another WriteBuffer and performs compression before writing data to it), and for other purposes – the names ConcatReadBuffer
, LimitReadBuffer
, ve HashingWriteBuffer
kendileri için konuşuyoruz.
Read / WriteBuffers sadece baytlarla ilgilenir. Fonksiyonları vardır ReadHelpers
ve WriteHelpers
başlık dosyaları biçimlendirme giriş/çıkış ile yardımcı olmak için. Örneğin, ondalık biçimde bir sayı yazmak için yardımcılar vardır.
Bir sonuç kümesi yazmak istediğinizde neler olduğuna bakalım JSON
stdout için biçimlendirin. Eğer bir sonuç getirilecek hazır set var IBlockInputStream
. Yarat createtığınız WriteBufferFromFileDescriptor(STDOUT_FILENO)
STDOUT için bayt yazmak için. Yarat createtığınız JSONRowOutputStream
bununla başlatıldı WriteBuffer
, satır yazmak için JSON
stdout. Yarat createtığınız BlockOutputStreamFromRowOutputStream
bu da yetmiyormuş gibi göstermek için IBlockOutputStream
. Sonra Ara copyData
veri aktarmak için IBlockInputStream
-e doğru IBlockOutputStream
ve her şey çalışıyor. İçten, JSONRowOutputStream
çeşitli json sınırlayıcıları yazacak ve IDataType::serializeTextJSON
bir referans ile yöntem IColumn
ve satır numarası argüman olarak. Sonuç olarak, IDataType::serializeTextJSON
bir yöntem çağırır WriteHelpers.h
: mesela, writeText
sayısal türler ve writeJSONString
için DataTypeString
.
Tablolar
Bu IStorage
arayüz tabloları temsil eder. Bu arayüzün farklı uygulamaları farklı tablo motorlarıdır. Örnekler şunlardır StorageMergeTree
, StorageMemory
ve bu yüzden. Bu sınıfların örnekleri sadece tablolardır.
Anahtar IStorage
yöntemler şunlardır read
ve write
. Ayrıca vardır alter
, rename
, drop
ve bu yüzden. Bu read
yöntem aşağıdaki bağımsız değişkenleri kabul eder: bir tablodan okunacak sütun kümesi, AST
dikkate alınması gereken sorgu ve döndürülmesi gereken akış sayısı. Bir veya birden fazla döndürür IBlockInputStream
nesneler ve sorgu yürütme sırasında bir tablo altyapısı içinde tamamlanan veri işleme aşaması hakkında bilgi.
Çoğu durumda, read yöntemi yalnızca belirtilen sütunları bir tablodan okumaktan sorumludur, daha fazla veri işleme için değil. Tüm diğer veri işleme sorgu yorumlayıcısı tarafından yapılır ve sorumluluk dışında IStorage
.
Ancak önemli istisnalar var:
- AST sorgusu için geçirilir
read
yöntemi ve tablo altyapısı dizin kullanımını türetmek ve bir tablodan daha az veri okumak için kullanabilirsiniz. - Bazen tablo motoru verileri belirli bir aşamaya kadar işleyebilir. Mesela,
StorageDistributed
uzak sunuculara sorgu gönderebilir, farklı uzak sunuculardan gelen verilerin birleştirilebileceği bir aşamaya veri işlemelerini isteyebilir ve bu önceden işlenmiş verileri döndürebilir. Sorgu yorumlayıcısı daha sonra verileri işlemeyi tamamlar.
Tablo read
yöntem birden çok döndürebilir IBlockInputStream
nesneleri paralel veri işleme izin vermek için. Bu çoklu blok giriş akışları bir tablodan paralel olarak okuyabilir. Ardından, bu akışları bağımsız olarak hesaplanabilen çeşitli dönüşümlerle (ifade değerlendirme veya filtreleme gibi) sarabilir ve bir UnionBlockInputStream
bunların üzerine, paralel olarak birden fazla akıştan okumak için.
Ayrıca vardır TableFunction
s. bunlar geçici olarak dönen işlevlerdir IStorage
içinde kullanılacak nesne FROM
bir sorgu yan tümcesi.
Tablo motorunuzu nasıl uygulayacağınıza dair hızlı bir fikir edinmek için, basit bir şeye bakın StorageMemory
veya StorageTinyLog
.
Bu sonucu
read
yöntem,IStorage
dönüşlerQueryProcessingStage
– information about what parts of the query were already calculated inside storage.
Ayrıştırıcılar
Elle yazılmış özyinelemeli iniş ayrıştırıcı bir sorgu ayrıştırır. Mesela, ParserSelectQuery
sorgunun çeşitli bölümleri için temel ayrıştırıcıları yinelemeli olarak çağırır. Ayrıştırıcılar bir AST
. Bu AST
örnekleri olan düğüm bylerle temsil edilir IAST
.
Ayrıştırıcı jeneratörler tarihsel nedenlerle kullanılmaz.
Tercümanlar
Sorgu yürütme kanalının oluşturulmasından tercümanlar sorumludur. AST
. Gibi basit tercümanlar vardır InterpreterExistsQuery
ve InterpreterDropQuery
veya daha sofistike InterpreterSelectQuery
. Sorgu yürütme boru hattı, blok giriş veya çıkış akışlarının birleşimidir. Örneğin, yorumlama sonucu SELECT
sorgu olduğunu IBlockInputStream
sonuç kümesini okumak için; INSERT sorgusunun sonucu IBlockOutputStream
ekleme için veri yazmak ve yorumlama sonucu INSERT SELECT
sorgu olduğunu IBlockInputStream
bu, ilk okumada boş bir sonuç kümesi döndürür, ancak verileri kopyalar SELECT
-e doğru INSERT
aynı zamanda.
InterpreterSelectQuery
kullanma ExpressionAnalyzer
ve ExpressionActions
sorgu analizi ve dönüşümler için makine. Bu, kural tabanlı sorgu iyileştirmelerinin çoğunun yapıldığı yerdir. ExpressionAnalyzer
oldukça dağınık ve yeniden yazılmalıdır: modüler dönüşümlere veya sorguya izin vermek için ayrı sınıflara çeşitli sorgu dönüşümleri ve optimizasyonlar çıkarılmalıdır.
İşlevler
Sıradan fonksiyonlar ve toplam fonksiyonlar vardır. Toplama işlevleri için bir sonraki bölüme bakın.
Ordinary functions don't change the number of rows – they work as if they are processing each row independently. In fact, functions are not called for individual rows, but for Block
's vectorized sorgu yürütme uygulamak için veri.
Gibi bazı çeşitli fonksiyonlar vardır blockSize, rowNumberİnBlock, ve runningAccumulate, blok işlemeyi istismar eden ve satırların bağımsızlığını ihlal eden.
Clickhouse'un güçlü yazımı var, bu yüzden örtük tür dönüşümü yok. Bir işlev belirli bir tür kombinasyonunu desteklemiyorsa, bir istisna atar. Ancak, birçok farklı tür kombinasyonu için işlevler çalışabilir (aşırı yüklenebilir). Örneğin, plus
fonksiyonu (uygulamak için +
operatör) sayısal türlerin herhangi bir kombinasyonu için çalışır: UInt8
+ Float32
, UInt16
+ Int8
ve bu yüzden. Ayrıca, bazı variadic işlevleri gibi bağımsız değişkenlerin herhangi bir sayıda kabul edebilir concat
işlev.
Bir işlev açıkça desteklenen veri türlerini gönderir ve desteklenen çünkü bir işlev uygulamak biraz rahatsız edici olabilir IColumns
. Örneğin, plus
işlev, sayısal türlerin ve sabit veya sabit olmayan sol ve sağ bağımsız değişkenlerin her birleşimi için bir C++ şablonunun örneklendirilmesiyle oluşturulan koda sahiptir.
Bu şablon kodu kabartmak önlemek için çalışma zamanı kodu nesil uygulamak için mükemmel bir yerdir. Ayrıca, kaynaşmış çarpma-ekleme gibi kaynaşmış işlevler eklemeyi veya bir döngü yinelemesinde birden fazla karşılaştırma yapmayı mümkün kılar.
Vektörize sorgu yürütme nedeniyle, işlevler kısa devre değildir. Örneğin, yazarsanız WHERE f(x) AND g(y)
, her iki taraf da satırlar için bile hesaplanır f(x)
sıfırdır (hariç f(x)
sıfır sabit bir ifadedir). Ama eğer seçicilik f(x)
durum yüksek ve hesaplama f(x)
çok daha ucuzdur g(y)
, çok geçişli hesaplama uygulamak daha iyidir. İlk önce hesaplayacaktı f(x)
, daha sonra sonucu sütunları süzün ve sonra hesaplayın g(y)
sadece daha küçük, filtrelenmiş veri parçaları için.
Toplama Fonksiyonları
Toplama işlevleri durumsal işlevlerdir. Geçirilen değerleri bir duruma biriktirir ve bu durumdan sonuç almanıza izin verir. İle Yönet theyil theirler. IAggregateFunction
Arabirim. Devletler oldukça basit olabilir (devlet AggregateFunctionCount
sadece bir tek UInt64
değeri) veya oldukça karmaşık (devlet AggregateFunctionUniqCombined
doğrusal bir dizi, bir karma tablo ve bir kombinasyonudur HyperLogLog
olasılıksal veri yapısı).
Devletler tahsis edilir Arena
(bir bellek havuzu) yüksek önemlilik yürütürken birden çok durumla başa çıkmak için GROUP BY
sorgu. Devletler önemsiz olmayan bir yapıcı ve yıkıcı olabilir: örneğin, karmaşık toplama durumları ek belleği kendileri tahsis edebilir. Devletlerin yaratılmasına ve yok edilmesine ve mülkiyet ve yıkım düzeninin doğru bir şekilde geçmesine biraz dikkat gerektirir.
Toplama durumları serileştirilmiş ve dağıtılmış sorgu yürütme sırasında ağ üzerinden geçmek veya bunları yeterli RAM olmadığı diskte yazmak için serileştirilmiş. Hatta bir tablo ile saklanabilir DataTypeAggregateFunction
verilerin artımlı toplanmasına izin vermek için.
Toplu işlev durumları için seri hale getirilmiş veri biçimi şu anda sürümlendirilmemiş. Toplama durumları yalnızca geçici olarak saklanırsa sorun olmaz. Ama biz var
AggregatingMergeTree
artan toplama için tablo motoru ve insanlar zaten üretimde kullanıyor. Gelecekte herhangi bir toplama işlevi için seri hale getirilmiş biçimi değiştirirken geriye dönük uyumluluğun gerekli olmasının nedeni budur.
Hizmetçi
Sunucu birkaç farklı arayüz uygular:
- Herhangi bir yabancı istemciler için bir HTTP arabirimi.
- Dağıtılmış sorgu yürütme sırasında yerel ClickHouse istemcisi ve sunucular arası iletişim için bir TCP arabirimi.
- Çoğaltma için veri aktarımı için bir arabirim.
DAHİLİ olarak, coroutines veya lifler olmadan sadece ilkel bir çok iş parçacıklı sunucudur. Sunucu, yüksek oranda basit sorguları işlemek için değil, nispeten düşük oranda karmaşık sorguları işlemek için tasarlandığından, her biri analitik için çok miktarda veri işleyebilir.
Sunucu başlatır Context
sorgu yürütme için gerekli ortama sahip sınıf: kullanılabilir veritabanlarının, kullanıcıların ve erişim haklarının, ayarların, kümelerin, işlem listesinin, sorgu günlüğünün vb. listesi. Tercümanlar bu ortamı kullanır.
Sunucu TCP protokolü için tam geriye ve ileriye dönük uyumluluk sağlıyoruz: eski istemciler yeni sunucularla konuşabilir ve yeni istemciler eski sunucularla konuşabilir. Ancak sonsuza dek korumak istemiyoruz ve yaklaşık bir yıl sonra eski sürümler için destek kaldırıyoruz.
!!! note "Not" Çoğu harici uygulama için, HTTP arayüzünü kullanmanızı öneririz, çünkü basit ve kullanımı kolaydır. TCP protokolü, iç veri yapılarına daha sıkı bir şekilde bağlanır: veri bloklarını geçirmek için bir iç biçim kullanır ve sıkıştırılmış veriler için özel çerçeveleme kullanır. Bu protokol için bir C kütüphanesi yayınlamadık çünkü pratik olmayan ClickHouse kod tabanının çoğunu bağlamayı gerektiriyor.
Dağıtılmış Sorgu Yürütme
Bir küme kurulumundaki sunucular çoğunlukla bağımsızdır. Sen-ebilmek yaratmak a Distributed
bir kümedeki bir veya tüm sunucularda tablo. Bu Distributed
table does not store data itself – it only provides a “view” Bir kümenin birden çok düğümündeki tüm yerel tablolara. Bir seçtiğinizde Distributed
tablo, bu sorguyu yeniden yazar, Yük Dengeleme ayarlarına göre uzak düğümleri seçer ve sorguyu onlara gönderir. Bu Distributed
tablo, farklı sunuculardan gelen Ara sonuçların birleştirilebileceği bir aşamaya kadar bir sorguyu işlemek için uzak sunuculardan ister. Sonra Ara sonuçları alır ve onları birleştirir. Dağıtılmış tablo, uzak sunuculara mümkün olduğunca fazla çalışma dağıtmaya çalışır ve ağ üzerinden çok fazla ara veri göndermez.
In veya JOIN yan tümcelerinde alt sorgular olduğunda işler daha karmaşık hale gelir ve her biri bir Distributed
Tablo. Bu sorguların yürütülmesi için farklı stratejilerimiz var.
Dağıtılmış sorgu yürütme için genel bir sorgu planı yoktur. Her düğüm, işin kendi kısmı için yerel sorgu planına sahiptir. Biz sadece basit tek geçişli dağıtılmış sorgu yürütme var: biz uzak düğümler için sorgular göndermek ve sonra sonuçları birleştirmek. Ancak bu, yüksek önemlilik grubu BYs ile veya JOIN için büyük miktarda geçici veri içeren karmaşık sorgular için mümkün değildir. Bu gibi durumlarda, gerek “reshuffle” ek koordinasyon gerektiren sunucular arasındaki veriler. ClickHouse bu tür bir sorgu yürütülmesini desteklemiyor ve üzerinde çalışmamız gerekiyor.
Ağaç Birleştirme
MergeTree
birincil anahtarla dizin oluşturmayı destekleyen bir depolama altyapısı ailesidir. Birincil anahtar, isteğe bağlı bir sütun veya ifade kümesi olabilir. Veri MergeTree
tablo saklanır “parts”. Her bölüm verileri birincil anahtar sırasına göre saklar, böylece veriler birincil anahtar tuple tarafından lexicographically sıralanır. Tüm tablo sütunları ayrı olarak saklanır column.bin
bu kısımlardaki dosyalar. Dosyalar sıkıştırılmış bloklardan oluşur. Her blok, ortalama değer boyutuna bağlı olarak genellikle 64 KB ila 1 MB sıkıştırılmamış veridir. Bloklar, birbiri ardına bitişik olarak yerleştirilmiş sütun değerlerinden oluşur. Sütun değerleri her sütun için aynı sıradadır (birincil anahtar siparişi tanımlar), bu nedenle birçok sütun tarafından yineleme yaptığınızda, karşılık gelen satırlar için değerler alırsınız.
Birincil anahtarın kendisi “sparse”. Her satır Adres yok ama verilerin sadece biraz değişir. Ayıran primary.idx
dosya, n'nin çağrıldığı her N-inci satır için birincil anahtarın değerine sahiptir index_granularity
(genellikle, n = 8192). Ayrıca, her sütun için, biz var column.mrk
dosyaları ile “marks,” veri dosyasındaki her N-inci satıra ofset olan. Her işaret bir çifttir: dosyadaki ofset sıkıştırılmış bloğun başlangıcına ve sıkıştırılmış bloktaki ofset verilerin başlangıcına. Genellikle, sıkıştırılmış bloklar işaretlerle hizalanır ve sıkıştırılmış bloktaki ofset sıfırdır. İçin veri primary.idx
her zaman bellekte bulunur ve veri column.mrk
dosyalar önbelleğe alınır.
Bir kısm aından bir şey okuy readacağımız zaman MergeTree
bak biz primary.idx
veri ve istenen verileri içerebilecek aralıkları bulun, ardından column.mrk
veri ve bu aralıkları okumaya başlamak için nerede için uzaklıklar hesaplayın. Çünkü seyrek, fazla veri okunabilir. ClickHouse, basit nokta sorgularının yüksek bir yükü için uygun değildir, çünkü tüm Aralık index_granularity
her anahtar için satırlar okunmalı ve her sütun için sıkıştırılmış bloğun tamamı sıkıştırılmalıdır. Dizin için fark edilebilir bellek tüketimi olmadan tek bir sunucu başına trilyonlarca satır tutabilmemiz gerektiğinden dizini seyrek yaptık. Ayrıca, birincil anahtar seyrek olduğundan, benzersiz değildir: ekleme zamanında tablodaki anahtarın varlığını denetleyemez. Bir tabloda aynı anahtara sahip birçok satır olabilir.
Ne zaman sen INSERT
içine veri bir demet MergeTree
, bu grup birincil anahtar sırasına göre sıralanır ve yeni bir bölüm oluşturur. Bazı parçaları periyodik olarak seçen ve parça sayısını nispeten düşük tutmak için bunları tek bir sıralanmış parçaya birleştiren arka plan iş parçacıkları vardır. Bu yüzden denir MergeTree
. Tabii ki, birleştirme yol açar “write amplification”. Tüm parçalar değişmez: sadece oluşturulur ve silinir, ancak değiştirilmez. SELECT yürütüldüğünde, tablonun bir anlık görüntüsünü (bir parça kümesi) tutar. Birleştirildikten sonra, arızadan sonra iyileşmeyi kolaylaştırmak için eski parçaları bir süre tutuyoruz, bu nedenle birleştirilmiş bir parçanın muhtemelen kırıldığını görürsek, kaynak parçalarıyla değiştirebiliriz.
MergeTree
içermediği için bir lsm ağacı değildir “memtable” ve “log”: inserted data is written directly to the filesystem. This makes it suitable only to INSERT data in batches, not by individual row and not very frequently – about once per second is ok, but a thousand times a second is not. We did it this way for simplicity's sake, and because we are already inserting data in batches in our applications.
MergeTree tabloları yalnızca bir (birincil) dizine sahip olabilir: herhangi bir ikincil dizin yoktur. Bir mantıksal tablo altında birden fazla fiziksel gösterime izin vermek, örneğin verileri birden fazla fiziksel sırayla depolamak veya hatta orijinal verilerle birlikte önceden toplanmış verilerle gösterimlere izin vermek güzel olurdu.
Arka plan birleştirmeleri sırasında ek iş yapan MergeTree motorları vardır. Örnekler şunlardır CollapsingMergeTree
ve AggregatingMergeTree
. Bu, güncellemeler için özel destek olarak kabul edilebilir. Kullanıcıların genellikle arka plan birleştirmeleri yürütüldüğünde zaman üzerinde hiçbir kontrole sahip çünkü bu gerçek güncellemeler olmadığını unutmayın, ve bir veri MergeTree
tablo hemen hemen her zaman tamamen birleştirilmiş formda değil, birden fazla bölümde saklanır.
Çoğalma
Clickhouse çoğaltma başına tablo bazında yapılandırılabilir. Aynı sunucuda bazı çoğaltılmış ve bazı çoğaltılmamış tablolar olabilir. Ayrıca, iki faktörlü çoğaltmaya sahip bir tablo ve üç faktörlü bir tablo gibi farklı şekillerde çoğaltılmış tablolar da olabilir.
Çoğaltma uygulanır ReplicatedMergeTree
depolama motoru. The path in ZooKeeper
depolama altyapısı için bir parametre olarak belirtilir. Aynı yolu olan tüm tablolar ZooKeeper
birbirlerinin kopyaları haline gelir: verilerini senkronize eder ve tutarlılığı korurlar. Yinelemeler, bir tablo oluşturarak veya bırakarak dinamik olarak eklenebilir ve kaldırılabilir.
Çoğaltma, zaman uyumsuz bir çoklu ana düzeni kullanır. Bir oturum olan herhangi bir yinelemeye veri ekleyebilirsiniz. ZooKeeper
ve veriler diğer tüm yinelemelere zaman uyumsuz olarak çoğaltılır. ClickHouse güncelleştirmeleri desteklemediğinden, çoğaltma çakışmaz. Eklerin çekirdek onayı olmadığından, bir düğüm başarısız olursa, yalnızca eklenen veriler kaybolabilir.
Çoğaltma için meta veri zookeeper saklanır. Hangi eylemlerin yapılacağını listeleyen bir çoğaltma günlüğü vardır. Eylemler şunlardır: parça al; parçaları Birleştir; bir bölüm bırak vb. Her çoğaltma, çoğaltma günlüğünü kendi kuyruğuna kopyalar ve sonra da sıradaki eylemleri yürütür. Örneğin, ekleme, “get the part” eylem günlüğüne oluşturulur ve her çoğaltma bu bölümü indirir. Birleştirmeler, baytla aynı sonuçları elde etmek için yinelemeler arasında koordine edilir. Tüm parçalar tüm kopyalarda aynı şekilde birleştirilir. Bir kopyayı lider olarak seçerek elde edilir ve bu çoğaltma birleştirir ve yazar “merge parts” günlük eylemler.
Çoğaltma fiziksel: yalnızca sıkıştırılmış parçalar sorgular değil düğümler arasında aktarılır. Birleştirmeler, çoğu durumda ağ amplifikasyonundan kaçınarak ağ maliyetlerini düşürmek için her yinelemede bağımsız olarak işlenir. Büyük birleştirilmiş parçalar, yalnızca önemli çoğaltma gecikmesi durumunda ağ üzerinden gönderilir.
Ayrıca, her bir kopya, ZooKeeper içindeki durumunu parça seti ve sağlama toplamı olarak depolar. Yerel dosya sistemindeki durum ZooKeeper referans durumundan ayrıldığında, kopya diğer kopyalardan eksik ve bozuk parçaları indirerek tutarlılığını geri yükler. Yerel dosya sisteminde beklenmeyen veya bozuk bazı veriler olduğunda, ClickHouse onu kaldırmaz, ancak ayrı bir dizine taşır ve unutur.
!!! note "Not" ClickHouse kümesi bağımsız parçalardan oluşur ve her parça kopyalardan oluşur. Küme elastik değil, böylece yeni bir parça ekledikten sonra, veriler otomatik olarak kırıklar arasında yeniden dengelenmez. Bunun yerine, küme yükünün eşit olmayan şekilde ayarlanması gerekiyor. Bu uygulama size daha fazla kontrol sağlar ve onlarca düğüm gibi nispeten küçük kümeler için uygundur. Ancak üretimde kullandığımız yüzlerce düğüm içeren kümeler için bu yaklaşım önemli bir dezavantaj haline gelir. Kümeler arasında otomatik olarak bölünebilen ve dengelenebilen dinamik olarak çoğaltılmış bölgelerle kümeye yayılan bir tablo altyapısı uygulamalıyız.
{## Orijinal makale ##}