QuestDB
QuestDB, timeseries ve event verileri için tasarlanmış ilişkisel sütun tabanlı bir veritabanıdır. Gerçek zamanlı analitiğe yardımcı olmak için timeseries için uzantılarla birlikte SQL kullanır.
Storage model
QuestDB, sütun tabanlı bir depolama modeli kullanır. Veriler, her sütun kendi dosyasında ve kendi yerel biçiminde saklanan tablolarda saklanır. Verilerin alındığı sırayla organik olarak alınmasına olanak sağlamak için her sütunun altına yeni veriler eklenir.
Append model
QuestDB, her seferinde bir sütun ekler ve her biri aynı yöntem kullanılarak güncellenir. Sütun dosyasının kuyruğu, RAM'deki bellek sayfasına eşlenir ve sütun ekleme, etkin bir şekilde bir adreste bellek yazma işlemidir. Memory page tükendiğinde maplenmez ve yeni bir sayfa maplenir.
Bu yöntem, minimum kaynak karmaşası ve tutarlı ekleme gecikmesi sağlar.
Read model
Tablo sütunlarına rastgele erişilebilir. Sabit boyutlu veri türlerine sahip sütunlar, kayıt numarası basit bir bit kaydırmayla bir dosya ofsetine çevrilerek okunur. Sütun dosyasındaki uzaklık, daha sonra, gerekli değerin okunduğu tembel(lazy) mapped bir bellek sayfasındaki bir kaymaya çevrilir.
Consistency and durability
QuestDB, tablo güncellemelerini atomik olarak uygulayarak tablo düzeyinde isolation ve consistency sağlar. Bir tabloya yönelik güncellemeler, atomik bir işlemde commitlenen veya rollback uygulanan bir tablo işlemi bağlamında uygulanır. Tablo güncellemeleriyle eşzamanlı sorgular, verileri tablo işleminin gerçekleştirilmesinden önce veya sonra olduğu gibi döndürmeleri açısından tutarlıdır — sorgu sonucunda hiçbir ara kaydedilmemiş veri gösterilmez.
Atomikliği garanti etmek için, her tablo ayrı bir dosyada bir last_comwed_record_count
tutar. Genel olarak, herhangi bir tablo okuyucu asla işlem sayısından daha fazla kayıt okumaz. Bu, isolation özelliğini etkinleştirir: burada kaydedilmemiş veriler okunamaz. Commitlenmemiş veriler doğrudan tabloya eklendiğinden, işlem boyutu yalnızca kullanılabilir disk alanıyla sınırlıdır.
Tüm veriler eklendikten sonra, QuestDB commit()
işlem sayısının hem multi-threaded hem de multi-process ortamlarda atomik olarak güncellenmesini sağlar. Eşzamanlı okumalar üzerinde minimum etki sağlamak için lock-free’dir.
Depolanan verilerin consistency güvencesi, QuestDB'nin anormal şekilde sonlandırılan işlemleri otomatik olarak onarmasıyla sınırlıdır. Henüz kullanıcı tanımlı kısıtlamalar, kontroller ve tetikleyiciler desteklenmiyor.
Varsayılan olarak QuestDB, işletim sistemi düzeyinde veri dayanıklılığına(durability) güvenir ve işletim sistemini diske dirty pages yazmaya bırakır. Veri dayanıklılığı, isteğe bağlı olarak senkron veya asenkron IO seçeneğiyle msync()'i
çağırabilen commit()
ile de yapılandırılabilir. msync()
çağrıları yalnızca sütun dosyaları için yapılır, dolayısıyla senkronizasyon/eşzamansız tamamlama modları genel dayanıklılığı artırırken, işletim sistemi hataları veya güç kaybı karşısında dayanıklılığı garanti etmez.
Designated timestamp
QuestDB, designated timestamp ****olarak bir sütun seçme seçeneği sunar. Bu, zamana dayalı dil özelliklerinden ve yüksek performanslı işlevselliklerden yararlanmak için tabloların hangi sütun tarafından indexleneceğini belirlemenizi sağlar.
Timestamp(columnName) fonksiyonu kullanılarak designated timestamp seçilir:
CREATE TABLE
işlemi sırasındaSELECT
işlemi sırasında (dynamic timestamp)- ILP yoluyla veri alınırken, QuestDB'de zaten mevcut olmayan tablolar için, partitionlar varsayılan olarak bir timestamp sütunuyla güne göre otomatik olarak uygulanır
SQL extensions
QuestDB, standart ANSI SQL'i uygulamaya çalışır. Veri depolama modelini desteklemek ve timeseries analitiğinin anlamını basitleştirmek için QeustDB’de SQL genişletilmiştir.
LATEST ON
LATEST ON
, bir SELECT
deyiminin parçası olarak belirli bir key veya key kombinasyonu için zaman damgasına göre en son girişi bulmaya yardımcı olmak için sunulan bir yan tümcedir.
SELECT * FROM balances
WHERE balance > 800
LATEST ON ts PARTITION BY customer_id, currency;
SAMPLE BY
SAMPLE BY
, efektif bir sözdizimi ile zamana dayalı aggregationlar için kullanılır. Aşağıdaki kısa sorgu, bir aylık bölümlere göre bir hesap listesinden ortalama bakiyeyi döndürür.
SELECT avg(balance) FROM accounts SAMPLE BY 1M
Timestamp search
Zaman damgası araması, >
, <=
vb. gibi normal işleçlerle gerçekleştirilebilir. Ancak, QuestDB, daha hızlı ve daha az ayrıntılı olan native bir notasyon sağlar.
SELECT * FROM scores WHERE ts IN '2018';
Differences from standard SQL
SELECT * FROM is optional
QuestDB'de SELECT * FROM
kullanmak isteğe bağlıdır, yani SELECT * FROM my_table;
my_table;
ile aynı sonucu döndürür. SELECT * FROM
eklenmesi SQL'in daha eksiksiz görünmesini sağlarken, bu anahtar sözcüklerin çıkarılmasının sorguları okumayı çok daha kolay hale getirdiği örnekler vardır.
my_table;
-- equivalent to:
SELECT * FROM my_table;
GROUP BY is optional
GROUP BY
yan tümcesi isteğe bağlıdır ve QuestDB iyileştiricisi SELECT
yan tümcesinden grup bazında uygulama türettiği için atlanabilir. Standart SQL'de, kullanıcılar aşağıdaki gibi bir sorgu yazabilir:
SELECT a, b, c, d, sum(e) FROM tab GROUP BY a, b, c, d;
Ancak, GROUP BY
yan tümcesinde SELECT
sütunlarının bir alt kümesini gezmek gereksizdir ve bu nedenle gereksizdir. QuestDB SQL-lehçesindeki(dialect) aynı SQL şu şekilde yazılabilir:
SELECT a, b, c, d, sum(e) FROM tab;
Implicit HAVING
Standart SQL'de HAVING
kullanan daha karmaşık başka bir örneğe bakalım:
SELECT a, b, c, d, sum(e)
FROM tab
GROUP BY a, b, c, d
HAVING sum(e) > 100;
QuestDB'nin lehçesinde alt sorgular, gereksiz yinelenen aggegationlar olmaksızın daha küçük, daha okunabilir bir sorgu oluşturmak için imdada yetişir. HAVING
işlevi dolaylı olarak şu şekilde elde edilebilir:
(SELECT a, b, c, d, sum(e) s FROM tab) WHERE s > 100;
Partitions
QuestDB, tabloları zaman aralıklarına göre bölümleme seçeneği sunar. Her aralık için veriler ayrı dosya kümelerinde saklanır.
Özellikler
- Kullanılabilir partition intervalleri
NONE
,YEAR
,MONTH
,DAY
, veHOUR
’dur. - ILP ingestion yoluyla
CREATE TABLE
vePARTITION BY DAY
kullanılırken varsayılan davranışPARTITION BY NONE
şeklindedir. - Partitionlar tablo oluşturulurken tanımlanır. Daha fazla bilgi için
CREATE TABLE
bölümüne bakın. - Partition dizinleri(directory) için adlandırma kuralı aşağıdaki gibidir:
Table Partition Partition format HOUR YYYY-MM-DD-HH DAY YYYY-MM-DD MONTH YYYY-MM YEAR YYYY
Avantajlar
- Timestamp interval aramaları için azaltılmış disk IO'su. Bunun nedeni, SQL optimiser’ın partitioningden yararlanmasıdır.
- Önemli ölçüde iyileştirilmiş hesaplamalar ve arama süreleri. Bu, önceki partitionlar için verilerin kronolojisinden ve göreli değişmezliğinden yararlanılarak elde edilir.
- Veri dosyalarının fiziksel olarak ayrılması. Bu, dosya saklama ilkelerini uygulamayı veya belirli aralıkları çıkarmayı kolaylaştırır.
Depolama Örneği
Her partition etkin bir şekilde ana makinede partitioning interval’e karşılık gelen bir directory’dir. Aşağıdaki örnekte, PARTITION BY MONTH
kullanılarak partitionlanmış bir tablonun gezindiğini varsayıyoruz.
[quest-user trips]$ dir
2017-03 2017-10 2018-05 2019-02
2017-04 2017-11 2018-06 2019-03
2017-05 2017-12 2018-07 2019-04
2017-06 2018-01 2018-08 2019-05
2017-07 2018-02 2018-09 2019-06
2017-08 2018-03 2018-10
2017-09 2018-04 2018-11
Diskteki her partition, karşılık gelen timestamp interval’in sütun veri dosyalarını içerir.
[quest-user 2019-06]$ dir
_archive cab_type.v dropoff_latitude.d ehail_fee.d
cab_type.d congestion_surcharge.d dropoff_location_id.d extra.d
cab_type.k dropoff_datetime.d dropoff_longitude.d fare_amount.d
Symbol
QuestDB, symbol
adı verilen bir veri türü sunar; tekrarlayan stringleri depolamak için kullanılan bir veri yapısı. Internal olarak, symbol
türleri bir tamsayı tablosu ve bunlara karşılık gelen string değerleri olarak saklanır.
Bu sayfada kavram, isteğe bağlı ayar ve symbol
türleri için göstergeler sunulmaktadır.
Avantajlar
- String işlemleri,
string
yerineint
türlerini karşılaştırıp yazdığından, sorgu performansı büyük ölçüde iyileşir. int
string
türlerine eşlendiğinden, depolama verimliliği büyük ölçüde iyileşir.- SQL execution, string değerlerinin işlenmesiyle aynı sonuca sahip olduğundan kullanıcı için göze batmaz.
- Ek tablolara veya joinlere olan ihtiyacı ortadan kaldırarak veritabanı şemalarının karmaşıklığı azalır.
Özellikler
Symbol
tabloları, sütun verilerinden ayrı olarak saklanır.- Verileri okurken veya yazarken
string
’denint
'a ve tersi yönde hızlı dönüşüm. Symbol
türleri olarak tanımlanan sütunlar indexing destekler.- Varsayılan olarak QuestDB, gelişmiş sorgu hızı ve ILP alım hızı için
Symbol
türlerini bellekte önbelleğe alır. Ayar yapılandırılabilir.
Örnek
Sütunlar, diğer türlere benzer şekilde CREATE TABLE
kullanılarak Symbol
olarak belirtilebilir:
CREATE TABLE my_table
(symb SYMBOL CAPACITY 128 NOCACHE, price DOUBLE, ts TIMESTAMP)
timestamp(ts);
Aşağıdaki ek symbol ayarları, sunucu yapılandırmasının bir parçası olarak genel olarak veya bir tablo oluşturulduğunda local olarak tanımlanır:
- Symbol kapasitesi: Bu sütunun kaç farklı değere sahip olması gerektiğini belirtmek için kullanılan isteğe bağlı ayar. Kullanılan değere bağlı olarak, gerektiğinde QuestDB'nin düzgün çalışmasına izin vermek için veri yapıları kendilerini yeniden boyutlandıracaktır. Symbol değeri sayısının az girilmesi performans düşüşüne neden olabilirken, fazla girilmesi daha yüksek disk alanı ve bellek tüketimi ile sonuçlanabilir. Symbol kapasitesi, cache etkinleştirildiğinde ilk symbol cache boyutunu ayarlamak için de kullanılır.
- Sunucu genelinde ayar:
cairo.default.symbol.capacity
, varsayılan256
- Sütun genelinde ayar:
CREATE TABLE
içinCAPACITY
seçeneği
- Sunucu genelinde ayar:
Cache
: Bir symbol’ün önbelleğe alınması gerekip gerekmediğini belirten isteğe bağlı ayar. Bir sembol sütunu önbelleğe alındığında, QuestDB, sembol değerlerini ve anahtarları çözmek için Java heap tabanlı bir hash tablosu kullanır. Bir sütunda çok sayıda farklı symbol değeri varsa (örneğin 100.000'in üzerinde), heap etkisi önemli olabilir ve heap boyutuna bağlı olarakOutOfMemory
hatalarına neden olabilir. Önbelleğe almamak, daha büyük değer sayımlarıyla başa çıkabilen ancak daha yavaş olan memory mapped bir yapıdan yararlanır.- Sunucu genelinde ayar:
cairo.default.symbol.cache.flag
, varsayılan değeritrue
- Bir tablo oluşturulduğunda sütun çapında ayar: CREATE TABLE için
CACHE | NOCACHE
anahtar sözcüğü
- Sunucu genelinde ayar:
Indexes
Bir index, daha hızlı okuma erişimi sağlamak için hedef sütunun her değeri için satır konumlarını saklar. WHERE
koşullu sorgular sırasında ilgili satırlara doğrudan erişerek tam tablo taramalarını(full table scan) atlamanızı sağlar.
Symbol sütunları için indeksleme mevcuttur. Diğer türler için index desteği zamanla eklenecektir.
Index oluşturma ve silme
Aşağıdakiler, bir sembol sütununu indekslemenin yollarıdır:
CREATE TABLE
kullanılarak tablo oluşturma sırasında- Mevcut bir symbol sütununu indekslemek için `ALTER TABLE ALTER COLUMN ADD INDEX` kullanma
Bir indexi silmek için:
• ALTER TABLE ALTER COLUMN DROP INDEX
Nasıl Çalışır?
Index, hedef symbol için her bir farklı değer için bir satır konumları tablosu oluşturur. Index oluşturulduktan sonra, tabloya veri eklemek indexi güncelleyecektir. Indexlenmiş değerler üzerindeki aramalar, öğelerin bellek konumlarını verecek olan index tablosunda doğrudan gerçekleştirilecek ve böylece gereksiz tablo taramalarından kaçınılacaktır.
Tablo örneği ve dizin tablosu;
Table Index
|Row ID | Symbol | Value | | Symbol | Row IDs |
| 1 | A | 1 | | A | 1, 2, 4 |
| 2 | A | 0 | | B | 3 |
| 3 | B | 1 | | C | 5 |
| 4 | A | 1 |
| 5 | C | 0 |
INSERT INTO Table values(B, 1);
biri Tablo için, diğeri Index için olmak üzere iki güncellemeyi tetikler.
Table Index
|Row ID | Symbol | Value | | Symbol | Row IDs |
| 1 | A | 1 | | A | 1, 2, 4 |
| 2 | A | 0 | | B | 3, 6 |
| 3 | B | 1 | | C | 5 |
| 4 | A | 1 |
| 5 | C | 0 |
| 6 | B | 1 |
Index capacity
Bir symbol sütunu indexe eklendiğinde, diskteki tek bir depolama bloğunda kaç satır id’nin depolanacağını belirtmek için ek bir index kapasitesi tanımlanabilir:
- Sunucu genelinde ayar:
cairo.index.value.block.size
, varsayılan değeri256
- Sütun genelinde ayar:
CREATE TABLE
için[index](https://questdb.io/docs/reference/sql/create-table/#column-indexes)
seçeneği - Sütun genelinde ayar: ALTER TABLE COLUMN ADD INDEX
Satır id’lerini depolamak için kullanılan daha az blok, daha iyi performans sağlar. Aynı zamanda ayarın gereğinden fazla boyutlandırılması, gerekenden daha yüksek disk alanı kullanımına neden olacaktır.
Avantajlar
Index, genellikle WHERE
yan tümceleri kullanırken, indexe alınmış bir sütunun bir alt kümesini kapsayan sorguların karmaşıklığını büyük ölçüde azaltmanıza olanak tanır.
Yukarıdaki tabloya uygulanan aşağıdaki sorguyu göz önünde bulundurun SELECT sum(Value) FROM Table WHERE Symbol='A';
- Index olmadan, sorgu engine’i, sorguyu gerçekleştirmek için tüm tabloyu tarayacaktır. 6 işlem yapması gerekecek (6 satırın her birini bir kez okuyacak).
- Index ile, sorgu engine’i önce, oldukça küçük olan indeks tablosunu tarar. Örneğimizde, ilk satırda A'yı bulacaktır. Ardından, sorgu engine’i, karşılık gelen değerleri okumak için tablodaki 1, 2, 4 numaralı belirli konumlardaki değerleri kontrol eder. Sonuç olarak, tablodaki yalnızca ilgili satırları tarar ve alakasız satırlara dokunmaz.
Trade-offs
- Depolama alanı: Index, her bir farklı symbol değerini ve bu symbollerin bulunabileceği konumları içeren bir tablo tutacaktır. Sonuç olarak, bir symbol alanını indekslemeyle ilişkili küçük bir depolama maliyeti vardır.
- Ingestion(alım) performansı: Tablodaki her yeni giriş, Index tablosunda bir girişi tetikler. Bu, herhangi bir yazmanın artık iki yazma işlemi gerektireceği ve dolayısıyla iki kat daha uzun süreceği anlamına gelir.
Index capacity
Zaman içinde 200 unique hisse senedi symbol’ü ve 1.000.000.000 kayıt içeren örnek bir tablo düşünün. Index, her sembol için 1.000.000.000 / 200 satır id’si, yani symbol başına 5.000.000 depolamalıdır.
- Bu durumda index kapasitesi 1.048.576 olarak ayarlanırsa, QuestDB satır id’lerini depolamak için 5 blok kullanır.
- Bu durumda index kapasitesi 1.024 olarak ayarlanırsa blok sayısı 4.883 olacaktır.