Database
 sql >> Teknologi Basis Data >  >> RDS >> Database

Memahami flush buffer log

Anda mungkin sudah sering mendengar sebelumnya bahwa SQL Server memberikan jaminan untuk properti transaksi ACID. Artikel ini berfokus pada bagian D, yang tentu saja berarti daya tahan. Lebih khusus lagi, artikel ini berfokus pada aspek arsitektur logging SQL Server yang memberlakukan ketahanan transaksi—penghapusan buffer log. Saya berbicara tentang fungsi yang dilayani buffer log, kondisi yang memaksa SQL Server untuk mem-flush buffer log ke disk, apa yang dapat Anda lakukan untuk mengoptimalkan kinerja transaksi, serta teknologi terkait yang baru-baru ini ditambahkan seperti daya tahan yang tertunda dan memori kelas penyimpanan yang tidak mudah menguap.

Pembersihan buffer log

Bagian D dalam properti transaksi ACID berarti daya tahan. Pada tingkat logis itu berarti bahwa ketika aplikasi mengirim SQL Server instruksi untuk melakukan transaksi (secara eksplisit, atau dengan transaksi auto-commit), SQL Server biasanya mengembalikan kontrol ke pemanggil hanya setelah itu dapat menjamin bahwa transaksi tersebut tahan lama. Dengan kata lain, setelah penelepon mendapatkan kembali kendali setelah melakukan transaksi, ia dapat percaya bahwa meskipun beberapa saat kemudian server mengalami kegagalan daya, perubahan transaksi berhasil masuk ke database. Selama server berhasil restart dan file database tidak rusak, Anda akan menemukan bahwa semua perubahan transaksi telah diterapkan.

Cara SQL Server memberlakukan ketahanan transaksi, sebagian, adalah dengan memastikan bahwa semua perubahan transaksi ditulis ke log transaksi database di disk sebelum mengembalikan kendali ke pemanggil. Dalam kasus kegagalan daya setelah komit transaksi diakui, Anda tahu bahwa semua perubahan itu setidaknya ditulis ke log transaksi di disk. Itulah yang terjadi bahkan jika halaman data terkait hanya dimodifikasi di cache data (buffer pool) tetapi belum di-flush ke file data di disk. Saat Anda memulai ulang SQL Server, selama fase ulangi proses pemulihan, SQL Server menggunakan informasi yang direkam di log untuk memutar ulang perubahan yang diterapkan setelah pos pemeriksaan terakhir dan yang belum berhasil sampai ke file data. Ada sedikit lebih banyak cerita tergantung pada model pemulihan yang Anda gunakan dan apakah operasi massal diterapkan setelah pos pemeriksaan terakhir, tetapi untuk tujuan diskusi kita, cukup untuk fokus pada bagian yang melibatkan pengerasan perubahan ke log transaksi.

Bagian tersulit dalam arsitektur logging SQL Server adalah penulisan log berurutan. Jika SQL Server tidak menggunakan semacam buffer log untuk meringankan penulisan log ke disk, sistem intensif penulisan—terutama yang melibatkan banyak transaksi kecil—akan dengan cepat mengalami kemacetan kinerja terkait penulisan log yang mengerikan.

Untuk mengurangi dampak kinerja negatif dari penulisan log berurutan yang sering ke disk, SQL Server menggunakan buffer log di memori. Penulisan log pertama-tama dilakukan ke buffer log, dan kondisi tertentu menyebabkan SQL Server mem-flush, atau mengeraskan, buffer log ke disk. Unit yang dikeraskan (alias blok log) dapat berkisar dari minimum ukuran sektor (512 byte) hingga maksimum 60 KB. Berikut adalah kondisi yang memicu flush buffer log (abaikan bagian yang muncul dalam tanda kurung siku untuk saat ini):

  • SQL Server mendapat permintaan komit dari transaksi [sepenuhnya tahan lama] yang mengubah data [dalam database selain tempdb]
  • Buffer log terisi, mencapai kapasitas 60 KB
  • SQL Server perlu mengeraskan halaman data yang kotor, misalnya, selama proses pos pemeriksaan, dan catatan log yang mewakili perubahan pada halaman tersebut belum dikeraskan (tulis di depan logging , atau singkatnya WAL)
  • Anda meminta flush buffer log secara manual dengan menjalankan prosedur sys.sp_flush_log
  • SQL Server menulis nilai pemulihan baru terkait cache-urutan [dalam database selain tempdb]

Empat kondisi pertama seharusnya cukup jelas, jika Anda mengabaikan informasi dalam tanda kurung untuk saat ini. Yang terakhir mungkin belum jelas, tetapi saya akan menjelaskannya secara rinci nanti di artikel.

Waktu SQL Server menunggu operasi I/O yang menangani flush buffer log untuk diselesaikan dicerminkan oleh tipe tunggu WRITELOG.

