Postgres: şişkinlik, pg_repack ve ertelenmiş kısıtlamalar

Postgres: şişkinlik, pg_repack ve ertelenmiş kısıtlamalar

Şişkinliğin tablolar ve dizinler üzerindeki etkisi yaygın olarak bilinmektedir ve yalnızca Postgres'te mevcut değildir. VACUUM FULL veya CLUSTER gibi kutudan çıktığı gibi bu sorunla başa çıkmanın yolları vardır, ancak bunlar çalışma sırasında tabloları kilitler ve bu nedenle her zaman kullanılamaz.

Makale, şişkinliğin nasıl oluştuğu, bununla nasıl mücadele edebileceğiniz, ertelenmiş kısıtlamalar ve bunların pg_repack uzantısının kullanımına getirdiği sorunlar hakkında küçük bir teori içerecektir.

Bu makale temel alınarak yazılmıştır. konuşmam PgConf.Rusya 2020'de.

Şişkinlik neden oluşur?

Postgres çok sürümlü bir modele dayanmaktadır (MVCC). Bunun özü, tablodaki her satırın birden fazla sürüme sahip olabilmesi, işlemlerde ise bu sürümlerden birden fazlasını görmemesi, ancak aynı sürümün olması gerekmemesidir. Bu, birden fazla işlemin aynı anda çalışmasına ve birbirleri üzerinde neredeyse hiçbir etkisinin olmamasına olanak tanır.

Açıkçası, tüm bu sürümlerin saklanması gerekiyor. Postgres bellekle sayfa sayfa çalışır ve bir sayfa, diskten okunabilen veya yazılabilen minimum veri miktarıdır. Bunun nasıl olduğunu anlamak için küçük bir örneğe bakalım.

Diyelim ki birkaç kayıt eklediğimiz bir tablomuz var. Tablonun saklandığı dosyanın ilk sayfasında yeni veriler belirdi. Bunlar, bir taahhütten sonra diğer işlemler için kullanılabilen satırların canlı versiyonlarıdır (basitlik açısından izolasyon düzeyinin Okuma Taahhütlü olduğunu varsayacağız).

Postgres: şişkinlik, pg_repack ve ertelenmiş kısıtlamalar

Daha sonra girişlerden birini güncelledik ve böylece eski sürümün artık geçerli olmadığını işaretledik.

Postgres: şişkinlik, pg_repack ve ertelenmiş kısıtlamalar

Adım adım satır versiyonlarını güncelleyip sildikten sonra verilerin yaklaşık yarısının “çöp” olduğu bir sayfayla karşılaştık. Bu veriler hiçbir işlemde görülmez.

Postgres: şişkinlik, pg_repack ve ertelenmiş kısıtlamalar

Postgres'in bir mekanizması var VAKUMeski sürümleri temizleyen ve yeni verilere yer açan. Ancak yeterince agresif bir şekilde yapılandırılmamışsa veya diğer tablolarda çalışmakla meşgulse, o zaman "çöp veriler" kalır ve yeni veriler için ek sayfalar kullanmamız gerekir.

Örneğimizde, bir noktada tablo dört sayfadan oluşacak, ancak yalnızca yarısı canlı veri içerecek. Sonuç olarak tabloya eriştiğimizde gereğinden çok daha fazla veri okuyacağız.

Postgres: şişkinlik, pg_repack ve ertelenmiş kısıtlamalar

VACUUM artık tüm ilgisiz satır sürümlerini silse bile durum önemli ölçüde iyileşmeyecektir. Yeni satırlar için sayfalarda ve hatta sayfaların tamamında boş alanımız olacak, ancak yine de gereğinden fazla veri okuyor olacağız.
Bu arada, dosyanın sonunda tamamen boş bir sayfa (örneğimizdeki ikinci sayfa) olsaydı, VACUUM onu kırpabilirdi. Ama artık ortada olduğundan ona hiçbir şey yapılamaz.

