SQL Server'da büyük veri ve performans


20

Bir SQL Server arka ucunu toplayan ve depolayan bir uygulama ve son derece büyük miktarda kayıt yazdım. Zirvede, ortalama kayıt miktarının günde 3-4 milyar (20 saatlik çalışma) caddesinde bir yerde olduğunu hesapladım.

Orijinal çözümüm (verilerin gerçek hesaplamasını yapmadan önce) uygulamamın, müşterilerim tarafından sorgulanan aynı tabloya kayıtları eklemesini sağlamaktı. Bu çok hızlı bir şekilde çöktü ve yakıldı, çünkü o kadar çok kaydın eklendiği bir tabloyu sorgulamak imkansız.

İkinci çözümüm, biri uygulama tarafından alınan ve diğeri istemciye hazır veriler için olmak üzere 2 veritabanı kullanmaktı.

Uygulamam veri alır, ~ 100k kayıtlar halinde yığınlar ve hazırlama tablosuna toplu olarak eklenir. ~ 100k kayıtlardan sonra, uygulama daha önce olduğu gibi aynı şemaya sahip başka bir hazırlama tablosu oluşturur ve bu tabloya eklemeye başlar. 100k kayıt içeren tablonun adıyla bir iş tablosunda bir kayıt oluşturacak ve SQL Server tarafında depolanmış bir yordam verileri hazırlama tablolarından istemci hazır üretim tablosuna taşıyacak ve benim uygulama tarafından oluşturulan tablo geçici tablo.

Her iki veritabanında da işler tablosu olan hazırlama veritabanı dışında aynı şemaya sahip 5 tablo kümesi bulunur. Evreleme veritabanında, kayıtların büyük bir kısmının yer alacağı tabloda bütünlük kısıtlamaları, anahtar, dizinler vb. Yoktur. Aşağıda gösterilen tablo adı SignalValues_staging. Amaç, uygulamamın verileri mümkün olan en kısa sürede SQL Server'a çarptırmaktı. Kolayca taşınabilmeleri için anında tablo oluşturma iş akışı oldukça iyi çalışır.

Aşağıda, hazırlama veritabanımdaki 5 ilgili tablo ve ayrıca işler tabloum yer almaktadır:

Evreleme tabloları Yazdığım saklı yordam, verilerin tüm hazırlama tablolarından taşınmasını ve üretime sokulmasını sağlar. Aşağıda, saklı yordamın hazırlama tablolarından üretime ekleyen bir parçasıdır:

-- Signalvalues jobs table.
SELECT *
      ,ROW_NUMBER() OVER (ORDER BY JobId) AS 'RowIndex'
INTO #JobsToProcess
FROM 
(
    SELECT JobId 
           ,ProcessingComplete  
           ,SignalValueStagingTableName AS 'TableName'
           ,(DATEDIFF(SECOND, (SELECT last_user_update
                              FROM sys.dm_db_index_usage_stats
                              WHERE database_id = DB_ID(DB_NAME())
                                AND OBJECT_ID = OBJECT_ID(SignalValueStagingTableName))
                     ,GETUTCDATE())) SecondsSinceLastUpdate
    FROM SignalValueJobs
) cte
WHERE cte.ProcessingComplete = 1
   OR cte.SecondsSinceLastUpdate >= 120