Jadi, mengapa informasi ini begitu menarik, dan apa yang kita lakukan dengannya? Memahami kondisi yang memicu flush buffer log dapat membantu Anda mengetahui mengapa beban kerja tertentu mengalami kemacetan terkait. Juga, dalam beberapa kasus ada tindakan yang dapat Anda ambil untuk mengurangi atau menghilangkan kemacetan tersebut. Saya akan membahas sejumlah contoh seperti satu transaksi besar versus banyak transaksi kecil, transaksi tahan lama sepenuhnya versus transaksi tahan lama yang tertunda, basis data pengguna versus tempdb, dan caching objek urutan.

Satu transaksi besar versus banyak transaksi kecil

Seperti disebutkan salah satu kondisi yang memicu flush buffer log adalah ketika Anda melakukan transaksi untuk menjamin ketahanan transaksi. Artinya, beban kerja yang melibatkan banyak transaksi kecil, seperti beban kerja OLTP, berpotensi mengalami kemacetan terkait penulisan log.

Meskipun hal ini sering tidak terjadi, jika Anda memiliki satu sesi yang mengirimkan banyak perubahan kecil, cara sederhana dan efektif untuk mengoptimalkan pekerjaan adalah dengan menerapkan perubahan dalam satu transaksi besar, bukan beberapa yang kecil.

Perhatikan contoh sederhana berikut (unduh PerformanceV3 di sini):

SET NOCOUNT ON;
 
USE PerformanceV3;
 
ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY = Disabled; -- default
 
DROP TABLE IF EXISTS dbo.T1;
 
CREATE TABLE dbo.T1(col1 INT NOT NULL);
 
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
 
  BEGIN TRAN
    INSERT INTO dbo.T1(col1) VALUES(@i);
  COMMIT TRAN;
 
  SET @i += 1;
END;

Kode ini mengeksekusi 1.000.000 transaksi kecil yang mengubah data dalam database pengguna. Pekerjaan ini akan memicu setidaknya 1.000.000 log buffer flushes. Anda bisa mendapatkan beberapa tambahan karena buffer log terisi. Anda dapat menggunakan template pengujian berikut untuk menghitung jumlah flush buffer log dan mengukur waktu yang dibutuhkan untuk menyelesaikan pekerjaan:

-- Test template
 
-- ... Preparation goes here ...
 
-- Count log flushes and measure time
DECLARE @logflushes AS INT, @starttime AS DATETIME2, @duration AS INT;
 
-- Stats before
SET @logflushes = ( SELECT cntr_value FROM sys.dm_os_performance_counters
                    WHERE counter_name = 'Log Flushes/sec'
                      AND instance_name = @db );
 
SET @starttime = SYSDATETIME();
 
-- ... Actual work goes here ...
 
-- Stats after
SET @duration = DATEDIFF(second, @starttime, SYSDATETIME());
SET @logflushes = ( SELECT cntr_value FROM sys.dm_os_performance_counters
                    WHERE counter_name = 'Log Flushes/sec'
                      AND instance_name = @db ) - @logflushes;
 
SELECT 
  @duration AS durationinseconds,
  @logflushes AS logflushes;

Meskipun nama penghitung kinerja adalah Log Flushes/sec, itu sebenarnya terus mengakumulasi jumlah buffer log flushes sejauh ini. Jadi, kode tersebut mengurangi hitungan sebelum bekerja dari hitungan setelah bekerja untuk mengetahui jumlah log flush yang dihasilkan oleh pekerjaan tersebut. Kode ini juga mengukur waktu dalam detik yang dibutuhkan untuk menyelesaikan pekerjaan. Meskipun saya tidak melakukan ini di sini, Anda dapat, jika mau, mencari tahu secara serupa jumlah catatan log dan ukuran yang ditulis ke log oleh pekerjaan dengan menanyakan status sebelum-kerja dan setelah-kerja dari fn_dblog fungsi.

Untuk contoh kami di atas, berikut adalah bagian yang perlu Anda tempatkan di bagian persiapan template tes:

-- Preparation
SET NOCOUNT ON;
USE PerformanceV3;
 
ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY = Disabled;
 
DROP TABLE IF EXISTS dbo.T1;
 
CREATE TABLE dbo.T1(col1 INT NOT NULL);
 
DECLARE @db AS sysname = N'PerformanceV3';
 
DECLARE @logflushes AS INT, @starttime AS DATETIME2, @duration AS INT;

Dan berikut ini adalah bagian yang perlu Anda tempatkan di bagian kerja yang sebenarnya:

-- Actual work
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
 
  BEGIN TRAN
    INSERT INTO dbo.T1(col1) VALUES(@i);
  COMMIT TRAN;
 
  SET @i += 1;