Postgres: şişkinlik, pg_repack ve ertelenmiş kısıtlamalar

Bu kadar boş ya da çok seyrek sayfaların sayısının artması buna şişkinlik denir ve performansı etkilemeye başlar.

Yukarıda açıklanan her şey, tablolarda şişkinlik oluşumunun mekaniğidir. Dizinlerde bu hemen hemen aynı şekilde olur.

Şişkinliğim var mı?

Şişkinliğinizin olup olmadığını belirlemenin birkaç yolu vardır. İlkinin fikri, tablolardaki satır sayısı, "canlı" satır sayısı vb. hakkında yaklaşık bilgiler içeren dahili Postgres istatistiklerini kullanmaktır. İnternette hazır komut dosyalarının birçok çeşidini bulabilirsiniz. Temel aldık senaryo Şişirme tablolarını tost ve şişirme btree dizinleriyle birlikte değerlendirebilen PostgreSQL Uzmanlarından. Deneyimlerimize göre hatası %10-20'dir.

Başka bir yol da uzantıyı kullanmaktır pgstattuple, sayfaların içine bakmanıza ve hem tahmini hem de kesin bir şişkinlik değeri elde etmenize olanak tanır. Ancak ikinci durumda tablonun tamamını taramanız gerekecektir.

%20'ye kadar küçük bir şişkinlik değerinin kabul edilebilir olduğunu düşünüyoruz. Doldurma faktörünün bir analoğu olarak düşünülebilir. listeleme и indeksler. %50 ve üzerinde performans sorunları başlayabilir.

Şişkinlikle mücadele yolları

Postgres'in şişkinlikle başa çıkmanın birkaç yolu vardır, ancak bunlar her zaman herkes için uygun değildir.

AUTOVACUUM'u şişkinlik oluşmayacak şekilde yapılandırın. Daha doğrusu sizin için kabul edilebilir bir seviyede tutmak. Bu “kaptanın” tavsiyesi gibi görünse de gerçekte bunu başarmak her zaman kolay değildir. Örneğin, veri şemasında düzenli değişiklikler içeren aktif bir geliştirmeniz var veya bir tür veri geçişi gerçekleşiyor. Sonuç olarak yük profiliniz sık sık değişebilir ve genellikle tablodan tabloya farklılık gösterir. Bu, sürekli olarak biraz ileriye doğru çalışmanız ve AUTOVACUUM'u her masanın değişen profiline göre ayarlamanız gerektiği anlamına gelir. Ama açıkçası bunu yapmak kolay değil.

AUTOVACUUM'un tablolara ayak uyduramamasının bir diğer yaygın nedeni de, uzun süredir devam eden işlemlerin bu işlemler için mevcut olan verileri temizlemesini engellemesidir. Buradaki öneri de açıktır: "Askıda kalan" işlemlerden kurtulun ve aktif işlemlerin süresini en aza indirin. Ancak uygulamanızdaki yük OLAP ve OLTP'nin bir meleziyse, o zaman aynı anda çok sayıda sık güncelleme ve kısa sorgunun yanı sıra uzun vadeli işlemlere (örneğin bir rapor oluşturmak) sahip olabilirsiniz. Böyle bir durumda, yükü farklı tabanlara dağıtmayı düşünmeye değer, bu da her birinin daha ince ayarlanmasına olanak sağlayacaktır.

Başka bir örnek - profil homojen olsa bile, ancak veritabanı çok yüksek bir yük altında olsa bile, en agresif AUTOVACUUM bile başa çıkamayabilir ve şişkinlik meydana gelebilir. Ölçeklendirme (dikey veya yatay) tek çözümdür.

AUTOVACUUM'u kurduğunuz ancak şişkinliğin büyümeye devam ettiği bir durumda ne yapmalısınız?

