{"id":8122,"date":"2013-06-20T12:34:53","date_gmt":"2013-06-20T10:34:53","guid":{"rendered":"https:\/\/blog.trifork.com\/?p=8122"},"modified":"2013-06-20T12:34:53","modified_gmt":"2013-06-20T10:34:53","slug":"efficiently-storing-your-domain-model-in-riak","status":"publish","type":"post","link":"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/","title":{"rendered":"Efficiently storing your domain model in Riak"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" class=\"alignleft size-full wp-image-8333\" style=\"float: left;margin-right: 10px\" src=\"https:\/\/trifork.nl\/articles\/wp-content\/uploads\/sites\/3\/2013\/06\/Riak_product_logo.png\" alt=\"Riak_product_logo\" width=\"300\" height=\"114\" srcset=\"https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2013\/06\/Riak_product_logo.png 400w, https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2013\/06\/Riak_product_logo-300x114.png 300w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/>Domain modelling and persistence appear to be at odds with each other, no matter which persistence store you use. Over the years practices have been developed for storing a model in a relational database using ORM frameworks like Hibernate, and various design patterns to help mitigate a number of issues. Not all of these translate very well when using one of the NoSQL persistence stores however. In this blog I will describe a situation we recently came across when working with a model we have to store in <a href=\"http:\/\/basho.com\/riak\/\" target=\"_blank\" rel=\"noopener\">Riak<\/a>. I will detail the model, the typical relational solution in Riak and what alternatives you may want to consider to better work with the strengths and weaknesses of Riak.<\/p>\n<p><!--more-->Why we chose Riak over a traditional RDBMS? That answer lies with various requirements including high volume usage patterns and multi data center support. Suffice to say we found the requirements were a better fit with Riak than any other system under consideration.<\/p>\n<h3>The domain model<\/h3>\n<p style=\"text-align: center\"><a href=\"https:\/\/trifork.nl\/articles\/wp-content\/uploads\/sites\/3\/2013\/06\/model1-cropped.png\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-8379 aligncenter\" src=\"https:\/\/trifork.nl\/articles\/wp-content\/uploads\/sites\/3\/2013\/06\/model1-cropped.png\" alt=\"model1-cropped\" width=\"515\" height=\"362\" srcset=\"https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2013\/06\/model1-cropped.png 818w, https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2013\/06\/model1-cropped-300x211.png 300w, https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2013\/06\/model1-cropped-768x539.png 768w\" sizes=\"auto, (max-width: 515px) 100vw, 515px\" \/><\/a><\/p>\n<p>For every exam being taken an <code>ExamSession<\/code> instance will be created; a persistent session object. The <code>Exam<\/code> and <code>Questions<\/code> will typically be uploaded and not changed often, if at all. The <code>ExamSession<\/code> and <code>Answers<\/code> typically change a lot during the exam and afterwards should only be read.<\/p>\n<h3>Riak: the relational approach<\/h3>\n<p>So, what happens to our domain if we take the relational approach? We probably want to put every class in a bucket of its own, add some <code>key<\/code> field, and for concurrency issues a <code>vClock<\/code> field as well. Specific to the domain model we described above a more invasive change will be necessary: the relationship from the <code>Questions<\/code> to the <code>Answers<\/code> has to be severed and instead we will have a relationship from <code>ExamSession<\/code> to the <code>Answers<\/code>.<\/p>\n<p>Relationships will be modelled with a key or a list of keys depending on the nature of the relationship. Bi-directional relationships will have to have those keys in both classes.<\/p>\n<p style=\"text-align: center\"><a href=\"https:\/\/trifork.nl\/articles\/wp-content\/uploads\/sites\/3\/2013\/06\/model2-cropped.png\"><img loading=\"lazy\" decoding=\"async\" class=\" wp-image-8380 aligncenter\" src=\"https:\/\/trifork.nl\/articles\/wp-content\/uploads\/sites\/3\/2013\/06\/model2-cropped.png\" alt=\"model2-cropped\" width=\"532\" height=\"408\" srcset=\"https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2013\/06\/model2-cropped.png 950w, https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2013\/06\/model2-cropped-300x230.png 300w, https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2013\/06\/model2-cropped-768x589.png 768w\" sizes=\"auto, (max-width: 532px) 100vw, 532px\" \/><\/a><\/p>\n<p>The <code>Answers<\/code> can be updated individually, when needed, as can the <code>ExamSession<\/code>.<\/p>\n<h3>Issues with the relational approach<\/h3>\n<p>Now, what happens if you have your <code>ExamSessionRepository<\/code> request a specific <code>ExamSession<\/code>? It will send a get to Riak for the <code>ExamSession<\/code> with a specific key. This will trigger Riak to send <em>n<\/em> requests to the replicas that should know how to retrieve the object for the given key. Not until at least <em>r<\/em> of those replicas have responded, will the original host in the cluster return.<\/p>\n<p>On return, the application will have to retrieve the keys for the objects stored in other buckets, do a get for those as well until the entire object tree is complete. For an <code>ExamSession<\/code> this comes down to 1 <code>ExamSession<\/code>, 1 <code>Exam<\/code>, <em>q<\/em> <code>Questions<\/code> and <em>a<\/em> <code>Answers<\/code> being retrieved. For an exam with 30 questions with answers that amounts to 1 + 1 + 30 + 30 = 62 full round trips to Riak!<\/p>\n<p>Some of this can be mitigated by using links and link walking in Riak, but even when using this technique the internal requests will still happen.<\/p>\n<p>Even if the <code>Exam<\/code> and <code>Questions<\/code> are cached, and they probably should as they are practically constants compared to the life cycle of an <code>ExamSession<\/code>, we are still looking at 31 objects that need to be retrieved from Riak. For a system that can be described as &#8216;high throughput, high latency&#8217;, that is not an optimal solution.<\/p>\n<h3>Riak: an alternative approach<\/h3>\n<p>Reducing the number of round trips, whether full or partial, means we have to rethink the way we retrieve our data from Riak. Having a domain model we should be able to hide this from the domain itself through the use of the repositories.<\/p>\n<p>A first step is to make a distinction between the data that has high and low update ratios. The <code>Exam<\/code> and <code>Questions<\/code> having a low update ratio means we can aggressively cache them. For the <code>Questions<\/code> we can also use lazy loading, as a candidate is only ever going to answer a single question at a time.<\/p>\n<p>The <code>ExamSession<\/code> and <code>Answers<\/code> are updated quite frequently while a candidate is working on the exam. <code>Answers<\/code> are also closely related to the <code>ExamSession<\/code> so combining the two into a single object in Riak would reduce the number of requests to about one, for the cost of having a bigger payload per request. To achieve this merge, we identified three different approaches:<\/p>\n<ol>\n<li>Write a custom converter, based on the <code>Converter<\/code> interface of the Riak Java client<\/li>\n<li>Write custom de-\/serialisers using the appropriate Jackson interfaces and feed them to the <code>ObjectMapper<\/code> in the <code>JSONConverter<\/code> used by the Riak Java client for conversion to and from JSON<\/li>\n<li>Use DTO objects to de-\/serialise the domain objects<\/li>\n<\/ol>\n<p>Though the custom converter allows for a lot of flexibility in what and how to store in Riak, it also requires intimate knowledge of the domain, Riak as well as the Riak Java client API. We therefor discarded this option for our implementation.<\/p>\n<p>A solution which requires less knowledge of Riak, almost none actually, was the de-\/serialiser option. This requires you to create an implementation of the <code>Deserializer<\/code> and <code>Serializer<\/code> interfaces in Jackson for those classes you want to have custom persistence behaviour for and register them with the <code>JSONConverter<\/code>. Every time one of those classes is encountered during the de-\/serialisation your custom implementation will be called. When I implemented this, I ran into some minor issues. The documentation for these interfaces is rather limited, so if you want to go beyond the &#8220;Hello World&#8221; scenario you are left to figure that out on your own. More serious I found the testability of the <code>Serializer<\/code> interface, or lack of testability actually.<\/p>\n<p>In the end I implemented a DTO for the <code>ExamSession<\/code> that only contains the fields we want to persist. It required that the domain <code>Repository<\/code> became responsible for translating the domain object into a dto and vice verse. Though this introduces a construction into the domain for the sake of the persistence, we considered it the less invasive option. As a positive side effect, this means the <code>@JsonIgnore<\/code> and other hints for the <code>JSONConverter<\/code> in the actual domain object are no longer necessary.<\/p>\n<p>A final change we made was to compress a particular string property in the <code>Answer<\/code> when storing it in Riak. This reduced the footprint of an <code>Answer<\/code> significantly (50+% reduction in size of an <code>Answer<\/code> was observed) and therefor of the entire <code>ExamSession<\/code>. Decompression can be done lazily so mitigating the added computational impact of this change.<\/p>\n<p>Looking back at the number of round trips we mentioned in the previous section, we managed to bring this down even further. From the original 62 round trips in the example, we would now need just a single round trip for the entire <code>ExamSession<\/code> to be retrieved!<\/p>\n<h3>Summary<\/h3>\n<p>As discussed, the initial, perhaps naive, approach to use a relational strategy for storing your object model in Riak can have a serious impact on your performance and therefore your user experience.<\/p>\n<p>However, taking a step back and combining the characteristics of Riak with the properties of your domain can lead to significant improvements. This may lead to changes in your domain model, but instead of changing your domain, you can also leverage your persistence service (a.k.a. <code>Repository<\/code>) to adapt your model to the persistence.<\/p>\n<p>Read more about our\u00a0<a href=\"http:\/\/www.trifork.nl\/en\/home\/products\/education\/ims-question-test-interoperability.html\" target=\"_blank\" rel=\"noopener\"><strong>QTI\u00a0Assessment\u00a0Delivery Engine<\/strong><\/a>\u00a0stored in a Riak database for one of our customers or <a href=\"http:\/\/info.trifork.nl\/TriforkQTIdemorequest.html\" target=\"_blank\" rel=\"noopener\"><strong>download a demo<\/strong><\/a> of the tool in action.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Domain modelling and persistence appear to be at odds with each other, no matter which persistence store you use. Over the years practices have been developed for storing a model in a relational database using ORM frameworks like Hibernate, and various design patterns to help mitigate a number of issues. Not all of these translate [&hellip;]<\/p>\n","protected":false},"author":112,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"content-type":"","footnotes":""},"categories":[125,65],"tags":[340,45],"class_list":["post-8122","post","type-post","status-publish","format-standard","hentry","category-nosql-2","category-big_data_search","tag-domain-modelling","tag-riak"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v24.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Efficiently storing your domain model in Riak - Trifork Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Efficiently storing your domain model in Riak - Trifork Blog\" \/>\n<meta property=\"og:description\" content=\"Domain modelling and persistence appear to be at odds with each other, no matter which persistence store you use. Over the years practices have been developed for storing a model in a relational database using ORM frameworks like Hibernate, and various design patterns to help mitigate a number of issues. Not all of these translate [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/\" \/>\n<meta property=\"og:site_name\" content=\"Trifork Blog\" \/>\n<meta property=\"article:published_time\" content=\"2013-06-20T10:34:53+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/trifork.nl\/articles\/wp-content\/uploads\/sites\/3\/2013\/06\/Riak_product_logo.png\" \/>\n<meta name=\"author\" content=\"Thomas Zeeman\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Thomas Zeeman\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/\",\"url\":\"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/\",\"name\":\"Efficiently storing your domain model in Riak - Trifork Blog\",\"isPartOf\":{\"@id\":\"https:\/\/trifork.nl\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/trifork.nl\/articles\/wp-content\/uploads\/sites\/3\/2013\/06\/Riak_product_logo.png\",\"datePublished\":\"2013-06-20T10:34:53+00:00\",\"author\":{\"@id\":\"https:\/\/trifork.nl\/blog\/#\/schema\/person\/efdddc5a3544e58b3f0b5c9e4d538380\"},\"breadcrumb\":{\"@id\":\"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/#primaryimage\",\"url\":\"https:\/\/trifork.nl\/articles\/wp-content\/uploads\/sites\/3\/2013\/06\/Riak_product_logo.png\",\"contentUrl\":\"https:\/\/trifork.nl\/articles\/wp-content\/uploads\/sites\/3\/2013\/06\/Riak_product_logo.png\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/trifork.nl\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Efficiently storing your domain model in Riak\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/trifork.nl\/blog\/#website\",\"url\":\"https:\/\/trifork.nl\/blog\/\",\"name\":\"Trifork Blog\",\"description\":\"Keep updated on the technical solutions Trifork is working on!\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/trifork.nl\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/trifork.nl\/blog\/#\/schema\/person\/efdddc5a3544e58b3f0b5c9e4d538380\",\"name\":\"Thomas Zeeman\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/trifork.nl\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/7d4352b454b0123a6a8e1b71a69bd5de?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/7d4352b454b0123a6a8e1b71a69bd5de?s=96&d=mm&r=g\",\"caption\":\"Thomas Zeeman\"},\"url\":\"https:\/\/trifork.nl\/blog\/author\/thomasz\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Efficiently storing your domain model in Riak - Trifork Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/","og_locale":"en_US","og_type":"article","og_title":"Efficiently storing your domain model in Riak - Trifork Blog","og_description":"Domain modelling and persistence appear to be at odds with each other, no matter which persistence store you use. Over the years practices have been developed for storing a model in a relational database using ORM frameworks like Hibernate, and various design patterns to help mitigate a number of issues. Not all of these translate [&hellip;]","og_url":"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/","og_site_name":"Trifork Blog","article_published_time":"2013-06-20T10:34:53+00:00","og_image":[{"url":"https:\/\/trifork.nl\/articles\/wp-content\/uploads\/sites\/3\/2013\/06\/Riak_product_logo.png","type":"","width":"","height":""}],"author":"Thomas Zeeman","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Thomas Zeeman","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/","url":"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/","name":"Efficiently storing your domain model in Riak - Trifork Blog","isPartOf":{"@id":"https:\/\/trifork.nl\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/#primaryimage"},"image":{"@id":"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/#primaryimage"},"thumbnailUrl":"https:\/\/trifork.nl\/articles\/wp-content\/uploads\/sites\/3\/2013\/06\/Riak_product_logo.png","datePublished":"2013-06-20T10:34:53+00:00","author":{"@id":"https:\/\/trifork.nl\/blog\/#\/schema\/person\/efdddc5a3544e58b3f0b5c9e4d538380"},"breadcrumb":{"@id":"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/#primaryimage","url":"https:\/\/trifork.nl\/articles\/wp-content\/uploads\/sites\/3\/2013\/06\/Riak_product_logo.png","contentUrl":"https:\/\/trifork.nl\/articles\/wp-content\/uploads\/sites\/3\/2013\/06\/Riak_product_logo.png"},{"@type":"BreadcrumbList","@id":"https:\/\/trifork.nl\/blog\/efficiently-storing-your-domain-model-in-riak\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/trifork.nl\/blog\/"},{"@type":"ListItem","position":2,"name":"Efficiently storing your domain model in Riak"}]},{"@type":"WebSite","@id":"https:\/\/trifork.nl\/blog\/#website","url":"https:\/\/trifork.nl\/blog\/","name":"Trifork Blog","description":"Keep updated on the technical solutions Trifork is working on!","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/trifork.nl\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/trifork.nl\/blog\/#\/schema\/person\/efdddc5a3544e58b3f0b5c9e4d538380","name":"Thomas Zeeman","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/trifork.nl\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/7d4352b454b0123a6a8e1b71a69bd5de?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/7d4352b454b0123a6a8e1b71a69bd5de?s=96&d=mm&r=g","caption":"Thomas Zeeman"},"url":"https:\/\/trifork.nl\/blog\/author\/thomasz\/"}]}},"_links":{"self":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/posts\/8122","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/users\/112"}],"replies":[{"embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/comments?post=8122"}],"version-history":[{"count":0,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/posts\/8122\/revisions"}],"wp:attachment":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/media?parent=8122"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/categories?post=8122"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/tags?post=8122"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}