END;

Secara keseluruhan, Anda mendapatkan kode berikut:

-- Example test with many small fully durable transactions in user database
-- ... Preparation goes here ...
 
-- Preparation
SET NOCOUNT ON;
USE PerformanceV3;
 
ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY = Disabled;
 
DROP TABLE IF EXISTS dbo.T1;
 
CREATE TABLE dbo.T1(col1 INT NOT NULL);
 
DECLARE @db AS sysname = N'PerformanceV3';
 
DECLARE @logflushes AS INT, @starttime AS DATETIME2, @duration AS INT;
 
-- Stats before
SET @logflushes = ( SELECT cntr_value FROM sys.dm_os_performance_counters
 
                    WHERE counter_name = 'Log Flushes/sec'
 
                      AND instance_name = @db );
 
SET @starttime = SYSDATETIME();
 
-- ... Actual work goes here ...
 
-- Actual work
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
 
  BEGIN TRAN
    INSERT INTO dbo.T1(col1) VALUES(@i);
  COMMIT TRAN;
 
  SET @i += 1;
END;
 
-- Stats after
SET @duration = DATEDIFF(second, @starttime, SYSDATETIME());
 
SET @logflushes = ( SELECT cntr_value FROM sys.dm_os_performance_counters
                    WHERE counter_name = 'Log Flushes/sec'
                      AND instance_name = @db ) - @logflushes;
 
SELECT 
  @duration AS durationinseconds,
  @logflushes AS logflushes;

Kode ini membutuhkan waktu 193 detik untuk diselesaikan di sistem saya, dan memicu 1.000.036 buffer log flush. Itu sangat lambat, tetapi dapat dijelaskan karena banyaknya log flush.

Dalam beban kerja OLTP tipikal, sesi yang berbeda mengirimkan perubahan kecil dalam transaksi kecil yang berbeda secara bersamaan, jadi Anda tidak benar-benar memiliki opsi untuk merangkum banyak perubahan kecil dalam satu transaksi besar. Namun, jika situasi Anda adalah bahwa semua perubahan kecil dikirimkan dari sesi yang sama, cara sederhana untuk mengoptimalkan pekerjaan adalah dengan merangkumnya dalam satu transaksi. Ini akan memberi Anda dua manfaat utama. Salah satunya adalah bahwa pekerjaan Anda akan menulis lebih sedikit catatan log. Dengan 1.000.000 transaksi kecil, setiap transaksi sebenarnya menulis tiga catatan log:satu untuk memulai transaksi, satu untuk perubahan, dan satu untuk melakukan transaksi. Jadi, Anda melihat sekitar 3.000.000 catatan log transaksi versus sedikit di atas 1.000.000 saat dieksekusi sebagai satu transaksi besar. Tetapi yang lebih penting, dengan satu transaksi besar, sebagian besar log flush hanya dipicu ketika buffer log terisi, ditambah satu log flush lagi di akhir transaksi saat dijalankan. Perbedaan kinerja bisa sangat signifikan. Untuk menguji pekerjaan dalam satu transaksi besar, gunakan kode berikut di bagian kerja sebenarnya dari template pengujian:

-- Actual work
BEGIN TRAN;
 
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
 
  INSERT INTO dbo.T1(col1) VALUES(@i);
  SET @i += 1;
 
END;
 
COMMIT TRAN;

Di sistem saya, pekerjaan ini selesai dalam 7 detik, dan memicu 1.758 log flush. Berikut perbandingan antara kedua opsi tersebut:

#transactions  log flushes  duration in seconds
-------------- ------------ --------------------
1000000        1000036      193
1              1758         7

Namun sekali lagi, dalam beban kerja OLTP biasa, Anda tidak benar-benar memiliki opsi untuk mengganti banyak transaksi kecil yang dikirimkan dari sesi yang berbeda dengan satu transaksi besar yang dikirimkan dari sesi yang sama.

Transaksi tahan lama versus tahan lama yang tertunda

Dimulai dengan SQL Server 2014, Anda dapat menggunakan fitur yang disebut daya tahan tertunda yang memungkinkan Anda meningkatkan kinerja beban kerja dengan banyak transaksi kecil, bahkan jika dikirimkan oleh sesi yang berbeda, dengan mengorbankan jaminan daya tahan penuh normal. Saat melakukan transaksi tahan lama yang tertunda, SQL Server mengakui komit segera setelah catatan log komit ditulis ke buffer log, tanpa memicu flush buffer log. Buffer log dihapus karena kondisi lain yang disebutkan di atas seperti saat terisi penuh, tetapi tidak saat transaksi tahan lama yang tertunda dilakukan.