Ekip VAKUM DOLU tabloların ve dizinlerin içeriğini yeniden oluşturur ve içlerinde yalnızca ilgili verileri bırakır. Şişmeyi ortadan kaldırmak için mükemmel çalışır, ancak yürütülmesi sırasında masadaki özel bir kilit yakalanır (AccessExclusiveLock), bu tablodaki sorguların yürütülmesine bile izin vermez. Hizmetinizi veya bir kısmını bir süreliğine (veritabanınızın ve donanımınızın boyutuna bağlı olarak onlarca dakikadan birkaç saate kadar) durdurmayı göze alabiliyorsanız, bu seçenek en iyisidir. Maalesef planlı bakım sırasında VACUUM FULL'u çalıştıracak zamanımız olmadığından bu yöntem bizim için uygun değildir.

Ekip KÜME Tabloların içeriğini VACUUM FULL ile aynı şekilde yeniden oluşturur, ancak verilerin diskte fiziksel olarak sıralanacağı bir dizin belirtmenize olanak tanır (ancak gelecekte yeni satırlar için sıra garanti edilmez). Bazı durumlarda bu, birden fazla kaydın dizine göre okunmasıyla bir dizi sorgu için iyi bir optimizasyondur. Komutun dezavantajı VACUUM FULL ile aynıdır - çalışma sırasında masayı kilitler.

Ekip YENİDEN DİZİN önceki ikisine benzer, ancak belirli bir dizini veya tablonun tüm dizinlerini yeniden oluşturur. Kilitler biraz daha zayıf: Tablodaki ShareLock (değişiklikleri önler ancak seçime izin verir) ve yeniden oluşturulan dizindeki AccessExclusiveLock (bu dizini kullanan sorguları engeller). Ancak Postgres'in 12. sürümünde bir parametre belirdi EŞ ZAMANLIBu, kayıtların eşzamanlı olarak eklenmesini, değiştirilmesini veya silinmesini engellemeden dizini yeniden oluşturmanıza olanak tanır.

Postgres'in önceki sürümlerinde, şunu kullanarak REINDEX'E AYNI ZAMANDA benzer bir sonuç elde edebilirsiniz: AYNI ANDA DİZİN OLUŞTURUN. Kesin kilitleme olmadan bir dizin oluşturmanıza (paralel sorguları engellemeyen ShareUpdateExclusiveLock), ardından eski dizini yenisiyle değiştirmenize ve eski dizini silmenize olanak tanır. Bu, uygulamanıza müdahale etmeden indeks şişkinliğini ortadan kaldırmanıza olanak tanır. Dizinleri yeniden oluştururken disk alt sisteminde ek bir yük olacağını dikkate almak önemlidir.

Bu nedenle, eğer indeksler için şişkinliği "anında" ortadan kaldırmanın yolları varsa, o zaman tablolar için hiçbiri yoktur. Çeşitli harici uzantıların devreye girdiği yer burasıdır: pg_repack (eski adıyla pg_reorg), kompakt, kompakt ve diğerleri. Bu yazıda bunları karşılaştırmayacağım ve sadece bazı değişikliklerden sonra kendimiz kullandığımız pg_repack'ten bahsedeceğim.

pg_repack nasıl çalışır?

Postgres: şişkinlik, pg_repack ve ertelenmiş kısıtlamalar
Diyelim ki tamamen sıradan bir tablomuz var - indeksler, kısıtlamalar ve maalesef şişkinlik ile. pg_repack'in ilk adımı, çalışırken tüm değişikliklerle ilgili verileri depolamak için bir günlük tablosu oluşturmaktır. Tetikleyici bu değişiklikleri her ekleme, güncelleme ve silme işleminde kopyalayacaktır. Daha sonra veri ekleme sürecini yavaşlatmamak için yapı olarak orijinaline benzer, ancak dizinler ve kısıtlamalar olmayan bir tablo oluşturulur.

Daha sonra pg_repack, verileri eski tablodan yeni tabloya aktarır, ilgisiz tüm satırları otomatik olarak filtreler ve ardından yeni tablo için dizinler oluşturur. Tüm bu işlemlerin yürütülmesi sırasında log tablosunda değişiklikler birikir.

