Ana içeriğe geç
Version: 1.0.1

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.

columnUpdate.svg

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.

columnRead.svg

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.

commitModel.svg


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ında
  • SELECT 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

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.

partitionModel.svg

Özellikler

  • Kullanılabilir partition intervalleriNONEYEARMONTHDAY, veHOUR ’dur.
  • ILP ingestion yoluyla CREATE TABLE ve PARTITION 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 PartitionPartition format
    HOURYYYY-MM-DD-HH
    DAYYYYY-MM-DD
    MONTHYYYY-MM
    YEARYYYY

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 yerine int 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’den int'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ılan 256
    • Sütun genelinde ayar: CREATE TABLE için CAPACITY seçeneği
  • 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ı olarak OutOfMemory 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ğeri true
    • Bir tablo oluşturulduğunda sütun çapında ayar: CREATE TABLE için CACHE | NOCACHE anahtar sözcüğü

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:

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ğeri 256
  • 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.