Sebelum menggunakan fitur ini, Anda harus berpikir dengan hati-hati apakah itu cocok untuk Anda. Dalam hal kinerja, dampaknya hanya signifikan pada beban kerja dengan banyak transaksi kecil. Jika untuk memulai dengan beban kerja Anda terutama melibatkan transaksi besar, Anda mungkin tidak akan melihat keuntungan kinerja apa pun. Lebih penting lagi, Anda perlu menyadari potensi kehilangan data. Katakanlah aplikasi melakukan transaksi tahan lama yang tertunda. Catatan komit ditulis ke buffer log dan segera diakui (kontrol diberikan kembali ke pemanggil). Jika SQL Server mengalami kegagalan daya sebelum buffer log dihapus, setelah restart, proses pemulihan membatalkan semua perubahan yang dibuat oleh transaksi, meskipun aplikasi berpikir bahwa itu dilakukan.

Jadi, kapan boleh menggunakan fitur ini? Satu kasus yang jelas adalah ketika kehilangan data tidak menjadi masalah, seperti contoh dari Melissa Connors dari SentryOne ini. Lain adalah ketika setelah restart Anda memiliki sarana untuk mengidentifikasi perubahan mana yang tidak berhasil masuk ke database, dan Anda dapat mereproduksinya. Jika situasi Anda tidak termasuk dalam salah satu dari dua kategori ini, jangan gunakan fitur ini meskipun ada godaan.

Untuk menangani transaksi tahan lama yang tertunda, Anda perlu menyetel opsi basis data yang disebut DELAYED_DURABILITY. Opsi ini dapat disetel ke salah satu dari tiga nilai:

  • Dinonaktifkan (default):semua transaksi dalam database sepenuhnya tahan lama, dan oleh karena itu setiap komit memicu flush buffer log
  • Dipaksa :semua transaksi dalam database tertunda tahan lama, dan oleh karena itu komit tidak memicu flush buffer log
  • Diizinkan :kecuali disebutkan sebaliknya, transaksi sepenuhnya tahan lama dan melakukan transaksi memicu buffer buffer log; namun, jika Anda menggunakan opsi DELAYED_DURABILITY =ON baik dalam pernyataan COMMIT TRAN atau blok atom (dari proc yang dikompilasi secara native), transaksi tersebut akan tertunda tahan lama dan oleh karena itu melakukan itu tidak memicu buffer buffer log

Sebagai pengujian, gunakan kode berikut di bagian persiapan template pengujian kami (perhatikan bahwa opsi database disetel ke Dipaksa):

-- Preparation
SET NOCOUNT ON;
USE PerformanceV3; -- http://tsql.solidq.com/SampleDatabases/PerformanceV3.zip
 
ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY = Forced;
 
DROP TABLE IF EXISTS dbo.T1;
 
CREATE TABLE dbo.T1(col1 INT NOT NULL);
 
DECLARE @db AS sysname = N'PerformanceV3';

Dan gunakan kode berikut di bagian kerja yang sebenarnya (perhatikan, 1.000.000 transaksi kecil):

-- Actual work
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
 
  BEGIN TRAN
    INSERT INTO dbo.T1(col1) VALUES(@i);
  COMMIT TRAN;
 
  SET @i += 1;
END;

Atau, Anda dapat menggunakan mode Diizinkan di tingkat basis data, lalu dalam perintah COMMIT TRAN, tambahkan WITH (DELAYED_DURABILITY =ON).

Di sistem saya, pekerjaan membutuhkan waktu 22 detik untuk diselesaikan dan memicu 95.407 log flush. Itu lebih lama daripada menjalankan pekerjaan sebagai satu transaksi besar (7 detik) karena lebih banyak catatan log dihasilkan (ingat, per transaksi, satu untuk memulai transaksi, satu untuk perubahan, dan satu untuk melakukan transaksi); namun, ini jauh lebih cepat daripada 193 detik yang diperlukan untuk menyelesaikan pekerjaan menggunakan 1.000.000 transaksi yang sepenuhnya tahan lama karena jumlah log flush turun dari lebih dari 1.000.000 menjadi kurang dari 100.000. Selain itu, dengan daya tahan yang tertunda, Anda akan mendapatkan peningkatan performa meskipun transaksi dikirim dari sesi berbeda yang bukan merupakan opsi untuk menggunakan satu transaksi besar.

Untuk menunjukkan bahwa tidak ada manfaat menggunakan daya tahan yang tertunda saat melakukan pekerjaan sebagai transaksi besar, simpan kode yang sama di bagian persiapan tes terakhir, dan gunakan kode berikut di bagian pekerjaan yang sebenarnya:

-- Actual work
BEGIN TRAN;
 
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
  INSERT INTO dbo.T1(col1) VALUES(@i);
 
  SET @i += 1;
END;
 
COMMIT TRAN;