Bir sonraki adım değişiklikleri yeni tabloya aktarmaktır. Geçiş birkaç yinelemede gerçekleştirilir ve günlük tablosunda 20'den az giriş kaldığında pg_repack güçlü bir kilit alır, en son verileri taşır ve eski tabloyu Postgres sistem tablolarındaki yeni tabloyla değiştirir. Bu, masayla çalışamayacağınız tek ve çok kısa zamandır. Bundan sonra eski tablo ve günlüklerin bulunduğu tablo silinir ve dosya sisteminde yer açılır. İşlem tamamlandı.

Teoride her şey harika görünüyor ama pratikte ne oluyor? pg_repack'i yüksüz ve yük altında test ettik ve erken durma durumunda çalışmasını kontrol ettik (başka bir deyişle Ctrl+C kullanarak). Tüm testler pozitif çıktı.

Markete gittik ama sonra her şey beklediğimiz gibi gitmedi.

İlk gözleme satışta

İlk kümede benzersiz bir kısıtlamanın ihlaliyle ilgili bir hata aldık:

$ ./pg_repack -t tablename -o id
INFO: repacking table "tablename"
ERROR: query failed: 
    ERROR: duplicate key value violates unique constraint "index_16508"
DETAIL:  Key (id, index)=(100500, 42) already exists.

Bu sınırlamanın otomatik olarak oluşturulmuş bir adı index_16508 vardı - pg_repack tarafından oluşturuldu. Bileşiminde yer alan niteliklere dayanarak ona karşılık gelen “bizim” kısıtımızı belirledik. Sorun şu ki, bu tamamen sıradan bir sınırlama değil, ertelenmiş bir sınırlamadır (ertelenmiş kısıtlama), yani. doğrulaması sql komutundan daha sonra gerçekleştirilir ve bu da beklenmeyen sonuçlara yol açar.

Ertelenmiş kısıtlamalar: bunlara neden ihtiyaç duyulur ve nasıl çalışırlar?

Ertelenmiş kısıtlamalar hakkında küçük bir teori.
Basit bir örnek düşünelim: İki özelliğe sahip bir araba referans kitabı tablomuz var: arabanın adı ve dizindeki sırası.
Postgres: şişkinlik, pg_repack ve ertelenmiş kısıtlamalar

create table cars
(
  name text constraint pk_cars primary key,
  ord integer not null constraint uk_cars unique
);



Diyelim ki birinci ve ikinci arabaları değiştirmemiz gerekti. Basit çözüm, ilk değeri ikinciye ve ikinciyi birinciye güncellemektir:

begin;
  update cars set ord = 2 where name = 'audi';
  update cars set ord = 1 where name = 'bmw';
commit;

Ancak bu kodu çalıştırdığımızda tablodaki değerlerin sırası benzersiz olduğundan kısıtlama ihlali bekliyoruz:

[23305] ERROR: duplicate key value violates unique constraint “uk_cars”
Detail: Key (ord)=(2) already exists.

Bunu nasıl farklı yapabilirim? Birinci seçenek: Tabloda bulunmaması garanti edilen bir siparişe ek bir değer değişimi ekleyin, örneğin "-1". Programlamada buna "iki değişkenin değerlerinin üçüncüyle değiştirilmesi" denir. Bu yöntemin tek dezavantajı ek güncellemedir.

İkinci seçenek: Sıra değeri için tamsayılar yerine kayan noktalı veri türünü kullanacak şekilde tabloyu yeniden tasarlayın. Daha sonra, değer örneğin 1'den 2.5'e güncellendiğinde, ilk giriş otomatik olarak ikinci ve üçüncü arasında "duracaktır". Bu çözüm işe yarıyor ancak iki sınırlaması var. Öncelikle değer arayüzün herhangi bir yerinde kullanılıyorsa işinize yaramaz. İkincisi, veri türünün kesinliğine bağlı olarak, tüm kayıtların değerlerini yeniden hesaplamadan önce sınırlı sayıda olası eklemeye sahip olacaksınız.