DECLARE @i INT = (SELECT COUNT(*) FROM #JobsToProcess)

DECLARE @jobParam UNIQUEIDENTIFIER
DECLARE @currentTable NVARCHAR(128) 
DECLARE @processingParam BIT
DECLARE @sqlStatement NVARCHAR(2048)
DECLARE @paramDefinitions NVARCHAR(500) = N'@currentJob UNIQUEIDENTIFIER, @processingComplete BIT'
DECLARE @qualifiedTableName NVARCHAR(128)

WHILE @i > 0
BEGIN

    SELECT @jobParam = JobId, @currentTable = TableName, @processingParam = ProcessingComplete
    FROM #JobsToProcess 
    WHERE RowIndex = @i 

    SET @qualifiedTableName = '[Database_Staging].[dbo].['+@currentTable+']'

    SET @sqlStatement = N'

        --Signal values staging table.
        SELECT svs.* INTO #sValues
        FROM '+ @qualifiedTableName +' svs
        INNER JOIN SignalMetaData smd
            ON smd.SignalId = svs.SignalId  


        INSERT INTO SignalValues SELECT * FROM #sValues

        SELECT DISTINCT SignalId INTO #uniqueIdentifiers FROM #sValues

        DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId

        DROP TABLE #sValues
        DROP TABLE #uniqueIdentifiers

        IF NOT EXISTS (SELECT TOP 1 1 FROM '+ @qualifiedTableName +') --table is empty
        BEGIN
            -- processing is completed so drop the table and remvoe the entry
            IF @processingComplete = 1 
            BEGIN 
                DELETE FROM SignalValueJobs WHERE JobId = @currentJob

                IF '''+@currentTable+''' <> ''SignalValues_staging''
                BEGIN
                    DROP TABLE '+ @qualifiedTableName +'
                END
            END
        END 
    '

    EXEC sp_executesql @sqlStatement, @paramDefinitions, @currentJob = @jobParam, @processingComplete = @processingParam;

    SET @i = @i - 1
END

DROP TABLE #JobsToProcess

Kullandığım sp_executesqlevreleme tablolar için tablo isimleri işler tablosundaki kayıtlardan metin olarak geldiği için.

Bu saklı yordam, bu dba.stackexchange.com yazıdan öğrendim hile kullanarak her 2 saniyede bir çalışır .

Çözdüğüm yaşam için çözemediğim sorun, ek parçaların üretime getirilme hızıdır. Uygulamam geçici evreleme tabloları oluşturur ve bunları inanılmaz hızlı bir şekilde kayıtlarla doldurur. Üretime eklenen tabloların miktarına ayak uyduramaz ve sonunda binlere fazladan tablolar eklenir. Sadece şimdiye kadar gelen veri yetişmek mümkün oldum yolu üretim üzerinde ... tüm anahtarlar, indeksler, kısıtlamaları vb kaldırmaktır SignalValuesmasaya. Sonra karşılaştığım sorun tablo o kadar çok kayıt ile sorgulamak imkansız hale gelir olmasıdır.

Ben [Timestamp]boşuna bir bölümleme sütun olarak kullanarak tablo bölümleme denedim . Herhangi bir dizine ekleme biçimi, eklentileri o kadar yavaşlatır ki, yetişemezler. Buna ek olarak, binlerce bölüm (her dakikada bir mi? Saat?) Önceden oluşturmam gerekir. Onları anında nasıl oluşturacağımı anlayamadım

Adı verilen tabloya hesaplanan bir sütun ekleyerek bölümleme oluşturma çalıştı TimestampMinutedeğer, üzerinde INSERT, DATEPART(MINUTE, GETUTCDATE()). Hala çok yavaş.

Bu Microsoft makalesine göre Bellek için Optimize Edilmiş Tablo yapmayı denedim . Belki nasıl yapılacağını anlamıyorum, ancak MOT uçları bir şekilde yavaşlattı.

Saklı yordamın Yürütme Planını kontrol ettim ve (sanırım?) En yoğun operasyonun

SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
    ON smd.SignalId = svs.SignalId

Benim için bu bir anlam ifade etmiyor: Aksi kanıtlanan saklı yordama duvar saati günlüğü ekledim.

Zaman kaydı açısından, yukarıdaki söz konusu ifade 100 bin kayıtta ~ 300 ms içinde yürütülür.

İfade

INSERT INTO SignalValues SELECT * FROM #sValues

100 bin kayıtta 2500-3000ms'de çalışır. Etkilenen kayıtların tablodan silinmesi,

DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId

300 ms daha alır.

Bunu nasıl daha hızlı yapabilirim? SQL Server günde milyarlarca kaydı işleyebilir mi?

Alakalıysa, bu SQL Server 2014 Enterprise x64'tür.

Donanım yapılandırması:

Bu sorunun ilk geçişine donanımı dahil etmeyi unuttum. Benim hatam.

Bunu şu ifadelerle önsöz edeceğim: Donanım yapılandırmam nedeniyle bazı performansları kaybettiğimi biliyorum . Birçok kez denedim ama bütçe, C-Level, gezegenlerin hizalanması, vb ... nedeniyle maalesef daha iyi bir kurulum elde etmek için yapabileceğim bir şey yok. Sunucu sanal bir makinede çalışıyor ve hafızayı bile arttıramıyorum çünkü artık yok.

İşte sistem bilgilerim:

Sistem bilgisi

Depolama, VM sunucusuna iSCSI arabirimi aracılığıyla bir NAS kutusuna bağlanır (Bu, performansı düşürür). NAS kutusunun RAID 10 yapılandırmasında 4 sürücüsü vardır. 6GB / s SATA arabirimine sahip 4 TB WD WD4000FYYZ dönen disk sürücüleridir. Sunucunun yapılandırılmış yalnızca bir veri deposu vardır, böylece tempdb ve veritabanım aynı veri deposundadır.

Maks DOP sıfırdır. Bunu sabit bir değere değiştirmeli miyim yoksa SQL Server'ın işlemesine izin vermeli miyim? RCSI hakkında okudum: RCSI'nin tek faydasının satır güncellemeleriyle geldiğini varsayarak doğru muyum? Bu belirli kayıtlarda hiçbir zaman güncelleme olmayacak, bunlar INSERTdüzenlenecek ve SELECTdüzenlenecektir. RCSI hala bana fayda sağlayacak mı?

Benim tempdb 8mb. Aşağıdaki cevaba göre jyao, #sValues ​​tempdb tamamen önlemek için normal bir tabloya değiştirdim. Performans yine de aynıydı. Tempdb'nin boyutunu ve büyümesini artırmaya çalışacağım, ancak #sValues ​​büyüklüğünün her zaman aynı boyutta olacağı göz önüne alındığında, çok fazla kazanç beklemiyorum.

Aşağıda eklediğim bir yürütme planı aldım. Bu yürütme planı, 100 k kayıtları olan bir aşama tablosunun tekrarıdır. Sorgunun yürütülmesi oldukça hızlıydı, yaklaşık 2 saniye, ancak bunun SignalValuestablo üzerinde dizinler olmadığını ve SignalValuestablonun hedefinin içinde INSERThiç kayıt olmadığını unutmayın.

Yürütme planı


3
Gecikmiş dayanıklılığı zaten denediniz mi?
Martin Smith

2
Yavaş üretim kesici uçlarla hangi indeksler mevcuttu?
paparazzo

Şimdiye kadar gerçekten çok fazla zaman harcadığını bulmak için yeterli veri olduğunu düşünmüyorum. CPU mu? ES mi? Saniyede 30 bin satır alıyor gibi göründüğünden, bana IO gibi görünmüyor. Mükemmel hedefinize ulaşmaya oldukça yakın olduğunuz bu hakkı anlıyor muyum? Saniyede 50 bin satıra ihtiyacınız vardır, bu nedenle her 2 saniyede bir 100 bin parti yeterlidir. Şu anda bir parti 3 saniye sürüyor. Bir destek teknisyeninin fiili yürütme planını kaydedin. En çok zaman alan operasyonlara saldırmayan herhangi bir öneri tartışmalıdır.
usr

İcra planını yayınladım.
Brandon

Yanıtlar:


7

Zirvede, ortalama kayıt miktarının günde 3-4 milyar (20 saatlik çalışma) caddesinde bir yerde olduğunu hesapladım.

Ekran görüntünüzden SADECE 8GB bellek toplam RAM ve SQL Server'a 6 GB tahsis edilmiştir. Bu, elde etmeye çalıştığınız şey için çok düşük bir yol.

Belleği daha yüksek bir değere (256GB) yükseltmenizi ve VM CPU'larınızı da artırmanızı öneririm.

İş yükünüz için bu noktada donanıma yatırım yapmanız gerekir.

Ayrıca veri yükleme performans kılavuzuna bakın - verileri verimli bir şekilde yüklemenin akıllı yollarını açıklar.

Benim tempdb 8mb.

Yaptığınız düzenlemeye bağlı olarak, mantıklı bir tempdb - tercihen TF 1117 ve 1118 etkinleştirilmiş örnek genişliğiyle eşit boyutta birden fazla tempdb veri dosyanız olmalıdır.

Profesyonel bir sağlık kontrolü yaptırmanızı ve oradan başlamanızı öneririm.

Şiddetle tavsiye ederim

  1. Sunucu özelliklerinizi artırın.

  2. Profesyonel * bir kişinin veritabanı sunucusu örneğinizin sağlık denetimini yapmasını ve önerileri takip etmesini sağlayın.

  3. Birkez. ve B. tamamlandığında, sorgu ayarlama ve bekleme istatistiklerine, sorgu planlarına vb. bakmak gibi diğer optimizasyonlara kendinizi kaptırın.

Not: Ben Bir profesyonel SQL Server uzmanı at hackhands.com - Bir Pluralsight şirketi, ancak hiçbir şekilde yardım için beni işe sizi düşündüren. Yalnızca düzenlemelerinize göre profesyonel yardım almanızı öneririm.

HTH.


Bunun için daha fazla donanım bir teklif (okuma: yalvarıyor) bir araya getirmek için çalışıyorum. Bunu göz önünde bulundurarak ve cevabınız burada, SQL Server yapılandırması veya sorgu optimizasyonu açısından daha hızlı yapmak için önerebileceğiniz başka bir şey yok mu?
Brandon

1

Bir duvara bakarken ve hiçbir şey çalışmadığında büyük verilerle ilgili bu tür sorunlar için genel tavsiye:

Bir yumurta yaklaşık 5 dakika pişirilecek. Yeterli elektrik ve su olması durumunda aynı anda 10 yumurta pişirilecektir.

Veya başka bir deyişle:

İlk olarak donanıma bakın; ikinci olarak, işlem mantığını (veri yeniden modelleme) ayırın ve paralel olarak yapın.

Tablo sayısı ve tablo boyutu başına dinamik ve otomatik olarak özel dikey bölümleme oluşturmak oldukça mümkündür; Çeyrek_1_2017, Çeyrek_2_2017, Çeyrek_3_2017, Çeyrek_4_2017, Çeyrek_1_2018 ... ve kayıtlarımın nerede olduğunu ve ne kadar bölümüm olduğunu bilmiyorsam, aynı sorguları aynı anda tüm özel bölümlerde, ayrı oturumlarda ve montajda çalıştır sonuç mantığım için ileriye doğru işlenecek.


OP'nin sorunu, haftalar veya aylar öncesindeki verileri işlemekten daha fazla ekleme ve yeni girilen verilere erişim gibi görünüyor. OP, zaman damgasındaki dakikaya göre bölümleme verisinden bahseder (böylece 60 bölüm, geçerli verileri ayrı bölümlere böler); çeyreğe bölmek çok fazla yardımcı olmayacaktır. Amacınız genel olarak iyi değerlendirildi, ancak bu özel durumdaki birine yardımcı olma olasılığı düşük.
RDFozz

-1

Aşağıdaki kontrolü / optimizasyonu yapacağım:

  1. Ekleme işlemi sırasında üretim veritabanının hem veri hem de günlük dosyasının büyümediğinden emin olun (gerekirse önceden büyütün)

  2. Kullanmayın

    select * into [dest table] from [source table];

    bunun yerine, [dest tablosu] 'nu önceden tanımlayın. Ayrıca [dest tablosunu] bırakıp yeniden oluşturmak yerine, tabloyu keseceğim. Bu şekilde, gerekirse geçici tablo kullanmak yerine normal tablo kullanırdım. (Birleştirme sorgusunun performansını kolaylaştırmak için [dest tablosu] dizinini de oluşturabilirim)

  3. Dinamik sql kullanmak yerine, hangi tablonun çalıştırılacağını seçmek için bazı kodlama mantığıyla sabit kodlu tablo adlarını kullanmayı tercih ederim.

  4. Ayrıca, büyük iş yükü sırasında kaynak açlığı olup olmadığını görmek için bellek, CPU ve disk G / Ç performansını izleyeceğim.

  5. Eklemeyi üretim tarafında dizinleri bırakarak işleyebileceğinizden bahsettiğinizden, meydana gelen birçok sayfa bölünmesi olup olmadığını kontrol edeceğim, eğer öyleyse, dizinlerin dolgu faktörünü azaltacağım ve düşmeyi düşünmeden önce dizinleri yeniden oluşturacağım dizinler.

İyi şanslar ve sorunuzu seviyorum.


Cevap için teşekkürler. Veritabanı boyutunu 1 gb olarak ayarladım ve büyüme işlemlerinin biraz zaman alacağını tahmin ederek büyüdüm, bu da başlangıçta hıza yardımcı oldu. Bugün ön büyümeyi uygulamaya çalışacağım. [Dest] tablosunu normal bir tablo olarak uyguladım ama çok fazla performans artışı görmedim. Son birkaç gündür fazla vaktim olmadı ama bugün diğerlerine ulaşmaya çalışacağım.
Brandon
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.