Saya mendapat waktu berjalan 8 detik (dibandingkan dengan 7 untuk satu transaksi besar yang sepenuhnya tahan lama) dan 1.759 log flushes (dibandingkan dengan 1.758). Jumlahnya pada dasarnya sama, tetapi dengan transaksi tahan lama yang tertunda, Anda memiliki risiko kehilangan data.

Berikut ringkasan angka performa untuk keempat pengujian:

durability          #transactions  log flushes  duration in seconds
------------------- -------------- ------------ --------------------
full                1000000        1000036      193
full                1              1758         7
delayed             1000000        95407        22
delayed             1              1759         8

Memori kelas penyimpanan

Fitur daya tahan tertunda dapat secara signifikan meningkatkan kinerja beban kerja gaya OLTP yang melibatkan sejumlah besar transaksi pembaruan kecil yang memerlukan frekuensi tinggi dan latensi rendah. Masalahnya adalah Anda mempertaruhkan kehilangan data. Bagaimana jika Anda tidak dapat membiarkan kehilangan data apa pun, tetapi Anda masih menginginkan peningkatan kinerja seperti daya tahan yang tertunda, di mana buffer log tidak memerah untuk setiap komit, melainkan saat terisi? Kita semua suka makan kue dan memilikinya juga, kan?

Anda dapat mencapai ini di SQL Server 2016 SP1 atau yang lebih baru dengan menggunakan memori kelas penyimpanan, alias penyimpanan non-volatil NVDIMM-N. Perangkat keras ini pada dasarnya adalah modul memori yang memberi Anda kinerja tingkat memori, tetapi informasi di sana tetap ada dan karena itu tidak hilang saat daya padam. Penambahan di SQL Server 2016 SP1 memungkinkan Anda mengonfigurasi buffer log sebagai yang bertahan pada perangkat keras tersebut. Untuk melakukan ini, Anda mengatur SCM sebagai volume di Windows dan memformatnya sebagai volume Direct Access Mode (DAX). Anda kemudian menambahkan file log ke database menggunakan perintah ALTER DATABASE ADD FILE LOG normal, dengan jalur file yang berada di volume DAX, dan atur ukurannya menjadi 20 MB. SQL Server, pada gilirannya, mengenali bahwa itu adalah volume DAX, dan sejak saat itu memperlakukan buffer log sebagai yang bertahan pada volume itu. Peristiwa komit transaksi tidak memicu flush buffer log lagi, melainkan setelah komit dicatat dalam buffer log, SQL Server tahu bahwa itu benar-benar bertahan, dan karena itu mengembalikan kontrol ke pemanggil. Ketika buffer log terisi, maka SQL Server mem-flush-nya ke file log transaksi pada penyimpanan tradisional.

Untuk detail selengkapnya tentang fitur ini, termasuk angka kinerja, lihat Akselerasi latensi Transaction Commit menggunakan Memori Kelas Penyimpanan di Windows Server 2016/SQL Server 2016 SP1 oleh Kevin Farlee.

Anehnya, SQL Server 2019 meningkatkan dukungan untuk memori kelas penyimpanan lebih dari sekadar skenario cache log yang bertahan. Ini mendukung penempatan file data, file log, dan file pos pemeriksaan OLTP Dalam Memori pada perangkat keras tersebut. Yang perlu Anda lakukan adalah mengeksposnya sebagai volume di level OS dan memformatnya sebagai drive DAX. SQL Server 2019 secara otomatis mengenali teknologi ini, dan bekerja dalam tercerahkan mode, langsung mengakses perangkat, melewati tumpukan penyimpanan OS. Selamat datang di masa depan!

Database pengguna versus tempdb

Database tempdb tentu saja dibuat dari awal sebagai salinan baru dari database model setiap kali Anda me-restart SQL Server. Dengan demikian, tidak perlu memulihkan data apa pun yang Anda tulis ke tempdb, baik Anda menulisnya ke tabel sementara, variabel tabel, atau tabel pengguna. Semuanya hilang setelah restart. Mengetahui hal ini, SQL Server dapat melonggarkan banyak persyaratan terkait logging. Misalnya, terlepas dari apakah Anda mengaktifkan opsi daya tahan tertunda atau tidak, peristiwa komit tidak memicu flush buffer log. Selain itu, jumlah informasi yang perlu dicatat berkurang karena SQL Server hanya membutuhkan informasi yang cukup untuk mendukung transaksi rolling back, atau membatalkan pekerjaan, jika diperlukan, tetapi tidak meneruskan transaksi, atau mengulang pekerjaan. Akibatnya, catatan log transaksi yang mewakili perubahan pada objek di tempdb cenderung lebih kecil dibandingkan saat perubahan yang sama diterapkan pada objek di database pengguna.