Üçüncü seçenek: Kısıtlamanın yalnızca taahhüt sırasında kontrol edilmesi için ertelenmesini sağlayın:

create table cars
(
  name text constraint pk_cars primary key,
  ord integer not null constraint uk_cars unique deferrable initially deferred
);

İlk isteğimizin mantığı, commit anında tüm değerlerin benzersiz olmasını sağladığı için başarılı olacaktır.

Yukarıda tartışılan örnek elbette çok sentetik ama fikri ortaya koyuyor. Uygulamamızda, kullanıcılar aynı anda panoda paylaşılan widget nesneleriyle çalışırken çakışmaları çözmekten sorumlu olan mantığı uygulamak için ertelenmiş kısıtlamaları kullanıyoruz. Bu tür kısıtlamaları kullanmak uygulama kodunu biraz daha basit hale getirmemize olanak tanır.

Genel olarak kısıtlama türüne bağlı olarak Postgres'in bunları kontrol etmek için üç ayrıntı düzeyi vardır: satır, işlem ve ifade düzeyleri.
Postgres: şişkinlik, pg_repack ve ertelenmiş kısıtlamalar
Kaynak: yalvaranlar

CHECK ve NOT NULL her zaman satır seviyesinde kontrol edilir; diğer kısıtlamalar için tablodan da görülebileceği gibi farklı seçenekler vardır. Daha fazlasını okuyabilirsiniz burada.

Kısaca özetlemek gerekirse, bazı durumlarda ertelenen kısıtlamalar daha okunabilir kod ve daha az komut sağlar. Ancak hatanın oluştuğu an ile bunu öğrendiğiniz an zaman içinde ayrıldığından, hata ayıklama sürecini zorlaştırarak bunun bedelini ödemeniz gerekir. Bir başka olası sorun da, eğer istek ertelenmiş bir kısıtlama içeriyorsa, zamanlayıcının her zaman optimal bir plan oluşturamayabilmesidir.

pg_repack'in iyileştirilmesi

Ertelenmiş kısıtlamaların ne olduğunu ele aldık, ancak bunların sorunumuzla nasıl bir ilişkisi var? Daha önce aldığımız hatayı hatırlayalım:

$ ./pg_repack -t tablename -o id
INFO: repacking table "tablename"
ERROR: query failed: 
    ERROR: duplicate key value violates unique constraint "index_16508"
DETAIL:  Key (id, index)=(100500, 42) already exists.

Veriler bir günlük tablosundan yeni bir tabloya kopyalandığında oluşur. Bu garip görünüyor çünkü... günlük tablosundaki veriler kaynak tablodaki verilerle birlikte işlenir. Orijinal tablonun kısıtlamalarını karşılıyorlarsa, yeni tablodaki aynı kısıtlamaları nasıl ihlal edebilirler?

Görünen o ki, sorunun kökü pg_repack'in önceki adımında yatıyor; bu adım yalnızca indeksler oluşturuyor ancak kısıtlamalar yaratmıyor: eski tablonun benzersiz bir kısıtlaması vardı ve yenisi bunun yerine benzersiz bir indeks oluşturdu.

Postgres: şişkinlik, pg_repack ve ertelenmiş kısıtlamalar

Kısıtlama normalse ve ertelenmemişse, bunun yerine oluşturulan benzersiz endeksin bu kısıtlamaya eşdeğer olduğunu burada belirtmek önemlidir, çünkü Postgres'teki benzersiz kısıtlamalar, benzersiz bir dizin oluşturularak uygulanır. Ancak ertelenmiş kısıtlama durumunda davranış aynı değildir çünkü indeks ertelenemez ve her zaman sql komutu yürütüldüğünde kontrol edilir.

