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:
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_executesql
karena 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 SignalValues
tabel 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 TimestampMinute
yang 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:
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 INSERT
diedit dan SELECT
diedit. 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 SignalValues
meja dan SignalValues
tabel, target INSERT
, tidak memiliki catatan di dalamnya.