Vítejte na blog.vyvojar.cz Přihlásit | Registrovat | Pomoc

Zdeněk Drlík

Vývoj aplikací, platforma .NET a další ...
Sekvenční GUID

Při návrhu sloupců primárních klíčů se u MS SQL poměrně často používá datový typ GUID. Jednou ze zajímavých výhod tohoto datového typu pro aplikačního vývojáře je fakt, že hodnoty primárních klíčů je možné generovat i v klientské aplikaci (standardně metodou Guid.NewGuid()). Odpadá tak určitá pracnost navíc při použití databází generovaných sekvenčních hodnot klíče (INT s IDENTITY apod.), kdy je nutné po vložení záznamu hodnoty z databáze do aplikace zpětně načítat. Z pohledu vývojáře je tedy GUID většinou méně pracné řešení a proto bývá také často jako primární klíč používán.

Pro databázový server nicméně primární klíče s datovým type GUID není úplně ideální volba. Datový typ GUID zabírá jednak o něco víc místa než například zmiňovaný INT (16 byte GUID oproti 4 byte INT). Díky tomu budou například databázové indexy s použitím GUID vždy větší, než indexy s použitím INT.

Možná ještě větším problémem může být skutečnost, že generovaná hodnota je z pohledu řazení náhodná. Ve spojení s primárním klíčem a navíc clustered indexem (pokud není explicitně řečeno jinak, je vytvářen s primárním klíčem “by default”) dochází tedy většinou k tomu, že nově vkládaný záznam není ukládán jako u sekvenčních hodnot v tabulce “na konec”, ale do různých datových stránek podle vygenerované hodnoty. Toto vede k častější nutnosti rozdělování stránek a reorganizaci dat v nich (blíže viz popis u Fill Factor). Výsledkem je obvykle daleko vyšší počet použitých stránek a také jejich vyšší fragmentace, než při použití klíčů se sekvenčně generovanými hodnotami.

Jedním z řešení, které dokáže tuto nevýhodu eliminovat, je generování sekvenčních GUID. Jedná se o způsob generování GUID, který zajišťuje, že hodnoty generované na jednom počítači budou jednak unikátní a také každá vytvořená hodnota bude větší než předchozí.

V databázi pro tento způsob vytváření lze využít funkci NEWSEQUENTIALID (bohužel lze využít pouze jako default hodnota sloupce), možnost generování takovéto hodnoty v aplikaci je ukázáno dále.

Vytváření sekvenčního GUID v .NET

Pro vytvoření sekvenčního GUID existuje API funkce UuidCreateSequential. Protože managed třída Guid metody pro její využití neobsahuje, je nutné toto implementovat s pomocí P/Invoke voláním této API funkce. Pomocnou třídu pro jejich vytváření nazveme například GuidCreator a vypadat by mohla například takto:

Code Snippet
  1. public static class GuidCreator
  2. {        
  3.     private enum RpcUuidCodes : int
  4.     {
  5.         RPC_S_OK = 0,
  6.         RPC_S_UUID_LOCAL_ONLY = 1824,
  7.         RPC_S_UUID_NO_ADDRESS = 1739
  8.     }
  9.     [System.Runtime.InteropServices.DllImport("rpcrt4.dll", SetLastError = true)]
  10.     static extern int UuidCreateSequential(out Guid guid);      
  11.         
  12.     public static Guid NewGuid()
  13.     {
  14.         return Guid.NewGuid();
  15.     }
  16.     public static Guid NewSequentialGuid()
  17.     {
  18.         return CreateSequentialGuid();
  19.     }
  20.     public static Guid NewSQLSequentialGuid()
  21.     {
  22.         return SQLLikeReverseBytes(CreateSequentialGuid());
  23.     }
  24.         
  25.     private static Guid CreateSequentialGuid()
  26.     {
  27.         Guid guid = Guid.Empty;
  28.         var resultCode = UuidCreateSequential(out guid);
  29.         switch (resultCode)
  30.         {
  31.             case (int)RpcUuidCodes.RPC_S_OK:
  32.                 break;
  33.             case (int)RpcUuidCodes.RPC_S_UUID_LOCAL_ONLY:
  34.                 throw new Exception(@"NewGuid failed - UuidCreateSequential returned RPC_S_UUID_LOCAL_ONLY");
  35.             case (int)RpcUuidCodes.RPC_S_UUID_NO_ADDRESS:
  36.                 throw new Exception(@"NewGuid failed - UuidCreateSequential returned RPC_S_UUID_NO_ADDRESS");
  37.             default:
  38.                 throw new Exception(String.Format(@"NewGuid failed - UuidCreateSequential returned {0}", resultCode));
  39.         }
  40.         return guid;
  41.     }
  42.     private static Guid SQLLikeReverseBytes(Guid value)
  43.     {
  44.         var bytes = value.ToByteArray();
  45.         Array.Reverse(bytes, 0, 4);
  46.         Array.Reverse(bytes, 4, 2);
  47.         Array.Reverse(bytes, 6, 2);
  48.         return new Guid(bytes); ;
  49.     }        
  50. }

