01 September 2013 Esent Robert Muehsig

Wer RavenDB verwendet, wird vermutlich bereits über Esent gestolpert sein. Unter der Haube verwendet RavenDB die “Extensible Storage Engine”, welche in Windows seit XP vorhanden ist. Hier schreibt Ayende warum er sich seit kurzem nach einer Alternative umschaut – wobei Esent generell wohl gute Dienste leistet. Aber das nur nebenbei…

Esent wird von Microsoft in vielen Bereichen gebraucht – darunter das Active-Directory und Exchange Mailbox Daten, d.h. ziemlich große Datenmengen. Ich zitiere mal diesen Blogpost, da die Features von ESENT ziemlich gut dargestellt wurden:

    Features

    Significant technical features of ESENT include:

    - </em>ACID transactions with savepoints, lazy commits, and robust crash recovery.
    -
    Snapshot isolation.
    -
    Record-level locking (multi-versioning provides non-blocking reads).
    - H
    ighly concurrent database access.
    -
    Flexible meta-data (tens of thousands of columns, tables, and indexes are possible).
    -
    Indexing support for integer, floating point, ASCII, Unicode, and binary columns.
    -
    Sophisticated index types, including conditional, tuple, and multi-valued.
    -
    Columns that can be up to 2GB with a maximum database size of 16TB.

      Note: The ESENT database file cannot be shared between multiple processes simultaneously. ESENT works best for applications with simple, predefined queries; if you have an application with complex, ad-hoc queries, a storage solution that provides a query layer will work better for you.

      </ul>

      Wenn Esent sowohl in diesen Bereichen als auch von Ayende verwendet wird, kann doch ein Blick darauf nicht schaden.

      Erste Schritte mit Esent- ManagedEsent

      Natürlich kann man Esent mit C++ ansprechen, aber so richtig vertrauenserweckend wirkt das nicht. Etwas praktischer wirkt da schon:

      image

      Das Projekt hat zwei Bestandteile:

      - ein .NET Wrapper zu Esent.dll

      - PersistentDictionary, welches den .NET Wrapper nimmt und eine nette API bietet

      Zudem gibt es einiges an Dokumentation dazu.

      .NET Wrapper

      Wer “purer” auf Esent zugreifen will, kann das über den .NET Wrapper machen. Wobei man schon die Internas von Esent kennen muss. Hier ist der Code einer Sample-Applikation, wobei diese Sample-App sehr “low-level” ist.

      namespace EsentSample
      {
          using System;
          using System.Text;
          using Microsoft.Isam.Esent.Interop;
      
          public class EsentSample
          {
              /// <summary>
              /// Main routine. Called when the program starts.
              /// </summary>
              /// <param name="args">
              /// The arguments to the program.
              /// </param>
              public static void Main(string[] args)
              {
                  JET_INSTANCE instance;
                  JET_SESID sesid;
                  JET_DBID dbid;
                  JET_TABLEID tableid;
      
                  JET_COLUMNDEF columndef = new JET_COLUMNDEF();
                  JET_COLUMNID columnid;
      
                  // Initialize ESENT. Setting JET_param.CircularLog to 1 means ESENT will automatically
                  // delete unneeded logfiles. JetInit will inspect the logfiles to see if the last
                  // shutdown was clean. If it wasn't (e.g. the application crashed) recovery will be
                  // run automatically bringing the database to a consistent state.
                  Api.JetCreateInstance(out instance, "instance");
                  Api.JetSetSystemParameter(instance, JET_SESID.Nil, JET_param.CircularLog, 1, null);
                  Api.JetInit(ref instance);
                  Api.JetBeginSession(instance, out sesid, null, null);
      
                  // Create the database. To open an existing database use the JetAttachDatabase and 
                  // JetOpenDatabase APIs.
                  Api.JetCreateDatabase(sesid, "edbtest.db", null, out dbid, CreateDatabaseGrbit.OverwriteExisting); 
      
                  // Create the table. Meta-data operations are transacted and can be performed concurrently.
                  // For example, one session can add a column to a table while another session is reading
                  // or updating records in the same table.
                  // This table has no indexes defined, so it will use the default sequential index. Indexes
                  // can be defined with the JetCreateIndex API.
                  Api.JetBeginTransaction(sesid);
                  Api.JetCreateTable(sesid, dbid, "table", 0, 100, out tableid);
                  columndef.coltyp = JET_coltyp.LongText;
                  columndef.cp = JET_CP.ASCII;
                  Api.JetAddColumn(sesid, tableid, "column1", columndef, null, 0, out columnid);
                  Api.JetCommitTransaction(sesid, CommitTransactionGrbit.LazyFlush);
      
                  // Insert a record. This table only has one column but a table can have slightly over 64,000
                  // columns defined. Unless a column is declared as fixed or variable it won't take any space
                  // in the record unless set. An individual record can have several hundred columns set at one
                  // time, the exact number depends on the database page size and the contents of the columns.
                  Api.JetBeginTransaction(sesid);
                  Api.JetPrepareUpdate(sesid, tableid, JET_prep.Insert);
                  string message = "Hello world";
                  Api.SetColumn(sesid, tableid, columnid, message, Encoding.ASCII);
                  Api.JetUpdate(sesid, tableid);
                  Api.JetCommitTransaction(sesid, CommitTransactionGrbit.None);    // Use JetRollback() to abort the transaction
      
                  // Retrieve a column from the record. Here we move to the first record with JetMove. By using
                  // JetMoveNext it is possible to iterate through all records in a table. Use JetMakeKey and
                  // JetSeek to move to a particular record.
                  Api.JetMove(sesid, tableid, JET_Move.First, MoveGrbit.None);
                  string buffer = Api.RetrieveColumnAsString(sesid, tableid, columnid, Encoding.ASCII);
                  Console.WriteLine("{0}", buffer);
      
                  // Terminate ESENT. This performs a clean shutdown.
                  Api.JetCloseTable(sesid, tableid);
                  Api.JetEndSession(sesid, EndSessionGrbit.None);
                  Api.JetTerm(instance);
              }
          }
      }

      Das Ganze speichert einen Wert – nicht mehr. Uhhh… aber naja – so ist das im Low-Level Gebiet.

      Etwas besseren Einblick bekommt man allerdings durch das Stock-Sample, hier wird über die ManagedEsent API eine “komplexere” Datenbank erzeugt und Aktiendaten gespeichert. Aber es geht noch einfacher:

      Persistent Dictionary

      Diese Variante verhält sich im Grunde wie ein Dictionary – nur das es als Esent-Datenbank gespeichert wird. In der Dokumentation steht noch einiges mehr.

      public static void Main(string[] args)
              {
                  var dictionary = new PersistentDictionary<string, string>("Names");
      
                  Console.WriteLine("What is your first name?");
                  string firstName = Console.ReadLine();
                  if (dictionary.ContainsKey(firstName))
                  {
                      Console.WriteLine("Welcome back {0} {1}",
                          firstName,
                          dictionary[firstName]);
                  }
                  else
                  {
                      Console.WriteLine(
                          "I don't know you, {0}. What is your last name?",
                          firstName);
                      dictionary[firstName] = Console.ReadLine();
                  }
              }

      Der Code ist recht trivial – und im Hintergrund wird über die Esent API ein Ordner namens “Names” angelegt. Darin befinden sich die eigentliche Esent Datenbank:

      image

      Wie und ob man die Daten auch irgendwie über ein Tool auslesen kann (also ohne Code), weiss ich aktuell leider nicht.

      Zur Performance

      Einfach 1.000.000 Eintrage in eine Datenbank zu schreiben dauerte über die PersistentDictionary-Variante einige Sekunden. Nahm ich GUIDs anstatt Integer dauerte es wesentlich länger. Vermutlich muss man sich näher damit beschäftigen um aussagekräftige Zahlen zu bekommen. Das Team hat ein paar Performance-Daten hier und hier veröffentlicht.

      LINQ Support!

      Seit der Version 1.6 gibt es auch LINQ support für die PersistentDictionary-Variante.

      Windows Store Apps

      Da Esent ein Bestandteil von Windows ist und vermutlich einige Bereiche gibt die diese API nutzen, kann man Esent auch in Windows Store Apps nutzen. Seit Version 1.8 geht es wohl – wobei das PersistentDictionary aktuell wohl nicht unterstützt wird. Hier gibt es ein Blogpost der näher auf das Thema Esent und Windows 8 Apps eingeht.

      Weitere Links zum Thema ManagedEsent

      - Performace of Persitent Dictionary (CodePlex Discussion)
      - Ayendes Fork
      - Blog über Esent und die ManagedEsent Api eines Microsoftis

      Esent Serialization

      Neben dem “PersistentDictionary” gibt es noch ein weitere Projekt, welches auf ManagedEsent setzt:

      image

      Im Grunde erlaut diese Projekt Datenstrukturen “einfach” in Esent reinzubringen. In diesem Demo kann man die Funktionsweise erkennen.

      Fazit

      Esent hat eine nicht gerade angenehme API, wird aber selbst in Windows 8 nach wie vor verwendet. Durch die ManagedEsent Api, PersistentDictionary und Esent Serialization sind die ersten Schritte aber schnell gemacht. Ob man wirklich damit näher arbeiten möchte kommt natürlich immer auf den Einsatzzweck an – immerhin gibt es viele Alternativen die man nehmen kann. Das RavenDB auf Esent aufbaut bedeutet für mich aber, dass es durchaus eine Variante sein kann.

      PS: Da der Code für den Blogpost vollständig aus den offiziellen Quellen kommt lade ich nix auf GitHub hoch. Die benötigten ManagedEsent.dll´s gibt es auf NuGet.

      Frage: Jemand schon Erfahrung mit Esent?

      Hat jemand denn schon intensiver mit Esent gearbeitet? Oder ist das doch eher ein Nischen-Gebiet (so wie es mir auch vorkommt ;) )?


    Written by Robert Muehsig

    Software Developer - from Dresden, Germany, now living & working in Switzerland. Microsoft MVP & Web Geek.
    Other Projects: KnowYourStack.com | ExpensiveMeeting | EinKofferVollerReisen.de

    If you like the content and want to support me you could buy me a beer or a coffee via Litecoin or Bitcoin - thanks for reading!