Data dan kinerja besar dalam SQL Server

20

Saya telah menulis sebuah aplikasi dengan backend SQL Server yang mengumpulkan dan menyimpan dan sejumlah besar catatan. Saya telah menghitung bahwa, pada puncaknya, jumlah rata-rata catatan adalah di suatu tempat di jalan 3-4 miliar per hari (20 jam operasi).

Solusi asli saya (sebelum saya melakukan perhitungan data yang sebenarnya) adalah meminta aplikasi saya memasukkan catatan ke dalam tabel yang sama yang diminta oleh klien saya. Itu crash dan terbakar cukup cepat, jelas, karena tidak mungkin untuk meminta tabel yang memiliki banyak catatan yang disisipkan.

Solusi kedua saya adalah menggunakan 2 database, satu untuk data yang diterima oleh aplikasi dan satu untuk data yang siap untuk klien.

Aplikasi saya akan menerima data, memotongnya menjadi kumpulan ~ 100 ribu catatan dan memasukkan secara massal ke dalam tabel pementasan. Setelah ~ 100k mencatat aplikasi akan, dengan cepat, membuat tabel pementasan lain dengan skema yang sama seperti sebelumnya, dan mulai memasukkan ke dalam tabel itu. Itu akan membuat catatan di tabel pekerjaan dengan nama tabel yang memiliki catatan 100k dan prosedur yang tersimpan di sisi SQL Server akan memindahkan data dari tabel pementasan ke tabel produksi yang siap-klien, dan kemudian menjatuhkan tabel tabel sementara yang dibuat oleh aplikasi saya.

Kedua database memiliki set 5 tabel yang sama dengan skema yang sama, kecuali database staging yang memiliki tabel pekerjaan. Pangkalan data staging tidak memiliki kendala integritas, kunci, indeks dll ... pada tabel di mana sebagian besar catatan akan berada. Ditampilkan di bawah, nama tabelnya adalah SignalValues_staging. Tujuannya adalah agar aplikasi saya membanting data ke SQL Server secepat mungkin. Alur kerja membuat tabel dengan cepat agar mudah dimigrasi berfungsi dengan baik.

Berikut ini adalah 5 tabel yang relevan dari basis data pementasan saya, ditambah tabel pekerjaan saya:

Tabel pementasan Prosedur tersimpan yang saya tulis menangani pemindahan data dari semua tabel pementasan dan memasukkannya ke dalam produksi. Di bawah ini adalah bagian dari prosedur tersimpan saya yang memasukkan produksi dari tabel pementasan:

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

Saya menggunakan sp_executesqlkarena nama tabel untuk tabel pementasan datang sebagai teks dari catatan di tabel pekerjaan.

Prosedur tersimpan ini berjalan setiap 2 detik menggunakan trik yang saya pelajari dari posting dba.stackexchange.com ini .

Masalah yang tidak bisa saya selesaikan seumur hidup adalah kecepatan di mana pemasukan ke dalam produksi dilakukan. Aplikasi saya membuat tabel pementasan sementara dan mengisinya dengan catatan dengan sangat cepat. Memasukkan ke dalam produksi tidak dapat mengikuti jumlah tabel dan akhirnya ada surplus tabel ke dalam ribuan. Satu- satunya cara saya bisa mengikuti data yang masuk adalah dengan menghapus semua kunci, indeks, batasan dll ... di SignalValuestabel produksi . Masalah yang saya hadapi adalah bahwa tabel tersebut berakhir dengan begitu banyak catatan sehingga tidak mungkin untuk meminta.

Saya sudah mencoba mempartisi tabel menggunakan [Timestamp]kolom partisi sebagai tidak berhasil. Segala bentuk pengindeksan sama sekali memperlambat sisipan sehingga mereka tidak bisa mengikutinya. Selain itu, saya harus membuat ribuan partisi (satu setiap menit? Jam?) Tahun sebelumnya. Saya tidak tahu bagaimana cara membuatnya dengan cepat

Saya mencoba membuat partisi dengan menambahkan kolom dihitung ke meja disebut TimestampMinuteyang nilainya adalah, pada INSERT, DATEPART(MINUTE, GETUTCDATE()). Masih terlalu lambat.

Saya sudah mencoba menjadikannya Tabel yang Dioptimalkan Memori sesuai artikel Microsoft ini . Mungkin saya tidak mengerti bagaimana melakukannya, tetapi MOT membuat prosesnya lebih lambat.

Saya telah memeriksa Rencana Eksekusi dari prosedur tersimpan dan menemukan bahwa (saya pikir?) Operasi yang paling intensif

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

Bagi saya ini tidak masuk akal: Saya telah menambahkan logging jam dinding ke prosedur tersimpan yang terbukti sebaliknya.

Dalam hal pencatatan waktu, pernyataan tertentu di atas dijalankan dalam ~ 300 ms pada catatan 100rb.

Pernyataan