Metoda NewGuid je zde uvedena jen pro úplnost a reprezentuje klasické vytváření náhodného GUID pomocí volání standardní .NET metody Guid.NewGuid(). Hodnoty jsou generovány náhodně:

965bb964-6eca-4607-a037-d2550b51aefa
6f7d260a-67b3-444b-a7e7-dba8fddcd7a6
1fecc9c7-50a7-4f78-99cc-469a8cf65fc4
7fbde154-be15-469a-8d91-c337b9d1c37a

Metoda NewSequentialGuid reprezentuje vytváření sekvenčního GUID pomocí přímého volání API funkce UuidCreateSequential:

b5aea086-aecc-11df-940d-001fe2e4f119
b5aea087-aecc-11df-940d-001fe2e4f119
bd77004c-aecc-11df-940d-001fe2e4f119
bd77004d-aecc-11df-940d-001fe2e4f119

Pro kompatibilitu s SQL metodou NEWSEQUENTIALID je potřeba ještě ve vygenerovaném GUID provést přehození některých bytů. Dle http://blogs.msdn.com/b/sqlprogrammability/archive/2006/03/23/559061.aspx se přehození provádí proto, aby výsledné hodnoty lépe vyhovovaly interním algoritmům SQL serveru pro porovnávání GUID hodnot. Tento postup implementuje metoda NewSQLSequentialGuid a generované hodnoty tak odpovídají hodnotám vraceným uvedenou SQL funkcí:

6cd9ef0e-cdae-df11-940d-001fe2e4f119
6dd9ef0e-cdae-df11-940d-001fe2e4f119
4ab28917-cdae-df11-940d-001fe2e4f119
4bb28917-cdae-df11-940d-001fe2e4f119

V případě použití takto generovaných hodnot je samozřejmě dobré si uvědomit, že sekvenčně generované budou hodnoty vždy pouze na jednom počítači. Lze to tedy použít u aplikací, u nichž lze toto zajistit (ASP.NET, vícevrstvé aplikace s aplikačním serverem apod.). V klientských aplikacích bez aplikačního serveru postrádá většinou použití sekvenčních ID smysl.

Testovací aplikace s příkladem generování hodnot pomocí výše uvedené třídy GuidCreator je k dispozici ke stažení zde.

Na závěr ještě pár odkazů na další informace:

http://msdn.microsoft.com/en-us/library/ms190215.aspx

http://www.mssqltips.com/tip.asp?tip=1600

http://www.jorriss.net/blog/jorriss/archive/2008/04/24/unraveling-the-mysteries-of-newsequentialid.aspx 

http://www.shirmanov.com/2010/05/generating-newsequentialid-compatible.html

Delay signing a TFS build server podruhé

Ve firmě jsme před nějakým časem začali používat na vývojářských počítačích delay signing s následným vytvořením plného podpisu při buildu na TFS build serveru. Nastavení vývojářských počítačů pro podporu delay signingu jsem popisoval v tomto článku . K vytvoření plného podpisu byl použit vlastní build task, který spouští nad každou zkompilovanou assembly nástroj sn.exe. Implementaci tasku a jeho použití jsem se věnoval v dalším článku.

Řešení s pomocí vlastního tasku bylo sice funkční, ale bylo poměrně pomalé a občas nestabilní, zejména při větším zatížení build serveru. Se zvyšujícím se počtem projektů v TFS tyto problémy narůstaly a bylo nutné najít jiné řešení.

Řešením se nakonec ukázalo využití konfiguračního elementu CustomPropertiesForBuild v projektovém souboru TFSBuild.proj. Tento element je možné využít k předání vlastností MSBuildu (+ případnému přepsání existujících) pro build jednotlivých projektů v solution. Výchozí nastavení pro “delay signing” jednotlivých projektů je tak možné přepsat na plné podepsání následujícím způsobem:

