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

Performa Selalu Terenkripsi:Tindak Lanjut

Minggu lalu, saya menulis tentang keterbatasan Always Encrypted serta dampak kinerjanya. Saya ingin memposting tindak lanjut setelah melakukan lebih banyak pengujian, terutama karena perubahan berikut:

  • Saya menambahkan tes untuk lokal, untuk melihat apakah overhead jaringan signifikan (sebelumnya, tes hanya jarak jauh). Padahal, saya harus memasukkan "overhead jaringan" dalam tanda kutip udara, karena ini adalah dua VM pada host fisik yang sama, jadi bukan benar-benar analisis bare metal.
  • Saya menambahkan beberapa kolom tambahan (tidak terenkripsi) ke tabel untuk membuatnya lebih realistis (tetapi tidak terlalu realistis).
      DateCreated  DATETIME NOT NULL DEFAULT SYSUTCDATETIME(),
      DateModified DATETIME NOT NULL DEFAULT SYSUTCDATETIME(),
      IsActive     BIT NOT NULL DEFAULT 1

    Kemudian ubah prosedur pengambilan yang sesuai:

    ALTER PROCEDURE dbo.RetrievePeople
    AS
    BEGIN
      SET NOCOUNT ON;
      SELECT TOP (100) LastName, Salary, DateCreated, DateModified, Active
        FROM dbo.Employees
        ORDER BY NEWID();
    END
    GO
  • Menambahkan prosedur untuk memotong tabel (sebelumnya saya melakukannya secara manual di antara pengujian):
    CREATE PROCEDURE dbo.Cleanup
    AS
    BEGIN
      SET NOCOUNT ON;
      TRUNCATE TABLE dbo.Employees;
    END
    GO
  • Menambahkan prosedur untuk merekam pengaturan waktu (sebelumnya saya secara manual mem-parsing keluaran konsol):
    USE Utility;
    GO
     
    CREATE TABLE dbo.Timings
    (
      Test NVARCHAR(32),
      InsertTime INT,
      SelectTime INT,
      TestCompleted DATETIME NOT NULL DEFAULT SYSUTCDATETIME(),
      HostName SYSNAME NOT NULL DEFAULT HOST_NAME()
    );
    GO
     
    CREATE PROCEDURE dbo.AddTiming
      @Test VARCHAR(32),
      @InsertTime INT,
      @SelectTime INT
    AS
    BEGIN
      SET NOCOUNT ON;
      INSERT dbo.Timings(Test,InsertTime,SelectTime)
        SELECT @Test,@InsertTime,@SelectTime;
    END
    GO
  • Saya menambahkan sepasang database yang menggunakan kompresi halaman – kita semua tahu bahwa nilai terenkripsi tidak terkompresi dengan baik, tetapi ini adalah fitur polarisasi yang dapat digunakan secara sepihak bahkan pada tabel dengan kolom terenkripsi, jadi saya pikir saya akan melakukannya profil ini juga. (Dan menambahkan dua string koneksi lagi ke App.Config .)
    <connectionStrings>
        <add name="Normal"  
             connectionString="...;Initial Catalog=Normal;"/>
        <add name="Encrypt" 
             connectionString="...;Initial Catalog=Encrypt;Column Encryption Setting=Enabled;"/>
        <add name="NormalCompress"
             connectionString="...;Initial Catalog=NormalCompress;"/>
        <add name="EncryptCompress" 
             connectionString="...;Initial Catalog=EncryptCompress;Column Encryption Setting=Enabled;"/>
    </connectionStrings>
  • Saya membuat banyak perbaikan pada kode C# (lihat Lampiran) berdasarkan umpan balik dari tobi (yang mengarah ke pertanyaan Tinjauan Kode ini) dan beberapa bantuan hebat dari rekan kerja Brooke Philpott (@Macromullet). Ini termasuk:
    • menghilangkan prosedur tersimpan untuk menghasilkan nama/gaji acak, dan melakukannya di C# sebagai gantinya
    • menggunakan Stopwatch alih-alih string tanggal/waktu yang canggung
    • penggunaan using() yang lebih konsisten dan penghapusan .Close()
    • konvensi penamaan (dan komentar!) yang sedikit lebih baik
    • mengubah while loop ke for loop
    • menggunakan StringBuilder alih-alih rangkaian naif (yang awalnya saya pilih dengan sengaja)
    • mengonsolidasikan string koneksi (meskipun saya masih sengaja membuat koneksi baru dalam setiap iterasi loop)