Untuk mendemonstrasikan ini, Anda akan menjalankan tes yang sama yang Anda jalankan sebelumnya di PerformanceV3, hanya kali ini di tempdb. Kami akan mulai dengan pengujian banyak transaksi kecil ketika opsi database DELAYED_DURABILITY diatur ke Disabled (default). Gunakan kode berikut di bagian persiapan template pengujian:

-- Preparation
SET NOCOUNT ON;
USE tempdb;
 
ALTER DATABASE tempdb SET DELAYED_DURABILITY = Disabled;
 
DROP TABLE IF EXISTS dbo.T1;
 
CREATE TABLE dbo.T1(col1 INT NOT NULL);
 
DECLARE @db AS sysname = N'tempdb';

Gunakan kode berikut di bagian pekerjaan yang sebenarnya:

-- Actual work
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
 
  BEGIN TRAN
    INSERT INTO dbo.T1(col1) VALUES(@i);
  COMMIT TRAN;
 
  SET @i += 1;
END;

Pekerjaan ini menghasilkan 5.095 log flush, dan butuh 19 detik untuk menyelesaikannya. Itu dibandingkan dengan lebih dari satu juta log flush dan 193 detik dalam database pengguna dengan daya tahan penuh. Itu bahkan lebih baik daripada dengan daya tahan yang tertunda dalam database pengguna (95.407 log flushes dan 22 detik) karena berkurangnya ukuran catatan log.

Untuk menguji satu transaksi besar, biarkan bagian persiapan tidak berubah, dan gunakan kode berikut di bagian pekerjaan yang sebenarnya:

-- Actual work
BEGIN TRAN;
 
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
  INSERT INTO dbo.T1(col1) VALUES(@i);
 
  SET @i += 1;
END;
 
COMMIT TRAN;

Saya mendapat 1.228 log flush, dan waktu berjalan 9 detik. Itu dibandingkan dengan 1.758 log flushes dan 7 detik run time di database pengguna. Waktu proses serupa, bahkan sedikit lebih cepat di database pengguna, tetapi bisa jadi ada sedikit variasi di antara pengujian. Ukuran catatan log di tempdb berkurang, dan karenanya Anda mendapatkan lebih sedikit log flush dibandingkan dengan database pengguna.

Anda juga dapat mencoba menjalankan pengujian dengan opsi DELAYED_DURABILITY disetel ke Dipaksa, tetapi ini tidak akan berdampak pada tempdb karena, seperti yang disebutkan, bagaimanapun, peristiwa komit tidak memicu log flush di tempdb.

Berikut adalah ukuran kinerja untuk semua pengujian, baik di database pengguna maupun di tempdb:

database       durability          #transactions  log flushes  duration in seconds
-------------- ------------------- -------------- ------------ --------------------
PerformanceV3  full                1000000        1000036      193
PerformanceV3  full                1              1758         7
PerformanceV3  delayed             1000000        95407        22
PerformanceV3  delayed             1              1759         8
tempdb         full                1000000        5095         19
tempdb         full                1              1228         9
tempdb         delayed             1000000        5091         18
tempdb         delayed             1              1226         9

Mengurutkan cache objek

Mungkin kasus mengejutkan yang memicu flush buffer log terkait dengan opsi cache objek urutan. Perhatikan sebagai contoh definisi urutan berikut:

CREATE SEQUENCE dbo.Seq1 AS BIGINT MINVALUE 1 CACHE 50; -- the default cache size is 50;

Setiap kali Anda membutuhkan nilai urutan baru, Anda menggunakan fungsi NEXT VALUE FOR, seperti:

SELECT NEXT VALUE FOR dbo.Seq1;

Properti CACHE adalah fitur kinerja. Tanpa itu, setiap kali nilai urutan baru diminta, SQL Server harus menulis nilai saat ini ke disk untuk tujuan pemulihan. Memang, itulah perilaku yang Anda dapatkan saat menggunakan mode NO CACHE. Sebaliknya, ketika opsi diatur ke nilai yang lebih besar dari nol, SQL Server menulis nilai pemulihan ke disk hanya sekali setiap jumlah permintaan ukuran cache. SQL Server memelihara dua anggota dalam memori, berukuran sebagai jenis urutan, satu memegang nilai saat ini, dan satu memegang jumlah nilai yang tersisa sebelum penulisan disk berikutnya dari nilai pemulihan diperlukan. Jika terjadi kegagalan daya, saat restart, SQL Server menetapkan nilai urutan saat ini ke nilai pemulihan.

Ini mungkin jauh lebih mudah dijelaskan dengan sebuah contoh. Pertimbangkan definisi urutan di atas dengan opsi CACHE diatur ke 50 (default). Anda meminta nilai urutan baru untuk pertama kalinya dengan menjalankan pernyataan SELECT di atas. SQL Server menetapkan anggota yang disebutkan di atas ke nilai berikut:

