05 Apr 2010

Creating a Time UUID (GUID) in .NET

4 Comments FluentCassandra

Previously I had written about how to setup Cassandra as a database on your Windows machine.  As I was diving in deeper to learn more about the subject, I realized that .NET lacks a critical type to Cassandra, for column comparison and sorting, called TimeUUIDType. TimeUUIDType is a Version 1 UUID used in the CompareWith attribute of the storage config file.  A Version 1 UUID is defined as the following:

Conceptually, the original (version 1) generation scheme for UUIDs was to concatenate the UUID version with the MAC address of the computer that is generating the UUID, and with the number of 100-nanosecond intervals since the adoption of the Gregorian calendar in the West. In practice, the actual algorithm is more complicated. This scheme has been criticized in that it is not sufficiently “opaque”; it reveals both the identity of the computer that generated the UUID and the time at which it did so.

What are the Cassandra CompareWith Types

The CompareWith attribute tells Cassandra how to sort the columns for slicing operations.  The default is BytesType, which is a straightforward lexical comparison of the bytes in each column.  Other options are AsciiType, UTF8Type, LexicalUUIDType, TimeUUIDType, and LongType.  You can also specify the fully-qualified class name to a class of your choice extending org.apache.cassandra.db.marshal.AbstractType.

SuperColumns have a similar CompareSubcolumnsWith attribute.

  • BytesType: Simple sort by byte value.  No validation is performed.
  • AsciiType: Like BytesType, but validates that the input can be parsed as US-ASCII.
  • UTF8Type: A string encoded as UTF8
  • LongType: A 64bit long
  • LexicalUUIDType: A 128bit UUID, compared lexically (by byte value)
  • TimeUUIDType: a 128bit version 1 UUID, compared by timestamp

NOTE: The CompareWith types are what we in the relation database world call Table keys.

How do these Types Relate to .NET Types

These types listed above have the following mapping to .NET types:

  • BytesType: Byte[]
  • AsciiType: String (generated by Encoding.ASCII)
  • UTF8Type: String (generated by Encoding.UTF8)
  • LongType: Int64
  • LexicalUUIDType: Guid (generated by Guid.NewGuid())
  • TimeUUIDType: Guid (no native way to generate from .NET Framework)

As you can see from above all of the types can easily be generated by .NET, except for the TimeUUIDType.

What is the point of the TimeUUIDType

I will let Arin Sarkissian describe why you should care about TimeUUIDType:

Since we’re going to want to display lists of entries in chronological order we’ll make sure each Columns name is a time UUID and set the ColumnFamilys CompareWith to TimeUUIDType. This will sort the Columns by time satisfying our “chronological order” requirement. So doing stuff like “get the latest 10 entries tagged ‘foo’” is going to be a super efficient operation.

As Arin’ says TimeUUIDTYpe is a “super efficient” way to perform chronological sorting and pulling of data from the Cassandra database.  And since our needs as developers to store chronological data in a database don’t really differ by programming language, I have created a Time UUID generator that can fit the data in to a standard Guid object.

The Time UUID generator was pretty easy to create after I figured out the byte array structure and the differences between how Java and .NET generate byte arrays.  Below is all the code you need to generate a Time UUID or Time-Based Guid object in .NET.

public static Guid GenerateTimeBasedGuid(DateTime dateTime)
{
	long ticks = dateTime.Ticks - GregorianCalendarStart.Ticks;

	byte[] guid = new byte[ByteArraySize];
	byte[] clockSequenceBytes = BitConverter.GetBytes(Convert.ToInt16(Environment.TickCount % Int16.MaxValue));
	byte[] timestamp = BitConverter.GetBytes(ticks);

	// copy node
	Array.Copy(Node, 0, guid, NodeByte, Node.Length);

	// copy clock sequence
	Array.Copy(clockSequenceBytes, 0, guid, GuidClockSequenceByte, clockSequenceBytes.Length);

	// copy timestamp
	Array.Copy(timestamp, 0, guid, 0, timestamp.Length);

	// set the variant
	guid[VariantByte] &= (byte)VariantByteMask;
	guid[VariantByte] |= (byte)VariantByteShift;

	// set the version
	guid[VersionByte] &= (byte)VersionByteMask;
	guid[VersionByte] |= (byte)((int)GuidVersion.TimeBased << VersionByteShift);

	return new Guid(guid);
}

You can find the actual code as part of the FluentCassandra project which contains useful type generators and serialization types for working with Cassandra.  The actual file for generating the time-based Guid is located here.  https://github.com/managedfusion/fluentcassandra/blob/master/src/GuidGenerator.cs

I hope this little tidbit helps in the adoption of Cassandra in the .NET community, because I believe NoSQL databases like Cassandra are where all the cool jobs you probably want to be working in are moving to.

NOTE: To use the above code to generate a time specific dates which can be used for pull data out of the database or putting current dates in to the database, you just need to pass in a valid .NET DateTime object like this:

// generate a date/time for now
Guid nowGuid = GenerateTimeBasedGuid(DateTime.Now);

// generate a date/time for a specific date
Guid thenGuid = GenerateTimeBasedGuid(new DateTime(1980, 3. 14));
Tags: ,
written by
Nick Berardi
subscribe
If you found this post valuable and would like to see more like it you can follow me.

4 Responses to “Creating a Time UUID (GUID) in .NET”

  1. Reply Rene says:

    Hi, i tried to use this code and got that the UUID was not version one, i made a modification to the VersionByte to be in Index 6 instead of index 7, and that did the trick, but then the time is being saved incorrectly, can you point me to where you based your code? so i can check it?, thanks.

  2. Reply Nick Berardi says:

    @Rene, I just tried to verify that changing the Version to index 6 worked, because I have verified that having the version on index 7 works on this site.

    http://www.famkruithof.net/uuid/uuidgen

    And I found that index 7 works and index 6 doesn’t. Can you please let me know what source you used to verify your UUID with index 6?

  3. Reply marko says:

    Hi Nick,
    Please correct the link above to the GiudGenerator.cs becasue it is not working!
    Please help me !
    thank you :marko

Leave a Reply