<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrej Szalma</title>
    <description>The latest articles on DEV Community by Andrej Szalma (@andycko).</description>
    <link>https://dev.to/andycko</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F405465%2F5b062efb-f37b-4fdd-a1b4-ed9a3ae8c182.jpeg</url>
      <title>DEV Community: Andrej Szalma</title>
      <link>https://dev.to/andycko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andycko"/>
    <language>en</language>
    <item>
      <title>Logging for beginners (in Python) - How and Why</title>
      <dc:creator>Andrej Szalma</dc:creator>
      <pubDate>Wed, 20 Sep 2023 10:51:13 +0000</pubDate>
      <link>https://dev.to/andycko/logging-for-beginers-in-python-how-and-why-3bna</link>
      <guid>https://dev.to/andycko/logging-for-beginers-in-python-how-and-why-3bna</guid>
      <description>&lt;p&gt;Logging is an essential part of software development. However, many developers struggle with writing good logs. In this post, we will explore what makes a good log and how to write one.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Brief Timeline of a Software Developer
&lt;/h2&gt;

&lt;p&gt;When people start programming, the first thing they do is produce their initial piece of code and say hi to the stdout/console/web browser/etc.. with the infamous sentence -&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hello, world."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not long after, come the comments. Comments are an integral part of every new developer's handbook. Everyone tells you to write them, but no one explains why, how, and when. As developers progress, they understand more code, patterns, and paradigms, and with that often come the assumption that every other engineer who is worth reading their masterpiece will understand it as well. It is crucial to understand that printing out the message "Error - process failed" will not lead other engineers to the issue that has been standing between them and happiness for the past two days.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes Good Logs?
&lt;/h2&gt;

&lt;p&gt;As you might know, there are whole books about writing well-logged, observable, and debuggable code. This post is not a drop-in replacement for these books, but it is a short write-up to help you easily level up your coding game.&lt;/p&gt;

&lt;p&gt;We are uncovering another parallel to nature, as there are four elements that form a good log and are vital for delivering a meaningful and helpful message.&lt;/p&gt;

&lt;h3&gt;
  
  
  (Water) The Element of Time
&lt;/h3&gt;

&lt;p&gt;Just as water flows, time does too, and therefore we must make sure that when reading logs, we know where the dam blocking our flow is. When searching for the source of an error, you will end up scrolling through a log file. It might be very long, but it just as well might be as short as a few lines. Regardless, you can't make assumptions as to which line in the log corresponds to your error without having information on when the specific log was written. It is crucial for every log to provide a timestamp (I would strongly recommend a human-readable format, like ISO 8601), so that someone doesn't get stuck in a vicious circle of debugging because of unknowingly looking at year-old logs that don't actually correspond to their issue.&lt;/p&gt;