On disk recovery value: 50, In-memory current value: 1, In-memory values left: 49, You get: 1

49 permintaan lebih tidak akan menyentuh disk, melainkan hanya memperbarui anggota memori. Setelah total 50 permintaan, anggota ditetapkan ke nilai berikut:

On disk recovery value: 50, In-memory current value: 50, In-memory values left: 0, You get: 50

Buat permintaan lain untuk nilai urutan baru, dan ini memicu penulisan disk dari nilai pemulihan 100. Anggota kemudian disetel ke nilai berikut:

On disk recovery value: 100, In-memory current value: 51, In-memory values left: 49, You get: 51

Jika pada titik ini sistem mengalami kegagalan daya, setelah restart, nilai urutan saat ini diatur ke 100 (nilai yang dipulihkan dari disk). Permintaan berikutnya untuk nilai urutan menghasilkan 101 (menulis nilai pemulihan 150 ke disk). Anda kehilangan semua nilai dalam kisaran 52 hingga 100. Nilai paling banyak yang dapat Anda hilangkan karena penghentian proses SQL Server yang tidak bersih, seperti dalam kasus kegagalan daya, adalah nilai sebanyak ukuran cache. Pertukarannya jelas; semakin besar ukuran cache, semakin sedikit disk yang menulis nilai pemulihan, dan karenanya semakin baik kinerjanya. Pada saat yang sama, semakin besar celah yang dapat dihasilkan antara dua nilai urutan jika terjadi kegagalan daya.

Semua ini cukup mudah, dan mungkin Anda sangat familiar dengan cara kerjanya. Apa yang mungkin mengejutkan adalah bahwa setiap kali SQL Server menulis nilai pemulihan baru ke disk (setiap 50 permintaan dalam contoh kami), itu juga mengeraskan buffer log. Itu tidak terjadi dengan properti kolom identitas, meskipun SQL Server secara internal menggunakan fitur caching yang sama untuk identitas seperti halnya untuk objek urutan, itu tidak membiarkan Anda mengontrol ukurannya. Ini aktif secara default dengan ukuran 10000 untuk BIGINT dan NUMERIC, 1000 untuk INT, 100 untuk SMALLINT dan 10 UNTUK TINYINT. Jika mau, Anda dapat mematikannya dengan trace flag 272 atau opsi konfigurasi cakupan IDENTITY_CACHE (2017+). Alasan SQL Server tidak perlu menyiram buffer log saat menulis nilai pemulihan terkait cache identitas ke disk adalah karena nilai identitas baru hanya dapat dibuat saat memasukkan baris ke dalam tabel. Jika terjadi kegagalan daya, baris yang dimasukkan ke dalam tabel oleh transaksi yang tidak dilakukan akan ditarik keluar dari tabel sebagai bagian dari proses pemulihan basis data saat sistem dimulai ulang. Jadi, bahkan jika setelah restart SQL Server menghasilkan nilai identitas yang sama seperti yang dibuat dalam transaksi yang tidak melakukan, tidak ada peluang untuk duplikat karena baris ditarik keluar dari tabel. Jika transaksi dilakukan, ini akan memicu log flush, yang juga akan mempertahankan penulisan nilai pemulihan terkait cache. Oleh karena itu, Microsoft tidak merasa terdorong untuk menghapus buffer log setiap kali penulisan disk yang terkait dengan cache identitas dari nilai pemulihan dilakukan.

Dengan objek urutan situasinya berbeda. Aplikasi dapat meminta nilai urutan baru dan tidak menyimpannya dalam database. Jika terjadi kegagalan daya setelah pembuatan nilai urutan baru dalam transaksi yang tidak dilakukan, setelah restart, tidak ada cara bagi SQL Server untuk memberi tahu aplikasi agar tidak bergantung pada nilai itu. Oleh karena itu, untuk menghindari membuat nilai urutan baru setelah restart yang sama dengan nilai urutan yang dihasilkan sebelumnya, SQL Server memaksa log flush setiap kali nilai pemulihan terkait urutan-cache ditulis ke disk. Satu pengecualian untuk aturan ini adalah ketika objek urutan dibuat di tempdb, tentu saja tidak perlu log flush seperti itu karena bagaimanapun juga setelah sistem restart tempdb dibuat lagi.

Dampak kinerja negatif dari log flush yang sering terlihat terutama saat menggunakan ukuran cache urutan yang sangat kecil, dan dalam satu transaksi menghasilkan banyak nilai urutan, misalnya, saat memasukkan banyak baris ke dalam tabel. Tanpa urutan, transaksi semacam itu sebagian besar akan mengeraskan buffer log saat terisi, ditambah satu kali lagi saat transaksi dilakukan. Tetapi dengan urutannya, Anda mendapatkan log flush setiap kali penulisan disk dari nilai pemulihan terjadi. Itulah mengapa Anda ingin menghindari penggunaan ukuran cache yang kecil — apalagi mode NO CACHE.

