{"id":21156,"date":"2024-10-16T10:52:19","date_gmt":"2024-10-16T08:52:19","guid":{"rendered":"https:\/\/trifork.nl\/blog\/?p=21156"},"modified":"2024-10-17T10:38:03","modified_gmt":"2024-10-17T08:38:03","slug":"divide-and-conquer-table-partitioning","status":"publish","type":"post","link":"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/","title":{"rendered":"Divide and Conquer: Table Partitioning"},"content":{"rendered":"\n<p>Many projects go live with a data store, most using a relational database. At the start of a project, we may not know if and how successful an application might become or how it may grow beyond the original scope. Thus we may not pull out all the stops on optimising the database structure e.g. longevity.<\/p>\n\n\n\n<p>One such thing that can happen in case your application continues to thrive is that some tables grow and grow and grow\u2026&nbsp; At some point you may want to consider your options to keep these tables manageable.<\/p>\n\n\n\n<p>One such option is to partition such a table. This means the table will be cut into pieces based on a property in the table, called the partition key. A property that does need to be chosen carefully, as it should help spread the data somewhat evenly over the partitions and should, ideally, be present in every query on that table.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The theory\u2026<\/h3>\n\n\n\n<p>In principle, partitioning of a table means that we set up our table such that it looks like it is a single table, but in practice it consists of several smaller tables (see <em>image 1<\/em>). Each with a particular partition of the data. This could be data based on a particular property having a specific value or because it fits in a defined range of values. The former can i.e. be a string or number, the latter is typically a range of numbers or dates, but many databases also support hashes. It would be ideal if that property is also the primary key of the table, but that\u2019s not always the case.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh7-rt.googleusercontent.com\/docsz\/AD_4nXf2c_zZfYGNxvpR8vfbdmt8ohPGm9BgAajqzTaKGWj2Asq6BG54wIDaoCIS8tlL0wmHYNRza0te-UnqItPhQVryqnjrTB9Lgo2wg9H54IDl0UdOpGWX3GuU8koybh6t-4HrtMz_qwkXZVo_BTzGlLDxLsTO?key=Kc--iz-qeEASUam-NL8qug\" alt=\"Table diagram showing how a partitioning could look like\" title=\"\"\/><\/figure>\n\n\n\n<p class=\"has-text-align-center\"><em>Image 1<\/em><\/p>\n\n\n\n<p>So, when should we consider partitioning? A couple of reasons mentioned in the various database manuals include<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>When a table reaches a certain size, e.g. <a href=\"https:\/\/docs.oracle.com\/en\/database\/oracle\/oracle-database\/19\/vldbg\/partition-concepts.html#GUID-E849DE8A-547D-4A2E-9324-706CAF574754\">Oracle mentions 2 GB<\/a><\/li>\n\n\n\n<li>When your table contains historical data and you add more data as time goes by; i.e. a billing system where you create new invoices over time<\/li>\n\n\n\n<li>When your data has different storage requirements based on its state; is it an active account or not, does it have to be searchable online or can it go into archival storage?<\/li>\n\n\n\n<li>If you want to <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/partitions\/partitioned-tables-and-indexes\">improve performance\u2026 possibly<\/a><\/li>\n\n\n\n<li>Data retention, when your data can, or should, be removed after some time<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\u2026and the practice<\/h3>\n\n\n\n<p>All of the major databases support partitioning of tables in some form. All of them have their own limitations on how many partitions they support, what data types are available as partition keys,&nbsp; whether indexes work across all partitions, and whether they can then also guarantee unique constraints across the entire table or only per partition. To top it off, it can also differ per version of a particular database. So for more information on those nitty, gritty details I\u2019ll refer to the individual database reference documentation. In this article I\u2019ll use PostgreSQL to illustrate some examples.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Scenario 1: multi-tenancy<\/h3>\n\n\n\n<p>So let\u2019s make this a bit more concrete and look at two scenarios. First an application where you store data for multiple customers in a way that can be described as multi-tenant. A single application with data for multiple users that should not be mixed up at all. Sure, we could start with a completely isolated application per tenant, but we all know that that may not be affordable when you start off, or even considered when it\u2019s built for the first customer.<\/p>\n\n\n\n<p>So we have a set of tables, each with a column <em>tenant<\/em>. That makes it an ideal candidate for the partition key.<br><br>Some of the benefits of partitioning the tables in this scenario are that you can isolate all the data for a particular tenant, maintenance on data for a particular tenant becomes possible without disturbing others, and, if needs be, it\u2019s also easier to migrate a tenant to a separate instance if it grows too big to handle or has additional requirements due to changing rules and regulations like GDPR.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Scenario 2: historical data<\/h3>\n\n\n\n<p>Another example would be historical data. Many businesses will have some kind of billing or financial records system and they typically have to keep those records for a very long time due to tax rules. That does not mean you have to keep them easily available all the time though, and at some point you may also be able to move them to cheaper, archival storage or even throw them away.<\/p>\n\n\n\n<p>The obvious benefit is of course that you can keep your active records set limited and thus keep performance in check, while not having to throw away or have more expensive data migration actions in place to get the same result. It also becomes easier to drop older data if it can finally be shredded. Just detach the partition, if you haven\u2019t done so already, and then drop that single table instead of executing a series of delete statements followed by an autovacuum of the table to make up for the freed up space. The detach is trivial and almost instantaneous, the delete and subsequent re-index and vacuum are more complex and can be very taxing.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Implementing it in your database<\/h3>\n\n\n\n<p>If you have by now decided to go this route, what do you have to do to get here then? The easy approach would be to rename the old table, then create the new table and copy over all the data as is done in <em>Listing 1<\/em>.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nALTER TABLE invoices RENAME TO invoices_legacy;\nCREATE TABLE invoices (\n    id                      BIGINT                  NOT NULL,\n    invoice_date   DATE                     NOT NULL,\n    description      VARCHAR(255)  NOT NULL,\n    amount            BIGINT                  NOT NULL,\n    discount          BIGINT                  DEFAULT 0\n) PARTITION BY RANGE (invoice_date);\n\n-- use an arbitrary old date to fill the FROM date. TO is exclusive.\nCREATE TABLE invoices_2023 PARTITION OF invoices\n    FOR VALUES FROM ('1900-01-01'::DATE) TO ('2024-01-01'::DATE);\nCREATE TABLE invoices_2024 PARTITION OF invoices\n    FOR VALUES FROM ('2024-01-01'::DATE) TO ('2025-01-01'::DATE);\n\nINSERT INTO invoices (id, invoice_date, description, amount, discount)\nSELECT (id, invoice_date, description, amount, discount) FROM invoices_legacy;\n<\/pre><\/div>\n\n\n<p class=\"has-text-align-center\"><em>Listing 1<\/em><\/p>\n\n\n\n<p>This is not taking into account any indexes created previously for the old table, queries that do not take the <em>invoice_date<\/em> into account or views on the old table that may now point to the renamed table or other things your database of choice may or may not do due to a rename. Oh, and also not taking into account whether or not the choice of partition key here is actually the best possible\u2026 that I leave as an exercise for the reader \ud83d\ude09<\/p>\n\n\n\n<p>Depending on the amount of data in the original table, this may also take quite some time to execute. In one case I was involved with, the original approach the developers took, the timer got to about a day for a dataset similar to production. Needless to say, at this point we asked a database administrator to help with optimising this. The resulting script still started with a rename, but didn\u2019t have to copy over the old data. So instead of creating the new partition for the old data, we used a temporary constraint to prevent the database from checking all the data to see if it matches the range set on the partition. <em>Listing 2<\/em> show how we could do this in our example:&nbsp;<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n-- ensure we do not check on the validity of the data, because we already know it's safe\nALTER TABLE invoices ADD CONSTRAINT invoices_y2023_c\n    CHECK(invoice_date &gt;= '1900-01-01'::DATE AND invoice_date &lt; '2024-01-01'::DATE) NOT VALID;\nALTER TABLE invoices ATTACH PARTITION invoices_legacy\n    FOR VALUES FROM ('1900-01-01'::DATE) TO ('2024-01-01'::DATE);\n-- now that we have attached the data, we can remove the temporary constraint\nALTER TABLE invoices_legacy DROP CONSTRAINT invoices_y2023_c;\n<\/pre><\/div>\n\n\n<p class=\"has-text-align-center\"><em>Listing 2<\/em><\/p>\n\n\n\n<p>You may also want to check your database documentation as some also have (limited) support to partition an existing table in place, e.g. <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/alter-table-partition-operations.html\">MySQL<\/a>.<\/p>\n\n\n\n<p>Now you have some idea how to implement it in your database. That does not mean you\u2019re done with it yet. Depending on the type of partition key, you will have to put some procedures in place to create new partitions or risk a grinding halt of your application as it tries to insert data into something that isn\u2019t there. Also detaching or even removing older partitions should be thought about. This could be automated but is not necessarily the best approach to take.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Implementing it in your application?<\/h3>\n\n\n\n<p>While table partitioning is primarily something in your database, it is not something you as an application developer can completely ignore. First and foremost, the choice of the partition key can be of effect on your queries to the database. If you do not take that into consideration, the database may have to query all partitions. That could lead to serious performance degradation.<br>Beyond that, you may be hit by some of the limitations of your database of choice when it comes to unique constraints (i.e. MySQL and Postgres have this) or the number of significant characters in names for tables, indexes and constraints (not too long ago limits of less than 64 characters were quite common, sometimes much less than that).<\/p>\n\n\n\n<p>For those that use SQL queries directly, i.e. because you use JDBC or MyBatis, you\u2019re on your own to go over all the queries in your application that use this table and rewrite your queries to use the partition key if and where necessary.<br><br>If you use JPA you may have a little less to do. Hibernate has <a href=\"https:\/\/in.relation.to\/2023\/02\/08\/hibernate-orm-62-partitioning\/\">added some basic support<\/a> in version 6.2 which allows you to mark the <em>@PartitionKey<\/em> such that it will try to take that into account for at least update and delete statements (see <em>Listing 3<\/em>). Custom queries written in <em>JPQL<\/em> still require attention from a developer to optimise.&nbsp;<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: java; highlight: [5,6]; title: ; notranslate\" title=\"\">\n@Entity\npublic class Invoices {\n    @Id\n    private Long id;\n    @PartitionKey\n    private LocalDate invoiceDate;\n    \u2026\n}\n<\/pre><\/div>\n\n\n<p class=\"has-text-align-center\"><em>Listing 3<\/em><\/p>\n\n\n\n<p>EclipseLink also has a series of <a href=\"https:\/\/wiki.eclipse.org\/EclipseLink\/Examples\/JPA\/Partitioning\">annotations for partitioning<\/a> but those target database partitioning or clustering. Also interesting and it can benefit from table partitioning, but not directly useful here.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">About that performance?<\/h3>\n\n\n\n<p>As one of the benefits, performance was mentioned but with a caveat. That is for a reason. Partitioning can indeed get you a nice performance boost, but only if your queries hit a single or a few partitions at most. Then it will only need to run over one or a few smaller indexes or smaller tables and that saves memory and going back-and-forth to the disk. So if you get a performance improvement heavily depends on whether you can get your queries to include the partition key and thus get the query planner to limit the amount of data you have to search through.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Summary<\/h3>\n\n\n\n<p>I hope you\u2019ve gotten an idea on what you could do within your existing database when your tables grow beyond what you may initially have expected. Or maybe you already expect them to grow this large and you decide to start with this approach from the get go.<\/p>\n\n\n\n<p>For some additional fun reading on an approach I have used on an existing set of tables, where we had constraints regarding existing data, possible downtime and some more, I leave you with <a href=\"https:\/\/database.one\/blog\/date_in_sequence\">this approach<\/a> where we <a href=\"https:\/\/database.one\/blog\/date_in_sequence_updates\">included the date in the id column of the table<\/a>. It has its pros and cons and requires some customization in the code as well to make Hibernate aware of it, but it has been serving us for some time now.<\/p>\n\n\n\n<p class=\"has-small-font-size\"><em>This article was previously published in <a href=\"https:\/\/nljug.org\/category\/java-magazine\/\">Java Magazine issue #2, 2024 by the NLJUG<\/a><\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Many projects go live with a data store, most using a relational database. At the start of a project, we may not know if and how successful an application might become or how it may grow beyond the original scope. Thus we may not pull out all the stops on optimising the database structure e.g. [&hellip;]<\/p>\n","protected":false},"author":112,"featured_media":21164,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"content-type":"","footnotes":""},"categories":[337,31,10,94],"tags":[73,438,11,439,443],"class_list":["post-21156","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-from-the-trenches","category-java","category-development","category-spring","tag-database","tag-hibernate","tag-java","tag-postgresql","tag-sql"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v24.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Divide and Conquer: Table Partitioning - Trifork Blog<\/title>\n<meta name=\"description\" content=\"Explore table partitioning for scaling databases, improving performance, and simplifying data management with PostgreSQL examples.\" \/>\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\/divide-and-conquer-table-partitioning\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Divide and Conquer: Table Partitioning - Trifork Blog\" \/>\n<meta property=\"og:description\" content=\"Explore table partitioning for scaling databases, improving performance, and simplifying data management with PostgreSQL examples.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/\" \/>\n<meta property=\"og:site_name\" content=\"Trifork Blog\" \/>\n<meta property=\"article:published_time\" content=\"2024-10-16T08:52:19+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-10-17T08:38:03+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2024\/10\/IG-Divide-Conquer.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1600\" \/>\n\t<meta property=\"og:image:height\" content=\"1600\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/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=\"8 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/\",\"url\":\"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/\",\"name\":\"Divide and Conquer: Table Partitioning - Trifork Blog\",\"isPartOf\":{\"@id\":\"https:\/\/trifork.nl\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2024\/10\/IG-Divide-Conquer.png\",\"datePublished\":\"2024-10-16T08:52:19+00:00\",\"dateModified\":\"2024-10-17T08:38:03+00:00\",\"author\":{\"@id\":\"https:\/\/trifork.nl\/blog\/#\/schema\/person\/efdddc5a3544e58b3f0b5c9e4d538380\"},\"description\":\"Explore table partitioning for scaling databases, improving performance, and simplifying data management with PostgreSQL examples.\",\"breadcrumb\":{\"@id\":\"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/#primaryimage\",\"url\":\"https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2024\/10\/IG-Divide-Conquer.png\",\"contentUrl\":\"https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2024\/10\/IG-Divide-Conquer.png\",\"width\":1600,\"height\":1600},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/trifork.nl\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Divide and Conquer: Table Partitioning\"}]},{\"@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":"Divide and Conquer: Table Partitioning - Trifork Blog","description":"Explore table partitioning for scaling databases, improving performance, and simplifying data management with PostgreSQL examples.","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\/divide-and-conquer-table-partitioning\/","og_locale":"en_US","og_type":"article","og_title":"Divide and Conquer: Table Partitioning - Trifork Blog","og_description":"Explore table partitioning for scaling databases, improving performance, and simplifying data management with PostgreSQL examples.","og_url":"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/","og_site_name":"Trifork Blog","article_published_time":"2024-10-16T08:52:19+00:00","article_modified_time":"2024-10-17T08:38:03+00:00","og_image":[{"width":1600,"height":1600,"url":"https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2024\/10\/IG-Divide-Conquer.png","type":"image\/png"}],"author":"Thomas Zeeman","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Thomas Zeeman","Est. reading time":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/","url":"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/","name":"Divide and Conquer: Table Partitioning - Trifork Blog","isPartOf":{"@id":"https:\/\/trifork.nl\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/#primaryimage"},"image":{"@id":"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/#primaryimage"},"thumbnailUrl":"https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2024\/10\/IG-Divide-Conquer.png","datePublished":"2024-10-16T08:52:19+00:00","dateModified":"2024-10-17T08:38:03+00:00","author":{"@id":"https:\/\/trifork.nl\/blog\/#\/schema\/person\/efdddc5a3544e58b3f0b5c9e4d538380"},"description":"Explore table partitioning for scaling databases, improving performance, and simplifying data management with PostgreSQL examples.","breadcrumb":{"@id":"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/#primaryimage","url":"https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2024\/10\/IG-Divide-Conquer.png","contentUrl":"https:\/\/trifork.nl\/blog\/wp-content\/uploads\/sites\/3\/2024\/10\/IG-Divide-Conquer.png","width":1600,"height":1600},{"@type":"BreadcrumbList","@id":"https:\/\/trifork.nl\/blog\/divide-and-conquer-table-partitioning\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/trifork.nl\/blog\/"},{"@type":"ListItem","position":2,"name":"Divide and Conquer: Table Partitioning"}]},{"@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\/21156","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=21156"}],"version-history":[{"count":6,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/posts\/21156\/revisions"}],"predecessor-version":[{"id":21174,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/posts\/21156\/revisions\/21174"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/media\/21164"}],"wp:attachment":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/media?parent=21156"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/categories?post=21156"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/tags?post=21156"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}