Tools: Kubernetes'te TimescaleDB Retention Testleri

Tools: Kubernetes'te TimescaleDB Retention Testleri

Source: Dev.to

Deney 1 - Manuel chunk droplama (drop_chunks) ## 1A) Kurulum ve veri basma ## 1B) Silmeden önce ölçüm ## 1C) 24 saatten eski chunk'ları dropla (manuel) ## 1D) Silmeden sonra ölçüm ## Deney 2 - Otomatik veri saklama politikası (add_retention_policy + run_job) ## 2A) Hypertable oluştur ve veri ekle ## 2B) Retention policy ekle (24 saat tut) ## 2C) Job'ı bul ve çalıştır ## 2D) Sonucu doğrula ## Temizlik ## SQL DELETE vs TimescaleDB retention ## SQL DELETE (satır bazlı) ## TimescaleDB retention (chunk bazlı) Önceki yazımızda Kubernetes üzerinde iki ayrı kurulum (vanilla PostgreSQL ve TimescaleDB) yapıp az veriyle tablo + index boyutlarını kıyaslamıştık. Bu yazıda aynı environment üzerinde TimescaleDB'nin data retention (eski veriyi silme) yaklaşımını pratikte test ediyoruz. Komutlarda iki değişken kullanacağız: NS ve TSPOD. Bu blok sonraki komutlarda kullanacağımız değişkenleri hazırlar. Bu deneyde 48 saatlik veri bastık (5 dakikada bir), sonra 24 saatten eski chunk'ları manuel olarak dropladık. Burada lab schema'sını ve 1 saatlik chunk'larla hypertable'ı oluşturup sonra 48 saatlik örnek veri basıyoruz. Burada ise silmeden önce satır sayısı, hangi chunk'ların oluştuğu ve chunk'ların toplam disk boyutunu görüyoruz. Benim makinemdeki çıktılar: İşte şimdi 'now() - 24 hours' çizgisinin tamamen solunda kalan chunk tablolarını topluca DROP ettik. Satır satır silme yok. Chunk bazında hızlıca droplar. Burada drop_chunks sonrasında satır/chunk/boyutun gerçekten azaldığını doğruladık. Benim makinemdeki çıktılar: Önemli not: 'older_than => 24 hours' her zaman 'tam 24 saatin dışındaki tüm satırlar gider' demek değildir. Cutoff çizgisini kesen bir chunk içinde hem eski hem yeni veri varsa, o chunk komple droplanmaz. Bu nedenle az miktarda daha eski satır kalabilir. Bu deneyde ise TimescaleDB'nin dahili job mekanizması ile otomatik retention kurduk ve job'ı manuel tetikledik. Bu blok ile otomatik retention deneyi için ikinci bir hypertable oluşturduk ve aynı şekilde 48 saatlik örnek veriyi doldurduk. Burada ise TimescaleDB içinde arka planda çalışacak bir retention job'u oluşturduk. Dönüş değeri job_id olur. Burası retention job'unun konfigürasyonunu gösterir ve beklemeden hemen çalıştırır. Normalde job schedule_interval'a göre otomatik çalışır. Bu blok policy çalıştıktan sonra kalan satır sayısını ve chunk toplam boyutunu ölçer. Yani job'un gerçekten eski chunk'ları dropladığını kanıtlar. Benim makinemdeki çıktılar: Bu, retention policy'nin temelde drop_chunks ile aynı chunk droplama mekanizmasını otomatik olarak çalıştırdığını gösteriyor. Burası retention policy'yi kaldırır ve lab schema'sını CASCADE ile silerek tüm test tablolarını ve internal chunk tablolarını temizler. TimescaleDB'nin sunduğu yerleşik, chunk tabanlı silme yöntemlerini kullanmak çoğu zaman en verimli yoldur. Yine de hangi yöntemin doğru olduğuna senaryo karar verir. Eksileri / dikkat edilmesi gerekenler: Hedefiniz 'X süreden eski zaman serisi ham verisini otomatik ve verimli şekilde kaldırmak' ise TimescaleDB'nin retention policy (add_retention_policy) veya manuel drop_chunks yaklaşımı genellikle en doğru ve yönetimi en kolay çözümdür. Buna rağmen seçici ve ince ayarlı silme ihtiyaçlarında SQL DELETE hala gerekli bir araçtır. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: NS=database TSPOD="$(kubectl get pod -n $NS -l release=timescaledb,role=master -o jsonpath='{.items[0].metadata.name}')" echo "TSPOD=$TSPOD" Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: NS=database TSPOD="$(kubectl get pod -n $NS -l release=timescaledb,role=master -o jsonpath='{.items[0].metadata.name}')" echo "TSPOD=$TSPOD" CODE_BLOCK: NS=database TSPOD="$(kubectl get pod -n $NS -l release=timescaledb,role=master -o jsonpath='{.items[0].metadata.name}')" echo "TSPOD=$TSPOD" COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' CREATE EXTENSION IF NOT EXISTS timescaledb; CREATE SCHEMA IF NOT EXISTS lab; DROP TABLE IF EXISTS lab.sensor_ret_manual; CREATE TABLE lab.sensor_ret_manual ( time TIMESTAMPTZ NOT NULL, device_id INT, temperature DOUBLE PRECISION ); SELECT create_hypertable( 'lab.sensor_ret_manual', 'time', chunk_time_interval => INTERVAL '1 hour', create_default_indexes => FALSE ); CREATE INDEX sensor_ret_manual_time_idx ON lab.sensor_ret_manual(time); INSERT INTO lab.sensor_ret_manual SELECT generate_series(NOW() - INTERVAL '48 hours', NOW(), INTERVAL '5 minutes'), (random() * 10)::int, random() * 100; SQL Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' CREATE EXTENSION IF NOT EXISTS timescaledb; CREATE SCHEMA IF NOT EXISTS lab; DROP TABLE IF EXISTS lab.sensor_ret_manual; CREATE TABLE lab.sensor_ret_manual ( time TIMESTAMPTZ NOT NULL, device_id INT, temperature DOUBLE PRECISION ); SELECT create_hypertable( 'lab.sensor_ret_manual', 'time', chunk_time_interval => INTERVAL '1 hour', create_default_indexes => FALSE ); CREATE INDEX sensor_ret_manual_time_idx ON lab.sensor_ret_manual(time); INSERT INTO lab.sensor_ret_manual SELECT generate_series(NOW() - INTERVAL '48 hours', NOW(), INTERVAL '5 minutes'), (random() * 10)::int, random() * 100; SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' CREATE EXTENSION IF NOT EXISTS timescaledb; CREATE SCHEMA IF NOT EXISTS lab; DROP TABLE IF EXISTS lab.sensor_ret_manual; CREATE TABLE lab.sensor_ret_manual ( time TIMESTAMPTZ NOT NULL, device_id INT, temperature DOUBLE PRECISION ); SELECT create_hypertable( 'lab.sensor_ret_manual', 'time', chunk_time_interval => INTERVAL '1 hour', create_default_indexes => FALSE ); CREATE INDEX sensor_ret_manual_time_idx ON lab.sensor_ret_manual(time); INSERT INTO lab.sensor_ret_manual SELECT generate_series(NOW() - INTERVAL '48 hours', NOW(), INTERVAL '5 minutes'), (random() * 10)::int, random() * 100; SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT count(*) AS rows_before FROM lab.sensor_ret_manual; SELECT show_chunks('lab.sensor_ret_manual') AS chunk_before; SELECT pg_size_pretty(sum(pg_total_relation_size(chunk::regclass))) AS total_before FROM show_chunks('lab.sensor_ret_manual') AS chunk; SQL Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT count(*) AS rows_before FROM lab.sensor_ret_manual; SELECT show_chunks('lab.sensor_ret_manual') AS chunk_before; SELECT pg_size_pretty(sum(pg_total_relation_size(chunk::regclass))) AS total_before FROM show_chunks('lab.sensor_ret_manual') AS chunk; SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT count(*) AS rows_before FROM lab.sensor_ret_manual; SELECT show_chunks('lab.sensor_ret_manual') AS chunk_before; SELECT pg_size_pretty(sum(pg_total_relation_size(chunk::regclass))) AS total_before FROM show_chunks('lab.sensor_ret_manual') AS chunk; SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT drop_chunks('lab.sensor_ret_manual', older_than => INTERVAL '24 hours'); SQL Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT drop_chunks('lab.sensor_ret_manual', older_than => INTERVAL '24 hours'); SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT drop_chunks('lab.sensor_ret_manual', older_than => INTERVAL '24 hours'); SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT count(*) AS rows_after FROM lab.sensor_ret_manual; SELECT show_chunks('lab.sensor_ret_manual') AS chunk_after; SELECT pg_size_pretty(sum(pg_total_relation_size(chunk::regclass))) AS total_after FROM show_chunks('lab.sensor_ret_manual') AS chunk; SQL Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT count(*) AS rows_after FROM lab.sensor_ret_manual; SELECT show_chunks('lab.sensor_ret_manual') AS chunk_after; SELECT pg_size_pretty(sum(pg_total_relation_size(chunk::regclass))) AS total_after FROM show_chunks('lab.sensor_ret_manual') AS chunk; SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT count(*) AS rows_after FROM lab.sensor_ret_manual; SELECT show_chunks('lab.sensor_ret_manual') AS chunk_after; SELECT pg_size_pretty(sum(pg_total_relation_size(chunk::regclass))) AS total_after FROM show_chunks('lab.sensor_ret_manual') AS chunk; SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' DROP TABLE IF EXISTS lab.sensor_ret_policy; CREATE TABLE lab.sensor_ret_policy ( time TIMESTAMPTZ NOT NULL, device_id INT, temperature DOUBLE PRECISION ); SELECT create_hypertable( 'lab.sensor_ret_policy', 'time', chunk_time_interval => INTERVAL '1 hour', create_default_indexes => FALSE ); CREATE INDEX sensor_ret_policy_time_idx ON lab.sensor_ret_policy(time); INSERT INTO lab.sensor_ret_policy SELECT generate_series(NOW() - INTERVAL '48 hours', NOW(), INTERVAL '5 minutes'), (random() * 10)::int, random() * 100; SQL Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' DROP TABLE IF EXISTS lab.sensor_ret_policy; CREATE TABLE lab.sensor_ret_policy ( time TIMESTAMPTZ NOT NULL, device_id INT, temperature DOUBLE PRECISION ); SELECT create_hypertable( 'lab.sensor_ret_policy', 'time', chunk_time_interval => INTERVAL '1 hour', create_default_indexes => FALSE ); CREATE INDEX sensor_ret_policy_time_idx ON lab.sensor_ret_policy(time); INSERT INTO lab.sensor_ret_policy SELECT generate_series(NOW() - INTERVAL '48 hours', NOW(), INTERVAL '5 minutes'), (random() * 10)::int, random() * 100; SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' DROP TABLE IF EXISTS lab.sensor_ret_policy; CREATE TABLE lab.sensor_ret_policy ( time TIMESTAMPTZ NOT NULL, device_id INT, temperature DOUBLE PRECISION ); SELECT create_hypertable( 'lab.sensor_ret_policy', 'time', chunk_time_interval => INTERVAL '1 hour', create_default_indexes => FALSE ); CREATE INDEX sensor_ret_policy_time_idx ON lab.sensor_ret_policy(time); INSERT INTO lab.sensor_ret_policy SELECT generate_series(NOW() - INTERVAL '48 hours', NOW(), INTERVAL '5 minutes'), (random() * 10)::int, random() * 100; SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT add_retention_policy('lab.sensor_ret_policy', INTERVAL '24 hours'); SQL Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT add_retention_policy('lab.sensor_ret_policy', INTERVAL '24 hours'); SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT add_retention_policy('lab.sensor_ret_policy', INTERVAL '24 hours'); SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT job_id, proc_name, schedule_interval, config FROM timescaledb_information.jobs WHERE proc_name = 'policy_retention' ORDER BY job_id DESC; CALL run_job(<yukaridaki_job_id>); SQL Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT job_id, proc_name, schedule_interval, config FROM timescaledb_information.jobs WHERE proc_name = 'policy_retention' ORDER BY job_id DESC; CALL run_job(<yukaridaki_job_id>); SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT job_id, proc_name, schedule_interval, config FROM timescaledb_information.jobs WHERE proc_name = 'policy_retention' ORDER BY job_id DESC; CALL run_job(<yukaridaki_job_id>); SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT count(*) AS rows_after FROM lab.sensor_ret_policy; SELECT pg_size_pretty(sum(pg_total_relation_size(chunk::regclass))) AS total_after FROM show_chunks('lab.sensor_ret_policy') AS chunk; SQL Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT count(*) AS rows_after FROM lab.sensor_ret_policy; SELECT pg_size_pretty(sum(pg_total_relation_size(chunk::regclass))) AS total_after FROM show_chunks('lab.sensor_ret_policy') AS chunk; SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT count(*) AS rows_after FROM lab.sensor_ret_policy; SELECT pg_size_pretty(sum(pg_total_relation_size(chunk::regclass))) AS total_after FROM show_chunks('lab.sensor_ret_policy') AS chunk; SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT remove_retention_policy('lab.sensor_ret_policy'); DROP SCHEMA IF EXISTS lab CASCADE; SQL Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT remove_retention_policy('lab.sensor_ret_policy'); DROP SCHEMA IF EXISTS lab CASCADE; SQL COMMAND_BLOCK: kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 <<'SQL' SELECT remove_retention_policy('lab.sensor_ret_policy'); DROP SCHEMA IF EXISTS lab CASCADE; SQL - Namespace: database - TimescaleDB pod: timescaledb-single chart (master pod) - StorageClass: local-path (tek node) - rows_before: 577 - chunk_before: 49 chunk - total_before: 1176 kB - rows_after: 299 - chunk_after: 25 chunk - total_after: 600 kB - rows_after: 299 - total_after: 600 kB - İnce ayar: Sadece belirli cihazın verisi, belirli aralık, GDPR gibi seçici silmeler için uygundur. - Chunk sınırına bağlı kalmadan hedeflediğin satırları silersin. - PostgreSQL MVCC nedeniyle satırlar diskten hemen silinmez; dead tuple olarak kalır. - Büyük silmeler table bloat üretir; disk alanını gerçekten geri almak için VACUUM (bazen agresif vacuum) ve kimi zaman REINDEX gerekebilir. - Çok miktarda veride satır satır silmek yavaş ve maliyetlidir (IO/CPU artışı). - Chunk'lar komple droplandığı için genellikle çok hızlıdır (DROP TABLE benzeri). - Relation size çoğu senaryoda hemen düşer (örneğin benim makinemde 1176 kB -> 600 kB). - add_retention_policy ile cron/script yazmadan, DB içindeki job mekanizmasıyla otomatik yönetim sağlanır. - Zaman serisi kullanımında en doğal model: 'ham veriyi X süre tut, eskisini at'. - Granularity: Retention satır bazlı değil chunk bazlıdır. Cutoff'u kesen chunk kalabilir; bu yüzden az miktarda daha eski satır tutulabilir. - Seçici silme için uygun değildir (ör. sadece device_id=3 verisini silmek). Bu tip işlerde DELETE gerekir. - Lock ihtiyacı: Chunk drop, ilgili chunk'lar üzerinde lock alır; uzun transaction varsa drop gecikebilir veya timeout görebiliriz.