&lt;p&gt;Examples of logs with timestamps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;2024-01-01T12:00:00.000Z - ...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;January 1, 2024, 12:00:00 UTC - ...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;1704067200000 - ...&lt;/code&gt; (I wouldn't prefer to use Unix epoch time when it comes to human readability, however, when logs are interpreted by a software like Sentry, it can do the translation into a more readable format for you)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  (Earth) The Element of Context
&lt;/h3&gt;

&lt;p&gt;In the context of software logs, just as the element of earth provides a foundation for life, contextual information provides a foundation for understanding what happened in a given event. Without context, logs can be difficult to interpret and can lead to incorrect conclusions about what happened. It is important to include contextual information such as the place where the log was written, depending on context and the size of your app, it could be a package name/module name/function name, whatever helps the reader to identify where the log is coming from. Generally, in Python, this would be the name of the app followed by the name of the module (utilizing the &lt;code&gt;__name__&lt;/code&gt; variable).&lt;/p&gt;

&lt;p&gt;Examples of logs with contextual information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;2024-01-01T12:00:00.000Z - myapp.mymodule - ...&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  (Fire) The Element of Information Level
&lt;/h3&gt;

&lt;p&gt;The roof is on fire, right? Maybe, or maybe not. So far, we have only talked about logs as part of errors, however, debugging is not the only point when you would look at logs. You might want to figure out how long something took, or what is your program doing just know. Different log levels are used for different purposes. It is important to understand each of them and know when to use which as the level is used for filtering which logs to show in different log outputs. The following are the most common log levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ERROR&lt;/strong&gt;- This one is quite self-explanatory. Something bad happened, your code ran into an error that is crucial for its health.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WARN&lt;/strong&gt; - Warnings show potentially harmful occurrences of issues that are not breaking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DEBUG&lt;/strong&gt; - This is the most granular level of your logs. It provides a very verbose way to write information about the state and actions of your program for developers and other diagnostic personnel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;INFO&lt;/strong&gt; - Info serves as a way for you to provide useful high-level information about the run of your program.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples of logs with different log levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;2024-01-01T12:00:00.000Z - myapp.mymodule - INFO - ...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2024-01-01T12:00:00.000Z - myapp.mymodule - DEBUG - ...&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  (Wind) The Element of (Error) Message
&lt;/h3&gt;

&lt;p&gt;A good message can carry information far and wide, just like the wind. Well crafted log messages can provide valuable insights into the behavior of a software application. A good log message should be clear and concise, providing enough information to understand what happened without overwhelming the reader with unnecessary details. For example, when you are logging a raised error, you don't need to include the error message in the log, as the error will already be included as part of the log. Rather choose to inform the user why the error occurred or provide more context which was maybe not included in the error itself.&lt;/p&gt;

&lt;p&gt;Examples of logs with clear and concise messages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;2024-01-01T12:00:00.000Z - myapp.mymodule - INFO - The API server has started listening on 0.0.0.0:3000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2024-01-01T12:00:00.000Z - myapp.mymodule - ERROR - Failed to login into container registry&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  An Extra One for You... Structure
&lt;/h3&gt;

&lt;p&gt;It is very important to make sure that your logs provide well-structured information, and that all of your logs are of the same structure. Consistency is crucial not just because of human readability, but also because logs are very often parsed by diagnostic software (e.g., (Sentry)[&lt;a href="https://sentry.io/%5D"&gt;https://sentry.io/]&lt;/a&gt;). There are many different ways to structure your logs, however, the following is a good start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[DATE/TIME] - [LOG LEVEL] - [MODULE] - [MESSAGE] - [STACK] - [DATA]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No need to make your logs look fancy, just make sure they are simple, structured, and easily comprehensible.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to write logs in python
&lt;/h2&gt;

&lt;p&gt;Python provides a built-in logging module that makes it easy to write logs in your application. Here are the basic steps to get started:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Import the logging module:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import logging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Configure the logging module:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;logging.basicConfig(level=logging.INFO, filename='example.log', filemode='w', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets the logging level to INFO (meaning that all the log messages with a level bellow will be ignored), specifies the filename for the log file, sets the file mode to 'w' (write), and specifies the log message format.&lt;/p&gt;

&lt;p&gt;This shows a simple way of configuring your logger, however, you can also use a configuration file where it is possible to declare much more complex configuration for the logging logic in your whole app. Please refer to the (logging module documentation)[&lt;a href="https://docs.python.org/3/library/logging.html"&gt;https://docs.python.org/3/library/logging.html&lt;/a&gt;] for more info.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write log messages:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This writes log messages at different severity levels to the log file.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use placeholders for dynamic values:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name = 'John'
logging.info('Hello, %s', name)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This writes a log message with a dynamic value (the name variable) using a placeholder.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pass raised exceptions to the logger:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try:
  # calculate the answer to everythin
  result = "42" + .0
except TypeError as e:
  logging.error("Exception occured while calculating the answer to everything", exc_info=True)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;exc_info=True&lt;/code&gt; you can easily attach the stack trace of the exception and provide valuable information.&lt;/p&gt;

&lt;p&gt;By following these steps, you can easily write logs in your Python application. Remember to include contextual information and use structured data to make your logs more useful and easy to analyze.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;In summary, good logs are composed of four elements: time, context, information level, and message. By following these guidelines and keeping the logs consistent, you can easily level up your coding game and write well-structured, readable, and useful logs.&lt;/p&gt;

</description>
      <category>python</category>
      <category>observability</category>
      <category>programming</category>
      <category>logging</category>
    </item>
    <item>
      <title>The mysteries of GraphQL clients' cache - The Showdown</title>
      <dc:creator>Andrej Szalma</dc:creator>
      <pubDate>Wed, 07 Sep 2022 08:01:51 +0000</pubDate>
      <link>https://dev.to/andycko/the-mysteries-of-graphql-clients-cache-the-showdown-4211</link>
      <guid>https://dev.to/andycko/the-mysteries-of-graphql-clients-cache-the-showdown-4211</guid>
      <description>&lt;p&gt;Recently I completed an internship at Microsoft where I had the privilege to work with people who are experts in their fields, more specifically in the field of GraphQL. I had numerous opportunities to learn from them and now I would like to teach you something. Let me take you on a journey through the midst of GraphQL clients &amp;amp; their cache. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a two-part series, where I'll talk about GraphQL client caches and compare the client's cache performance.&lt;/p&gt;

&lt;p&gt;Part 1: &lt;a href="https://dev.to/andycko/the-mysteries-of-graphql-clients-cache-the-introduction-3g13"&gt;The mysteries of GraphQL clients' cache - The Introduction&lt;/a&gt;&lt;br&gt;
Part 2: &lt;a href="https://dev.to/andycko/the-mysteries-of-graphql-clients-cache-the-showdown-3mpe-temp-slug-8775347?preview=cb1919c6d006563cba0c6feef0275c95fd198dde21ae1d8220b9f9f3472f896386b2e9c06fc7cde54780670f571a3d0b27a2f49922f554a518bdc522"&gt;The mysteries of GraphQL clients' cache - The Showdown&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;As promised in the previous part (&lt;a href="https://dev.to/andycko/the-mysteries-of-graphql-clients-cache-3j3m-temp-slug-7832453?preview=704245c70cba2f6b37ca453f41aaaed0a29c1a423b89f5f5038162d893caa34c6344981ce974c421bafb6f3cf4036cbef634bcc07704523aa2c16364"&gt;Part 1&lt;/a&gt;), I would like to show you a head-to-head comparison of the client caches performance using a benchmark that I have been working on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Benchmark
&lt;/h2&gt;

&lt;p&gt;So what is this benchmark I've been talking about? Simply put it is a tool to compare the latency performance of different GraphQL clients using &lt;strong&gt;your&lt;/strong&gt; example queries. Thanks to this benchmark anyone can get performance data that can be used to make well-informed, data-driven decisions when choosing a GraphQL client for their project. Not only that, but it can be also used for testing experimental cache implementations, and finding out their strengths and weaknesses to know what needs to be optimised. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnc47ihjo5d23160fcc0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnc47ihjo5d23160fcc0w.png" alt="GraphQL Client Cache Benchmark interface" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're interested, feel free to check it out for yourself - &lt;a href="https://github.com/Andycko/graphql-client-benchmarks"&gt;GraphQL Client Cache Benchmark&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This tool was originally developed by &lt;a href="https://github.com/convoyinc/graphql-client-benchmarks"&gt;Convoy&lt;/a&gt;. We have upgraded and expanded it to fit our purpose. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Methods
&lt;/h2&gt;

&lt;p&gt;In this part of the post, I would like to reach deep into my pocket and find the academic part of me to give you an overview of the experiment setup and testing methods so you know what and why are we testing. &lt;/p&gt;

&lt;h3&gt;
  
  
  Experiment setup
&lt;/h3&gt;

&lt;p&gt;For the possibility of duplication of our results, I'd like to share the conditions under that the experiment has been run. However, regardless of the conditions, the benchmark results should show the same trends and patterns, only with different numbers. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OS&lt;/strong&gt;: macOS Monterey 12.5&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU&lt;/strong&gt;: 2.4 GHz 8-Core Intel Core i9&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPU&lt;/strong&gt;: Intel UHD Graphics 630 1536 MB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAM&lt;/strong&gt;: 32 GB 2667 MHz DDR4&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser&lt;/strong&gt;: Google Chrome  - Version 103.0.5060.134 (Official Build) (x86_64)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Clients
&lt;/h3&gt;

&lt;p&gt;The main important part of this whole project are GraphQL clients, and even though you might already know which clients are we going to compare, there is one more thing I need to explain before we continue. As I mentioned before, we have decided to compare two clients - &lt;strong&gt;Apollo client&lt;/strong&gt; and  &lt;strong&gt;Relay&lt;/strong&gt;. However, we have also decided that it would be beneficial to see the effects on the performance of Apollo if we disabled the &lt;code&gt;ResultCache&lt;/code&gt;, therefore you will see Apollo client twice in the benchmark. This proved to be a great idea, as you will see later, it turns out that depending on your specific scenario, it might be worth thinking about disabling it after all. &lt;/p&gt;

&lt;h3&gt;
  
  
  Example queries
&lt;/h3&gt;

&lt;p&gt;As I have mentioned before, this benchmark uses arbitrary example queries and evaluates them with different clients throughout the whole test suite. These queries can include fragments as well, and they can represent your own real-life needs. Herein lies the beauty of this benchmark.&lt;/p&gt;

&lt;p&gt;We have included a couple of example queries that you can use to play around with the benchmark, however, we expect you to add your own. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer: There is an inbuilt query editor in the tool, however, it needed to be disabled since the newest Relay version needs pre-compiled &lt;code&gt;gql&lt;/code&gt; artefacts. This compilation is performed by a compilator written in Rust, and it does not have a JS API which could be used to compile artefacts during the runtime. That being said, you will have to get your hands a little dirty and add your examples to the code manually. But don't worry, the &lt;a href="https://github.com/Andycko/graphql-client-benchmarks#examples"&gt;documentation&lt;/a&gt; explains it very well.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Data access patterns
&lt;/h3&gt;

&lt;p&gt;In our benchmark, we have been testing for three data access patterns - &lt;strong&gt;Read&lt;/strong&gt;, &lt;strong&gt;Write&lt;/strong&gt; and &lt;strong&gt;Update&lt;/strong&gt;. In the following sections, I will provide you with tables that contain detailed descriptions of all the test cases and some example scenarios these might represent in the real life. &lt;/p&gt;

&lt;h4&gt;
  
  
  Read
&lt;/h4&gt;

&lt;p&gt;As you can see in the table below, we have tested reading from empty/full cache and reading using identical/same shape queries. The latter is especially important to demonstrate the reliance of the clients on &lt;strong&gt;AST objects&lt;/strong&gt; vs &lt;strong&gt;query strings&lt;/strong&gt;. (Hint: Apollo's &lt;code&gt;ResultCache&lt;/code&gt; is reliant on identical AST objects, therefore having the same query but with a different AST, will not be able to trigger reading from the &lt;code&gt;ResultCache&lt;/code&gt; leading to a new &lt;code&gt;write&lt;/code&gt; to it.)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Read type&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;th&gt;Example Scenario&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;R1&lt;/td&gt;
&lt;td&gt;Read (empty cache)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Setup&lt;/strong&gt;: None, &lt;strong&gt;Test&lt;/strong&gt;: Read from empty cache, &lt;strong&gt;Comment&lt;/strong&gt;: This demonstrates a client overhead for no-op queries.&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R2&lt;/td&gt;
&lt;td&gt;Read, fully cached, identical query written&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Setup&lt;/strong&gt;: Write query data to cache, &lt;strong&gt;Test&lt;/strong&gt;: Read the identical query, &lt;strong&gt;Comment&lt;/strong&gt;: This demonstrates the performance of the first read after write. Identical query is when the AST object is strictly equal.&lt;/td&gt;
&lt;td&gt;Explicitly warming the cache (eg. with chat members, or slimcore data), identical query is used to read and write.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R3&lt;/td&gt;
&lt;td&gt;Duplicate read, fully cached, identical query&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Setup&lt;/strong&gt;: Write query data to cache, read the identical query, &lt;strong&gt;Test&lt;/strong&gt;: Read the identical query, &lt;strong&gt;Comment&lt;/strong&gt;: This demonstrates the second read of the identical query.&lt;/td&gt;
&lt;td&gt;Test out the repeated reads, eg, pulling the chat list every time a user switches from Channels to chats.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R4&lt;/td&gt;
&lt;td&gt;Duplicate read, fully cached, same query shape&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Setup&lt;/strong&gt;: Write query data to cache, read the identical query, &lt;strong&gt;Test&lt;/strong&gt;: Read a query with same shape but different AST, &lt;strong&gt;Comment&lt;/strong&gt;: This demonstrates the reliance of a client on AST vs query string in its caches.&lt;/td&gt;
&lt;td&gt;Tests our repeated reads from separate components that would request the data from differently crafted queries.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R5&lt;/td&gt;
&lt;td&gt;Read 50% of fields, fully cached&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Setup&lt;/strong&gt;: Write query data to cache, &lt;strong&gt;Test&lt;/strong&gt;: Read a query with half of the fields of the original query, &lt;strong&gt;Comment&lt;/strong&gt;: This demonstrates how the client reads subsets of queries.&lt;/td&gt;
&lt;td&gt;Explicitly warming the cache (eg. with chat members, or slimcore data), but data queried by components requires only smaller set of fields.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R6&lt;/td&gt;
&lt;td&gt;Random read, fully cached, expanded response&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Setup&lt;/strong&gt;: Write query data with big number of items to cache, &lt;strong&gt;Test&lt;/strong&gt;: Read a fragment for a random item, &lt;strong&gt;Comment&lt;/strong&gt;: This demonstrates the overhead of big cache size on the client read behavior.&lt;/td&gt;
&lt;td&gt;Showing behavior of a client in a situation of growing cache size overtime during Teams session.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Write
&lt;/h4&gt;

&lt;p&gt;Similar to reads, we have been again testing writing from empty/full cache, with identical/same shape queries. Furthermore, you can also see &lt;strong&gt;observers&lt;/strong&gt; here, which represent &lt;code&gt;watchedQueries&lt;/code&gt; in Apollo and &lt;code&gt;subscriptions&lt;/code&gt; in Relay. Conceptually, if you imagine having multiple components on a site, which are all watching a certain query or a fragment, these are our observers. We tested for 1, 25, and 125 of these to see the growing overhead in different clients. The writes in a fully cached state were figuring as &lt;strong&gt;empty-writes&lt;/strong&gt; as no data was updated, so it only triggered a refetch of queries at the &lt;strong&gt;observers&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Write type&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;th&gt;Example Scenario&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;W1&lt;/td&gt;
&lt;td&gt;Write, empty cache, no observers&lt;/td&gt;
&lt;td&gt;Setup: None, Test: Write query data to cache&lt;/td&gt;
&lt;td&gt;Writing startup data response to cache before any observers are setup on clean boot.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W2&lt;/td&gt;
&lt;td&gt;Write, empty cache, 1, 25, 125 observers&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Setup&lt;/strong&gt;: Create observers, &lt;strong&gt;Test&lt;/strong&gt;: Write query data to cache, &lt;strong&gt;Comment&lt;/strong&gt;: This demonstrates the overhead caused by observers when writing to empty cache.&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W3&lt;/td&gt;
&lt;td&gt;Write, fully cached, 25, 125 observers&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Setup&lt;/strong&gt;: Write query data into the cache, set up observers for partial queries, &lt;strong&gt;Test&lt;/strong&gt;: Write the main query data to cache again, &lt;strong&gt;Comment&lt;/strong&gt;: This demonstrates if the client updates the observers even when the data didn’t change.&lt;/td&gt;
&lt;td&gt;Refetch query from the network, Eg: people, presence data where no data has changed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W4&lt;/td&gt;
&lt;td&gt;Write, fully cached, 1, 25, 125 identical observers, identical query&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Setup&lt;/strong&gt;: Write query data into the cache, create identical observers, &lt;strong&gt;Test&lt;/strong&gt;: Write the main query data to cache again, &lt;strong&gt;Comment&lt;/strong&gt;: This demonstrates the differences between the clients for different vs identical ASTs&lt;/td&gt;
&lt;td&gt;Refetch query from the network for components which observe an identical query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W5&lt;/td&gt;
&lt;td&gt;Repeated write, fully cached, identical query, no observers&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Setup&lt;/strong&gt;: Write query data into the cache, &lt;strong&gt;Test&lt;/strong&gt;: Write the same identical query data into the cache again, &lt;strong&gt;Comment&lt;/strong&gt;: Compare against writing with the empty cache (W1) to see the overhead caused by the initial write or cached write.&lt;/td&gt;
&lt;td&gt;Refetch query from the network without any observers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Update
&lt;/h4&gt;

&lt;p&gt;When it comes to updates, we have tested the same things as with the previous tests, only with updating the data in the cache. However, it is important to mention, that these update tests represent the absolute worst-case scenario in real-life when all the observers are updated. Normally only a small subset would be notified. This particullarly affects Apollo client's records in the &lt;code&gt;ResultCache&lt;/code&gt; which need to be rewritten on every observer update. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Update type&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;th&gt;Example Scenario&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;U1&lt;/td&gt;
&lt;td&gt;Update, fully cached, 1, 25, 125 observers&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Setup&lt;/strong&gt;: Write main query into the cache, create 1, 25, 125 partial query observers, &lt;strong&gt;Test&lt;/strong&gt;: Write same query shape to the main query with an updated response and verify if all affected partial queries have had their responses updated, &lt;strong&gt;Comment&lt;/strong&gt;:  This demonstrates the degradation of performance with frequently updated data.&lt;/td&gt;
&lt;td&gt;Presence and user detail or also, emotions are updated quite often in teams and this example could represent that. (Edge case, eg. XL meetings)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Findings (🥁 Drumroll.. 🥁)
&lt;/h2&gt;

&lt;p&gt;Without further ado, I'd like to present you our findings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0g3yk2aty5gnnuuwln4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0g3yk2aty5gnnuuwln4.png" alt="Results of the benchmark - Relay performs better by an order of magnitude nearly all across the board" width="800" height="888"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Okay, now that you had time to digest that, let's analyze these results together a little. From the results of the benchmark, we have found that generally through different examples and tests the Relay client has been performing 10x faster throughout the board. This raises questions about what are they doing better and what is stopping people from making Relay their first choice. One of the factors in decision-making is most probably ease of use, as Relay has a steeper learning curve compared to Apollo or others. Furthemore, the need to precompile GraphQL artifacts in Relay might be an incovenience for some, but definitelly not for many. &lt;/p&gt;

&lt;h3&gt;
  
  
  Patterns
&lt;/h3&gt;

&lt;p&gt;Looking at the benchmark results, you can notice a few patterns happening again, and again, no matter what type of query you test. I'd like to highlight these using the following image, and explain why are they occuring. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fttpfedp3njg869ce3gvp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fttpfedp3njg869ce3gvp.png" alt="Results of the benchmark with A, B1, B2 pattern markers" width="800" height="888"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A&lt;/strong&gt; - This simply shows us that Relay being best across the board with just a few exemptions is a reoccuring pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;B1&lt;/strong&gt;, &lt;strong&gt;B2&lt;/strong&gt; - This pattern provides a bit more interesting insight, and that is the effects of Apollo client's &lt;code&gt;ResultCache&lt;/code&gt;. At &lt;strong&gt;B1&lt;/strong&gt; we can see that this is a &lt;em&gt;duplicate read, fully cached, identical query&lt;/em&gt; test, which is exactly what &lt;code&gt;ResultCache&lt;/code&gt; is optimised for. To explain better, when the first read from the cache, after the initial write, is performed, Apollo client memoizes the query data, and saves it to the &lt;code&gt;ResultCache&lt;/code&gt;. After this, anytime a query with identical AST is read from the cache, instead of going through the denormalization process from the &lt;code&gt;EntityStore&lt;/code&gt;, it is returned straight from the &lt;code&gt;ResultCache&lt;/code&gt;. Very quickly. The same scenario occurs on &lt;strong&gt;B2&lt;/strong&gt;, where we are testing empty writes. The &lt;code&gt;ResultCache&lt;/code&gt; shines here again, as creation of the observers, it memoizes all the query data that is being watched, and since the writes are empty, and do not update any data, all that the observers need to do is to refetch the same data from the &lt;code&gt;ResultCache&lt;/code&gt;. If you compare this to Apollo client with &lt;code&gt;ResultCache&lt;/code&gt; disabled, you can see that the latency there is even 10x slower at some points.&lt;/p&gt;

&lt;p&gt;Of course, as everywhere, exceptions occur and the results for each query are ever so-slightly different. However, these patterns are repetitve and have been showing up in all of our tests, so I believe they give us a nice representation of the results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;If you reached this point, I'd like to thank you for reading my post. It has been an amazing experience learning all this during the course of my internship, and now trying to teach you something new as well. But before the tears come out, let me leave you with some of my final conclusions and thoughts.&lt;/p&gt;

&lt;p&gt;When it comes to Relay, it is a highly optimized React framework which provides the &lt;strong&gt;best&lt;/strong&gt;, and &lt;strong&gt;most reliable&lt;/strong&gt; latency performance. No doubts about that. Apollo on the other hand, has libraries for the whole stack and for a whole lot of languages, but they are definitely lacking behind on the performance side. However, it is important to mention, that there have been experimental caches for Apollo client previosly, which provided a simillar performance to Relay, so ... perhaps, instead of hating it after reading this post, let's try to come up with a way to optimise it. That's what open-source is about afterall 😉&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>opensource</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The mysteries of GraphQL clients' cache - The Introduction</title>
      <dc:creator>Andrej Szalma</dc:creator>
      <pubDate>Wed, 07 Sep 2022 07:56:50 +0000</pubDate>
      <link>https://dev.to/andycko/the-mysteries-of-graphql-clients-cache-the-introduction-3g13</link>
      <guid>https://dev.to/andycko/the-mysteries-of-graphql-clients-cache-the-introduction-3g13</guid>
      <description>&lt;p&gt;Recently I completed an internship at Microsoft where I had the privilege to work with people who are experts in their fields, more specifically in the field of GraphQL. I had numerous opportunities to learn from them and now I would like to teach you something. Let me take you on a journey through the midst of GraphQL clients &amp;amp; their cache. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a two-part series, where I'll talk about GraphQL client caches and compare the client's cache performance.&lt;/p&gt;

&lt;p&gt;Part 1: &lt;a href="https://dev.to/andycko/the-mysteries-of-graphql-clients-cache-3j3m-temp-slug-7832453?preview=704245c70cba2f6b37ca453f41aaaed0a29c1a423b89f5f5038162d893caa34c6344981ce974c421bafb6f3cf4036cbef634bcc07704523aa2c16364"&gt;The mysteries of GraphQL clients' cache - The Introduction&lt;/a&gt;&lt;br&gt;
Part 2: &lt;a href="https://dev.to/andycko/the-mysteries-of-graphql-clients-cache-the-showdown-4211"&gt;The mysteries of GraphQL clients' cache - The Showdown&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;"There are only two hard things in Computer Science: cache invalidation and naming things."&lt;br&gt;
-- Phil Karton&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As every blog post that even just remotely mentions the subject of caches, I too have felt the need to include this infamous quote. Regardless if we are going to speak about cache invalidation or not, this quote always brightens up the mood a little bit.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick intro to GraphQL clients
&lt;/h2&gt;

&lt;p&gt;The conventional (but not only) way to use GraphQL is as an API that communicates through HTTP POST requests. Because the response of a GraphQL API is a bit more complex than a simple REST response, libraries have been built to make your life easier when communicating with such APIs. Libraries such as Apollo client, Relay, URQL, etc., help you automatically handle things like batching, &lt;strong&gt;caching&lt;/strong&gt;, constructing queries, managing UI state and much more. &lt;/p&gt;

&lt;p&gt;With GraphQL becoming ever-so-popular, the support and development for these have skyrocketed in the past few years. When starting a new project, there is plenty to choose, but the question is, which one is the best for you?&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching - not the &lt;a href="https://www.geocaching.com/play"&gt;geo&lt;/a&gt; one
&lt;/h2&gt;

&lt;p&gt;As "hinted" in the name of this post, our main focus today is going to be caching. Caching in GraphQL, as anywhere else, is used to save data received from some data layer (eg. server) so the next time our application needs that data, it can read it from our cache memory instead of doing another network request to our data layer. This way, we can save precious resources and minimize client load times. The above-explained type of cache can also be called an In-Memory cache.&lt;/p&gt;

&lt;p&gt;However, implementing and maintaining a cache is not as easy as it sounds and every client has their way of doing it. I have researched mainly two popular clients - &lt;strong&gt;Apollo client&lt;/strong&gt; and &lt;strong&gt;Relay&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Before I start speaking about this thought, let me explain what is data normalization, and why is it important to us.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data normalization
&lt;/h3&gt;

&lt;p&gt;Data normalization is the process of restructuring some data to reduce redundancy. You might have heard about this from relational databases, where you have 5 normal forms to get through before you can even start thinking about being happy.. :) Both clients have a normalized cache (which is the de facto standard now) and they need to perform this process to convert the JSON blob they receive from the GraphQL server into a relational structure. The algorithm used for normalization varies between them, however, the principles remain the same. &lt;/p&gt;

&lt;h3&gt;
  
  
  Apollo client
&lt;/h3&gt;

&lt;p&gt;As a GraphQL client - Apollo is the more simplistic and flexible of the two. It provides an easier way of getting started, a bit more comprehensive documentation, and perhaps better community support. However, when it comes to cache implementation, it is, unfortunately, lacking behind. &lt;/p&gt;

&lt;p&gt;However, before we get to compare the performance of the two, let me explain how Apollo's cache works. As mentioned previously, they are using an In-Memory cache and this consists of two main parts - &lt;strong&gt;&lt;code&gt;EntityStore&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;ResultCache&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;EntityStore&lt;/code&gt;&lt;/strong&gt; is the main cache which holds normalized data in a flat lookup table, therefore when data is read from it, it needs to be de-normalized.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ResultCache&lt;/code&gt;&lt;/strong&gt; has been introduced to help with the denormalization problem. Therefore, when the first read of a query is executed against the EntityStore the de-normalized data is memoized in the ResultCache and this then makes all upcoming reads of the identical query very fast. However, this comes with the overhead of having to write into the ResultCache on every first read operation on some query that has not yet been memoized. (The match has to be 1:1 exact here)&lt;/p&gt;

&lt;h4&gt;
  
  
  Normalization algorithm
&lt;/h4&gt;

&lt;p&gt;As previously mentioned, Apollo maintains a normalized In-Memory cache by default, and now I'd like to explain in a few words how their normalization algorithm works. &lt;/p&gt;

&lt;p&gt;Firstly, let's say we have a GraphQL query to get all the users, which looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getAllUsers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;firstname&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;lastname&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a GraphQL response reaches the client, it comes in a JSON format, looking something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;    
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contents of the &lt;code&gt;data&lt;/code&gt; object are the actual response, therefore from now on, we will only work with what is inside of &lt;code&gt;data&lt;/code&gt;. &lt;br&gt;
Now, the first step to normalize this data would be to split our &lt;code&gt;users&lt;/code&gt; Array into single objects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"__typename"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"firstname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Doe"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice, that a &lt;code&gt;__typename&lt;/code&gt; field has been added to our response, even though we have not requested it in our query. This is because Apollo client requests this field automatically, even if you don't explicitly do so. In the next step, you will see why.&lt;/p&gt;

&lt;p&gt;Now that we have extracted all our objects, we can perform another step of normalization, which would be creating a globally unique identifier for every object, so that it can be saved in a key-value type lookup table (Hashmap). By default, Apollo client uses a composite key made of the &lt;code&gt;__typename&lt;/code&gt; + &lt;code&gt;id&lt;/code&gt;. Now you see, why Apollo client requested the &lt;code&gt;__typename&lt;/code&gt; field automatically. However, not all objects in our response always have a unique &lt;code&gt;id&lt;/code&gt; which could be in the composite key, therefore Apollo gives us the possibility to choose which fields we want to use for creating this unique identifier key using the &lt;code&gt;typePolicies&lt;/code&gt; setting in the &lt;code&gt;InMemoryCache&lt;/code&gt; &lt;a href="https://github.com/apollographql/apollo-client/blob/main/src/cache/inmemory/inMemoryCache.ts#L67"&gt;config&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once that we have created the unique identifier for an object, we need to look into it and find any nested objects. Should there be any, they will need to be extracted, and assigned with a unique identifier key, which will then be placed in their original position as a reference. See the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"__typename"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"firstname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"__typename"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Address"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"line1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;lookup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;would&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;look&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;something&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;normalization&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;above&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;object.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"User:1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"__ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Address:1"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Address:1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;    
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;However, it is important to mention, that should these be simply scalar fields, let's say an array of objects, which are not a GraphQL type, then these will &lt;strong&gt;not&lt;/strong&gt; get extracted from the object.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And now, you know the overview of how Apollo performs normalization, not that hard, right? :) &lt;/p&gt;

&lt;p&gt;Next, let's jump over to our friend Relay.&lt;/p&gt;

&lt;h3&gt;
  
  
  Relay
&lt;/h3&gt;

&lt;p&gt;As previously mentioned, Apollo is the more simplistic and versatile one, whereas Relay is the more optimized and narrow-scoped one. What I mean by this, is that whereas Apollo Client has frameworks for multiple languages, Relay was made specifically for React and is highly optimised. &lt;/p&gt;

&lt;p&gt;However, when it comes to cache, they are not that different from their core implementations. If you forget about Apollo's &lt;code&gt;ResultCache&lt;/code&gt; for a minute, then they are indeed very similar. However, since Relay is based on granular &lt;code&gt;fragments&lt;/code&gt; instead of whole &lt;code&gt;queries&lt;/code&gt; as Apollo is, it does not need anything such as a &lt;code&gt;ResultCache&lt;/code&gt;. This is why Apollo client had to add another layer of complexity to their &lt;code&gt;InMemoryCache&lt;/code&gt; to optimize for second reads. (Read re-renders of whole queries are very frequent in apollo, and therefore the memoization process performed by the &lt;code&gt;ResultCache&lt;/code&gt; helps to speed this up)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Store&lt;/code&gt;&lt;/strong&gt; is the one source of truth for an instance of &lt;code&gt;RelayRuntime&lt;/code&gt; which holds a collection of entities presented by the &lt;code&gt;RecordSource&lt;/code&gt; type. These are (as before) a collection of normalized records belonging to a single query/mutation/etc. The query goes through a normalization process and its entities are extracted and saved into &lt;code&gt;Records&lt;/code&gt; which are then all collected in one &lt;code&gt;RecordSource&lt;/code&gt;. The &lt;code&gt;RecordSource&lt;/code&gt; object is then merged into the &lt;code&gt;Store&lt;/code&gt; and subscribers (observers) to a fragment that was affected are notified.&lt;/p&gt;

&lt;h4&gt;
  
  
  Normalization algorithm
&lt;/h4&gt;

&lt;p&gt;Since I have provided an in-depth explanation of the normalization algorithm in Apollo, I am not going to do the same here, as they are similar in their nature. However, one thing, that I believe is worth mentioning, is how they choose their unique identifiers for normalized records.&lt;/p&gt;

&lt;p&gt;As you already know, Apollo client uses the composite key of &lt;code&gt;__typename&lt;/code&gt; + &lt;code&gt;id&lt;/code&gt; fields and only extracts nested objects if they have GraphQL types (not scalars). However, Relay, on the other hand, takes a different approach, where &lt;strong&gt;every&lt;/strong&gt; nested object, is extracted into a &lt;code&gt;Record&lt;/code&gt; and is assigned a &lt;code&gt;DataId&lt;/code&gt;. This is a globally unique identifier in the scope of the cache and it can be made from the &lt;code&gt;id&lt;/code&gt; field of the objects, or if they don't have such fields, it can be based on the path to the record from the nearest object with an id (such path-based ids are called client ids). Thanks to this logic, even nested scalar objects are always extracted and normalized.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next up
&lt;/h2&gt;

&lt;p&gt;Now that I have explained the basics of how GraphQL client cache works, I would like to show you a showdown between the clients, comparing their caches head-to-head. Use this link to read more about that in Part 2 - &lt;a href="https://dev.to/andycko/the-mysteries-of-graphql-clients-cache-the-showdown-3mpe-temp-slug-8775347?preview=cb1919c6d006563cba0c6feef0275c95fd198dde21ae1d8220b9f9f3472f896386b2e9c06fc7cde54780670f571a3d0b27a2f49922f554a518bdc522"&gt;The mysteries of GraphQL clients' cache - The Showdown&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>opensource</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