Untuk mendemonstrasikannya, gunakan kode berikut di bagian persiapan template pengujian kami:

-- Preparation
SET NOCOUNT ON;
USE PerformanceV3; -- try PerformanceV3, tempdb
 
ALTER DATABASE PerformanceV3         -- try PerformanceV3, tempdb
  SET DELAYED_DURABILITY = Disabled; -- try Disabled, Forced
 
DROP TABLE IF EXISTS dbo.T1;
 
DROP SEQUENCE IF EXISTS dbo.Seq1;
 
CREATE SEQUENCE dbo.Seq1 AS BIGINT MINVALUE 1 CACHE 50; -- try NO CACHE, CACHE 50, CACHE 10000
 
DECLARE @db AS sysname = N'PerformanceV3'; -- try PerformanceV3, tempdb

Dan kode berikut di bagian kerja yang sebenarnya:

-- Actual work
SELECT
  -- n -- to test without seq
  NEXT VALUE FOR dbo.Seq1 AS n -- to test sequence
INTO dbo.T1
FROM PerformanceV3.dbo.GetNums(1, 1000000) AS N;

Kode ini menggunakan satu transaksi untuk menulis 1.000.000 baris ke dalam tabel menggunakan pernyataan SELECT INTO, menghasilkan nilai urutan sebanyak jumlah baris yang disisipkan.

Seperti yang diinstruksikan dalam komentar, jalankan pengujian dengan NO CACHE, CACHE 50 dan CACHE 10000, baik di PerformanceV3 dan tempdb, dan coba transaksi yang tahan lama sepenuhnya dan transaksi tahan lama yang tertunda.

Berikut adalah angka kinerja yang saya dapatkan di sistem saya:

database       durability          cache     log flushes  duration in seconds
-------------- ------------------- --------- ------------ --------------------
PerformanceV3  full                NO CACHE  1000047      171
PerformanceV3  full                50        20008        4
PerformanceV3  full                10000     339          < 1
tempdb         full                NO CACHE  96           4
tempdb         full                50        74           1
tempdb         full                10000     8            < 1
PerformanceV3  delayed             NO CACHE  1000045      166
PerformanceV3  delayed             50        20011        4
PerformanceV3  delayed             10000     334          < 1
tempdb         delayed             NO CACHE  91           4
tempdb         delayed             50        74           1
tempdb         delayed             10000     8            < 1

Ada beberapa hal menarik yang perlu diperhatikan.

Dengan NO CACHE Anda mendapatkan log flush untuk setiap nilai urutan tunggal yang dihasilkan. Oleh karena itu, sangat disarankan untuk menghindarinya.

Dengan ukuran cache urutan kecil, Anda masih mendapatkan banyak log flush. Mungkin situasinya tidak seburuk dengan NO CACHE, tetapi perhatikan bahwa beban kerja membutuhkan waktu 4 detik untuk diselesaikan dengan ukuran cache default 50 dibandingkan dengan kurang dari satu detik dengan ukuran 10.000. Saya pribadi menggunakan 10.000 sebagai nilai pilihan saya.

In tempdb you don’t get log flushes when a sequence cache-related recovery value is written to disk, but the recovery value is still written to disk every cache-sized number of requests. That’s perhaps surprising since such a value would never need to be recovered. Therefore, even when using a sequence object in tempdb, I’d still recommend using a large cache size.

Also notice that delayed durability doesn’t prevent the need for log flushes every time the sequence cache-related recovery value is written to disk.

Kesimpulan

This article focused on log buffer flushes. Understanding this aspect of SQL Server’s logging architecture is important especially in order to be able to optimize OLTP-style workloads that require high frequency and low latency. Workloads using In-Memory OLTP included, of course. You have more options with newer features like delayed durability and persisted log buffer with storage class memory. Make sure you’re very careful with the former, though, since it does incur potential for data loss unlike the latter.

Be careful not to use the sequence object with a small cache size, not to speak of the NO CACHE mode. I find the default size 50 too small and prefer to use 10,000. I’ve heard people expressing concerns that with a cache size 10000, after multiple power failures they might lose all the values in the type. However, even with a four-byte INT type, using only the positive range, 10,000 fits 214,748 times. If your system experience that many power failures, you have a completely different problem to worry about. Therefore, I feel very comfortable with a cache size of 10,000.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Parsing nilai default parameter menggunakan PowerShell – Bagian 1

  2. SQL ANTARA Operator untuk Pemula

  3. Menghubungkan IBM DB2 dengan Perangkat Lunak IRI

  4. CREATE TABLE in SQL – Semua yang Perlu Anda Ketahui Tentang Membuat Tabel di SQL

  5. Aqua Data Studio