sql >> Databáze >  >> RDS >> Database

Vždy šifrovaný výkon:Následná akce

Minulý týden jsem psal o omezeních Always Encrypted a také o dopadu na výkon. Po provedení dalších testů jsem chtěl zveřejnit následnou zprávu, především kvůli následujícím změnám:

  • Přidal jsem test pro místní, abych zjistil, zda byla režie sítě významná (dříve byl test pouze vzdálený). I když bych měl uvést „síťovou režii“ do vzduchových uvozovek, protože se jedná o dva virtuální počítače na stejném fyzickém hostiteli, takže ve skutečnosti nejde o skutečnou analýzu holých kovů.
  • Do tabulky jsem přidal několik dalších (nešifrovaných) sloupců, aby byla realističtější (ale ve skutečnosti ne tak realistická).
      DateCreated  DATETIME NOT NULL DEFAULT SYSUTCDATETIME(),
      DateModified DATETIME NOT NULL DEFAULT SYSUTCDATETIME(),
      IsActive     BIT NOT NULL DEFAULT 1

    Poté odpovídajícím způsobem změňte postup vyhledávání:

    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
  • Přidána procedura pro zkrácení tabulky (dříve jsem to dělal ručně mezi testy):
    CREATE PROCEDURE dbo.Cleanup
    AS
    BEGIN
      SET NOCOUNT ON;
      TRUNCATE TABLE dbo.Employees;
    END
    GO
  • Přidán postup pro záznam časování (dříve jsem ručně analyzoval výstup konzole):
    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
  • Přidal jsem pár databází, které používaly kompresi stránek – všichni víme, že zašifrované hodnoty se špatně komprimují, ale toto je polarizační funkce, kterou lze použít jednostranně i na tabulkách se zašifrovanými sloupci, takže jsem si řekl, profilujte i tyto. (A přidal dva další připojovací řetězce do 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>
  • Provedl jsem mnoho vylepšení kódu C# (viz příloha) na základě zpětné vazby od tobiho (což vedlo k této otázce Code Review) a skvělé pomoci od kolegyně Brooke Philpott (@Macromullet). Patří mezi ně:
    • odstranění uložené procedury pro generování náhodných jmen/platů a místo toho to v C#
    • pomocí Stopwatch místo nemotorných řetězců data/času
    • důslednější používání using() a odstranění .Close()
    • trochu lepší konvence pojmenování (a komentáře!)
    • změna while smyčky do for smyčky
    • pomocí StringBuilder místo naivního zřetězení (které jsem původně zvolil záměrně)
    • konsolidace připojovacích řetězců (ačkoli stále záměrně vytvářím nové připojení v rámci každé iterace smyčky)

Potom jsem vytvořil jednoduchý dávkový soubor, který by spustil každý test 5krát (a opakoval to na místním i vzdáleném počítači):

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

Po dokončení testů by bylo měření doby trvání a použitého prostoru triviální (a vytváření grafů z výsledků by vyžadovalo jen malou manipulaci v Excelu):

-- 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');

Výsledky trvání

Zde jsou nezpracované výsledky z výše uvedeného dotazu na dobu trvání (CANUCK je název počítače, který je hostitelem instance SQL Server, a HOSER je stroj, který spustil vzdálenou verzi kódu):

Nezpracované výsledky dotazu na trvání

Zjevně bude snazší si to představit v jiné formě. Jak ukazuje první graf, vzdálený přístup měl významný dopad na dobu trvání vložek (nárůst více než 40 %), ale komprese měla vůbec malý dopad. Samotné šifrování zhruba zdvojnásobilo dobu trvání jakékoli testovací kategorie:

Délka (milisekundy) pro vložení 100 000 řádků

U čtení měla komprese mnohem větší dopad na výkon než šifrování nebo vzdálené čtení dat:

Doba trvání (milisekundy) na přečtení 100 náhodných řádků 1000krát

Výsledky v prostoru

Jak jste možná předpověděli, komprese může výrazně snížit množství místa potřebného k uložení těchto dat (zhruba na polovinu), zatímco u šifrování lze pozorovat dopad na velikost dat v opačném směru (téměř ztrojnásobit ji). A komprimovat zašifrované hodnoty se samozřejmě nevyplácí:

Použitý prostor (KB) k uložení 100 000 řádků s kompresí nebo bez ní a s nebo bez šifrování

Shrnutí

To by vám mělo poskytnout přibližnou představu o tom, jaký dopad bude mít implementace Always Encrypted. Mějte však na paměti, že se jednalo o velmi konkrétní test a že jsem používal rané sestavení CTP. Vaše data a vzorce přístupu mohou přinést velmi odlišné výsledky a další pokroky v budoucích CTP a aktualizace rozhraní .NET Framework mohou některé z těchto rozdílů snížit i v tomto testu.

Také si všimnete, že výsledky zde byly vesměs trochu jiné než v mém předchozím příspěvku. To lze vysvětlit:

  • Časy vkládání byly ve všech případech rychlejší, protože již nepotřebuji další zpáteční cestu do databáze, abych vygeneroval náhodné jméno a plat.
  • Doby výběru byly ve všech případech rychlejší, protože již nepoužívám nedbalou metodu zřetězení řetězců (která byla zahrnuta jako součást metriky trvání).
  • Použitý prostor byl v obou případech o něco větší, mám podezření, že kvůli odlišnému rozložení náhodných řetězců, které byly vygenerovány.

Příloha A – Kód aplikace konzoly 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. Tabulky vs. databáze:Je čas přejít? Část 1

  2. Najděte pondělí mezi 2 daty

  3. SQLite SUM

  4. Jak najít duplicitní hodnoty v tabulce SQL