Dolayısıyla sorunun özü, kontrolün "gecikmesinde" yatmaktadır: orijinal tabloda taahhüt sırasında ve yeni tabloda sql komutu yürütüldüğünde ortaya çıkar. Bu, kontrollerin her iki durumda da aynı şekilde yapıldığından emin olmamız gerektiği anlamına gelir: ya her zaman gecikmeli ya da her zaman hemen.

Peki hangi fikirlerimiz vardı?

Ertelenene benzer bir dizin oluşturun

İlk fikir, her iki kontrolü de anında modda gerçekleştirmektir. Bu, birçok yanlış pozitif kısıtlamaya neden olabilir, ancak bunlardan az sayıda varsa, bu tür çatışmalar onlar için normal bir durum olduğundan, bu durum kullanıcıların çalışmasını etkilememelidir. Örneğin, iki kullanıcı aynı anda aynı widget'ı düzenlemeye başladığında ve ikinci kullanıcının istemcisinin, widget'in birinci kullanıcı tarafından düzenlenmesinin zaten engellendiği bilgisini alacak zamanı olmadığında bunlar meydana gelir. Böyle bir durumda sunucu ikinci kullanıcıyı reddeder ve istemcisi değişiklikleri geri alarak widget'ı engeller. Kısa bir süre sonra, ilk kullanıcı düzenlemeyi tamamladığında, ikincisi widget'in artık engellenmediğine dair bilgi alacak ve eylemini tekrarlayabilecektir.

Postgres: şişkinlik, pg_repack ve ertelenmiş kısıtlamalar

Kontrollerin her zaman ertelenmemiş modda olmasını sağlamak için orijinal ertelenmiş kısıtlamaya benzer yeni bir dizin oluşturduk:

CREATE UNIQUE INDEX CONCURRENTLY uk_tablename__immediate ON tablename (id, index);
-- run pg_repack
DROP INDEX CONCURRENTLY uk_tablename__immediate;

Test ortamında yalnızca birkaç beklenen hata aldık. Başarı! Üretimde pg_repack'i tekrar çalıştırdık ve bir saatlik çalışma sonucunda ilk kümede 5 hata aldık. Bu kabul edilebilir bir sonuçtur. Ancak ikinci kümede zaten hataların sayısı önemli ölçüde arttı ve pg_repack'i durdurmak zorunda kaldık.

Neden oldu? Bir hatanın meydana gelme olasılığı, kaç kullanıcının aynı anda aynı widget'larla çalıştığına bağlıdır. Görünüşe göre, o anda ilk kümede depolanan verilerde diğerlerine göre çok daha az rekabetçi değişiklik vardı; biz sadece “şanslıydık”.

Fikir işe yaramadı. Bu noktada iki çözüm daha gördük: ertelenmiş kısıtlamalardan kurtulmak için uygulama kodumuzu yeniden yazmak veya pg_repack'e bunlarla çalışmayı "öğretmek". Biz ikinciyi seçtik.

Yeni tablodaki dizinleri orijinal tablodaki ertelenmiş kısıtlamalarla değiştirin

Revizyonun amacı açıktı - eğer orijinal tablonun ertelenmiş bir kısıtlaması varsa, o zaman yenisi için böyle bir kısıtlama oluşturmanız gerekir, bir dizin değil.

Değişikliklerimizi test etmek için basit bir test yazdık:

  • ertelenmiş kısıtlama ve bir kayıt içeren tablo;
  • mevcut bir kayıtla çakışan bir döngüye veri eklemek;
  • bir güncelleme yapın – veriler artık çakışmıyor;
  • değişiklikleri uygulayın.

create table test_table
(
  id serial,
  val int,
  constraint uk_test_table__val unique (val) deferrable initially deferred 
);

INSERT INTO test_table (val) VALUES (0);
FOR i IN 1..10000 LOOP
  BEGIN
    INSERT INTO test_table VALUES (0) RETURNING id INTO v_id;
    UPDATE test_table set val = i where id = v_id;
    COMMIT;
  END;