<CustomPropertiesForBuild>
    SignAssembly=true;DelaySign=false;AssemblyOriginatorKeyFile=c:\Keys\firemniKlic.snk
</CustomPropertiesForBuild>

Za inspiraci díky http://ozgrant.com/2008/03/13/strong-name-your-assemblies-with-team-build-using-a-private-key/ a pokud by někdo chtěl experimentovat s uložením privátního klíče do úložiště klíčů namísto souboru, tak to je možné také – viz http://blogs.msdn.com/nagarajp/archive/2005/11/08/490501.aspx .

Použití delay signing při vývoji

Už nějakou dobu ve firmě používáme podepisování assembly pomocí strong name. Před necelým rokem jsme se začali zabývat použitím odloženého podepisování (trošku váhám jak se toto vlastně má správně přeložit - odložený podpis, opožděný podpis nebo nějak jinak?). Základním důvodem k tomuto rozhodnutí je snaha aby "běžní" vývojáři neměli přístup k firemnímu privátnímu klíči. Podle různých doporučení ze strany Microsoftu jsme nabyli dojmu že je to ta "správná" cesta pro použití strong name ve firmě a rozhodli jsme se ji použít. Popis toho jaké nástrahy s sebou použití odloženého podepisování přináší pro práci vývojáře se budu snažit na základě vlastních zkušeností popsat v tomto příspěvku, v pokračování někdy příště se zmíním také o nástrahách které použití přináší ve spojení s Team Foundation Serverem a jeho buildovacím serverem.

Jen malá poznámka na úvod - nebudu se zde příliš zabývat tím, k čemu všemu slouží strong name je a k čemu konkrétně odloženého podepisování. Zájemce o detaily k této problematice odkážu na kompetentní zdroje v MSDN.

Příprava na použití - začínáme

Pro podepisování assembly pomocí strong name je potřeba pomocí nástroje sn.exe vygenerovat pár klíčů (privátní a veřejný jak bývá zvykem). To lze provést zhruba takto:

sn.exe -k firemniKlic.snk

Pokud chceme privátní klíč před běžnými vývojáři schovat, provedeme vyextrahování veřejného klíče z tohoto souboru pomocí:

sn.exe -p firemniKlic.snk firemniKlicPublic.snk

a původní soubor firemniKlic.snk uložíme někam, kam nemají běžní vývojáří přístup. Měli by tam ale mít samozřejmě přístup alespoň ti "neběžní", aby mohli před distribucí assembly "opravdicky" podepsat. Těm běžným poté dáme pouze veřejný klíč uložený v tuto chvíli v souboru firemniKlicPublic.snk a jak odpovědět na dotazy vývojářů typu "A co s tím teď jako mám dělat?" si popíšeme dále.

Nastavení opožděného podepisování ve zdrojácích .NET projektu

Nastavení podepisování assembly pomocí strong name se provádí pomocí atributu AssemblyKeyFileAttribute, kterým určíme soubor ve kterém je uložen klíč pro vytvoření strong name. Jen pro úplnost - existuje i možnost klíče místo ze souboru brát z CSP kontejneru, k tomu lze využít atribut AssemblyKeyNameAttribute. V našem případě používáme umístění klíče v souboru, protože se nám toto jeví jako praktičtější kvůli umístění souboru do source control systému společně se zdrojáky. Vývojář kromě stažení zdrojových kódů z TFSka nemusí pro práci na projektu na svém PC dělat nic jiného
(umisťování klíčů do CSP kontejneru apod.).

Použití odloženého podepsání se následně specifikuje atributem AssemblyDelaySignAttribute a jeho parametrem true/false. Oba tyto atributy se samozřejmě váží k assembly. Ve VS 2002/2003 je tedy dobrým zvykem je umístit "ručně" do souboru AssemblyInfo.cs:

[assembly: AssemblyKeyFile("firemniKlicPublic.snk")] [assembly: AssemblyDelaySign(true)]

Ve VS 2005 se nastavení provádí ve vlastnostech projektu na záložce Signing - volby "Sign the assembly" a "Delay sign only".

Poté co toto nastavení provedeme, projekt je možné bez problémů přeložit a jako vývojáři jsme spokojení jak jsme to všechno skvěle zvládli a jak nám to odložený podepisování krásně bude teď fungovat. Ale bohužel tak jednoduché to zase není, protože problém se většinou dostaví hned poté co budeme chtít takto nastavené projekty spouštět a ladit.

Použití delay-signed assembly

