PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Raporda izin veren bazı yaklaşımlar sunulmaktadır. Günde milyonlarca sorgu olduğunda SQL sorgularının performansını izleyinve izlenen yüzlerce PostgreSQL sunucusu var.

Hangi teknik çözümler bu kadar büyük miktarda bilgiyi verimli bir şekilde işlememize olanak tanıyor ve bu sıradan bir geliştiricinin hayatını nasıl kolaylaştırıyor?


Kim ilgileniyor? Belirli problemlerin analizi ve çeşitli optimizasyon teknikleri PostgreSQL'de SQL sorguları ve tipik DBA sorunlarını çözme - ayrıca bir dizi makale okuyun bu konuda.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)
Adım Kirill Borovikov, temsil ediyorum Tensör şirketi. Özellikle şirketimizdeki veritabanlarıyla çalışma konusunda uzmanım.

Bugün size, tek bir sorgunun performansını "ayırmak" yerine sorunu topluca çözmek zorunda olduğunuzda sorguları nasıl optimize ettiğimizi anlatacağım. Milyonlarca istek olduğunda ve bazılarını bulmanız gerektiğinde çözüm yaklaşımları bu büyük sorun.

Genel olarak bir milyon müşterimiz için Tensor VLSI bizim uygulamamızdır: kurumsal sosyal ağ, görüntülü iletişim çözümleri, iç ve dış belge akışı için çözümler, muhasebe ve depolar için muhasebe sistemleri... Yani, entegre iş yönetimi için 100'den fazla farklı özelliğin bulunduğu böyle bir "mega birleştirme" iç projeler.

Hepsinin normal şekilde çalışmasını ve gelişmesini sağlamak için ülke genelinde 10 geliştirme merkezimiz var ve bunların sayısı daha da artıyor 1000 geliştirici.

2008'den beri PostgreSQL ile çalışıyoruz ve işlediğimiz verilerin büyük bir kısmını (müşteri verileri, istatistiksel, analitik, harici bilgi sistemlerinden gelen veriler) biriktirdik. 400 TB'tan fazla. Yalnızca üretimde 250'ye yakın sunucu var ve toplamda izlediğimiz 1000'e yakın veritabanı sunucusu var.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

SQL bildirimsel bir dildir. Bir şeyin "nasıl" çalışması gerektiğini değil, "neyi" başarmak istediğinizi tanımlarsınız. DBMS, JOIN'in nasıl yapılacağını daha iyi bilir - tablolarınızı nasıl bağlayacağınızı, hangi koşulları uygulayacağınızı, dizinden nelerin geçeceğini, nelerin geçemeyeceğini...

Bazı DBMS'ler şu ipuçlarını kabul eder: "Hayır, bu iki tabloyu falan kuyruğa bağlayın" ama PostgreSQL bunu yapamaz. Bu, önde gelen geliştiricilerin bilinçli tutumudur: "Geliştiricilerin bir tür ipucu kullanmasına izin vermektense, sorgu iyileştiriciyi bitirmeyi tercih ederiz."

