{"id":1847,"date":"2024-10-23T17:02:54","date_gmt":"2024-10-23T15:02:54","guid":{"rendered":"https:\/\/www.loicmathieu.fr\/wordpress\/?p=1847"},"modified":"2024-10-24T13:04:37","modified_gmt":"2024-10-24T11:04:37","slug":"jooq-tip-ne-convertissez-pas-jsonb-en-string","status":"publish","type":"post","link":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/jooq-tip-ne-convertissez-pas-jsonb-en-string\/","title":{"rendered":"jOOQ tip: don&#8217;t convert JSONB to a String"},"content":{"rendered":"<p>A few weeks ago, while investigating possible performance improvements for <a href=\"https:\/\/kestra.io\/\" rel=\"noopener\" target=\"_blank\">Kestra<\/a>&#8216;s JDBC backend, I noticed that a method we were using to map an entity to be persisted in the database into its <code>JSONB<\/code> representation was taking up a lot of time in our CPU profiles.<\/p>\n<p>In the following flame graph, we can see that the <code>JdbcQueue.map()<\/code> method accounts for more than 21% of the samples and the <code>Repository.map()<\/code> method for 3.2% of the samples of a CPU profile obtained using <a href=\"https:\/\/github.com\/async-profiler\/async-profiler\" rel=\"noopener\" target=\"_blank\">async-profiler<\/a>.<\/p>\n<img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/366507373-c44206a5-0085-43cc-902e-97756319b0ea.png?resize=640%2C461&#038;ssl=1\" alt=\"\" width=\"640\" height=\"461\" class=\"alignnone size-large wp-image-1853\" srcset=\"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/366507373-c44206a5-0085-43cc-902e-97756319b0ea.png?resize=1024%2C737&amp;ssl=1 1024w, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/366507373-c44206a5-0085-43cc-902e-97756319b0ea.png?resize=300%2C216&amp;ssl=1 300w, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/366507373-c44206a5-0085-43cc-902e-97756319b0ea.png?resize=768%2C553&amp;ssl=1 768w, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/366507373-c44206a5-0085-43cc-902e-97756319b0ea.png?resize=375%2C270&amp;ssl=1 375w, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/366507373-c44206a5-0085-43cc-902e-97756319b0ea.png?w=1166&amp;ssl=1 1166w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/>\n<p>These two methods map a <code>JSONB<\/code> column into the entity&#8217;s target type. To do this, we use a <a href=\"https:\/\/github.com\/FasterXML\/jackson\" rel=\"noopener\" target=\"_blank\">Jackson<\/a> object mapper. The code can be simplified to :<\/p>\n<pre>\nMAPPER.readValue(record.get(\"value\", String.class), MyEntity.class);\n<\/pre>\n<p>Instinctively, I thought I&#8217;d better read the record in <code>JSONB<\/code> rather than in <code>String<\/code>, so I made the following change:<\/p>\n<pre>\nMAPPER.readValue(record.get(\"value\", JSONB.class).data(), MyEntity.class);\n<\/pre>\n<p>And the results was immediate: a clear improvement in performance!<\/p>\n<p><code>JdbcQueue.map()<\/code> drops from over 21% of samples to a mere 4.6% and the <code>Repository.map()<\/code> method from 3.2% of samples to 2.7%. In my test, the Kestra queue is heavily used, which explains why the impact is much greater on this component.<\/p>\n<img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/366507464-11f17f92-ed72-4609-ad3c-adb393bc88c7.png?resize=640%2C456&#038;ssl=1\" alt=\"\" width=\"640\" height=\"456\" class=\"alignnone size-large wp-image-1852\" srcset=\"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/366507464-11f17f92-ed72-4609-ad3c-adb393bc88c7.png?resize=1024%2C730&amp;ssl=1 1024w, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/366507464-11f17f92-ed72-4609-ad3c-adb393bc88c7.png?resize=300%2C214&amp;ssl=1 300w, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/366507464-11f17f92-ed72-4609-ad3c-adb393bc88c7.png?resize=768%2C547&amp;ssl=1 768w, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/366507464-11f17f92-ed72-4609-ad3c-adb393bc88c7.png?resize=379%2C270&amp;ssl=1 379w, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/366507464-11f17f92-ed72-4609-ad3c-adb393bc88c7.png?w=1179&amp;ssl=1 1179w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/>\n<p>Pleased with the improvement, I opened a PR which I merged quickly: https: <a href=\"https:\/\/github.com\/kestra-io\/kestra\/pull\/4899\/files\" rel=\"noopener\" target=\"_blank\">\/\/github.com\/kestra-io\/kestra\/pull\/4899\/files<\/a>.<\/p>\n<p>But, after thought, I&#8217;d like to know why?<\/p>\n<p>So I open my favorite IDE and browse the code:<\/p>\n<ul><li><code>Record.get(String, Class)<\/code> is implemented by <code>AbstractRecord.get(String, Class)<\/code>.<\/li>\n\n<li><code>AbstractRecord.get(String, Class)<\/code> lead to <code>AbstractRecord.get(int, Class)<\/code> where a method <code>converterOrFail()<\/code> is called. Oh!, a conversion can explain the impact on performance!<\/li>\n\n<li>Conversion takes place via a <code>ConvertedProvider<\/code> which provides a <code>Converter<\/code>.<\/li>\n\n<li>After a quick search, converters use the utility class <code>Convert<\/code> for type conversion, we&#8217;re getting closer!<\/li>\n\n<li><code>Convert.from()<\/code>, in line 716, converts any type to String by calling its <code>toString()<\/code> method.<\/li>\n<\/ul>\n<pre>\n\/\/ All types can be converted into String\nelse if (toClass == String.class) {\n    if (from instanceof EnumType e)\n        return (U) e.getLiteral();\n    }\n    return (U) from.toString();\n}\n<\/pre>\n<p>This leads to the <code>JSONB.toString()<\/code> method, which uses <code>JSONValue.toJSONString(parsed())<\/code> to normalize the JSON representation and enable equality between two JSONs of different structure, but with the same attributes.<\/p>\n<p>This method is clearly documented as one to be avoided in a performance-sensitive context and it&#8217;s clearly specified in the JavaDoc.<\/p>\n<blockquote>\nuses a normalised representation of the JSON content, meaning that two equivalent JSON documents are considered equal. This impacts both behaviour and performance!\n<\/blockquote>\n<p>So now I know the why!<\/p>\n<p>As a matter of conscience, I&#8217;ve looked at our MySQL implementation, which uses the <code>JSON<\/code> type, which doesn&#8217;t perform any normalization and returns the subjasent JSON directly via <code>String.valueOf(data)<\/code>, so it doesn&#8217;t suffer from the same performance issue.<\/p>\n<p>That&#8217;s the whole story, I hope this dive into the <a href=\"https:\/\/www.jooq.org\/\" rel=\"noopener\" target=\"_blank\">jOOQ<\/a> code has interested you as much as it has me ;)<\/p>\n<p><strong>Update<\/strong> : after publication of this article on social networks, <a href=\"https:\/\/x.com\/JavaOOQ\/status\/1849366704373059603\" rel=\"noopener\" target=\"_blank\">jOOQ a r\u00e9pondu<\/a> pointing to two GitHub issues on this subject:<\/p>\n<ol><li><a href=\"https:\/\/github.com\/jOOQ\/jOOQ\/issues\/15751\" rel=\"noopener\" target=\"_blank\">Revert JSONB::toString to produce JSONB::data<\/a><\/li>\n\n<li><a href=\"https:\/\/github.com\/jOOQ\/jOOQ\/issues\/14954\" rel=\"noopener\" target=\"_blank\">Implement faster JSONB normalisation<\/a><\/li>\n<\/ol>\n<p>The second issue is worth reading, as it explains the issue of standardization, suggests avenues for improvement and discusses the limitations of the current JSON parser. A problem that might seem simple at first glance is often much more complicated than it appears.<\/p>\n<p>To take part in the discussion, I&#8217;ve opened an issue <a href=\"https:\/\/github.com\/jOOQ\/jOOQ\/issues\/17497\" rel=\"noopener\" target=\"_blank\">Switch JSONB record convertion for String to use the data() method<\/a> which proposes to fix this specific case of conversion without having to wait to fix the more general problem of the performance of the <code>toString()<\/code> method.\n<\/p>","protected":false},"excerpt":{"rendered":"<p>A few weeks ago, while investigating possible performance improvements for Kestra&#8216;s JDBC backend, I noticed that a method we were using to map an entity to be persisted in the database into its JSONB representation was taking up a lot of time in our CPU profiles. In the following flame graph, we can see that the JdbcQueue.map() method accounts for more than 21% of the samples and the Repository.map() method for 3.2% of the samples of a CPU profile obtained&#8230;<p class=\"read-more\"><a class=\"btn btn-default\" href=\"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/jooq-tip-ne-convertissez-pas-jsonb-en-string\/\"> Read More<span class=\"screen-reader-text\">  Read More<\/span><\/a><\/p><\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":4,"activitypub_interaction_policy_quote":"anyone","activitypub_status":"","footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[9],"tags":[11,223,159,187],"class_list":["post-1847","post","type-post","status-publish","format-standard","hentry","category-informatique","tag-java","tag-jooq","tag-performance","tag-profiling"],"aioseo_notices":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":1459,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/profiler-un-pod-dans-kubernetes-avec-kubectl-flame\/","url_meta":{"origin":1847,"position":0},"title":"Profiling a pod in Kubernetes with kubectl flame","author":"admin","date":"Wednesday May  4th, 2022","format":false,"excerpt":"Kubectl flame is a plugin for kubectl that allows you to profile applications in production with low overhead by generating FlameGraphs. It is a Yahoo project. It is installed via krew, the plugin manager for kubectl, and allows you to generate FlameGraphs for applications in Go, Java (all JVM languages),\u2026","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/cpu-mysql-updated.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/cpu-mysql-updated.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/cpu-mysql-updated.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/cpu-mysql-updated.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":1153,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/profiler-une-image-native-graalvm-avec-perf\/","url_meta":{"origin":1847,"position":1},"title":"Profiling a GraalVM native image with perf","author":"admin","date":"Monday November 16th, 2020","format":false,"excerpt":"The GraalVM native-image tool allows you to generate a native executable (or native image) from your Java application. This native executable will start very quickly and have a much smaller memory footprint than a traditional Java application; at the cost of reduced peak performance and a relatively high packaging build\u2026","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/flamegraph-2.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/flamegraph-2.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/flamegraph-2.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/loicmathieu.fr\/wordpress\/wp-content\/uploads\/flamegraph-2.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":1946,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/java-25-whats-new\/","url_meta":{"origin":1847,"position":2},"title":"Java 25: what&#8217;s new?","author":"admin","date":"Friday July  4th, 2025","format":false,"excerpt":"Now that Java 25 is features complete (Rampdown Phase One at the day of writing), it\u2019s time to walk through all the functionalities that bring to us, developers, this new version. This article is part of a series on what\u2019s new on the last versions of Java, for those who\u2026","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1180,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/benchmark-conversion-de-long-en-byte\/","url_meta":{"origin":1847,"position":3},"title":"Benchmark : conversion from long to byte[]","author":"admin","date":"Tuesday December  8th, 2020","format":false,"excerpt":"I've been using Kafka a lot lately, and in Kafka a lot of things are byte arrays, even headers! As I have many components that exchange messages, I added headers to help with message tracking, including a timestamp header which has the value System.currentTimeMillis(). So I had to transform a\u2026","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1258,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/profiler-une-application-java-dans-un-conteneur-deploye-dans-kubernetes-avec-jfr-java-flight-recorder\/","url_meta":{"origin":1847,"position":4},"title":"(Fran\u00e7ais) Profiler une application Java dans un conteneur d\u00e9ploy\u00e9 dans kubernetes avec JFR &#8211; Java Flight Recorder","author":"admin","date":"Monday April 12th, 2021","format":false,"excerpt":"Sorry, this entry is only available in Fran\u00e7ais.","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1330,"url":"https:\/\/www.loicmathieu.fr\/wordpress\/informatique\/devoxx-france-2021-ledition-9-3-4\/","url_meta":{"origin":1847,"position":5},"title":"(Fran\u00e7ais) Devoxx France 2021 &#8211; l&#8217;\u00e9dition 9 3\/4","author":"admin","date":"Friday October  1st, 2021","format":false,"excerpt":"Sorry, this entry is only available in Fran\u00e7ais.","rel":"","context":"In &quot;informatique&quot;","block_context":{"text":"informatique","link":"https:\/\/www.loicmathieu.fr\/wordpress\/category\/informatique\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/posts\/1847","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/comments?post=1847"}],"version-history":[{"count":12,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/posts\/1847\/revisions"}],"predecessor-version":[{"id":1862,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/posts\/1847\/revisions\/1862"}],"wp:attachment":[{"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/media?parent=1847"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/categories?post=1847"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.loicmathieu.fr\/wordpress\/wp-json\/wp\/v2\/tags?post=1847"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}