END LOOP;

pg_repack'in orijinal sürümü her zaman ilk eklemede çöküyordu, değiştirilmiş sürüm hatasız çalışıyordu. Harika.

Üretime gidiyoruz ve verileri günlük tablosundan yenisine kopyalamanın aynı aşamasında yine bir hata alıyoruz:

$ ./pg_repack -t tablename -o id
INFO: repacking table "tablename"
ERROR: query failed: 
    ERROR: duplicate key value violates unique constraint "index_16508"
DETAIL:  Key (id, index)=(100500, 42) already exists.

Klasik durum: Her şey test ortamlarında çalışıyor ama üretimde çalışmıyor mu?

APPLY_COUNT ve iki grubun birleşimi

Kodu tam anlamıyla satır satır analiz etmeye başladık ve önemli bir nokta keşfettik: veriler günlük tablosundan yeni bir tabloya gruplar halinde aktarılıyor, APPLY_COUNT sabiti grubun boyutunu gösteriyor:

for (;;)
{
num = apply_log(connection, table, APPLY_COUNT);

if (num > MIN_TUPLES_BEFORE_SWITCH)
     continue;  /* there might be still some tuples, repeat. */
...
}

Sorun, birkaç işlemin potansiyel olarak kısıtlamayı ihlal edebileceği orijinal işlemden gelen verilerin, aktarıldığında iki grubun birleştiği yerde bulunabilmesidir - komutların yarısı ilk grupta işlenecek ve diğer yarısı saniyede. Ve burada, şansınıza bağlı olarak: eğer takımlar ilk partide hiçbir şeyi ihlal etmezse, o zaman her şey yolunda demektir, ancak ihlal ederse bir hata meydana gelir.

APPLY_COUNT 1000 kayda eşittir, bu da testlerimizin neden başarılı olduğunu açıklamaktadır; bunlar "toplu bağlantı" durumunu kapsamamaktadır. Ekleme ve güncelleme olmak üzere iki komut kullandık, yani iki komutun tam olarak 500 işlemi her zaman toplu olarak yerleştirildi ve herhangi bir sorun yaşamadık. İkinci güncellemeyi ekledikten sonra düzenlememiz çalışmayı durdurdu:

FOR i IN 1..10000 LOOP
  BEGIN
    INSERT INTO test_table VALUES (1) RETURNING id INTO v_id;
    UPDATE test_table set val = i where id = v_id;
    UPDATE test_table set val = i where id = v_id; -- one more update
    COMMIT;
  END;
END LOOP;

Dolayısıyla bir sonraki görev, bir işlemde değiştirilen orijinal tablodaki verilerin yine bir işlem içinde yeni tabloda yer almasını sağlamaktır.

Toplu işlemin reddedilmesi

