QuestDB (TR)

03/02/2023

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ı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.

Özellikler

  • Kullanılabilir partition intervalleriNONE, YEAR, MONTH, DAY, 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 Partition
    Partition format

    HOUR

    YYYY-MM-DD-HH

    DAY

    YYYY-MM-DD

    MONTH

    YYYY-MM

    YEAR

    YYYY

<aside> 💡 Partitioning, yalnızca designated timestamp’a sahip tablolarda mümkündür.

</aside>

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 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.

Last updated

Was this helpful?