Pro .NET Runtime představuje delay-signed assembly problém, bez dalšího nastavení ji odmítne použít. Vývojář je o tomto většinou informován hned při pokusu o ladění aplikace v debuggeru VS (nejpozději při pokusu o spuštění aplikace mimo VS, pokud debugger nepoužije) hláškou "Error while trying to run project: Could not load file or asembly...". Tato hláška je výsledkem pokusu o ověření platnosti strong name při nahrávání assembly a protože podpis nebyl vytvořen privátním klíčem tak toto ověření bohužel selže.

Problém je možné vyřešit tím, že se tato assembly zaregistruje do seznamu assembly u kterých nemá být ověření platnosti strong name prováděno. Použijeme k tomu opět nástroj sn.exe:

sn.exe -Vr assembly.dll

Po zařazení assembly do seznamu nebude runtime při nahrávání verifikaci provádět a assembly bez dalšího protestování nahraje. (Pokud je spuštěno VS a assembly do seznamu umístěna až poté co jsme se ji pokusili použít a nastala výše uvedená chyba, "zabere" zařazení assembly do seznamu až po restartu VS. Vypadá to na nějakou formu cachování načtených assembly ve VS, ale nijak blíž jsem tuto domněnku nezkoumal...)

Jak pozorný čtenář asi tuší, vyřazení kontroly strong name u assembly představuje určité bezpečnostní riziko. Pokud nebude strong name při nahrávání assembly kontrolován, může kdokoliv místo původní assembly podstrčit svoji upravenou (stačí mu k tomu náš veřejný klíč). Proto je dobré tento příkaz používat pouze na počítačích vývojářů nebo případně na testovacím stroji, nikdy v produkčním prostředí.

Pro práci se seznamem nástroj nabízí samozřejmě i další funkce. K vyjmutí assembly ze seznamu slouží příkaz

sn.exe -Vr assembly.dll

a k případnému úplnému vyčištění seznamu (ve stylu "MrProper") příkaz

sn.exe -Vx

Poté co assembly zařadíme do seznamu a runtime přestane její strong name ověřovat, je možné na počítači vývojáře tyto assembly vesele používat. Pokud ale tyto assembly poté chceme distribuovat jako finální produkt zákazníkovi, musíme zajistit jejich "plnokrevné" podepsání. To typicky bude provádě onen "neběžný" vývojář, který má přístup k privátnímu klíči spuštěním známého nástroje sn.exe:

sn.exe -Ra assembly.dll firemniKlic.snk

Usnadnění práce - aneb příkazový řádek ano, ale... Smile

Snaha o použití strong name a odloženého podepisování při vývoji je nyní korunována úspěchem a bylo by možné zde skončit. Nicméně vývojáři často pracují se solution které čítají několik (nebo i několik desítek) projektů a z tohoto způsobu použití by nebyli asi nijak zvlášť nadšeni. Představa že musí pro každou assembly na svém počítači spustit z příkazové řádky sn.exe s příslušnými parametry a poté případně metodou pokus-omyl za neustálého restartování VS zjišťovat kterou že assembly ještě zapoměli do seznamu přidat není příliš lákavá. Na toto by se hodil nějaký nástroj, který by umožnil tuto činnost nějak zjednodušit.

Přiznám se rovnou že se mi žádný podobný nástroj od MS ani třetích stran nepodařilo najít, tak jsem to nakonec vyřešil tak že jsem si jej napsal sám. Je to jednoduchá WinForm aplikace která je integrovaná do VS jako externí nástroj a po spuštění vyhledá rekurzivně v podadresářích solution všechny delay-signed assembly a umožní jejich zařazení do seznamu. Stejně tak umožňuje poté seznam mazat apod. Není to žádná velká sláva, ale oproti ručnímu spouštění nástroje z příkazového řádku je to pro vývojáře přece jen o chlup jednodušší.

Vynález kola?

Docela by mě zajímalo jaké zkušenosti s problematikou mají ostatní firmy. Používají strong name a pokud ano tak používají ten snazší scénář kdy mají všichni vývojáří přístup k privátnímu klíči anebo jdou jako my složitější cestou použití odloženého podepisování? A pokud někdo toto používá, našel snazší cestu než popisuji (aneb vynalézali jsme znovu kolo)?

Příště se jak jsem avizoval podělím o zkušenosti se zprovozněním opětovného podepsání delay-signed assembly privátním klíčem při buildu na Team Foundation Serveru. Buď jsem opět něco přehlédl (a znovu vynalézal kolo), nebo podpora pro tento scénář opět od MS zcela chybí.

Vyvojar.cz na prodej!