Ancak PostgreSQL, “dışarıdakinin” kendisini kontrol etmesine izin vermese de, mükemmel bir şekilde izin veriyor bakalım içinde neler oluyorsorgunuzu ne zaman çalıştırdığınızı ve nerede sorun yaşadığını öğrenin.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Genel olarak, bir geliştiricinin (DBA'ya) genellikle hangi klasik sorunları vardır? “Burada talebi yerine getirdik ve bizde her şey yavaş, her şey asılı duruyor, bir şeyler oluyor... Bir çeşit sorun var!”

Sebepler neredeyse her zaman aynıdır:

  • verimsiz sorgu algoritması
    Geliştirici: "Şimdi ona JOIN aracılığıyla SQL'de 10 tablo veriyorum..." - ve koşullarının mucizevi bir şekilde etkili bir şekilde "çözülmesini" ve her şeyi hızlı bir şekilde elde etmesini bekliyor. Ancak mucizeler gerçekleşmez ve bu kadar değişkenliğe sahip herhangi bir sistem (bir FROM'da 10 tablo) her zaman bir tür hata verir. [makale]
  • eski istatistikler
    Bu nokta özellikle PostgreSQL için çok önemlidir; sunucuya büyük bir veri kümesi "döktüğünüzde", bir istekte bulunduğunuzda ve bu tabletinizi "seks canittiğinde" gerçekleştirir. Çünkü dün 10 kayıt vardı bugün 10 milyon ama PostgreSQL henüz bunun farkında değil ve bunu anlatmamız gerekiyor. [makale]
  • Kaynaklara "tak"
    Yeterli disk, bellek veya işlemci performansına sahip olmayan zayıf bir sunucuya büyük ve ağır yüklü bir veritabanı yüklediniz. Ve hepsi bu... Bir yerlerde artık üstüne çıkamayacağınız bir performans tavanı var.
  • Kilitlemek
    Bu zor bir nokta, ancak bunlar en çok çeşitli değiştirme sorguları (INSERT, UPDATE, DELETE) ile ilgilidir - bu ayrı bir büyük konudur.

Plan almak

...Ve diğer her şey için bir plana ihtiyacım var! Sunucunun içinde neler olduğunu görmemiz gerekiyor.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

PostgreSQL için sorgu yürütme planı, metin gösterimindeki sorgu yürütme algoritmasının bir ağacıdır. Planlayıcının yaptığı analiz sonucunda en etkili olduğu tespit edilen algoritma kesinlikle budur.

Her ağaç düğümü bir işlemdir: bir tablodan veya dizinden veri almak, bir bitmap oluşturmak, iki tabloyu birleştirmek, seçimleri birleştirmek, kesiştirmek veya hariç tutmak. Bir sorgunun yürütülmesi, bu ağacın düğümleri arasında dolaşmayı içerir.

Sorgu planını almanın en kolay yolu ifadeyi yürütmektir EXPLAIN. Tüm gerçek nitelikleri elde etmek, yani aslında tabanda bir sorgu yürütmek için - EXPLAIN (ANALYZE, BUFFERS) SELECT ....

Kötü kısmı: Çalıştırdığınızda, "burada ve şimdi" gerçekleşir, bu nedenle yalnızca yerel hata ayıklama için uygundur. Güçlü bir veri akışına sahip, oldukça yüklü bir sunucuyu alırsanız ve şunu görürseniz: “Ah! Burada yavaş bir uygulamamız varXia rica etmek." Yarım saat, bir saat önce - siz çalıştırırken ve günlüklerden bu isteği alıp sunucuya geri getirirken, tüm veri kümeniz ve istatistikleriniz değişti. Hata ayıklamak için çalıştırırsınız ve hızlı çalışır! Ve nedenini, nedenini anlayamıyorsun oldu yavaşça.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Akıllı insanlar, isteğin sunucuda yürütüldüğü anda tam olarak ne olduğunu anlamak için şunu yazdı: auto_explain modülü. Hemen hemen en yaygın PostgreSQL dağıtımlarının hepsinde bulunur ve yapılandırma dosyasından kolayca etkinleştirilebilir.

Bazı isteklerin sizin söylediğiniz sınırdan daha uzun süre çalıştığını fark ederse, Bu isteğin planının “anlık görüntüsünü” alır ve bunları günlüğe birlikte yazar.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Şimdi her şey yolunda görünüyor, kütüğe gidiyoruz ve orada görüyoruz... [metin ayak örtüsü]. Ancak bunun mükemmel bir plan olduğu gerçeği dışında bu konuda hiçbir şey söyleyemeyiz çünkü yürütülmesi 11 ms sürdü.

Her şey yolunda gibi görünüyor ama gerçekte ne olduğu konusunda hiçbir şey net değil. Genel saatin dışında aslında hiçbir şey görmüyoruz. Çünkü böylesine düz bir metin "kuzusuna" bakmak genellikle görsel değildir.

Ancak bariz olmasa da, sakıncalı olsa da daha temel sorunlar var:

  • Düğüm şunu gösterir: tüm alt ağacın kaynaklarının toplamı onun altında. Yani, eğer altında iç içe geçmiş bir durum varsa, bu belirli Dizin Taraması için ne kadar zaman harcandığını öğrenemezsiniz. İçeride "çocuklar" ve koşullu değişkenler, CTE'ler olup olmadığına dinamik olarak bakmalı ve tüm bunları "zihnimizde" çıkarmalıyız.
  • İkinci nokta: düğümde gösterilen zaman tek düğüm yürütme süresi. Bu düğüm, örneğin bir döngü tablosunun birkaç kez kaydedilmesi sonucunda yürütüldüyse, o zaman planda döngülerin (bu düğümün döngülerinin) sayısı artar. Ancak atomik yürütme süresinin kendisi plan açısından aynı kalır. Yani, bu düğümün toplamda ne kadar süreyle gerçekleştirildiğini anlamak için, bir şeyi diğeriyle - yine "kafanızda" çarpmanız gerekir.

Bu gibi durumlarda “En zayıf halka kim?” sorusunu anlayın. neredeyse imkansız. Bu nedenle, geliştiricilerin kendileri bile "kılavuzda" şunu yazıyor: “Planı anlamak öğrenilmesi, deneyimlenmesi gereken bir sanattır...”.

Ama 1000 geliştiricimiz var ve bu deneyimi her birine aktaramazsınız. Ben, sen, o biliyor ama oradaki biri artık bilmiyor. Belki öğrenir ya da öğrenmez ama şimdi çalışması gerekiyor; peki bu deneyimi nereden edinecek?

Görselleştirmeyi planlayın

Bu nedenle, bu sorunlarla başa çıkabilmek için ihtiyacımız olduğunu fark ettik. planın iyi görselleştirilmesi. [madde]

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

İlk önce "piyasaya" gittik - neyin var olduğunu görmek için internete bakalım.

Ancak az çok gelişmekte olan nispeten "canlı" çözümlerin çok az olduğu ortaya çıktı - kelimenin tam anlamıyla yalnızca bir tane: açıkla.depesz.com Hubert Lubaczewski'nin yazdığı. Planın metin temsilini "besleme" alanına girdiğinizde, size ayrıştırılmış verileri içeren bir tablo gösterilir:

  • düğümün kendi işlem süresi
  • alt ağacın tamamı için toplam süre
  • istatistiksel olarak beklenen alınan kayıt sayısı
  • düğüm gövdesinin kendisi

Bu hizmet aynı zamanda bir bağlantı arşivini paylaşma özelliğine de sahiptir. Planınızı oraya attınız ve şöyle dediniz: “Hey Vasya, işte bir bağlantı, orada bir sorun var.”

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Ama aynı zamanda küçük sorunlar da var.

İlk olarak, büyük miktarda "kopyala-yapıştır". Kütüğün bir parçasını alıp oraya yapıştırıyorsunuz ve tekrar tekrar yapıştırıyorsunuz.

İkinci olarak, okunan veri miktarının analizi yok — çıktı veren aynı tamponlar EXPLAIN (ANALYZE, BUFFERS), burada göremiyoruz. Onları nasıl parçalara ayıracağını, anlayacağını ve onlarla nasıl çalışacağını bilmiyor. Çok fazla veri okurken diski ve bellek önbelleğini yanlış konumlandırıyor olabileceğinizi fark ettiğinizde bu bilgi çok önemlidir.

Üçüncü olumsuz nokta ise bu projenin gelişiminin oldukça zayıf olmasıdır. Taahhütler çok küçük, altı ayda bir olması iyidir ve kod Perl'dedir.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Ama bunların hepsi “şarkı sözü”, bununla bir şekilde yaşayabilirdik ama bizi bu hizmetten büyük ölçüde uzaklaştıran bir şey var. Bunlar Ortak Tablo İfadesinin (CTE) ve InitPlan/SubPlan gibi çeşitli dinamik düğümlerin analizindeki hatalardır.

Bu resme inanıyorsanız, her bir düğümün toplam yürütme süresi, tüm isteğin toplam yürütme süresinden daha fazladır. Basit - bu CTE'nin üretim süresi CTE Tarama düğümünden çıkarılmadı. Bu nedenle, CTE taramasının ne kadar sürdüğü sorusunun doğru cevabını artık bilmiyoruz.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Sonra kendi yazımızı yazmanın zamanının geldiğini fark ettik - yaşasın! Her geliştirici şunu söylüyor: "Artık kendimizinkini yazacağız, çok kolay olacak!"

Web hizmetlerine özgü bir yığın aldık: güzel diyagramlar için Node.js + Express tabanlı bir çekirdek, Bootstrap ve D3.js kullanıldı. Ve beklentilerimiz tamamen haklı çıktı; ilk prototipi 2 hafta içinde aldık:

  • özel plan ayrıştırıcı
    Yani artık PostgreSQL tarafından oluşturulan herhangi bir planı ayrıştırabiliriz.
  • dinamik düğümlerin doğru analizi - CTE Taraması, InitPlan, SubPlan
  • tampon dağılımının analizi - veri sayfalarının bellekten, nerede yerel önbellekten, nerede diskten okunduğu
  • netlik kazandı
    Tüm bunları günlüğe "kazmak" değil, resimdeki "en zayıf halkayı" hemen görmek için.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Sözdizimi vurgulamanın da dahil olduğu buna benzer bir şey elde ettik. Ancak genellikle geliştiricilerimiz artık planın tam bir temsiliyle değil, daha kısa bir temsiliyle çalışıyor. Sonuçta, tüm sayıları zaten ayrıştırdık ve sola ve sağa attık ve ortada sadece ilk satırı bıraktık, ne tür bir düğüm: CTE Taraması, CTE üretimi veya bazı işaretlere göre Sıra Taraması.

Bu, adlandırdığımız kısaltılmış gösterimdir. planı şablonu.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Başka ne uygun olabilir? Toplam zamanımızın ne kadarının hangi düğüme ayrıldığını görmek ve bunu bir kenara "bağlamak" uygun olacaktır. yuvarlak diyagram.

Düğüme işaret ediyoruz ve görüyoruz - Sıra Taramasının toplam sürenin dörtte birinden azını aldığı ve kalan 3/4'ünün CTE Taraması tarafından alındığı ortaya çıktı. Korku! Bu, sorgularınızda aktif olarak kullanıyorsanız CTE Taramasının "ateş hızı" hakkında küçük bir nottur. Çok hızlı değiller - normal masa taramasından bile daha düşükler. [madde] [madde]

Ancak genellikle bu tür diyagramlar daha ilginç, daha karmaşık olur; hemen bir parçayı işaret ettiğimizde ve örneğin, bazı Sıralı Taramaların yarıdan fazlasının "yediğini" gördüğümüzde. Üstelik içeride bir tür Filtre vardı, buna göre pek çok kayıt atılmıştı... Bu resmi doğrudan geliştiriciye atıp şöyle diyebilirsiniz: “Vasya, burada senin için her şey kötü! Anlayın, bakın, bir şeyler ters gidiyor!”

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Doğal olarak işin içinde bazı “tırmıklar” da vardı.

İlk karşılaştığımız şey yuvarlama sorunuydu. Plandaki her bir düğümün zamanı 1 μs doğrulukla gösterilir. Ve düğüm döngüsü sayısı örneğin 1000'i aştığında - PostgreSQL yürütüldükten sonra "doğruluk dahilinde" bölünür, ardından geriye doğru hesaplama yaparken toplam süreyi "0.95 ms ile 1.05 ms arasında" elde ederiz. Sayım mikrosaniyelere ulaştığında sorun değil, ancak zaten [mili]saniye olduğunda, kaynakları "kim ne kadar tüketti" planının düğümleriyle "bağlantısını çözerken" bu bilgiyi dikkate almanız gerekir.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Daha karmaşık olan ikinci nokta, kaynakların (bu arabelleklerin) dinamik düğümler arasındaki dağıtımıdır. Bu bize prototipin ilk 2 haftasına artı 4 haftaya mal oldu.

Bu tür bir sorunla karşılaşmak oldukça kolaydır - bir CTE yapıyoruz ve sözde içinde bir şeyler okuyoruz. Aslında PostgreSQL "akıllıdır" ve oradaki hiçbir şeyi doğrudan okumaz. Daha sonra ondan ilk plağı ve aynı CTE'den yüz birinci plağı alıyoruz.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Plana bakıyoruz ve anlıyoruz - bu garip, Sıralı Taramada "tüketilen" 3 arabelleğimiz (veri sayfaları), CTE Taramasında 1 tane daha ve ikinci CTE Taramasında 2 tane daha var. Yani, her şeyi özetlersek 6 elde ederiz, ancak tabletten sadece 3 okuruz! CTE Scan hiçbir yerden hiçbir şey okumaz ancak doğrudan işlem belleğiyle çalışır. Yani burada açıkça bir sorun var!

Aslında burada Seq Scan'den istenen 3 sayfalık verinin tamamı var, önce 1'i 1. CTE Taramasını istedi, sonra 2.'yi istedi ve 2 sayfa daha kendisine okundu. 3 sayfa değil 6 sayfa veri okundu.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Ve bu resim bizi, bir planın uygulanmasının artık bir ağaç değil, sadece bir tür döngüsel olmayan grafik olduğunun anlaşılmasına götürdü. Ve elimizde şöyle bir diyagram var, böylece “neyin nereden geldiğini” anlayalım. Yani burada pg_class'tan CTE oluşturup iki kez istedik ve 2. kez istediğimizde neredeyse zamanımızın tamamı şubede geçti. 101. girişi okumanın tabletten 1. girişi okumaktan çok daha pahalı olduğu açıktır.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Bir süre nefes verdik. Dediler ki: “Şimdi Neo, kung fu'yu biliyorsun! Artık deneyimimiz tam ekranınızda. Artık onu kullanabilirsin." [madde]

Günlük birleştirme

1000 geliştiricimiz rahat bir nefes aldı. Ancak yalnızca yüzlerce "savaş" sunucumuz olduğunu ve geliştiriciler açısından tüm bu "kopyala-yapıştır" işlemlerinin hiç de uygun olmadığını anladık. Kendimiz toplamamız gerektiğini anladık.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Genel olarak, istatistikleri toplayabilen standart bir modül vardır, ancak bunun da yapılandırmada etkinleştirilmesi gerekir - bu modül pg_stat_statements. Ama bize yakışmadı.

Öncelikle aynı veri tabanında farklı şemalar kullanarak aynı sorgulara atama yapar. farklı Sorgu Kimlikleri. Yani, eğer ilk önce bunu yaparsanız SET search_path = '01'; SELECT * FROM user LIMIT 1;Sonra SET search_path = '02'; ve aynı istek, o zaman bu modülün istatistikleri farklı kayıtlara sahip olacak ve şemaları dikkate almadan, özellikle bu istek profili bağlamında genel istatistikleri toplayamayacağım.

Kullanmamızı engelleyen ikinci nokta ise plan eksikliği. Yani plan yok, sadece talebin kendisi var. Neyin yavaşladığını görüyoruz ama nedenini anlamıyoruz. Ve burada hızla değişen veri seti sorununa geri dönüyoruz.

Ve son an - "gerçeklerin" eksikliği. Yani, sorgu yürütmenin belirli bir örneğini ele alamazsınız; yoktur, yalnızca toplu istatistikler vardır. Bununla çalışmak mümkün olmasına rağmen çok zordur.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Bu nedenle kopyala-yapıştırla mücadele etmeye karar verdik ve yazmaya başladık. коллектор.

Toplayıcı, SSH aracılığıyla bağlanır, bir sertifika kullanarak veritabanıyla sunucuya güvenli bir bağlantı kurar ve tail -F günlük dosyasında ona "yapışır". Yani bu oturumda tüm günlük dosyasının tam bir “aynasını” alıyoruz, sunucunun oluşturduğu. Sunucunun üzerindeki yük minimum düzeydedir çünkü orada hiçbir şeyi ayrıştırmıyoruz, sadece trafiği yansıtıyoruz.

Node.js'de arayüzü yazmaya zaten başladığımız için toplayıcıyı onun içine yazmaya devam ettik. Ve bu teknoloji kendini haklı çıkardı, çünkü JavaScript'i zayıf biçimlendirilmiş metin verileriyle, yani günlüklerle çalışmak için kullanmak çok uygundur. Ve bir arka uç platformu olarak Node.js altyapısının kendisi, ağ bağlantılarıyla ve aslında her türlü veri akışıyla kolay ve rahat bir şekilde çalışmanıza olanak tanır.

Buna göre, iki bağlantıyı "uzatıyoruz": birincisi kütüğün kendisini "dinlemek" ve onu kendimize almak, ikincisi ise periyodik olarak üsse sormak. "Fakat kayıt, oid 123'e sahip işaretin engellendiğini gösteriyor" ancak bu, geliştirici için hiçbir şey ifade etmiyor ve veritabanına "OID = 123 nedir?" diye sormak güzel olurdu. Ve böylece periyodik olarak üsse kendimiz hakkında henüz bilmediğimiz şeyleri soruyoruz.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

“Hesaplamadığınız tek şey var, fil benzeri bir arı türü var!..” 10 sunucuyu izlemek istediğimizde bu sistemi geliştirmeye başladık. Anlayışımız açısından en kritik olanı, başa çıkılması zor olan bazı sorunların ortaya çıktığı yerdi. Ancak ilk çeyrekte izleme için yüz tane aldık - çünkü sistem çalışıyordu, herkes bunu istiyordu, herkes rahattı.

Bütün bunların eklenmesi gerekiyor, veri akışı büyük ve aktif. Aslında izlediğimiz, başa çıkabildiğimiz şey, kullandığımız şeydir. PostgreSQL'i veri depolama alanı olarak da kullanıyoruz. Ve hiçbir şey veriyi operatörden "dökmek" kadar hızlı olamaz COPY Henüz değil.

Ancak verileri basitçe “dökmek” aslında bizim teknolojimiz değil. Çünkü yüz sunucuda saniyede yaklaşık 50 bin isteğiniz varsa bu, günde 100-150 GB log üretecektir. Bu nedenle tabanı dikkatlice "kesmek" zorunda kaldık.

Öncelikle şunu yaptık güne göre bölümlemeÇünkü genel olarak hiç kimse günler arasındaki korelasyonla ilgilenmiyor. Bu gece uygulamanın yeni bir sürümünü ve şimdiden bazı yeni istatistikleri yayınlasanız, dün sahip olduklarınızın ne önemi kalır?

İkincisi öğrendik (zorlandık) kullanarak yazmak çok çok hızlı COPY. Yani sadece değil COPYçünkü o daha hızlı INSERTve hatta daha hızlı.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Üçüncü nokta; bunu yapmak zorundaydım sırasıyla tetikleyicileri ve yabancı anahtarları terk edin. Yani hiçbir şekilde referans bütünlüğümüz yok. Çünkü bir çift FK içeren bir tablonuz varsa ve veritabanı yapısında "işte FK tarafından örneğin bir kayıt grubuna referans verilen bir log kaydı" diyorsanız, onu eklediğinizde PostgreSQL onu nasıl alıp dürüstçe yapacağından başka hiçbir şey kalmadı SELECT 1 FROM master_fk1_table WHERE ... eklemeye çalıştığınız tanımlayıcıyla - sadece bu kaydın orada mevcut olup olmadığını, ekleme işleminizle bu Yabancı Anahtarı "kırmadığınızı" kontrol etmek için.

Hedef tabloya ve dizinlerine tek bir kayıt yerine, onun başvurduğu tüm tabloları okumanın ek faydasını elde ederiz. Ancak buna hiç ihtiyacımız yok - görevimiz mümkün olduğu kadar çok ve en az yükle mümkün olduğu kadar hızlı kayıt yapmaktır. Yani FK - aşağı!

Bir sonraki nokta toplama ve karmadır. Başlangıçta bunları veritabanına uyguladık - sonuçta, bir kayıt geldiğinde bunu hemen bir tür tablette yapmak uygun olur Tetikleyicinin tam ortasında "artı bir". Uygun, ama aynı kötü şey - bir kayıt eklersiniz, ancak başka bir tablodan başka bir şey okumak ve yazmak zorunda kalırsınız. Üstelik sadece okuyup yazmakla kalmıyorsunuz, aynı zamanda bunu her zaman yapıyorsunuz.

Şimdi, belirli bir ana bilgisayardan geçen isteklerin sayısını basit bir şekilde saydığınız bir tablonuz olduğunu hayal edin: +1, +1, +1, ..., +1. Ve prensip olarak buna ihtiyacınız yok - hepsi mümkün toplayıcının hafızasındaki toplamı ve tek seferde veritabanına gönder +10.

Evet, bazı sorunlar durumunda mantıksal bütünlüğünüz "çökebilir", ancak bu neredeyse gerçekçi olmayan bir durumdur - çünkü normal bir sunucunuz var, denetleyicide bir pil var, bir işlem günlüğünüz var, bir günlük var dosya sistemi... Genel olarak buna değmez. Tetikleyicileri/FK'yi çalıştırmaktan elde ettiğiniz üretkenlik kaybı, yaptığınız masrafa değmez.

Karma işleminde de durum aynıdır. Belirli bir istek size uçuyor, veritabanında ondan belirli bir tanımlayıcı hesaplıyorsunuz, bunu veritabanına yazıyorsunuz ve sonra bunu herkese anlatıyorsunuz. Kayıt sırasında, aynı şeyi kaydetmek isteyen ikinci bir kişi yanınıza gelinceye ve engelleninceye kadar her şey yolunda ve bu zaten kötü. Bu nedenle, bazı kimliklerin oluşturulmasını istemciye (veritabanına göre) aktarabiliyorsanız, bunu yapmak daha iyidir.

MD5'i metinden kullanmak bizim için mükemmeldi - istek, plan, şablon,... Bunu toplayıcı tarafta hesaplıyoruz ve hazır kimliği veritabanına "döküyoruz". MD5'in uzunluğu ve günlük bölümleme, olası çarpışmalar konusunda endişelenmememizi sağlıyor.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Ancak tüm bunları hızlı bir şekilde kaydetmek için kayıt prosedürünün kendisini değiştirmemiz gerekiyordu.

Verileri genellikle nasıl yazarsınız? Bir tür veri kümemiz var, onu birkaç tabloya bölüyoruz ve sonra onu KOPYALIYORUZ - önce birinciye, sonra ikinciye, üçüncüye... Bu sakıncalı çünkü üç adımda bir veri akışı yazıyor gibiyiz sırayla. Hoş olmayan. Daha hızlı yapılabilir mi? Olabilmek!

Bunu yapmak için bu akışları birbirine paralel olarak ayrıştırmak yeterlidir. Hatalarımız, isteklerimiz, şablonlarımız, engellemelerimiz olduğu, ... ayrı başlıklarda uçtuğu ortaya çıktı - ve hepsini paralel olarak yazıyoruz. Bunun için yeterli her bir hedef tablo için bir KOPYALAMA kanalını sürekli açık tutun.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Yani koleksiyoncuda her zaman bir akış vardıriçine ihtiyacım olan verileri yazabileceğim. Ancak veritabanının bu verileri görmesi ve birisinin bu verinin yazılmasını bekleyerek takılıp kalmaması için, KOPYALAMA belirli aralıklarla kesilmelidir. Bizim için en etkili süre yaklaşık 100ms idi; onu kapatıp hemen aynı tabloya tekrar açıyoruz. Bazı zirvelerde tek bir akıştan yeterince yararlanamıyorsak, belirli bir sınıra kadar havuzlama yapıyoruz.

Ek olarak, böyle bir yük profili için, kayıtlar gruplar halinde toplandığında herhangi bir toplamanın kötü olduğunu öğrendik. Klasik kötülük INSERT ... VALUES ve ayrıca 1000 kayıt. Çünkü o noktada medyada yazmanın zirvesi var ve diske bir şeyler yazmaya çalışan herkes bekliyor olacak.

Bu tür anormalliklerden kurtulmak için hiçbir şeyi birleştirmeyin, hiç tamponlama. Ve eğer diske ara belleğe alma gerçekleşirse (neyse ki, Node.js'deki Stream API bunu öğrenmenize olanak sağlar) - bu bağlantıyı erteleyin. Tekrar ücretsiz olduğuna dair bir etkinlik aldığınızda, birikmiş kuyruktan ona yazın. Ve meşgulken havuzdan bir sonraki ücretsiz olanı alın ve ona yazın.

Veri kaydına bu yaklaşımı uygulamadan önce yaklaşık 4K yazma işlemimiz vardı ve bu sayede yükü 4 kat azalttık. Şimdi yeni izlenen veritabanları sayesinde 6 kat daha büyüdüler - 100 MB/s'ye kadar. Ve şimdi son 3 aya ait günlükleri yaklaşık 10-15 TB'lık bir hacimde saklıyoruz ve herhangi bir geliştiricinin herhangi bir sorunu yalnızca üç ay içinde çözebileceğini umuyoruz.

Sorunları anlıyoruz

Ancak tüm bu verileri basitçe toplamak iyi, faydalı, alakalı ancak yeterli değil; anlaşılması gerekiyor. Çünkü bunlar günde milyonlarca farklı plan.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Ama milyonlar yönetilemez, önce “küçük” yapmalıyız. Ve her şeyden önce bu "küçük" şeyi nasıl organize edeceğinize karar vermeniz gerekiyor.

Üç önemli nokta belirledik:

  • kim bu isteği gönderdi
    Yani, hangi uygulamadan "geldi": web arayüzü, arka uç, ödeme sistemi veya başka bir şey.
  • nerede oldu
    Hangi spesifik sunucuda? Çünkü, bir uygulama altında birden fazla sunucunuz varsa ve biri aniden "aptallaşırsa" ("disk çürümüş", "bellek sızdırılmış", başka bir sorun nedeniyle), o zaman sunucuyu özel olarak ele almanız gerekir.
  • gibi sorun öyle ya da böyle kendini gösterdi

Bize "kimin" istek gönderdiğini anlamak için standart bir araç kullanıyoruz: oturum değişkeni ayarlama: SET application_name = '{bl-host}:{bl-method}'; — isteğin geldiği iş mantığı ana bilgisayarının adını ve onu başlatan yöntem veya uygulamanın adını göndeririz.

İsteğin "sahibini" ilettikten sonra, günlüğe çıktılanması gerekir - bunun için değişkeni yapılandırırız log_line_prefix = ' %m [%p:%v] [%d] %r %a'. İlgilenenler için belki kılavuza baktüm bunların anlamı ne. Günlükte gördüğümüz ortaya çıktı:

  • zaman
  • süreç ve işlem tanımlayıcıları
  • veri tabanı ismi
  • Bu isteği gönderen kişinin IP'si
  • ve yöntem adı

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Daha sonra, bir isteğin farklı sunucular arasındaki korelasyonuna bakmanın pek de ilginç olmadığını fark ettik. Bir uygulamanın orada burada eşit derecede başarısız olduğu bir durumla sık sık karşılaşmazsınız. Ancak aynı olsa bile bu sunuculardan herhangi birine bakın.

İşte kesim "bir sunucu - bir gün" herhangi bir analiz için bizim için yeterli olduğu ortaya çıktı.

İlk analitik bölüm aynı "örnek" - planın tüm sayısal göstergelerden arındırılmış kısaltılmış bir sunum şekli. İkinci kesim uygulama veya yöntemdir, üçüncü kesim ise bize sorun çıkaran spesifik plan düğümüdür.

Belirli örneklerden şablonlara geçtiğimizde aynı anda iki avantaj elde ettik:

  • analiz için nesnelerin sayısında çoklu azalma
    Sorunu artık binlerce sorgu veya planla değil, onlarca şablonla analiz etmemiz gerekiyor.
  • zaman çizelgesi
    Yani “gerçekleri” belli bir bölüm içerisinde özetleyerek gün içindeki görünümlerini sergileyebilirsiniz. Ve burada anlayabilirsiniz ki, örneğin saatte bir gerçekleşen bir tür modeliniz varsa, ancak bunun günde bir kez olması gerekiyorsa, neyin yanlış gittiğini düşünmelisiniz - buna kimin sebep olduğunu ve neden olduğunu, belki de burada olması gerektiğini anlayabilirsiniz. yapmamalı. Bu başka bir sayısal olmayan, tamamen görsel analiz yöntemidir.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Geriye kalan yöntemler plandan çıkardığımız göstergelere dayanmaktadır: böyle bir modelin kaç kez gerçekleştiği, toplam ve ortalama süre, diskten ne kadar veri okunduğu ve bellekten ne kadar veri okunduğu...

Çünkü örneğin ana bilgisayarın analiz sayfasına geliyorsunuz, bakın - diskte bir şeyler çok fazla okumaya başlıyor. Sunucudaki disk bunu kaldıramıyor; onu kim okuyor?

Ve herhangi bir sütuna göre sıralayabilir ve şu anda neyle ilgileneceğinize karar verebilirsiniz - işlemci veya disk üzerindeki yük veya toplam istek sayısı... Sıraladık, "en iyi" olanlara baktık, düzelttik ve uygulamanın yeni versiyonunu yayınladı.
[videolu ders]

Ve aynı şablonla gelen farklı uygulamaları aşağıdaki gibi bir istekten anında görebilirsiniz. SELECT * FROM users WHERE login = 'Vasya'. Ön uç, arka uç, işleme... Ve kullanıcı etkileşime girmezse işlemenin neden kullanıcıyı okuyacağını merak ediyorsunuz.

Bunun tersi ise uygulamadan ne yaptığını anında görmektir. Örneğin, ön uç şu şekildedir: bu, bu, bu ve bu saatte bir kez (zaman çizelgesi yardımcı olur). Ve hemen şu soru ortaya çıkıyor: Görünüşe göre saatte bir bir şey yapmak ön yüzün işi değil...

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Bir süre sonra toplamın eksik olduğunu fark ettik. plan düğümlerine göre istatistikler. Planlardan yalnızca tabloların verileriyle bir şeyler yapan düğümleri ayırdık (bunları dizine göre okuyup/yazmayın). Aslında önceki resme göre yalnızca bir yön eklenmiştir - Bu düğüm bize kaç kayıt getirdi?ve kaç tanesinin atıldığı (Filtre Tarafından Kaldırılan Satırlar).

Plakada uygun indeks yok, talepte bulunuyorsunuz, indeksin üzerinden uçuyor, Seq Scan'e düşüyor... Biri hariç tüm kayıtları filtrelemişsiniz. Neden günde 100 milyon filtrelenmiş kayda ihtiyacınız var? Dizini özetlemek daha iyi değil mi?

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Tüm planları düğüm nokta analiz ettikten sonra, planlarda şüpheli görünme olasılığı yüksek bazı tipik yapıların bulunduğunu fark ettik. Ve geliştiriciye şunu söylemek güzel olurdu: "Arkadaş, burada önce dizine göre okursun, sonra sıralarsın ve sonra kesersin" - kural olarak, bir kayıt vardır.

Sorgu yazan herkes muhtemelen şu kalıpla karşılaşmış: “Bana Vasya'nın son emrini ver, tarihini ver.” Eğer tarihe göre bir indeksiniz yoksa ya da kullandığınız indekste tarih yoksa o zaman tamamen aynı "tırmığa" basın.

Ancak bunun bir "tırmık" olduğunu biliyoruz - öyleyse neden geliştiriciye ne yapması gerektiğini hemen söylemiyorsunuz? Buna göre, şimdi bir plan açarken, geliştiricimiz hemen ipuçlarının olduğu güzel bir resim görüyor ve ona hemen şunu söylüyorlar: "Burada ve orada sorunlarınız var, ancak bunlar şu şekilde ve bu şekilde çözüldü."

Sonuç olarak, sorunları çözmek için başlangıçta ve şimdi ihtiyaç duyulan deneyim miktarı önemli ölçüde azaldı. Bu, sahip olduğumuz türden bir araçtır.

PostgreSQL sorgularının toplu optimizasyonu. Kirill Borovikov (Tensör)

Kaynak: habr.com

Yorum ekle