INSERT INTO SignalValues SELECT * FROM #sValues

dijalankan dalam 2500-3000 ms pada catatan 100rb. Menghapus dari tabel catatan yang terpengaruh, per:

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

Dibutuhkan 300ms lagi.

Bagaimana saya bisa membuat ini lebih cepat? Bisakah SQL Server menangani miliaran catatan per hari?

Jika relevan, ini adalah SQL Server 2014 Enterprise x64.

Konfigurasi Perangkat Keras:

Saya lupa menyertakan perangkat keras pada bagian pertama dari pertanyaan ini. Salahku.

Saya akan mengawali ini dengan pernyataan berikut: Saya tahu saya kehilangan beberapa kinerja karena konfigurasi perangkat keras saya. Saya sudah mencoba berkali-kali tetapi karena anggaran, C-Level, penyelarasan planet, dll ... tidak ada yang bisa saya lakukan untuk mendapatkan pengaturan yang lebih baik sayangnya. Server berjalan di mesin virtual dan saya bahkan tidak bisa menambah memori karena kami tidak punya apa-apa lagi.

Inilah informasi sistem saya:

Sistem Informasi

Penyimpanan terpasang ke server VM melalui antarmuka iSCSI ke kotak NAS (Ini akan menurunkan kinerja). Kotak NAS memiliki 4 drive dalam konfigurasi RAID 10. Mereka 4TB WD WD4000FYYZ drive disk berputar dengan antarmuka SATA 6GB / s. Server hanya memiliki satu data-store yang dikonfigurasi sehingga tempdb dan database saya berada di datastore yang sama.

Max DOP adalah nol. Haruskah saya mengubahnya ke nilai konstan atau biarkan SQL Server menanganinya? Saya membaca di RCSI: Apakah saya benar dengan asumsi bahwa satu-satunya manfaat dari RCSI datang dengan pembaruan baris? Tidak akan pernah ada pembaruan untuk catatan tertentu ini, mereka akan INSERTdiedit dan SELECTdiedit. Apakah RCSI masih akan menguntungkan saya?

Tempdb saya adalah 8mb. Berdasarkan jawaban di bawah dari jyao, saya mengubah #sValues ​​menjadi tabel biasa untuk menghindari tempdb sama sekali. Performanya hampir sama. Saya akan mencoba meningkatkan ukuran dan pertumbuhan tempdb, tetapi mengingat bahwa ukuran #sValues ​​akan lebih atau kurang selalu menjadi ukuran yang sama saya tidak mengantisipasi banyak keuntungan.

Saya telah mengambil rencana eksekusi yang saya lampirkan di bawah ini. Rencana eksekusi ini adalah salah satu iterasi dari tabel pementasan - 100 ribu catatan. Eksekusi query cukup cepat, sekitar 2 detik, tetapi perlu diingat bahwa ini tanpa indeks di atas SignalValuesmeja dan SignalValuestabel, target INSERT, tidak memiliki catatan di dalamnya.

Rencana eksekusi

Brandon
sumber
3
Apakah Anda sudah bereksperimen dengan daya tahan tertunda?
Martin Smith
2
Indeks apa yang ada dengan sisipan produksi lambat?
paparazzo
Sejauh ini saya tidak berpikir ada cukup data di sini untuk mencari tahu apa yang sebenarnya menghabiskan banyak waktu. Apakah itu CPU? Apakah itu IO? Karena Anda tampaknya mendapatkan 30rb baris per detik, itu tidak terlihat seperti IO bagi saya. Apakah saya mengerti hak ini bahwa Anda cukup dekat untuk mencapai tujuan perf Anda? Anda perlu 50k baris per detik, jadi satu 100k batch setiap 2 detik sudah cukup. Sekarang satu batch sepertinya butuh 3 detik. Posting rencana eksekusi aktual dari satu perwakilan yang dijalankan. Setiap saran yang tidak menyerang operasi yang paling memakan waktu adalah diperdebatkan.
usr
Saya sudah memposting rencana eksekusi.
Brandon

Jawaban:

7

Saya telah menghitung bahwa, pada puncaknya, jumlah rata-rata catatan adalah di suatu tempat di jalan 3-4 miliar per hari (20 jam operasi).

Dari tangkapan layar Anda, Anda HANYA memiliki total memori 8GB RAM dan 6 GB dialokasikan untuk SQL Server. Ini adalah cara yang terlalu rendah untuk apa yang ingin Anda capai.

Saya sarankan Anda untuk meningkatkan memori ke nilai yang lebih tinggi - 256GB dan meningkatkan VM CPU Anda juga.

Anda perlu berinvestasi pada perangkat keras pada titik ini untuk beban kerja Anda.

Lihat juga panduan kinerja pemuatan data - ini menjelaskan cara cerdas memuat data secara efisien.

Tempdb saya adalah 8mb.

