<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Notes From Heck]]></title><description><![CDATA[I’m a developer, a hobbyist biker, and a Linux enthusiast. When not riding into the sunset, and not being a general nuisance, I like to experiment with new syst]]></description><link>https://adityamukho.com</link><generator>RSS for Node</generator><lastBuildDate>Sat, 11 Apr 2026 02:36:13 GMT</lastBuildDate><atom:link href="https://adityamukho.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Exploring Temporality in Databases]]></title><description><![CDATA[Getting Started with Time
Time is of some importance to humans. The best scientific minds have yet to unravel all its mysteries. Even its definition is a recurring subject of debate. Yet, we all innately have a sense of what time is. It would be hard...]]></description><link>https://adityamukho.com/exploring-temporality-in-databases</link><guid isPermaLink="true">https://adityamukho.com/exploring-temporality-in-databases</guid><category><![CDATA[Databases]]></category><category><![CDATA[time]]></category><category><![CDATA[versioning]]></category><dc:creator><![CDATA[Aditya Mukhopadhyay]]></dc:creator><pubDate>Tue, 04 May 2021 11:06:26 GMT</pubDate><content:encoded><![CDATA[<h1 id="getting-started-with-time">Getting Started with Time</h1>
<p>Time is of some importance to humans. The best scientific minds have yet to unravel all its mysteries. Even its definition is a recurring subject of debate. Yet, we all innately have a sense of what time is. It would be hard to discuss any subject at length without reference to the aspect of time. It is a fundamental component of all knowledge. In fact, it is a fundamental component of language itself - try constructing a contextually self-contained sentence without a single verb.</p>
<p>It is evident that in all accessories we've devised to store and dispense knowledge, time has always been a deeply integrated, first-class citizen. Databases are no exception. It is not uncommon to see a time-like field associated with a fact stored in a database. </p>
<h1 id="facts-that-change-with-time">Facts That Change with Time</h1>
<p>For the purpose of this post, we can think of timestamped facts as being of one of two kinds - <strong>event</strong> and <strong>state</strong>.</p>
<p>An <strong>event</strong> is a record in the database of something that occurred in the real world. The actual event, once it has occurred, can no longer be altered (unless you're Dr. Who). However, its database record, although ideally immutable, may sometimes need to be updated or superseded (to correct an error in data entry, for example).</p>
<p>A <strong>state</strong>, on the other hand, represents a portion of our overall knowledge of an object or entity of interest. For example, it could be a person's employment record, their address, or the location of a parcel in transit. State records in a database are generally mutable.</p>
<p><strong>Note:</strong> An important distinction must be made between an entity's identity and its state. The entity itself is usually referenced with an immutable identifier. It is only the stateful attributes associated with the entity that are mutable. There could be exceptions, but they are more often due to poor schema design than to constraints imposed by the real world.</p>
<p><strong>Note:</strong> The most common relation between events and state is one of causality. An event is an agent that causes a mutation in state. For example, when a parcel in transit reaches a particular hub, its location on record is updated. In this case, the event is <em>the parcel having reached the hub</em>, and the state is its location (old location before event, new location after). The event (parcel reached hub) causes the state (location) to get updated.</p>
<h1 id="tracking-mutations">Tracking Mutations</h1>
<p>Having established that both events and state have a temporal aspect to them, we next explore how their mutations over time can be tracked. Hereon, the term <em>temporal field</em> refers to <strong>a time-like field that can be used to identify versions of an individual fact</strong>. A <em>temporal dimension</em> is the domain of a <em>temporal field</em>, i.e. the ordered set of valid values for that field.</p>
<ol>
<li>For immutable events, temporality does not actually come into play. A timestamp field to record the time of occurrence may be present, but since this record is never altered, the event only ever has a single version, and hence the timestamp may be treated like any other non-temporal field.</li>
<li><p>For mutable events and for state, change can be fundamentally recorded in one of two ways:</p>
<p> i. <strong>Overwrite the previous record with new data</strong> - this is easy to implement, but we lose the older version. The older version may be retrieved from the DB replication logs (if we diligently save them), but it is a tedious, time-consuming process.</p>
<p> ii. <strong>Append the new data to the DB</strong> - supersede the old data related to an entity or event with new (timestamped) data, but preserve the old data such that it is <strong>easily</strong> accessible. This has the added overhead of providing a mechanism to retrieve the right version of the fact, based on a timestamp and a non-temporal entity/event identifier.</p>
</li>
</ol>
<p>It is the latter case (timestamped revisions using appended updates) that is the subject of interest in this post. This is obviously much harder to implement than simple overwriting, especially if we have to design the versioning layer in the DB schema ourselves (as opposed to the DB internally tracking versions for us), but it does give us time-traveling superpowers.</p>
<h1 id="approaches-to-time-based-fact-versioning">Approaches to Time-Based Fact-Versioning</h1>
<p>Let us take a moment to try and visualize the two different approaches to storing and updating mutable facts that we've seen so far.</p>
<h2 id="case-0-temporal-dimensions">Case: 0 Temporal Dimensions</h2>

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620017526391/zLKDZ4ykk.png" alt="0-dim-mutations.png" />
Figure 1: Mutation history with 0 temporal dimensions.


<p><a class="post-section-overview" href="#0-dim-mutations">Figure 1</a> is an abstract representation of what happens in a database where old data for an entity (row/document/key) is always overwritten by new data. The diagram shows the evolution of state through time of a single entity. On the timeline, each state that this entity has even been in is indicated by a box labelled <em>S₁</em>, <em>S₂</em>, <em>S₃</em>, etc.</p>
<p>Even though from our perspective we can see historic states in the diagram, the database only gets to see whatever is projected on the line labelled <em>Projection Hyperplane</em>, i.e., the latest recorded state. This imaginary hyperplane always crosses the timeline through the present instant, and hence acts as a marker of the present moment. Past states are inaccessible to the database. This is depicted in the figure by the projections of older states getting occluded by subsequent newer states.</p>
<p>We shall see shortly why this abstract representation is a useful tool in understanding temporality in databases.</p>
<h2 id="case-1-temporal-dimension">Case: 1 Temporal Dimension</h2>

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620017613090/mLDjllYGn.png" alt="1-dim-mutations.png" />
Figure 2: Mutation history with 1 temporal dimension.


<p><a class="post-section-overview" href="#1-dim-mutations">Figure 2</a> demonstrates the case where the database updates data such that it still retains the old versions. This is represented in the figure by reorienting the <em>Projection Hyperplane</em> so that it is now parallel to the time axis. Due to this, older states can now project their presence onto the hyperplane without occlusion. The last known state gets to project its presence to ∞ along the temporal dimension. ∞ is represented by a dotted line crossing the time axis.</p>
<p><strong>A note on implementation specifics:</strong> The visible temporal axis may be implemented as an internal database feature, or as part of explicit schema design (visible to the application layer), or as schema-transparent middleware residing in the persistence layer. In all cases, the concepts explored in this post remain the same.</p>
<h2 id="case-2-temporal-dimensions">Case: 2 Temporal Dimensions</h2>
<p>An interesting property of our representational model is that it is not constrained to just one temporal field or temporal dimension. By simply adding another temporal dimension to the diagram, (and to the hyperplane), we can now track object revisions using two temporal fields instead of one. We will shortly see whether this makes any sense in a real-world context, but for now, <a target="_blank" href="2-dim-mutations">Figure 3</a> presents an example of a bi-temporal projection.</p>

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1620017961425/7x2Mn5lZQ.png" alt="2-dim-mutations.png" />
Figure 3: Mutation history with 2 temporal dimensions. For an interactive, 3D version (where you get to rotate/zoom the drawing to see it from different angles), visit https://www.geogebra.org/m/ey3sky2s.


<p>Here, the states <em>S₁₁</em>,<em>S₁₂</em>, <em>S₂₁</em>, <em>S₃₁</em>, etc. all represent doubly-timestamped historic states of the same entity. The two temporal dimensions - <em>Time₁</em> and <em>Time₂</em> are laid orthogonal to each other as coordinate axes. The <em>Projection Hyperplane</em> is now a 2-dimensional plane hovering above the bi-temporal coordinate plane.</p>
<p>Each state gets to project its presence onto the <em>Projection Hyperplane</em>, bounded below by the timestamps of its creation, and above by the timestamps of its respective successors along each temporal dimension. The last known states on each dimension get to project their presence to ∞ along that dimension. ∞ is represented by a dotted line crossing each axis.</p>
<p><strong>Note:</strong> The boxes on the bi-temporal coordinate plane, representing states at different tuples from the pair of temporal dimensions, would rarely be arranged in a perfectly aligned grid. In reality, they would be more likely scattered around seemingly haphazardly. The example in <a target="_blank" href="2-dim-mutations">Figure 3</a> has adopted a less chaotic arrangement to try and simplify a rather complex diagram.</p>
<h1 id="putting-it-all-together">Putting it All Together</h1>
<p>We will now explore how these representational state plots map to data in the real world. For up to two time dimensions, the ideal mappings have already been extensively <a target="_blank" href="https://www.researchgate.net/publication/221212735_A_Taxonomy_of_Time_in_Databases">researched</a> and widely <a target="_blank" href="https://www.datasciencecentral.com/profiles/blogs/temporal-databases-why-you-should-care-and-how-to-get-started">adopted</a>, and I will just reiterate here, the conventions already in use:</p>
<ol>
<li><p>A <em>transaction time</em> dimension consists of values from a temporal field that tracks the actual time of recording a fact. This is the point of time in the real world when the fact was recorded into the database, and is often auto-filled by the database itself. This field (along with the associated state or event data) is best kept immutable to maintain the sanctity of the system of record.</p>
<p> It represents <strong>what was known to the system at the time of record</strong>, or <strong>the truth, as it was best known at the time of record</strong>. It is impossible for fact versions stored along the <em>transaction time</em> dimension to be placed chronologically out of order. This is because they are ordered by their insertion timestamps.</p>
</li>
<li><p>A <em>valid time</em> dimension consists of values from a temporal field that tracks the <strong>effective contextual time</strong> of a fact, as it was known at the time of record. This is perhaps better explained by a few examples:</p>
<p> i. If an employee joined an organization on the 5th of June, 2020, but their employment record was preemptively created on 3rd June 2020, then the former is a <em>valid time</em> field and the latter is a <em>transaction time</em> field. This is an instance of recording <strong>before the fact</strong>.</p>
<p> ii. If a parcel got delivered to a customer at 12 PM, but the system was updated later at 2 PM to mark the delivery, then the former is the <em>valid time</em> and the latter is the <em>transaction time</em>. This is an instance of recording <strong>after the fact</strong>.</p>
<p> iii. If a user places an order on an e-commerce portal, then the time of recording the order is itself considered to be the effective time of placing the order. In this case, the <em>transaction time</em> and <em>valid time</em> are always in sync. For cases like this, a bi-temporal representation can gracefully degenerate to a uni-temporal representation. In this post, we will refer to them as <strong>actual-time systems</strong>.</p>
<p> A <em>valid time</em> field (and its associated state or event data) is generally kept mutable, and is often updated to correct erroneous data entries or to consolidate conflicting inputs from multiple systems. Fact versions stored along the <em>valid time</em> dimension may arrive preemptively, retroactively, or chronologically out of order w.r.t. each other.</p>
</li>
</ol>
<h2 id="an-example">An Example</h2>
<p>When working with the two most commonly used temporal dimensions described above, certain interesting observations can be made.</p>
<p>Let us work with an example to help us understand better. Consider a flight booking system. Typically, for a given flight, the price changes over time - you get a different price depending on when you book. There are usually a lot of other factors affecting price (nowadays, almost in real time), but for this example, let us keep things simple by having the price change on a daily basis.</p>
<p>Now, for a given flight, the following attributes would be required at the minimum:</p>
<ol>
<li>Flight number,</li>
<li>Flight date,</li>
<li>Booking date,</li>
<li>Price</li>
</ol>
<p><strong>Note:</strong> This example provides another interesting insight - that not every date field in an entity can or should be treated as temporal. For instance, in our example, the flight's "flight date" can actually be considered to be part of its immutable identifier. Together with the flight number, it forms a complete and unique identifier.</p>
<p>In our example, the booking date acts as the <em>valid time</em> dimension, since the price point (mutable state) goes in and out of validity based on the booking date. The time of record must invariably serve as the <em>transaction time</em> dimension for any sane use case.</p>
<p>Any system that stores this flight information would have to record some or all of the above fields for each flight, along with a explicit or implicit time of record. In principle, something like the following has to be recorded:</p>
<table id="flight-record">
<caption>Table 1: A tabular representation of versioned flight data</caption>
    <thead>
        <th>
            </th></thead><td>Record Time</td>
            <td>Booking Date</td>
            <td>Price</td>
        
    
    <tbody>
        <tr>
            <td>T₁</td>
            <td>D₁</td>
            <td>P₁</td>
        </tr>
        <tr>
            <td>T₂</td>
            <td>D₂</td>
            <td>P₂</td>
        </tr>
        <tr>
            <td>T₃</td>
            <td>D₁</td>
            <td>P₃</td>
        </tr>
        <tr>
            <td>T₄</td>
            <td>D₂</td>
            <td>P₄</td>
        </tr>
    </tbody>
</table>

<p>However, as we shall see, the actual mechanism (and therefore capabilities) of storage and retrieval vary from DB to DB (or schema to schema), depending on whether it is uni-temporal w/ <em>transaction time</em>, uni-temporal w/ <em>valid time</em>, or bi-temporal.</p>
<p><strong>Note:</strong> If you've gone through some of the literature referenced here or elsewhere on temporal dimensions in databases, you will have observed that both <em>transaction time</em> and <em>valid time</em> fields are marked using a pair of <em>start time</em> and <em>end time</em> values. This is implicitly handled in our example by marking only the start times explicitly, and assuming the end times either coincide with the start time of the next version or stretch to ∞.</p>
<h3 id="transaction-time-database"><em>Transaction Time</em> Database</h3>
<p>A <em>transaction time</em> database is an immutable system of versioning facts. A fact version, once stored in such a database, is indelible for the lifetime of the system. In our example, both price and booking date are essential attributes of a flight, and so this database must store them both (along with a record date).</p>
<p>However, the fact that it is a uni-temporal, <em>transaction time</em> database means that the "booking date" field is not available for use as a filter for the temporal version selection process. This could be due to the way it encodes its data for storage (storing deltas between versions instead of fully built state objects, for example).</p>
<table id="flight-record-txn-time">
    <caption>Table 2: Versioned flight data in a <em>transaction time</em> database</caption>
    <thead>
        <th>
            </th></thead><td>Record Time</td>
            <td>State</td>
        
    
    <tbody>
        <tr>
            <td>T₁</td>
            <td>Booking Date: D₁, Price: P₁</td>
        </tr>
        <tr>
            <td>T₂</td>
            <td>Booking Date: D₂, Price: P₂</td>
        </tr>
        <tr>
            <td>T₃</td>
            <td>Booking Date: D₁, Price: P₃</td>
        </tr>
        <tr>
            <td>T₄</td>
            <td>Booking Date: D₂, Price: P₄</td>
        </tr>
    </tbody>
</table>

<p>In order to find the last known price for a given booking date, this system would have to scan its version record (in reverse) until it finds the first version with that booking date. This is obviously a highly inefficient system for this use case.</p>
<p>A <em>transaction time</em>-based uni-temporal system is usually only useful in <em>actual-time</em> (as defined above) cases where the <em>transaction time</em> dimension doubles as the <em>valid time</em> dimension. An example would be the booking management sub-system for an airline, since a booking, once made, is processed in actual time (i.e., neither preemptively nor retroactively) for the span of its active lifecycle (booked -&gt; minor changes like seat allocation, meals, etc. -&gt; check-in -&gt; security -&gt; boarding -&gt; destination).</p>
<p>So, although a <em>transaction time</em> database is useless for serving a catalog, it works just fine for live order management.</p>
<h3 id="valid-time-database"><em>Valid Time</em> Database</h3>
<p>A <em>valid time</em> database is a mutable system of versioning facts. A fact version stored here (along with its <em>valid</em> timestamp) may be altered whenever new knowledge of the truth surfaces, and differs from the existing record. In our example, since the booking date is the <em>valid time</em> dimension, the state stored against a particular booking date can be modified in-place.</p>
<p>By doing so, however we lose the older price information that was known for that state. In order to find the last known price for a booking date, a simple filter on the booking date field would yield the required state.</p>
<table id="flight-record-valid-time">
    <caption>Table 3: Versioned flight data in a <em>valid time</em> database</caption>
    <thead>
        <th>
            </th></thead><td>Booking Date</td>
            <td>State</td>
        
    
    <tbody>
        <tr>
            <td><strike>D₁</strike></td>
            <td><strike>Record Time: T₁, Price: P₁</strike></td>
        </tr>
        <tr>
            <td><strike>D₂</strike></td>
            <td><strike>Record Time: T₂, Price: P₂</strike></td>
        </tr>
        <tr>
            <td>D₁</td>
            <td>Record Time: T₃, Price: P₃</td>
        </tr>
        <tr>
            <td>D₂</td>
            <td>Record Time: T₄, Price: P₄</td>
        </tr>
    </tbody>
</table>

<h3 id="bi-temporal-database">Bi-Temporal Database</h3>
<p>Finally, a bi-temporal database would have access to both "record time" and "booking date" to filter on, during its version search phase. On such a database, we can ask questions like - "What was the price for booking date D₁ as it was known to the database at record time T₂? (Answer: P₁)". The data storage scheme is identical to the scheme presented in <a class="post-section-overview" href="#flight-record">Table 1</a>.</p>
<h1 id="appendix-a-beyond-bi-temporal">Appendix A: Beyond Bi-Temporal</h1>
<p>Continuing with our projection-based representation model, we note that we needn't stop at just two dimensions. In fact this model can be extended to an arbitrary number of dimensions, and it is possible to design a database capable of storing and querying facts with <em>n</em> temporal dimensions. However, beyond two dimensions, the semantic mappings quickly turn murky.</p>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Temporal_database">This</a> Wikipedia article speaks of tri-temporal databases (with the 3rd temporal dimension being <em>decision time</em>), but the semantics of &gt;2 dimensions seem to remain unclear, while also adding to our cognitive load. Even Snodgrass, et al, in their <a target="_blank" href="https://www.researchgate.net/publication/221212735_A_Taxonomy_of_Time_in_Databases">1985 ACM SIGMOID paper</a>, limited themselves to just the two dimensions discussed above, with every other time-like field being relegated to the category of <em>user-defined time</em> (a polite way of saying "none of the DB's temporal business").</p>
<p>I think one way of understanding multiple temporal dimensions is to imagine each dimension as the <em>transaction time</em> of some system - the first being the <em>transaction time</em> of the database itself, the second, third.. and so on being <em>transaction times</em> of external systems (including non-computer systems, such as record books, meeting minutes, journals, diaries, etc.) through which the data has traveled to reach our database, and the final being the <em>valid time</em> dimension, i.e. the <em>transaction time</em> of the real world itself, also known as the <em>clock time</em> or <em>wall time</em>. Fact versions along all but the 1st dimension should be mutable.</p>
<p><strong>Disclaimer:</strong> I have not put this scheme to test. It is just a hypothesis that may or may not yield usable designs.</p>
<h1 id="appendix-b-parallelbranching-realities">Appendix B: Parallel/Branching Realities</h1>
<p>Since we're discussing version control for data, it is natural to draw parallels with file-based version control, and ask how branching fits into the scheme. My take is that all the dimensions and fact versions taken together represent one version of reality. To spawn an alternate reality is to hatch a whole new set of <em>n</em> dimensions and start recording fact versions from scratch there. In this grand scheme of things, a branch is nothing but a child reality that has been pre-populated with data from a parent reality.</p>
]]></content:encoded></item><item><title><![CDATA[Introducing foxx-tracer]]></title><description><![CDATA[About OpenTracing
Application performance monitoring and distributed tracing have become invaluable tools in the hands of modern application developers, providing critical insights into the inner workings of applications and helping isolate performan...]]></description><link>https://adityamukho.com/foxx-tracer-an-opentracing-implementation-for-foxx-microservices</link><guid isPermaLink="true">https://adityamukho.com/foxx-tracer-an-opentracing-implementation-for-foxx-microservices</guid><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Aditya Mukhopadhyay]]></dc:creator><pubDate>Mon, 17 Aug 2020 05:09:00 GMT</pubDate><content:encoded><![CDATA[<h1 id="about-opentracing">About OpenTracing</h1>
<p>Application performance monitoring and distributed tracing have become invaluable tools in the hands of modern application developers, providing critical insights into the inner workings of applications and helping isolate performance bottlenecks and stress points.</p>
<p><a target="_blank" href="https://opentracing.io/">OpenTracing</a> is a free and widely used standard for implementing distributed tracing. It allows developers and operations teams to trace execution pathways across applications built on different platforms and running on different servers, even across multiple data centers, tracking trace context across application and service boundaries as they communicate with each other as long as they all implement the common tracing semantics specified by the OpenTracing standard.</p>
<blockquote>
<p>...per-process logging and metric monitoring have their place, but neither can reconstruct the elaborate journeys that transactions take as they propagate across a distributed system. Distributed traces are these journeys.</p>
<p>Source: https://medium.com/opentracing/towards-turnkey-distributed-tracing-5f4297d1736</p>
</blockquote>
<h1 id="about-arangodb">About ArangoDB</h1>
<p>ArangoDB is a free and open-source native multi-model database system developed by ArangoDB GmbH. The database system supports three data models (key/value, documents, graphs) with one database core and a unified query language AQL (ArangoDB Query Language). The query language is declarative and allows the combination of different data access patterns in a single query. ArangoDB is a NoSQL database system but AQL is similar in many ways to SQL.</p>
<p>ArangoDB has been referred to as a universal database but its creators refer to it as a "native multi-model" database to indicate that it was designed specifically to allow key/value, document, and graph data to be stored together and queried with a common language.</p>
<h2 id="the-foxx-runtime">The Foxx Runtime</h2>
<p>Quoted from the ArangoDB website:</p>
<blockquote>
<p>Foxx is a JavaScript framework for writing data-centric HTTP microservices that run directly inside of ArangoDB.</p>
<p>Traditionally, server-side projects were developed as standalone applications that guide communications between the client-side front-end and the database back-end. Through the Foxx Microservice Framework, ArangoDB allows application developers to write their data access and domain logic as microservices and running directly within the database with native access to in-memory data.</p>
<p>Source: https://www.arangodb.com/why-arangodb/foxx/</p>
</blockquote>
<p>Foxx services consist of JavaScript code running in the V8 JavaScript runtime embedded inside ArangoDB. Each service is mounted in each available V8 context (the number of contexts can be adjusted in the server configuration). Incoming requests are distributed across these contexts automatically.</p>
<p>Great, so this is just like programming for the Node.js environment, which also runs on V8 and supports the CommonJS module loading mechanism.</p>
<p><strong>Except, there's a catch - Foxx is 100% synchronous!</strong></p>
<h1 id="why-foxx-tracer">Why <em>foxx-tracer</em>?</h1>
<p>Most tracing libraries in the nodeverse are asynchronous, and so do not work in the synchronous V8 runtime that ArangoDB uses to run its Foxx services. <a target="_blank" href="https://github.com/RecallGraph/foxx-tracer">foxx-tracer</a> bridges this gap by being a 100% synchronous, dedicated module built for the Foxx runtime.</p>
<p>It is a CommonJS-loadable package available through the NPM registry. However, it relies on a number of features only available in a Foxx environment. It also depends on a companion <a target="_blank" href="https://github.com/RecallGraph/foxx-tracer-collector">collector service</a> which itself is a Foxx microservice. <strong>These dependencies make this module incompatible with Node.js and browser-based runtimes.</strong></p>
<h2 id="the-foxx-tracer-ecosystem">The <em>foxx-tracer ecosystem</em></h2>
<p><em>foxx-tracer</em> works in conjunction with a bunch of other applications/modules which together comprise the <em>foxx-tracer ecosytem</em>. These are:</p>
<ol>
<li><a target="_blank" href="https://github.com/RecallGraph/foxx-tracer">foxx-tracer</a> - a module that you include in your foxx microservice when you want to enable distributed tracing for it.</li>
<li><a target="_blank" href="https://github.com/RecallGraph/foxx-tracer-collector">foxx-tracer-collector</a> - a collector agent that receives OpenTracing spans within the sychronous Foxx environment, and then asynchrounously pushes them to multiple, configurable destinations. The collector supports a simple plugin mechanism through which one can add multiple <em>reporters</em> to talk to different destinations.</li>
<li>A bunch of <a target="_blank" href="https://www.npmjs.com/search?q=foxx-tracer-reporter">reporters</a> that let the collector push its incoming spans to different APM endpoints (like Datadog, NewRelic, etc). It is very easy to write your own reporter if you don't find one for your specific endpoint in the NPM registry. Two reporters written by me are:<ol>
<li>A <a target="_blank" href="https://github.com/RecallGraph/foxx-tracer-reporter-console">console reporter</a> that prints traces to the ArangoDB log.</li>
<li>A <a target="_blank" href="https://github.com/RecallGraph/foxx-tracer-reporter-datadog">production-ready reporter</a> available for the Datadog Cloud Monitoring Service.</li>
</ol>
</li>
</ol>
<p>All components are well documented and there is a reference implementation in <a target="_blank" href="https://github.com/RecallGraph/RecallGraph">RecallGraph</a>, which one can look up to see how all the pieces fit together.</p>
]]></content:encoded></item><item><title><![CDATA[RecallGraph Presented @ Open Source Directions]]></title><description><![CDATA[A webinar recording of RecallGraph, where I discuss its roadmap, adoption and development efforts. Apologies in advance for the occasional drop in quality.
https://www.youtube.com/watch?v=A953O3hT1Os]]></description><link>https://adityamukho.com/recallgraph-presented-open-source-directions</link><guid isPermaLink="true">https://adityamukho.com/recallgraph-presented-open-source-directions</guid><category><![CDATA[graph database]]></category><category><![CDATA[version control]]></category><dc:creator><![CDATA[Aditya Mukhopadhyay]]></dc:creator><pubDate>Tue, 28 Jul 2020 18:30:00 GMT</pubDate><content:encoded><![CDATA[<p>A webinar recording of RecallGraph, where I discuss its roadmap, adoption and development efforts. Apologies in advance for the occasional drop in quality.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=A953O3hT1Os">https://www.youtube.com/watch?v=A953O3hT1Os</a></div>
]]></content:encoded></item><item><title><![CDATA[RecallGraph v1 Released]]></title><description><![CDATA[RecallGraph is a versioned-graph data store - it retains all changes that its data (vertices and edges) have gone through to reach their current state. It supports point-in-time graph traversals, letting the user query any past state of the graph jus...]]></description><link>https://adityamukho.com/recallgraph-v1-released</link><guid isPermaLink="true">https://adityamukho.com/recallgraph-v1-released</guid><category><![CDATA[graph database]]></category><category><![CDATA[version control]]></category><dc:creator><![CDATA[Aditya Mukhopadhyay]]></dc:creator><pubDate>Tue, 28 Jul 2020 18:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1619242269782/2cjrqQ47T.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>RecallGraph is a versioned-graph data store - it retains all changes that its data (vertices and edges) have gone through to reach their current state. It supports point-in-time graph traversals, letting the user query any past state of the graph just as easily as the present.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/RecallGraph/RecallGraph/releases/tag/v1.0.0">https://github.com/RecallGraph/RecallGraph/releases/tag/v1.0.0</a></div>
]]></content:encoded></item><item><title><![CDATA[RecallGraph Presented at ArangoDB Online Meetup]]></title><description><![CDATA[A webinar recording of RecallGraph, presented at ArangoDB Online Meetup, as part of their Community Pioneers Initiative.
https://www.youtube.com/watch?v=UP2KDQ_kL4I]]></description><link>https://adityamukho.com/recallgraph-presented-at-arangodb-online-meetup</link><guid isPermaLink="true">https://adityamukho.com/recallgraph-presented-at-arangodb-online-meetup</guid><category><![CDATA[graph database]]></category><category><![CDATA[version control]]></category><dc:creator><![CDATA[Aditya Mukhopadhyay]]></dc:creator><pubDate>Tue, 28 Jul 2020 18:30:00 GMT</pubDate><content:encoded><![CDATA[<p>A webinar recording of RecallGraph, presented at ArangoDB Online Meetup, as part of their Community Pioneers Initiative.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=UP2KDQ_kL4I">https://www.youtube.com/watch?v=UP2KDQ_kL4I</a></div>
]]></content:encoded></item><item><title><![CDATA[Introducing CivicGraph]]></title><description><![CDATA[I would like to introduce an open source, Apache 2.0 licensed project of mine:
https://github.com/CivicGraph/CivicGraph
CivicGraph is a versioned-graph data store - it retains all changes that its data (vertices and edges) have gone through to reach ...]]></description><link>https://adityamukho.com/introducing-civicgraph</link><guid isPermaLink="true">https://adityamukho.com/introducing-civicgraph</guid><category><![CDATA[graph database]]></category><category><![CDATA[version control]]></category><dc:creator><![CDATA[Aditya Mukhopadhyay]]></dc:creator><pubDate>Fri, 31 Jan 2020 18:30:00 GMT</pubDate><content:encoded><![CDATA[<p>I would like to introduce an open source, Apache 2.0 licensed project of mine:</p>
<p>https://github.com/CivicGraph/CivicGraph</p>
<p>CivicGraph is a versioned-graph data store - it retains all changes that its data (vertices and edges) have gone through to reach their current state. It supports point-in-time graph traversals, letting the user query any past state of the graph just as easily as the present.</p>
<p>It is a  <a target="_blank" href="https://www.arangodb.com/why-arangodb/foxx/">Foxx Microservice</a>  for  <a target="_blank" href="https://www.arangodb.com/">ArangoDB </a> that features VCS-like semantics in many parts of its interface, and is backed by a transactional event tracker. It is currently being developed and tested on ArangoDB v3.5, with support for v3.6 in the pipeline.</p>
<p>CivicGraph is a potential fit for scenarios where data is best represented as a network of vertices and edges (i.e., a graph) having the following characteristics:</p>
<ol>
<li><p>Both vertices and edges can hold properties in the form of attribute/value pairs (equivalent to JSON objects).</p>
</li>
<li><p>Documents (vertices/edges) mutate within their lifespan (both in their individual attributes/values and in their relations with each other).</p>
</li>
<li><p>Past states of documents are as important as their present, necessitating retention and queryability of their change history.</p>
</li>
</ol>
<p>Its API is split into 3 top-level categories:</p>
<h1 id="document">Document</h1>
<p><strong>Create</strong> - Create single/multiple documents (vertices/edges).</p>
<p><strong>Replace</strong> - Replace entire single/multiple documents with new content.</p>
<p><strong>Delete</strong> - Delete single/multiple documents.</p>
<p><strong>Update</strong> - Add/Update specific fields in single/multiple documents.</p>
<p><strong>(Planned) Explicit Commits</strong> - Commit a document's changes separately, after it has been written to DB via other means (AQL / Core REST API / Client).</p>
<p><strong>(Planned) CQRS/ES Operation Mode</strong> - Async implicit commits.</p>
<h1 id="event">Event</h1>
<p><strong>Log</strong> - Fetch a log of events (commits) for a given path pattern (path determines scope of documents to pick). The log can be optionally grouped/sorted/sliced within a specified time interval.</p>
<p><strong>Diff</strong> - Fetch a list of forward or reverse commands (diffs) between commits for specified documents.</p>
<p><strong>(Planned) Branch/Tag</strong> - Create parallel versions of history, branching off from a specific event point of the main timeline. Also, tag specific points in branch+time for convenient future reference.</p>
<p><strong>(Planned) Materialization</strong> - Point-in-time checkouts.</p>
<h1 id="history">History</h1>
<p><strong>Show</strong> - Fetch a set of documents, optionally grouped/sorted/sliced, that match a given path pattern, at a given point in time.</p>
<p><strong>Filter</strong> - In addition to a path pattern like in 'Show', apply an expression-based, simple/compound post-filter on the retrieved documents.</p>
<p><strong>Traverse</strong> - A point-in-time traversal (walk) of a past version of the graph, with the option to apply additional post-filters to the result.</p>
<p>I hope some of you may find this a useful service to address several types of data modelling challenges pertaining to retention and querying of historical graph data.</p>
]]></content:encoded></item><item><title><![CDATA[A Closer Look at Delta Arithmetic]]></title><description><![CDATA[In their 1996 paper published in ACM Transactions on Database Systems, Ghandeharizadeh et al. defined a formal algebra around Deltas (the encoded difference between any two states of a system) in a relational database. Their definition is actually ge...]]></description><link>https://adityamukho.com/a-closer-look-at-delta-arithmetic</link><guid isPermaLink="true">https://adityamukho.com/a-closer-look-at-delta-arithmetic</guid><category><![CDATA[version control]]></category><category><![CDATA[graph database]]></category><dc:creator><![CDATA[Aditya Mukhopadhyay]]></dc:creator><pubDate>Sun, 30 Jun 2019 18:30:00 GMT</pubDate><content:encoded><![CDATA[<p>In their <a target="_blank" href="https://www.seas.upenn.edu/~zives/03s/cis650/P370.PDF">1996 paper published in ACM Transactions on Database Systems</a>, Ghandeharizadeh et al. defined a formal algebra around <em>Deltas</em> (the encoded difference between any two states of a system) in a relational database. Their definition is actually generic enough to apply to any system whose state can be represented as a set of tuples, which is why it is still used in contemporary research on database versioning, including those involving non-relational data models (Khurana et al. <a target="_blank" href="https://www.researchgate.net/publication/282403421_Storing_and_Analyzing_Historical_Graph_Data_at_Scale">Storing and Analyzing Historical Graph Data at Scale</a>). In order to support my deep dives into a couple of modern designs that use <em>Delta Arithmetic</em> for versioning graph databases, I will spend some time in this post laying down its foundations, clarifying a few under-explained points along the way.</p>
<h2 id="delta-arithmetic-the-ground-rules">Delta Arithmetic - The Ground Rules</h2>
<p>We start with a database containing the following relations or tables, for the fictional inventory management system of a bicycle manufacturer:</p>
<ol>
<li><code>Suppliers</code> - A list of vendors and the parts they sell,</li>
<li><code>Orders</code> - A record of orders placed to vendors, the parts and their quantities.</li>
</ol>
<p>The contents of the two tables described above are shown below:</p>
<table id="suppliers">
    <caption>Table 1: Suppliers</caption>
    <thead>
        <tr>
            <th>Supplier</th>
            <th>Part</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Trek</td>
            <td>frame</td>
        </tr>
        <tr>
            <td>Campy</td>
            <td>brakes</td>
        </tr>
        <tr>
            <td>Trek</td>
            <td>pedals</td>
        </tr>
    </tbody>
