Never version things by date

Suddenly our CI-machine started to fail on code that we had not touched for ages.

Test(s) failed. Expected string length 6 but was 4. Strings differ at index 0.
Expected: “foobar”
But was: “test”

The test that broke the build was very straight forward:

        [Test]
        public void SettingsShouldSupportCRUD() {
            ApplicationSetting.Delete("Test.ABC");
 
            var setting = ApplicationSetting.Get("Test.ABC");
 
            Assert.IsNull(setting);
 
            ApplicationSetting.Set("Test.ABC", "test"); // Create
 
            setting = ApplicationSetting.Get("Test.ABC"); // Read
 
            Assert.AreEqual("test", setting);
 
            ApplicationSetting.Set("Test.ABC", "foobar"); // Update
 
            setting = ApplicationSetting.Get("Test.ABC");
 
            Assert.AreEqual("foobar", setting);
 
            ApplicationSetting.Delete("Test.ABC"); // Delete
 
            setting = ApplicationSetting.Get("Test.ABC");
 
            Assert.IsNull(setting);
        }

Notice how we do the update here.
A setting is an immutable object in this system so we use the same semantics as when creating.

So instead of modifying the old object, we just bind the key to the new value using the set method and make sure we always return the latest object upon get.

        public static string Get(string key) {
            var result = ActiveRecordMediator<ApplicationSetting>.FindAllByProperty("Date", "Name", key);
            if (result.Length == 0) return null;
            return result.Last().Value;
        }

As you can see, the get method is pretty trivial as well, just fetch all the settings for a given key ordered by date and return the youngest.

So why is this code broken?

I had to dig into the database to see what was going on here.
By putting a breakpoint in the test just before the last call to Delete I could dump the data before it was deleted.

(1, 'application', '2011-05-31 16:54:04', 'Test.ABC', 'test')
(2, 'application', '2011-05-31 16:54:04', 'Test.ABC', 'foobar')

According the time-stamp the two settings where both created at the same time and obviously that was bad.
(And what makes it extra bad is that MySQL only supports precision down to a second.)

The assumption when writing this code was that such ordering still would be deterministic (e.g. by falling back to using the primary key) but no.
Our failing test just proved that the order might change at any given time.

We solved this by switching to sorting by primary key instead (which is what we should have done from the beginning anyway).
(Here sequential UUIDs come into handy.)

No comments yet.

Leave a Reply