Berdasarkan hasil edit Anda .. Anda harus memiliki tempdb yang masuk akal - lebih disukai beberapa file data tempdb berukuran sama dengan TF 1117 dan 1118 diaktifkan misalnya lebar.

Saya menyarankan Anda untuk mendapatkan pemeriksaan kesehatan profesional dan mulai dari sana.

Sangat disarankan

  1. Manjakan spesifikasi server Anda.

  2. Dapatkan orang * profesional melakukan pemeriksaan kesehatan contoh server database Anda dan ikuti rekomendasi.

  3. Sekali a. dan B. selesai, lalu benamkan diri Anda dalam penyetelan kueri dan optimisasi lainnya seperti melihat statistik tunggu, rencana kueri, dll.

Catatan: Saya seorang ahli server sql profesional di hackhands.com - sebuah perusahaan pluralsight, tetapi tidak berarti menyarankan Anda untuk mempekerjakan saya untuk bantuan. Saya hanya menyarankan Anda untuk mengambil bantuan profesional berdasarkan suntingan Anda saja.

HTH.

Kin Shah
sumber
Saya mencoba menyusun proposal (baca: memohon) lebih banyak perangkat keras untuk ini. Dengan mengingat hal itu dan jawaban Anda di sini, tidak ada hal lain dari konfigurasi SQL Server atau sudut pandang optimisasi kueri yang Anda sarankan untuk membuatnya lebih cepat?
Brandon
1

Saran umum untuk masalah data besar seperti itu, ketika menghadap tembok dan tidak ada yang berhasil:

Satu telur akan dimasak sekitar 5 menit. 10 telur akan dimasak dalam waktu bersamaan jika cukup listrik dan air.

Atau, dengan kata lain:

Pertama, lihat perangkat kerasnya; kedua, pisahkan logika proses (data remodeling) dan lakukan secara paralel.

Sangat dimungkinkan untuk membuat partisi vertikal kustom secara dinamis dan otomatis, per tabel dan ukuran tabel; Jika saya memiliki Quarter_1_2017, Quarter_2_2017, Quarter_3_2017, Quarter_4_2017, Quarter_1_2018 ... dan saya tidak tahu di mana catatan saya dan berapa banyak partisi yang saya miliki, jalankan kueri yang sama terhadap semua partisi khusus dalam waktu yang sama, sesi dan perakitan terpisah hasil yang akan diproses untuk logika saya.

Goran Mancevski
sumber
Masalah OP tampaknya menangani penyisipan dan akses ke data yang baru dimasukkan, lebih dari memproses data dari minggu atau bulan lalu. OP menyebutkan data partisi berdasarkan menit pada stempel waktunya (jadi 60 partisi, memisahkan data saat ini menjadi ember terpisah); membaginya dengan seperempat tidak akan banyak membantu. Poin Anda diterima dengan baik secara umum, tetapi tidak mungkin membantu seseorang dalam situasi khusus ini.
RDFozz
-1

Saya akan melakukan pemeriksaan / optimasi berikut:

  1. Pastikan data dan file log dari basis data produksi tidak tumbuh selama operasi penyisipan (pra-tumbuh jika diperlukan)

  2. Jangan gunakan

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

    tetapi, tentukan sebelumnya [dest table]. Juga alih-alih menjatuhkan [dest table] dan membuatnya kembali, saya akan memotong tabel. Dengan cara ini, jika perlu, alih-alih menggunakan tabel temp, saya akan menggunakan tabel biasa. (Saya juga dapat membuat indeks pada [dest table] untuk memfasilitasi kinerja permintaan bergabung)

  3. Alih-alih menggunakan sql dinamis, saya lebih suka menggunakan nama tabel hard-coded dengan beberapa logika pengkodean untuk memilih tabel mana yang akan dioperasikan.

  4. Saya juga akan memonitor kinerja memori, CPU dan disk I / O untuk melihat apakah ada sumber daya yang kelaparan selama beban kerja yang besar.

  5. Karena Anda menyebutkan Anda dapat menangani penyisipan dengan menjatuhkan indeks pada sisi produksi, saya akan memeriksa apakah ada banyak pemisahan halaman yang terjadi, jika demikian, saya akan mengurangi fillfactor indeks dan membangun kembali indeks sebelum saya mempertimbangkan untuk menjatuhkan indeks.

Semoga beruntung dan cintai pertanyaan Anda.

jyao
sumber
Terima kasih atas jawabannya. Saya telah menetapkan ukuran basis data menjadi 1gb dan tumbuh sebesar 1gb mengantisipasi bahwa operasi pertumbuhan akan memakan waktu, yang memang membantu kecepatan pada awalnya. Saya akan mencoba menerapkan pra-pertumbuhan hari ini. Saya memang mengimplementasikan tabel [dest] sebagai tabel biasa tetapi tidak melihat banyak peningkatan kinerja. Saya belum punya banyak waktu beberapa hari terakhir tetapi saya akan mencoba untuk mendapatkan yang lain hari ini.
Brandon