</table>

<table id="orders">
    <caption>Table 2: Orders</caption>
    <thead>
        <tr>
            <th>Part</th>
            <th>Quantity</th>
            <th>Supplier</th>
            <th>Expected</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>frame</td>
            <td>400</td>
            <td>Trek</td>
            <td>8/31/93</td>
        </tr>
        <tr>
            <td>brakes</td>
            <td>150</td>
            <td>Campy</td>
            <td>9/1/93</td>
        </tr>
    </tbody>
</table>

<h3 id="tuple-representation">Tuple Representation</h3>
<p>Every record in the database is mapped to a classified tuple of the form \(RelName(field\_value_1, field\_value_2, ...)\). For example the first row in the <code>Suppliers</code> table is represented as \(Suppliers(Trek, frame)\) and the first row in the <code>Orders</code> table as \(Orders(frame, 400, Trek, 8/31/93)\). The order of values in the tuple is the same as the order of field definitions in the tables above. In this way, every row in every table in the database is mapped to a tuple. For this small database, we can write the entire initial state \(S_a\) as a set of tuples, as shown below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619245661306/1oYFhQB5F.png" alt="Screenshot 2021-04-24 115725.png" /></p>
<p>It should be noted at this point that none of the tables above sport row identifiers or primary keys. However, since the algebraic definitions assume the pure relational model, every tuple is considered unique in itself, and cannot exist more than once anywhere in the database. This constraint is captured mathematically by the property of sets that require every element to be unique. Additionally, if one or more of the fields in the tuple constitute a key, then at most one tuple with a particular combination of values for those fields can exist at a time in the state set. For example, in the <code>Orders</code> table, if the fields <code>(Part, Quantity, Supplier)</code> fields constituted the key (a bad design in reality, but will suffice for this example), then every tuple in the set \(S_a\), apart from being unique in itself, must also be unique w.r.t to the sub-tuple formed by the above 3 fields.</p>
<p><strong>Aside:</strong> The simplest way to remediate the uniqueness problem without enforcing uniqueness in tuples with semantic (business-relevant) fields is to add a field representing a <em>dumb</em> primary key. For example, a <em>Order ID</em> field in the orders table. This also permits repeating values for semantic sub-tuples in the <code>Orders</code> set.</p>
<h3 id="signed-atom">Signed Atom</h3>
<p>This is an expression of the form \(\pm\langle\text{RelName}\rangle\langle\text{Tuple}\rangle\) and corresponds to an insertion or deletion operation, depending on the \(+\) or the \(-\) prefix respectively. For example, \(+Suppliers(Shimano, brakes)\). This is the smallest unit of modification that can happen to the overall state set.</p>
<p><strong>Aside:</strong> An <em>update</em> operation would require use of two atoms:</p>
<ol>
<li>One for deletion of the old value, and</li>
<li>One for insertion of the new value.</li>
</ol>
<p>As far as the set algebra used for delta arithmetic is concerned, the order of the above two operations does not matter, as long as the <em>consistency constraints</em> defined in the next section are met. Most databases, of course, allow atomic updates.</p>
<p>Since we're using sets to represent a pure relational model, <strong>a signed atom representing insertion of a tuple already present in the current state results in a <code>No-Op</code></strong>. Similarly, <strong>a signed atom representing deletion of a non-existent tuple from a set also results in a <code>No-Op</code></strong>.</p>
<h3 id="delta">Delta</h3>
<p>A delta is an <strong>unordered</strong>, finite set of signed atoms. For example,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619245747143/l41fBqSot.png" alt="Screenshot 2021-04-24 115839.png" /></p>
<h4 id="consistent-and-failed-deltas">Consistent and Failed Deltas</h4>
<p>A delta is called <em>consistent</em> if it does not contain both positive and negative versions of the same atom. Otherwise, it is called an inconsistent, or <em>failed</em> delta. For example, the delta defined in (2) is a <strong>consistent</strong> delta, whereas a <strong>failed</strong> delta would look like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619245797490/hculKE-mX.png" alt="Screenshot 2021-04-24 115943.png" /></p>
<p>Also, the delta being a set subject to the same uniqueness constraints imposed by keys (if present), we have the following: For a relation with two fields denoted by \(R[A, B]\), if \(A\) is the key and there exist two signed atoms \(+R[a, b]\) and \(+R[a, c]\) in a delta, then we must necessarily have \(b = c\) for the delta to be consistent (essentially collapsing them to a single signed atom).</p>
<p><strong>Aside:</strong> One question that rose to my mind when I looked at (3) is why this should be disallowed. After all, the signed atoms within the delta are inverse operations of each other (insertion and deletion), and hence should just cancel each other out, resulting in a <code>No-Op</code> at worst. The paper does not directly address this question, though, as we shall see in a <a class="post-section-overview" href="#whyinversesignedatomsleadtoafaileddelta">later section</a>, this restriction is  necessary to allow for delta operations to be safely applicable in any order (remember, the delta is an <strong>unordered</strong> set).</p>
<h4 id="delta-breakdown-snapshot-fragments">Delta Breakdown - Snapshot Fragments</h4>
<p>The paper defines the following relations for a <strong>consistent</strong> delta \(\Delta\):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619246055266/16TykLHDZ.png" alt="Screenshot 2021-04-24 120359.png" /></p>
<p>The consistency requirement can now be expressed as:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619246094134/j7ZSYve-5.png" alt="Screenshot 2021-04-24 120439.png" /></p>
<p>For example, \(\Delta_1\) from (2) would be split into the following:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619246157177/hfaFUz-5s.png" alt="Screenshot 2021-04-24 120538.png" /></p>
<p>Although the paper doesn't explicitly name the definitions in (4), I will label them here as <strong>snapshot fragments</strong>. These represent the same <em>type</em> of elements in a set as the snapshot (tuples categorized by relation). The original delta, which represents events or operations, cannot be a direct algebraic operand along with the snapshot but its derivative snapshot fragments <strong>can</strong>.</p>
<p>Now that we have defined delta components that can directly combine with the snapshot algebraically, we define the <em>application</em> of a delta \(\Delta\) to a snapshot \(S\) as the following equivalent functions:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619246201075/3atMuMCBq.png" alt="Screenshot 2021-04-24 120626.png" /></p>
<h4 id="why-inverse-signed-atoms-lead-to-a-failed-delta">Why Inverse Signed Atoms Lead to a Failed Delta</h4>
<p>A few sections earlier, we saw that a delta of the form illustrated in (3) is a <em>failed</em> delta, but did not elaborate on why it is so. Now that we have the commutative criterion of the delta function as described in (6), we can get a clearer picture.</p>
<p>We will examine two examples of failed deltas:</p>
<ol>
<li>One where the signed atoms represent an element already present in current state, and</li>
<li>One where they represent a new element.</li>
</ol>
<p>Say our current state has the following elements:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619246299788/NK4FRdbF8.png" alt="Screenshot 2021-04-24 120754.png" /></p>
<p>First let us consider case 1. Let \(\Delta_{f} = \left\{
        \begin{array}{l}
            +Suppliers(Trek, frame), \\
            -Suppliers(Trek, frame)
        \end{array}
    \right\}\).</p>