Ve yine iki çözümümüz vardı. İlk olarak: gruplara ayırmayı tamamen bırakalım ve verileri tek bir işlemle aktaralım. Bu çözümün avantajı basitliğiydi - gerekli kod değişiklikleri minimum düzeydeydi (bu arada, pg_reorg'un eski sürümlerinde tam olarak bu şekilde çalışıyordu). Ancak bir sorun var; uzun vadeli bir işlem yaratıyoruz ve bu, daha önce de söylediğimiz gibi, yeni bir şişkinliğin ortaya çıkmasına yönelik bir tehdit oluşturuyor.

İkinci çözüm daha karmaşıktır ancak muhtemelen daha doğrudur: günlük tablosunda, tabloya veri ekleyen işlemin tanımlayıcısını içeren bir sütun oluşturun. Daha sonra verileri kopyaladığımızda bu özelliğe göre gruplandırıp ilgili değişikliklerin birlikte aktarılmasını sağlayabiliriz. Toplu işlem birkaç işlemden (veya büyük bir işlemden) oluşturulacak ve boyutu, bu işlemlerde ne kadar verinin değiştirildiğine bağlı olarak değişecektir. Farklı işlemlere ait veriler günlük tablosuna rastgele bir sırayla girdiğinden, bunları daha önce olduğu gibi sıralı olarak okumanın artık mümkün olmayacağını unutmamak önemlidir. Her istek için tx_id'ye göre filtreleme ile seqscan çok pahalıdır, bir dizin gereklidir, ancak aynı zamanda güncelleme ek yükü nedeniyle yöntemi de yavaşlatacaktır. Genel olarak her zaman olduğu gibi bir şeyleri feda etmeniz gerekiyor.

Bu yüzden daha basit olduğu için ilk seçenekle başlamaya karar verdik. Öncelikle uzun bir işlemin gerçek bir sorun olup olmayacağını anlamak gerekiyordu. Eski tablodan yeni tabloya asıl veri aktarımı da tek ve uzun bir işlemde gerçekleştiği için soru “bu işlemi ne kadar artıracağız?” sorusuna dönüştü. İlk işlemin süresi esas olarak tablonun boyutuna bağlıdır. Yenisinin süresi, veri aktarımı sırasında tabloda kaç değişiklik biriktiğine bağlıdır; yükün yoğunluğuna göre. pg_repack çalıştırması, hizmet yükünün minimum düzeyde olduğu bir dönemde gerçekleşti ve değişikliklerin hacmi, tablonun orijinal boyutuyla karşılaştırıldığında orantısız derecede küçüktü. Yeni bir işlemin süresini ihmal edebileceğimize karar verdik (karşılaştırma için ortalama 1 saat 2-3 dakikadır).

Deneyler olumluydu. Üretime de başlayın. Açıklık sağlamak için, çalıştırdıktan sonra veritabanlarından birinin boyutunu gösteren bir resim:

Postgres: şişkinlik, pg_repack ve ertelenmiş kısıtlamalar

Bu çözümden tamamen memnun kaldığımız için ikinciyi uygulamaya çalışmadık ancak bunu uzantı geliştiricileriyle tartışma olasılığını düşünüyoruz. Maalesef mevcut revizyonumuz henüz yayına hazır değil, çünkü sorunu yalnızca ertelenmiş kısıtlamalarla çözdük ve tam teşekküllü bir yama için diğer türlere destek sağlamak gerekiyor. Gelecekte bunu başarabilmeyi umuyoruz.

Belki de bir sorunuz var, neden pg_repack'in modifikasyonuyla bu hikayeye dahil olduk ve örneğin analoglarını kullanmadık? Bir noktada biz de bunu düşündük, ancak bunu daha önce ertelenmiş kısıtlamaların olmadığı tablolarda kullanmanın olumlu deneyimi, bizi sorunun özünü anlamaya ve çözmeye çalışmaya motive etti. Ayrıca diğer çözümleri kullanmak da test yapmak için zaman gerektirir, bu yüzden önce buradaki sorunu çözmeye çalışacağımıza ve bunu makul bir sürede yapamayacağımızı anladığımızda analoglara bakmaya karar verdik. .

Bulgular

Kendi deneyimlerimize dayanarak önerebileceklerimiz:

  1. Şişkinliğinizi izleyin. İzleme verilerine dayanarak, otovakumun ne kadar iyi yapılandırıldığını anlayabilirsiniz.
  2. Şişmeyi kabul edilebilir bir seviyede tutmak için AUTOVACUUM'u ayarlayın.
  3. Şişkinlik hala büyüyorsa ve kullanıma hazır araçları kullanarak bunun üstesinden gelemiyorsanız, harici uzantıları kullanmaktan korkmayın. Önemli olan her şeyi iyi test etmektir.
  4. İhtiyaçlarınıza uyacak şekilde harici çözümleri değiştirmekten korkmayın; bazen bu, kendi kodunuzu değiştirmekten daha etkili ve hatta daha kolay olabilir.

Kaynak: habr.com

Yorum ekle