Kemudian saya membuat file batch sederhana yang akan menjalankan setiap pengujian 5 kali (dan mengulanginya di komputer lokal dan jarak jauh):

for /l %%x in (1,1,5) do (        ^
AEDemoConsole "Normal"          & ^
AEDemoConsole "Encrypt"         & ^
AEDemoConsole "NormalCompress"  & ^
AEDemoConsole "EncryptCompress" & ^
)

Setelah pengujian selesai, mengukur durasi dan ruang yang digunakan akan menjadi hal yang sepele (dan membuat bagan dari hasil hanya memerlukan sedikit manipulasi di Excel):

-- duration
 
SELECT HostName, Test, 
  AvgInsertTime = AVG(1.0*InsertTime), 
  AvgSelectTime = AVG(1.0*SelectTime)
FROM Utility.dbo.Timings
GROUP BY HostName, Test
ORDER BY HostName, Test;
 
-- space
 
USE Normal; -- NormalCompress; Encrypt; EncryptCompress;
 
SELECT COUNT(*)*8.192 
  FROM sys.dm_db_database_page_allocations(DB_ID(), 
    OBJECT_ID(N'dbo.Employees'), NULL, NULL, N'LIMITED');

Hasil Durasi

Berikut adalah hasil mentah dari kueri durasi di atas (CANUCK adalah nama mesin yang menghosting instance SQL Server, dan HOSER adalah mesin yang menjalankan versi kode jarak jauh):

Hasil mentah dari kueri durasi

Jelas akan lebih mudah untuk memvisualisasikan dalam bentuk lain. Seperti yang ditunjukkan pada grafik pertama, akses jarak jauh memiliki dampak signifikan pada durasi penyisipan (meningkat lebih dari 40%), tetapi kompresi memiliki dampak yang kecil sama sekali. Enkripsi saja secara kasar menggandakan durasi untuk kategori pengujian apa pun:

Durasi (milidetik) untuk menyisipkan 100.000 baris

Untuk pembacaan, kompresi memiliki dampak yang jauh lebih besar pada kinerja daripada enkripsi atau membaca data dari jarak jauh:

Durasi (milidetik) untuk membaca 100 baris acak 1.000 kali

Hasil Luar Angkasa

Seperti yang mungkin telah Anda prediksi, kompresi dapat secara signifikan mengurangi jumlah ruang yang diperlukan untuk menyimpan data ini (kira-kira setengahnya), sedangkan enkripsi dapat dilihat memengaruhi ukuran data ke arah yang berlawanan (hampir tiga kali lipat). Dan, tentu saja, mengompresi nilai terenkripsi tidak akan membuahkan hasil:

Ruang yang digunakan (KB) untuk menyimpan 100.000 baris dengan atau tanpa kompresi dan dengan atau tanpa enkripsi

Ringkasan

Ini akan memberi Anda gambaran kasar tentang dampak yang diharapkan saat menerapkan Always Encrypted. Namun, perlu diingat bahwa ini adalah tes yang sangat khusus, dan saya menggunakan build CTP awal. Data dan pola akses Anda mungkin menghasilkan hasil yang sangat berbeda, dan kemajuan lebih lanjut dalam CTP dan pembaruan di masa mendatang pada .NET Framework dapat mengurangi beberapa perbedaan ini bahkan dalam pengujian ini.

Anda juga akan melihat bahwa hasil di sini sedikit berbeda di seluruh papan daripada di posting saya sebelumnya. Ini bisa dijelaskan:

  • Waktu penyisipan lebih cepat dalam semua kasus karena saya tidak lagi mengeluarkan biaya bolak-balik ekstra ke database untuk menghasilkan nama dan gaji acak.
  • Waktu yang dipilih lebih cepat dalam semua kasus karena saya tidak lagi menggunakan metode penggabungan string yang ceroboh (yang disertakan sebagai bagian dari metrik durasi).
  • Ruang yang digunakan sedikit lebih besar dalam kedua kasus, saya menduga karena distribusi string acak yang dihasilkan berbeda.

Lampiran A – Kode Aplikasi Konsol C#