<p>Applying (6), we get:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619246351566/eop4DQuwS.png" alt="Screenshot 2021-04-24 120855.png" /></p>
<p>We also note that 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619246465346/CBe0g6e5T.png" alt="Screenshot 2021-04-24 121048.png" />
 violating (5). To see if this delta still satisfies the commutative criterion of (6), we need:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619246518295/zhRln38-b.png" alt="Screenshot 2021-04-24 121145.png" /></p>
<p><strong>which is a contradiction</strong>.</p>
<p>Now let us consider case 2. Let \(\Delta_{f'} = \left\{
        \begin{array}{l}
            +Suppliers(Shimano, brakes), \\
            -Suppliers(Shimano, brakes)
        \end{array}
    \right\}\).</p>
<p>Applying (6), we get:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619246737756/PKCiX29Ao.png" alt="Screenshot 2021-04-24 121524.png" /></p>
<p>We also note that 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619246894616/SvTB3TYno.png" alt="Screenshot 2021-04-24 121654.png" />
violating (5). To see if this delta still satisfies the commutative criterion of (6), we need:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619246928031/Fuux-kOQi.png" alt="Screenshot 2021-04-24 121833.png" /></p>
<p><strong>which is also a contradiction</strong>.</p>
<p>Therefore, we see that in order to satisfy the requirements in (6), we need to satisfy (5).</p>
<h4 id="delta-composition">Delta Composition</h4>
<p>Finally, I examine how the paper has formalized delta composition (or chaining) operations, thereby allowing us to apply at succession of deltas on a given state to arrive at the final state.</p>
<h5 id="smash-composition">Smash Composition</h5>
<p>One type of composition operation described is called a <em>smash</em>, denoted by \("!"\). This is the composition used by most active databases. Algebraically, a smash of two deltas is their union, with conflicts resolved in favour of the second argument. Given:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619247116262/PuQt0-iy2.png" alt="Screenshot 2021-04-24 122123.png" /></p>
<p>Then using (2) and (7) we get:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619247192064/Prjk5wo-3.png" alt="Screenshot 2021-04-24 122254.png" /></p>
<p>The formal definition for the <em>smash</em> operation is given by:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619247237176/kNjm5j9U3.png" alt="Screenshot 2021-04-24 122336.png" /></p>
<p>The reader is encouraged verify whether the above holds true for our example, using (2), (7), (4), and plugging into (9).</p>
<p>The most important characteristic of the <em>smash</em> composition is that it supports function composition, i.e.:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619247280310/1ZBBHW4sr.png" alt="Screenshot 2021-04-24 122424.png" /></p>
<h5 id="merge-composition">Merge Composition</h5>
<p>The second type of composition defined by the paper is the <em>merge</em>. This is less common in real world databases, and hence is not examined in detail. The <em>merge</em> is denoted by \("\&amp;"\) and its formal definition is given by:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619247315294/1WXVbJWs7.png" alt="Screenshot 2021-04-24 122501.png" /></p>
<p>Refer the paper for a slightly more detailed explanation of this.</p>
<h2 id="summary">Summary</h2>
<p>The authors have used elementary set algebra to come up with some elegant mathematical formalizations for representing database changes over time. Though they have presumed the presence of a pure relational context, there is nothing exceptional being done at the set algebra level - all its rules are strictly followed. This opens up the possibility of using delta arithmetic to analyze any kind of database whose states can be represented as sets of tuples. As we shall see in a future post, this is exactly what Khurana et al.[^2] have done when designing their <em>Temporal Graph Index</em>.</p>
]]></content:encoded></item><item><title><![CDATA[Exploring Graph Database Versioning Approaches]]></title><description><![CDATA[Building on my previous post, where I emphasize the need for versioned graph databases, I explore a few of the possible approaches to designing one. This is not a ground-up approach that delves into the design of the database engine itself, but rathe...]]></description><link>https://adityamukho.com/exploring-graph-database-versioning-approaches</link><guid isPermaLink="true">https://adityamukho.com/exploring-graph-database-versioning-approaches</guid><category><![CDATA[graph database]]></category><category><![CDATA[version control]]></category><dc:creator><![CDATA[Aditya Mukhopadhyay]]></dc:creator><pubDate>Thu, 27 Jun 2019 18:30:00 GMT</pubDate><content:encoded><![CDATA[<p>Building on my <a target="_blank" href="/the-case-for-versioned-graph-databases/">previous post</a>, where I emphasize the need for versioned graph databases, I explore a few of the possible approaches to designing one. This is not a ground-up approach that delves into the design of the database engine itself, but rather an <em>overlay</em> approach that can let us run version control atop any standard graph database.</p>
<h2 id="a-naive-approach">A Naive Approach</h2>
<p>Think of a historical key-value store - one where the history of values against every key is retained, and retrieved using a composite of the key and a revision number (absolute or relative). Optionally, to get the <strong>current</strong> value of a key, we may omit the revision number. This is one of the simplest conceptual forms of a historical database. Since many real world graph databases are built on top of an underlying key-value store (<a target="_blank" href="https://www.arangodb.com/docs/stable/architecture-storage-engines.html#rocksdb">ArangoDB</a>, <a target="_blank" href="https://docs.janusgraph.org/latest/storage-backends.html">JanusGraph</a>, <a target="_blank" href="https://www.datastax.com/wp-content/uploads/resources/datasheets/DataStax-Enterprise-Graph-DS.pdf">DataStax Enterprise Graph</a>), we will try to use our historical key-value store to see if it gives our graph database some degree of history retention.</p>
<p>We can naively concoct a graph representation on this database by storing documents as key-value pairs - keys being the unique document ids and values being their respective attribute-value pairs or property lists. For the sake of this thought experiment, we will not bother with how the connections are internally represented, i.e. whether source and destination node ids are stored as edge attributes, or incoming and outgoing edge lists are stored as node attributes, or some other fancy scheme (In real-life graph databases, this is dictated by the design the underlying implementation).</p>
<h3 id="the-historical-kv-store-a-closer-look">The Historical KV Store - A Closer Look</h3>

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619248652851/7jOdzUHUW.png" alt="historical_keyvalue_store.png" />
Figure 1: Evolution of stored data over time in the historical KV store


<p>The figure above shows what the contents of this key-value store might look like for a few sample key-value pairs. Every write operation (create/update/delete) for a given KV pair is associated with a <strong>revision number</strong> and a <strong>timestamp</strong>. The latest value for a key is always immediately available using a simple key lookup. This can happen in <code>O(1)</code> for an in-memory store. This is depicted in the figure by projecting the latest values of existing pairs onto the line <code>Tnow</code>. However, when we want to find out the state of the database at some point of time in the past, things get a little more complicated. The table below shows the values of all the keys depicted in the figure, at different times.</p>
<table id="projection-historical-values">
    <caption>Table 1: Projection of historical values at different times</caption>
    <thead>
        <tr>
            <th></th>
            <th>T<sub>1</sub></th>
            <th>T<sub>2</sub></th>
            <th>T<sub>3</sub></th>
            <th>T<sub>4</sub></th>
            <th>T<sub>now</sub></th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><b>K<sub>1</sub></b></td>
            <td>V<sub>0</sub> @ (R<sub>0</sub>, T<sub>a</sub>)</td>
            <td>V<sub>0</sub> @ (R<sub>0</sub>, T<sub>a</sub>)</td>
            <td>V<sub>0</sub> @ (R<sub>0</sub>, T<sub>a</sub>)</td>
            <td>V<sub>1</sub> @ (R<sub>1</sub>, T<sub>f</sub>)</td>
            <td>V<sub>1</sub></td>
        </tr>
        <tr>
            <td><b>K<sub>2</sub></b></td>
            <td>--</td>
            <td>V<sub>0</sub> @ (R<sub>0</sub>, T<sub>c</sub>)</td>
            <td>V<sub>1</sub> @ (R<sub>1</sub>, T<sub>e</sub>)</td>
            <td>V<sub>1</sub> @ (R<sub>1</sub>, T<sub>e</sub>)</td>
            <td>D @ (R<sub>2</sub>, T<sub>h</sub>)</td>
        </tr>
        <tr>
            <td><b>K<sub>3</sub></b></td>
            <td>--</td>
            <td>V<sub>0</sub> @ (R<sub>0</sub>, T<sub>d</sub>)</td>
            <td>V<sub>0</sub> @ (R<sub>0</sub>, T<sub>d</sub>)</td>
            <td>D @ (R<sub>1</sub>, T<sub>g</sub>)</td>
            <td>D @ (R<sub>1</sub>, T<sub>g</sub>)</td>
        </tr>
        <tr>
            <td><b>K<sub>4</sub></b></td>
            <td>V<sub>0</sub> @ (R<sub>0</sub>, T<sub>b</sub>)</td>
            <td>V<sub>0</sub> @ (R<sub>0</sub>, T<sub>b</sub>)</td>
            <td>V<sub>0</sub> @ (R<sub>0</sub>, T<sub>b</sub>)</td>
            <td>V<sub>0</sub> @ (R<sub>0</sub>, T<sub>b</sub>)</td>
            <td>V<sub>0</sub></td>
        </tr>
    </tbody>