using System;
using System.Configuration;
using System.Text;
using System.Data;
using System.Data.SqlClient;
 
namespace AEDemo
{
    class AEDemo
    {
        static void Main(string[] args)
        {
            // set up a stopwatch to time each portion of the code
            var timer = System.Diagnostics.Stopwatch.StartNew();
 
            // random object to furnish random names/salaries
            var random = new Random();
 
            // connect based on command-line argument
            var connectionString = ConfigurationManager.ConnectionStrings[args[0]].ToString();
 
            using (var sqlConnection = new SqlConnection(connectionString))
            {
                // this simply truncates the table, which I was previously doing manually
                using (var sqlCommand = new SqlCommand("dbo.Cleanup", sqlConnection))
                {
                    sqlConnection.Open();
                    sqlCommand.ExecuteNonQuery();
                }
            }
 
            // first, generate 100,000 name/salary pairs and insert them
            for (int i = 1; i <= 100000; i++)
            {
                // random salary between 32750 and 197500
                var randomSalary = random.Next(32750, 197500);
 
                // random string of random number of characters
                var length = random.Next(1, 32);
                char[] randomCharArray = new char[length];
                for (int byteOffset = 0; byteOffset < length; byteOffset++)
                {
                    randomCharArray[byteOffset] = (char)random.Next(65, 90); // A-Z
                }
                var randomName = new string(randomCharArray);
 
                // this stored procedure accepts name and salary and writes them to table
                // in the databases with encryption enabled, SqlClient encrypts here
                // so in a trace you would see @LastName = 0xAE4C12..., @Salary = 0x12EA32...
                using (var sqlConnection = new SqlConnection(connectionString))
                {
                    using (var sqlCommand = new SqlCommand("dbo.AddEmployee", sqlConnection))
                    {
                        sqlCommand.CommandType = CommandType.StoredProcedure;
                        sqlCommand.Parameters.Add("@LastName", SqlDbType.NVarChar, 32).Value = randomName;
                        sqlCommand.Parameters.Add("@Salary", SqlDbType.Int).Value = randomSalary;
                        sqlConnection.Open();
                        sqlCommand.ExecuteNonQuery();
                    }
                }
            }
 
            // capture the timings
            timer.Stop();
            var timeInsert = timer.ElapsedMilliseconds;
            timer.Reset();
            timer.Start();
 
            var placeHolder = new StringBuilder();
 
            for (int i = 1; i <= 1000; i++)
            {
                using (var sqlConnection = new SqlConnection(connectionString))
                {
                    // loop through and pull 100 rows, 1,000 times
                    using (var sqlCommand = new SqlCommand("dbo.RetrieveRandomEmployees", sqlConnection))
                    {
                        sqlCommand.CommandType = CommandType.StoredProcedure;
                        sqlConnection.Open();
                        using (var sqlDataReader = sqlCommand.ExecuteReader())
                        {
                            while (sqlDataReader.Read())
                            {
                                // do something tangible with the output
                                placeHolder.Append(sqlDataReader[0].ToString());
                            }
                        }
                    }
                }
            }
 
            // capture timings again, write both to db
            timer.Stop();
            var timeSelect = timer.ElapsedMilliseconds;
 
            using (var sqlConnection = new SqlConnection(connectionString))
            {
                using (var sqlCommand = new SqlCommand("Utility.dbo.AddTiming", sqlConnection))
                {
                    sqlCommand.CommandType = CommandType.StoredProcedure;
                    sqlCommand.Parameters.Add("@Test", SqlDbType.NVarChar, 32).Value = args[0];
                    sqlCommand.Parameters.Add("@InsertTime", SqlDbType.Int).Value = timeInsert;
                    sqlCommand.Parameters.Add("@SelectTime", SqlDbType.Int).Value = timeSelect;
                    sqlConnection.Open();
                    sqlCommand.ExecuteNonQuery();
                }
            }
        }
    }
}

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Instruksi Pembuatan dan Penerapan Server Basis Data Umum

  2. Menerjemahkan Data Tenaga Penjualan Ke Format EDI

  3. Dasar-dasar ekspresi tabel, Bagian 4 – Tabel turunan, pertimbangan pengoptimalan, lanjutan

  4. Cadangan hanya basis data di WHM

  5. Apakah Pengemudi Tenaga Penjualan Anda Mendukung Tindakan Massal?