</table>

<h3 id="temporal-queries">Temporal Queries</h3>
<p>This table was easy to build from the visual depiction in <a class="post-section-overview" href="#historical-keyvalue-store">Figure 1</a>. But how does our historical KV store figure out the state of the DB at, say time <code>T3</code>? Here's what we have to work with:</p>
<ol>
<li>Revision numbers can be relatively referenced (similar to Git), so we can assume these to be integer values starting with 0. Positive numbers reflect revisions w.r.t. the beginning of revision history (<code>R0</code>) and negative numbers w.r.t to the end (<code>Rlatest</code>).</li>
<li>Since the database allows both <strong>key-only</strong> lookups (for latest revisions) and <strong>key+revision</strong> lookups, we can assume that it internally maintains a 2-field hash index <code>(key, revision)</code>, keeping the 2nd level sorted in reverse order of the revision number (to facilitate retrieving the latest version for key-only lookups).</li>
</ol>
<p>Therefore, to find the value of, say, <code>K2</code> at time <code>T3</code>, we need to perform the following steps:</p>
<ol>
<li>Determine if <code>T3</code> is closer to <code>T0</code> or <code>Tnow</code>. Based on this, we will start from the oldest or the newest revision of <code>K2</code> respectively.</li>
<li>If <code>T3</code> is closer to <code>T0</code>.
 i. Set <code>n</code> to <code>0</code>.
 ii. Lookup <code>K2 @ Rn</code> and note its timestamp <code>T</code>.
 iii. If <code>T &gt; T3</code> then return <code>K2 @ R(n-1)</code> (If <code>n = 0</code> then return <code>null</code>).
 iv. Else increase <code>n</code> by <code>1</code> and go to step (ii).</li>
<li>Else if <code>T3</code> is closer to <code>Tnow</code>.
 i. Get <code>K2 @ Tnow</code>. Note the revision number and assign to <code>N</code>. Note its timestamp <code>T</code>.
 ii. Set <code>n</code> to <code>N</code>.
 iii. If <code>T &lt; T3</code> then return <code>K2 @ Rn</code>.
 iv. Else decrease <code>n</code> by <code>1</code>.
 v. Get <code>K2 @ Rn</code>, note its timestamp <code>T</code> and go to step (iii).</li>
</ol>
<p>This is evidently far more complex than a simple key or <em>key+revision</em> lookup, and not in <code>O(1)</code> anymore. It is now <code>O(N(K))</code> where <code>N(K)</code> is the number of revisions for key <code>K</code>. This greatly inefficient lookup can be speeded up drastically by additionally maintaining a 2-field skiplist (see below) index <code>(key, timestamp)</code>, keeping the 2nd level sorted in reverse chronological order. This is still at best an <code>O(log(N(K)))</code> operation.</p>
<blockquote>
<p>A <a target="_blank" href="https://en.wikipedia.org/wiki/Skip_list">skiplist</a> index supports both equality and range queries. So we can ask it to return all values for key = <code>K</code> and timestamp &lt; <code>T</code>.</p>
</blockquote>
<h2 id="conclusion">Conclusion</h2>
<p>We see that while this design provides a history of every individual document out of the box, and the history can be queried by revision number or point-in-time,</p>
<ol>
<li>It does not readily expose the the structure of the graph as a whole (or subgraphs, or <code>k-hop</code> neighborhoods - all of which might be of interest to a network analyst.),</li>
<li>Fetching a past state of the graph is more expensive than fetching its current state (this should be an acceptable trade-off for most <a target="_blank" href="https://en.wikipedia.org/wiki/Online_transaction_processing">OLTP</a> scenarios),</li>
<li>The revision-number based historical KV store does not offer any intrinsic benefits for time-based lookups, and</li>
<li>There is potential for a lot of storage and write-bandwidth overhead when storing multiple versions of large documents where each version only updates a small portion of a document, since it results in redundant storage.</li>
</ol>
<p>We see that this approach has several drawbacks which make it unsuitable for an efficient historical graph data store. We will explore other, hopefully better approaches in future posts.</p>
]]></content:encoded></item><item><title><![CDATA[The Case for Versioned Graph Databases]]></title><description><![CDATA[Why Graph Databases?
Graph databases have become ubiquitous over the years due to their incredible representational and querying capabilities, when it comes to highly interconnected data. Wherever data has an inherent networked structure, graph datab...]]></description><link>https://adityamukho.com/the-case-for-versioned-graph-databases</link><guid isPermaLink="true">https://adityamukho.com/the-case-for-versioned-graph-databases</guid><category><![CDATA[graph database]]></category><category><![CDATA[version control]]></category><dc:creator><![CDATA[Aditya Mukhopadhyay]]></dc:creator><pubDate>Thu, 27 Jun 2019 18:30:00 GMT</pubDate><content:encoded><![CDATA[<h2 id="why-graph-databases">Why Graph Databases?</h2>
<p>Graph databases have become ubiquitous over the years due to their incredible representational and querying capabilities, when it comes to highly interconnected data. Wherever data has an inherent networked structure, graph databases fare better at storing and querying that data than other NoSQL databases as well as relational databases, because they naturally persist the underlying connected structure. This allows for traversal semantics in declarative graph query languages, and also, better performance than SQL - especially for deep traversals. Additionally, they often help unravel emergent network topologies in legacy data, that had not previously been mined for such structures. At the very least, they make the process a lot less tedious.</p>
<p>Most real world network data intrinsically lend themselves to graphical representations, and hence, can be modeled in graph databases. These include, to name a few:</p>
<ol>
<li>Wired and wireless computer networks and cellular networks</li>
<li>Road, rail, air and shipping routes,</li>
<li>Supply and distribution chains,</li>
<li>Biological and artificial neural networks,</li>
<li>Complex chemical and nuclear reaction chains,</li>
<li>Social networks,</li>
<li>Software package and library dependency trees, and many more.</li>
</ol>
<h2 id="why-versioned-graphs">Why Versioned Graphs?</h2>
<p>In addition to reaping the benefits of living in graph databases, many real world applications also stand to take advantage of network evolution models, i.e. a record of changes to a network over time; for example, analyzing railway track utilization efficiency as a function of signal array timing, or the simulation of nucleotide concentration changes over time in a nuclear fission reactor. However, in most of the prominent mainstream graph databases that are freely available at the time of this writing, I have not come across any that offer some sort of built-in revision tracking (meaning older versions of data are retained for future retrieval).</p>
<p>Particularly for graph databases, the concept of revisions applies not only to individual nodes and edges, but also to the structure of the graph as a whole, i.e. it should be relatively easy to store and retrieve not only individual document (node/edge) histories, but also the structural history of the graph or a portion of it. This is a key difference between a hypothetical <em>versioned or historical graph database</em> and a <em>general purpose event store</em> (see below), which is usually tuned for the former but not the latter.</p>
<blockquote>
<p>An event store is a database that records entity write operations (creates/updates/deletes) as a series of deltas wrapped in events. Each delta is the difference between the contents of the updated entity and its previous version. It is part of an <em>event payload</em>, where the event represents the particular write operation (create/update/delete) that occurred. Thus, deltas encode the entire write history of the entity.</p>
</blockquote>
<p>I therefore submit that there is a need for a practical, historical graph database that has the following minimal set of characteristics:</p>
<ol>
<li>A mechanism for efficiently recording individual document (node/edge) writes (creates/updates/deletes) in such a way that they can be rewound and replayed.</li>
<li>An internal storage architecture that not only maintains the current structure of the graph, but also allows for a quick rebuild and retrieval of its structure at any point of time in the past. This could, optionally, be optimized to retrieve recent structures faster than older ones.</li>
<li>An efficient query engine that can traverse current/past graph structures to retrieve subgraphs or <code>k-hop</code> neighborhoods of specified nodes. In case of historical traversals, this should be optimized to rebuild only the relevant portions of the graph, where feasible.</li>
</ol>
<h2 id="the-current-state-of-historical-graph-databases">The Current State of Historical Graph Databases</h2>
<p>There is a general consensus in the computing and scientific research community for the need of a historical graph database, and to the best of my knowledge, research has been carried out along two primary forks:</p>
<ol>
<li><p>Graph databases with built-in revision support at the DB engine level, i.e. they are designed from the ground up to support revisions:</p>
<p> i. Vijitbenjaronk et al. <a target="_blank" href="https://ieeexplore.ieee.org/document/8258092">Scalable time-versioning support for property graph databases</a>.</p>
</li>
<li><p>Database designs, supplemented by external application/service layers to provide a <em>revision tracking facade</em> on top of conventional static (based on the database taxonomy proposed by Snodgrass et al. in their 1985 ACM SIGMOID paper titled <a target="_blank" href="https://www.researchgate.net/publication/221212735_A_Taxonomy_of_Time_in_Databases">A Taxonomy of Time in Databases</a>.) graph database engines:</p>
<p> i. Khurana et al. <a target="_blank" href="https://www.researchgate.net/publication/229556866_Efficient_Snapshot_Retrieval_over_Historical_Graph_Data">Efficient Snapshot Retrieval over Historical Graph Data</a>,</p>
<p> ii. Khurana et al. <a target="_blank" href="https://www.researchgate.net/publication/282403421_Storing_and_Analyzing_Historical_Graph_Data_at_Scale">Storing and Analyzing Historical Graph Data at Scale</a>.</p>
</li>
</ol>
<p>There would be many more publications and implementations available upon a quick search, but I believe they would all fall under one of the above two categories.</p>
]]></content:encoded></item></channel></rss>