<?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>Forem: marijaselakovic</title>
    <description>The latest articles on Forem by marijaselakovic (@marijaselakovic).</description>
    <link>https://forem.com/marijaselakovic</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%2F754431%2Fe43b9819-78f1-48eb-880d-d4741d30a3f6.JPG</url>
      <title>Forem: marijaselakovic</title>
      <link>https://forem.com/marijaselakovic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/marijaselakovic"/>
    <language>en</language>
    <item>
      <title>From data storage to data analysis: Tutorial on CrateDB and pandas 2</title>
      <dc:creator>marijaselakovic</dc:creator>
      <pubDate>Wed, 03 May 2023 06:55:15 +0000</pubDate>
      <link>https://forem.com/crate/from-data-storage-to-data-analysis-tutorial-on-cratedb-and-pandas-2-48i0</link>
      <guid>https://forem.com/crate/from-data-storage-to-data-analysis-tutorial-on-cratedb-and-pandas-2-48i0</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Pandas is an open-source data manipulation and analysis library for Python. It is widely used for handling and analyzing data in a variety of fields, including finance, research, etc.&lt;/p&gt;

&lt;p&gt;One of the key benefits of pandas is its ability to handle and manipulate large datasets, making it a valuable tool for data scientists and analysts. The library provides easy-to-use data structures and functions for data cleaning, transformation, and analysis, making it an essential part of the data analysis workflow.&lt;/p&gt;

&lt;p&gt;Using CrateDB and pandas together can be a powerful combination for handling large volumes of data and performing complex data analysis tasks. In this tutorial, we will showcase using the real-world dataset how to use CrateDB and pandas together for effective data analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;To follow along with this tutorial, you will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A running instance of CrateDB 5.2.&lt;/li&gt;
&lt;li&gt;Python 3.x with the &lt;a href="https://pandas.pydata.org/pandas-docs/version/2.0/whatsnew/v2.0.0.html" rel="noopener noreferrer"&gt;pandas 2&lt;/a&gt; and &lt;a href="https://github.com/crate/crate-python" rel="noopener noreferrer"&gt;crate 0.31&lt;/a&gt; packages installed.&lt;/li&gt;
&lt;li&gt;A real-world dataset in CSV format. In this tutorial, we will be using the shop customer data available on &lt;a href="https://www.kaggle.com/datasets/datascientistanna/customers-dataset" rel="noopener noreferrer"&gt;Kaggle&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting up CrateDB
&lt;/h2&gt;

&lt;p&gt;Before we can start using CrateDB, we need to set it up. You can either download and install CrateDB locally via &lt;a href="https://crate.io/docs/crate/tutorials/en/latest/basic/index.html#docker" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; or &lt;a href="https://crate.io/docs/crate/tutorials/en/latest/basic/index.html#try-cratedb-without-installing" rel="noopener noreferrer"&gt;tarball&lt;/a&gt; or use a &lt;a href="https://crate.io/download?hsCtaTracking=caa20047-f2b6-4e8c-b7f9-63fbf818b17f%7Cf1ad6eaa-39ac-49cd-8115-ed7d5dac4d63" rel="noopener noreferrer"&gt;CrateDB Cloud&lt;/a&gt; instance with an option of the free cluster.&lt;/p&gt;

&lt;p&gt;Once you have a running instance of CrateDB, create a new table to store the customer data dataset. Here is an SQL command to create a table:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 sql
CREATE TABLE IF NOT EXISTS "doc"."customer_data" (
   "customerid" INTEGER,
   "gender" TEXT,
   "age" INTEGER,
   "annualincome" INTEGER,
   "spendingscore" INTEGER,
   "profession" TEXT,
   "workexperience" INTEGER,
   "familysize" INTEGER
)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After creating the table, you can import the customer data dataset into CrateDB using the &lt;code&gt;COPY FROM&lt;/code&gt;command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt; &lt;span class="nv"&gt;"doc"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"customer_data"&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="s1"&gt;'file:///path/to/Customers.csv'&lt;/span&gt; 
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'csv'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;delimiter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once you have CrateDB running, you can start exploring data with pandas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Querying data with CrateDB and pandas
&lt;/h2&gt;

&lt;p&gt;The first step is to import the &lt;code&gt;pandas&lt;/code&gt; library and specify the query you want to execute on CrateDB. In our example, we want to fetch all customer data.&lt;/p&gt;

&lt;p&gt;To read data from CrateDB and work with it in a pandas DataFrame use &lt;code&gt;read_sql&lt;/code&gt; method as illustrated below.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM customer_data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;crate://localhost:4200&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In the above code, we establish a connection to a local CrateDB instance running on localhost on port 4200, execute a SQL query, and return the results as a pandas DataFrame. You can further modify the query to retrieve only the columns you need or to filter the data based on some condition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analyze the data
&lt;/h2&gt;

&lt;p&gt;Now that data are loaded into the pandas DataFrame, we can perform various analyses and manipulations on it. For instance, we can group the data by a certain column and calculate the average value of another column:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;avg_income&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;profession&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;annualincome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In this example, we group the data in the DataFrame by the &lt;code&gt;profession&lt;/code&gt; column and calculate the average annual income for each profession. You can plot the data about average incomes using &lt;code&gt;df.plot()&lt;/code&gt; method, specifying the type of plot (a bar chart), and the columns to use for the x and y axes:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;

&lt;span class="n"&gt;income_by_profession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bar&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;legend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rot&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We also use &lt;code&gt;plt.show()&lt;/code&gt; from &lt;code&gt;matplotlib&lt;/code&gt; to display the plot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fly5008r8tol329qtihra.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fly5008r8tol329qtihra.png" alt="Plot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;That's it! You should now have a good idea of how to use CrateDB and pandas together to analyze large datasets stored in CrateDB. This allows you to take advantage of the powerful data manipulation capabilities of pandas to analyze and visualize your data.&lt;br&gt;
 To learn more about updates, features, and other questions you might have, join our &lt;a href="https://community.crate.io/" rel="noopener noreferrer"&gt;CrateDB&lt;/a&gt; community.&lt;/p&gt;

</description>
      <category>python</category>
      <category>database</category>
      <category>dataframe</category>
      <category>pandas</category>
    </item>
    <item>
      <title>Guide to bitwise operators in CrateDB</title>
      <dc:creator>marijaselakovic</dc:creator>
      <pubDate>Thu, 02 Mar 2023 09:30:44 +0000</pubDate>
      <link>https://forem.com/crate/guide-to-bitwise-operators-in-cratedb-41ag</link>
      <guid>https://forem.com/crate/guide-to-bitwise-operators-in-cratedb-41ag</guid>
      <description>&lt;p&gt;Bitwise operators are useful because they allow you to perform efficient and concise operations on individual bits within integer values, which can be very useful in a variety of SQL queries.&lt;/p&gt;

&lt;p&gt;CrateDB continues to provide many valuable features. In the 5.2 version, we added support for bitwise operators. Now, you may wonder when this feature is handy. There are at least a couple of scenarios:&lt;/p&gt;

&lt;p&gt;If you want to store multiple pieces of information in a single column. For example, you can use a bitwise OR operator to combine multiple flags into a single value.&lt;/p&gt;

&lt;p&gt;Simplifying conditional statements. Bitwise operators can be used to check the state of individual bits within a value. For example, you can use a bitwise AND operator to check if a particular bit is set or not.&lt;/p&gt;

&lt;p&gt;Easier data manipulation. Bitwise operators can be used to set or manipulate specific bits within a value. For example, you can use a bitwise OR operator to set a particular bit to 1, or a bitwise XOR to swap bit value (1 to 0 or 0 to 1).&lt;/p&gt;

&lt;p&gt;CrateDB supports three bitwise operators:&lt;/p&gt;

&lt;p&gt;BITWISE AND (&amp;amp;)&lt;/p&gt;

&lt;p&gt;BITWISE OR (|)&lt;/p&gt;

&lt;p&gt;BITWISE XOR (#)&lt;/p&gt;

&lt;p&gt;Now, let’s take a look at each operator and some interesting examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bitwise AND
&lt;/h2&gt;

&lt;p&gt;The Bitwise AND operator compares each bit of two values and returns a new value with the bits that are set in both of the original values.&lt;/p&gt;

&lt;p&gt;The syntax for this operator is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;value1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;value2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;value1&lt;/code&gt; and &lt;code&gt;value2&lt;/code&gt; are the two values that you want to compare using the bitwise AND operator. These values can be any valid expressions or constants in SQL, such as columns, variables, or literals.&lt;/p&gt;

&lt;p&gt;For example, let’s imagine a table of &lt;code&gt;employees&lt;/code&gt; with a column &lt;code&gt;status&lt;/code&gt; that stores a &lt;a href="https://crate.io/docs/crate/reference/en/5.1/general/ddl/data-types.html#bit-strings" rel="noopener noreferrer"&gt;bitmask&lt;/a&gt; value representing the status of each employee. The status value is stored as a byte and each bit represents a different aspect of the employee status. To save storage space, we recommend a byte data structure for storing up to 7 states simultaneously. The first bit always represents a sign and we don't use negative values to encode states. Similarly, use short data structure for storing up to 15 states, integer for up to 31 states, long for up to 63 states, and for more than 63 states that use Bit String type. To learn more about data types supported in CrateDB, check out our &lt;a href="https://crate.io/docs/crate/reference/en/5.1/general/ddl/data-types.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first bit in status value says whether the employee is working full-time, the second bit says if the employee is remotely, and so on. For instance, if the employee is working full-time, the status value will be 1 (&lt;code&gt;B'01'&lt;/code&gt;) if remote the status value will be 2 (&lt;code&gt;B'10'&lt;/code&gt;) and if both, the status value will be 3 (&lt;code&gt;B'11'&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Now, let’s create a table and populate it with sample data&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;BYTE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;comment&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; 
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Ana'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Ana is working full-time from office'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Mary'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Mary is working full-time remotely'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Sara'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Sara is working part-time remotly'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To select all employees who are working full-time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;comment&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="n"&gt;Ana&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Mary&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query will select all rows from the employees table where the first bit (full-time status) is set to 1.&lt;/p&gt;

&lt;p&gt;To find all employees who are working full-time and remotely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;comment&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="n"&gt;Mary&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also use the bitwise AND operator in combination with other logical operators, such as OR and NOT, to create more complex queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bitwise OR
&lt;/h2&gt;

&lt;p&gt;The bitwise OR operator is used to compare two binary values and return a new binary value where the resulting bit is set to 1 if either of the input bits is 1. It is represented by the symbol &lt;code&gt;|&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To understand how the bitwise OR operator works, let's consider two binary values: 1011 and 1100. The bitwise OR operation would compare each bit position of the two values and return a new binary value based on the following rules:&lt;/p&gt;

&lt;p&gt;If either of the input bits is 1, the resulting bit is set to 1.&lt;/p&gt;

&lt;p&gt;If both of the input bits are 0, the resulting bit is set to 0.&lt;/p&gt;

&lt;p&gt;Applying these rules to the example above, we get the following result: &lt;code&gt;1011 | 1100 = 1111&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The resulting binary value is 1111, which is equivalent to 15 in decimal.&lt;/p&gt;

&lt;p&gt;Considering the example with &lt;code&gt;employees&lt;/code&gt; table, let’s say we would like to select all employees that are working full-time, remotely or both. Here's how you can use the bitwise OR operator in the query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;comment&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following query illustrates how to change a flag specifying whether an employee is working full-time without changing the existing flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="cm"&gt;/* FULL-TIME */&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Sara'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s important to note that the bitwise OR operator only works with binary values. If you want to perform a logical OR operation with non-binary values, you can use the OR operator in SQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bitwise XOR
&lt;/h2&gt;

&lt;p&gt;The bitwise XOR operator, represented by the symbol &lt;code&gt;#&lt;/code&gt;, compares two binary values and returns a new value based on the following rules:&lt;/p&gt;

&lt;p&gt;If both values are 0, the result is 0.&lt;br&gt;
If both values are 1, the result is 0.&lt;br&gt;
If one value is 1 and the other is 0, the result is 1.&lt;br&gt;
Using these rules in the example from above, the result of the XOR operator is: &lt;code&gt;1011 # 1100 = 0111&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;One interesting example of using the bitwise XOR operator is changing the status of an employee from “working remotely” to “working in the office“ and vice versa.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Ana'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the status of the employee with the name Ana will be toggled from “working remotely“ to “working in the office“ or vice versa, depending on the current value of the status field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;In summary, the bitwise operators in CrateDB allow you to perform bitmasking operations on values in your database and use the resulting values to filter or modify rows in your tables. By combining this operator with other logical operators, you can create powerful queries that can manipulate and extract specific data from your database. If you have any further questions or would like to learn more about CrateDB, check out our &lt;a href="https://crate.io/docs" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; and join the &lt;a href="https://community.crate.io/" rel="noopener noreferrer"&gt;CrateDB community&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>watercooler</category>
      <category>employment</category>
      <category>career</category>
    </item>
    <item>
      <title>Find the latest reported values with ease. Introducing max_by and min_by aggregations in CrateDB 5.2</title>
      <dc:creator>marijaselakovic</dc:creator>
      <pubDate>Thu, 23 Feb 2023 08:42:04 +0000</pubDate>
      <link>https://forem.com/crate/find-the-latest-reported-values-with-ease-introducing-maxby-and-minby-aggregations-in-cratedb-52-3ig8</link>
      <guid>https://forem.com/crate/find-the-latest-reported-values-with-ease-introducing-maxby-and-minby-aggregations-in-cratedb-52-3ig8</guid>
      <description>&lt;p&gt;&lt;strong&gt;CrateDB 5.2 adds two new aggregation functions: max_by and min_by&lt;/strong&gt;&lt;br&gt;
These aggregation functions allow users to quickly and easily search the value of one column based on the minimum or maximum value of another column, making them useful for analyzing trends, identifying outliers, or simply understanding the range of values within a dataset. An example use case is getting the latest measurement by using the time column and &lt;code&gt;max_by(measurement, time)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MIN_BY&lt;/code&gt; and &lt;code&gt;MAX_BY&lt;/code&gt; functions allow you to find the minimum or maximum value in a given column based on the values in another column. For example, if you have a table with two columns, product, category and price, you can use the &lt;code&gt;min_by(product, price)&lt;/code&gt; to find the row with the product with the lowest price in each category:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;min_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;cheapest_product&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;product_list&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;max_by(returned_value, maximized_value)&lt;/code&gt; and &lt;code&gt;min_by(returned_value, minimized_value)&lt;/code&gt; return the value of the first column for which the value of the second column is maximized or minimized. If multiple rows maximize or minimize the result of the second column, the output will be non-deterministic and CrateDB can return any value from the list of resulting rows.&lt;/p&gt;

&lt;p&gt;Both &lt;code&gt;max_by&lt;/code&gt; and &lt;code&gt;min_by&lt;/code&gt; can be used for numerical and non-numerical data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Load the dataset
&lt;/h2&gt;

&lt;p&gt;Let’s start with examples using the dataset about power consumption. First, create a table with the schema below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;power_consumption&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="nv"&gt;"ts"&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nv"&gt;"Global_active_power"&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nv"&gt;"Global_reactive_power"&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nv"&gt;"Voltage"&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nv"&gt;"Global_intensity"&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nv"&gt;"Sub_metering_1"&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nv"&gt;"Sub_metering_2"&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nv"&gt;"Sub_metering_3"&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nv"&gt;"meter_id"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nv"&gt;"location"&lt;/span&gt; &lt;span class="n"&gt;GEO_POINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nv"&gt;"city"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To import data, use the following &lt;code&gt;COPY FROM&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;power_consumption&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="s1"&gt;'https://srv.demo.crate.io/datasets/power_consumption.json'&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;SUMMARY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dataset illustrates the consumption data for a few years and shows the differences between several measured utilities. For instance, the column &lt;code&gt;"Sub_metering_1"&lt;/code&gt; shows how much energy is consumed in the kitchen. Similarly, columns &lt;code&gt;"Sub_metering_2"&lt;/code&gt; and &lt;code&gt;"Sub_metering_3"&lt;/code&gt; show the consumed energy in laundry and climate control systems. The full description of the dataset can be found here.&lt;/p&gt;

&lt;p&gt;Example queries&lt;br&gt;
Given the data set, let’s find the ids of house meters that had the highest consumption at one point in time for the kitchen and laundry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;max_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meter_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"Sub_metering_1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;max_kitchen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
       &lt;span class="n"&gt;max_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meter_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"Sub_metering_2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;max_laundry&lt;/span&gt;  
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;power_consumption&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result of this query should contain the following meter ids:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------+-------------+
| max_kitchen | max_laundry |
+-------------+-------------+
| 84007B127R  | 840070504U  |
+-------------+-------------+
SELECT 1 row in set (7.423 sec)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another example would be to find the meter id for the house with the lowest unused power:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;min_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meter_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"Global_reactive_power"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;min_unused&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;power_consumption&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The return value will tell us for which house meter we had the lowest value of unused power:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+------------+
| min_unused |
+------------+
| 84007B008L |
+------------+
SELECT 1 row in set (0.197 sec)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also combine these functions with &lt;code&gt;WHERE&lt;/code&gt; or &lt;code&gt;GROUP BY&lt;/code&gt; clauses in CrateDB. For example, let's find for each meter id, the consumption of other, unmapped appliances when the unused power was the lowest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;meter_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;min_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"Global_active_power"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;"Global_reactive_power"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_consumption&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;power_consumption&lt;/span&gt; 
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;meter_id&lt;/span&gt; 
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query result will list the consumption for each meter id:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+------------+-------------------+
| meter_id   | total_consumption |
+------------+-------------------+
| 840073190N |             0.202 |
| 840071457E |             0.258 |
| 840072897V |             0.14  |
| 840072655G |             0.218 |
| 840072219H |             0.274 |
| 840071893D |             1.342 |
| 840075260N |             0.246 |
| 840076398A |             0.226 |
| 840072328B |             0.212 |
| 840071760J |             0.222 |
+------------+-------------------+
SELECT 10 rows in set (0.067 sec)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance and alternatives
&lt;/h2&gt;

&lt;p&gt;As you have seen above &lt;code&gt;min_by&lt;/code&gt; and &lt;code&gt;max_by&lt;/code&gt; provide a very concise and convenient way to easily find the value of one column based on the minimum or maximum value of another column. Not only is it a convenient feature, but it also provides significant performance gains to alternative queries one had to write in earlier versions of CrateDB.&lt;/p&gt;

&lt;p&gt;Let us look at another example and see how much easier and faster it is to get the right results in CrateDB 5.2. We start with our often-used dataset containing IoT device data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readings&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; 
       &lt;span class="nv"&gt;"time"&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
       &lt;span class="n"&gt;device_id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
       &lt;span class="n"&gt;battery_level&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
       &lt;span class="n"&gt;battery_status&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CLUSTERED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="n"&gt;SHARDS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want to find the latest reported &lt;code&gt;battery_level&lt;/code&gt; and &lt;code&gt;battery_status&lt;/code&gt; for each device in our dataset holding 30 Million total records. In CrateDB 5.1 and earlier versions, one could fallback to a 2-step approach and use a JOIN like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;battery_level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;battery_temperature&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readings&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
    &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
            &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;device_id&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readings&lt;/span&gt;
        &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;device_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;max_r&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;max_r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;max_r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not only makes the nested structure query adjustments more difficult and one needs to remember the pattern, but also the performance - due to the expensive &lt;code&gt;JOIN&lt;/code&gt; - is not really that great with a runtime of roughly 9 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Runtime (in ms):
    mean:    8982.507 ± 57.494
    min/max: 8578.380 → 9843.830
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is an improvement of 85% in query speed (8.9s → 1.3s), also using a simpler syntax.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;Overall, the &lt;code&gt;max_by&lt;/code&gt; and &lt;code&gt;min_by&lt;/code&gt; functions in CrateDB provide an easy and efficient way to find the maximum or minimum value of a given column in a table based on the values in a different column. These functions can be used in a variety of scenarios to quickly and easily find the highest or lowest values in a set of data.&lt;/p&gt;

&lt;p&gt;If you like this blog post and want to learn more about CrateDB, check out our documentation and join the CrateDB community!&lt;/p&gt;

</description>
      <category>cratedb</category>
      <category>sql</category>
      <category>performance</category>
      <category>aggregations</category>
    </item>
    <item>
      <title>Guide to sharding and partitioning best practices in CrateDB</title>
      <dc:creator>marijaselakovic</dc:creator>
      <pubDate>Fri, 17 Feb 2023 14:36:47 +0000</pubDate>
      <link>https://forem.com/crate/guide-to-sharding-and-partitioning-best-practices-in-cratedb-5207</link>
      <guid>https://forem.com/crate/guide-to-sharding-and-partitioning-best-practices-in-cratedb-5207</guid>
      <description>&lt;p&gt;Sharding and partitioning are very important concepts when it comes to system scaling. When defining your strategy, you should account upfront for any future growth, given the significant burden of moving data and restructuring the tables. In this article, we will give you a thorough understanding of how sharding and partitioning work in CrateDB. We will start by covering the basic definitions, discussing the principles behind shard distribution and replication in CrateDB, and how to avoid common bottlenecks. &lt;/p&gt;

&lt;h2&gt;
  
  
  Partition, shard, and Lucene index
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A table&lt;/strong&gt; in CrateDB is a collection of data. It consists of a specified number of columns and any number of rows. Every table must have a schema describing the table structure. Very often, the table is divided into independent, smaller parts based on a particular column. This “smaller part“ is called &lt;strong&gt;partition&lt;/strong&gt;. The table becomes a partitioned table if, during the table creation, a partition column is defined. In this case, when a new record is inserted, a new partition is created if the partition for the same column value doesn’t exist yet. Partitioning is done for easier maintenance of large tables and improving the performance of particular SQL operations. However, a bad selection of a partition column can lead to too many partitions, which can slow the system's performance.&lt;/p&gt;

&lt;p&gt;Now let’s take a look into the concept of shard. In CrateDB, &lt;strong&gt;the shard&lt;/strong&gt; is a division of a table or a table partition based on a configurable number and stored on a node in the cluster. When a node is added or removed from the cluster or when the data distribution becomes unbalanced, CrateDB automatically redistributes the shards across the nodes in the cluster to ensure an even data distribution. If the number of shards is not defined during table creation, CrateDB will apply a sensible default value depending on &lt;a href="https://crate.io/docs/crate/reference/en/3.3/sql/statements/create-table.html#ref-clustered-clause" rel="noopener noreferrer"&gt;the number of nodes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, each shard in CrateDB represents a Lucene index. A Lucene index is a collection of Lucene segments where each segment represents an inverted index, doc value, or k-d trees. Take a look at &lt;a href="https://crate.io/blog/guide-to-write-operations-in-cratedb" rel="noopener noreferrer"&gt;our previous article&lt;/a&gt; to get a better overview of the Lucene index and Lucene segments. The diagram below illustrates the best connection between partitions, shards, and the Lucene index.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxtqdabscguhpg6tjp9y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxtqdabscguhpg6tjp9y.png" alt="connections" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the data is distributed
&lt;/h2&gt;

&lt;p&gt;CrateDB uses &lt;em&gt;row-based sharding&lt;/em&gt; to split data across multiple shards. This means that data is split based on the values in specific columns of the table, and distributed across multiple nodes. The column that the data should be sharded on is called the &lt;em&gt;routing column&lt;/em&gt;. When you insert, update, or delete a row, CrateDB will use the routing column value to determine which shard to access. The number of shards and the routing column can be specified in the &lt;a href="https://crate.io/docs/crate/reference/en/5.1/sql/statements/create-table.html#clustered" rel="noopener noreferrer"&gt;CLUSTERED&lt;/a&gt; clause when creating a table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CLUSTERED&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;SHARDS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example, the product table is sharded into four shards. If the primary key is set, as illustrated, the routing column can be ignored, as CrateDB uses the primary key for routing by default. However, if the primary key or the routing column is not set, the &lt;a href="https://crate.io/docs/crate/reference/en/5.1/general/ddl/system-columns.html#sql-administration-system-column-id" rel="noopener noreferrer"&gt;internal document ID&lt;/a&gt; is used.&lt;/p&gt;

&lt;p&gt;To distribute data across the cluster, CrateDB uses a hash-based approach based on the simple formula:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;shard number = hash(routing column) % total primary shards&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As a result, all documents with the same value of &lt;code&gt;CLUSTERED BY&lt;/code&gt; column will be stored in the same shard. With a hash function, CrateDB will try to distribute the data roughly equally, even if the original data values are not evenly distributed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shard replication
&lt;/h2&gt;

&lt;p&gt;Shard replication in CrateDB is a feature that allows you to replicate data across multiple nodes in a cluster. This can be useful for increasing data availability, improving performance, and reducing the risk of data loss. You can &lt;a href="https://crate.io/docs/crate/reference/en/5.1/sql/statements/create-table.html#sql-create-table-number-of-replicas" rel="noopener noreferrer"&gt;configure&lt;/a&gt; the number of replicas for each shard with the &lt;code&gt;number_of_replicas&lt;/code&gt; table setting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number_of_replicas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When there are multiple copies of the same shard, CrateDB will mark one copy as the &lt;em&gt;primary shard&lt;/em&gt; and treat the rest as &lt;em&gt;replica shards&lt;/em&gt;. When data is written to a shard, it is first written to the primary shard. The primary shard then replicates the data to one or more replica shards on other nodes in the cluster. This process is done in real-time, ensuring that data is always up-to-date across all replicas.&lt;/p&gt;

&lt;p&gt;In the event of a node failure, CrateDB will automatically promote one of the replica shards to become the new primary shard. Having more shard replicas means a lower chance of permanent data loss and more throughput as queries will utilize the extra replica shards so that the primary shard is not congested with many requests. In terms of the cost you pay, you will have higher disk space utilization and inter-node network traffic which leads to increased latency of inserts and updates. Additionally, CrateDB supports automatic failover, where the system &lt;em&gt;automatically&lt;/em&gt; detects a failed node and promotes a replica shard to take its place as the primary shard.&lt;/p&gt;

&lt;p&gt;It is also possible to specify the number of replicas for a specific table by using &lt;code&gt;ALTER TABLE&lt;/code&gt; command. For instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number_of_replicas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that changing the number of replicas after a table is created will cause the cluster to redistribute the shards and may take some time to complete (if you want only new partitions to be affected, use the &lt;a href="https://crate.io/docs/crate/reference/en/5.1/sql/statements/alter-table.html" rel="noopener noreferrer"&gt;ONLY keyword&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic creation of new partitions
&lt;/h2&gt;

&lt;p&gt;Tables in CrateDB are designed to be dynamic and expandable, allowing for the addition of new rows and columns as needed. This means that you can create a table with an unlimited number of rows. Every time when new data is inserted, CrateDB dynamically creates a new table partition based on the partition column as illustrated by the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
  &lt;span class="nv"&gt;"name"&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
  &lt;span class="nv"&gt;"ts"&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"month"&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;date_trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'month'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
  &lt;span class="nv"&gt;"value"&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt; &lt;span class="nb"&gt;PRECISION&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CLUSTERED&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;SHARDS&lt;/span&gt;&lt;span class="err"&gt;​&lt;/span&gt;
  &lt;span class="n"&gt;PARTITIONED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmiixo7mlf5ch2ri4fn29.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmiixo7mlf5ch2ri4fn29.png" alt="example sales" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For every unique value in the &lt;code&gt;month&lt;/code&gt; column, a new partition will be created. In our example, the table can have up to twelve partitions, one for each month in the year. If more columns are used for partitioning a new partition will be created for every unique combination of values. The partition column can also be a generated column: columns whose values are calculated based on other columns. For instance, if you have a column containing a timestamp value, you can partition the data by a column that extracts the day value from the timestamp.&lt;/p&gt;

&lt;p&gt;The automatic creation of new partitions allows for horizontal scaling and enables the database to handle large amounts of data by distributing it across multiple partitions. Each partition’s shard is stored on a separate node in a CrateDB cluster, which helps to improve query performance and reduce the load on individual nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to avoid too many shards
&lt;/h2&gt;

&lt;p&gt;If the routing column is badly chosen you can end up with too many shards in the cluster, affecting the overall stability and performance negatively. To find out how many shards your tables need, you need to consider the type of data you are processing, required queries, and hardware configuration. However, if you end up with too many shards you will have to manually reduce the number of shards by merging and moving them to the same node which is a time-consuming and tedious operation. To get an idea of how many shards your cluster needs, check out &lt;a href="https://community.crate.io/t/sharding-and-partitioning-guide-for-time-series-data/737" rel="noopener noreferrer"&gt;our recent tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The general rule for avoiding performance bottlenecks is to have as least as many shards for a table as there are CPUs in the cluster. This increases the chances that a query can be parallelized and distributed maximally. However, if most nodes have more shards per table than they have CPUs, you could actually see performance degradation. Each shard comes with a cost in terms of open files, RAM, and CPU cycles. Having too many small shards can negatively impact performance and scalability for a few reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Increased overhead&lt;/em&gt; in terms of managing and maintaining your cluster.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Reduced performance&lt;/em&gt; because it takes longer for CrateDB to gather the results, especially true for queries that need to join data from multiple shards&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Limited scalability&lt;/em&gt; because it becomes more difficult to scale your cluster if you have too many shards. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Increased complexity&lt;/em&gt; as too many shards can make it more difficult to understand and troubleshoot your data distribution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, for performance reasons, consider one thousand shards per node the highest recommended configuration. Also, a single table should not have more than one thousand partitions. If you exceed these numbers you will experience a failing cluster check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Sharding and partitioning in CrateDB are two key concepts that help to improve the scalability and performance of your database. In this article, we explored the basic principles of sharding and partitioning in CrateDB, and how they can be used to improve performance and scalability in your database. If you have any further questions or would like to learn more about CrateDB, check out our &lt;a href="https://crate.io/docs" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; and join &lt;a href="https://community.crate.io/" rel="noopener noreferrer"&gt;the CrateDB community&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>support</category>
      <category>discuss</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>Monitoring an on-premises CrateDB cluster with Prometheus and Grafana</title>
      <dc:creator>marijaselakovic</dc:creator>
      <pubDate>Thu, 19 Jan 2023 12:28:56 +0000</pubDate>
      <link>https://forem.com/crate/monitoring-an-on-premises-cratedb-cluster-with-prometheus-and-grafana-31g1</link>
      <guid>https://forem.com/crate/monitoring-an-on-premises-cratedb-cluster-with-prometheus-and-grafana-31g1</guid>
      <description>&lt;p&gt;If you are running CrateDB in a production environment, you have probably wondered what would be the best way to monitor the servers to identify issues before they become problematic and to collect statistics that you can use for capacity planning.&lt;/p&gt;

&lt;p&gt;We recommend pairing two well-known OSS solutions, &lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt; which is a system that collects and stores performance metrics, and &lt;a href="https://grafana.com/" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt; which is a system to create dashboards.&lt;/p&gt;

&lt;p&gt;For a CrateDB environment, we are interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CrateDB-specific metrics, such as the number of shards or number of failed queries&lt;/li&gt;
&lt;li&gt;and OS metrics, such as available disk space, memory usage, or CPU usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For what concerns CrateDB-specific metrics we recommend making these available to Prometheus by using the &lt;a href="https://crate.io/docs/crate/reference/en/5.1/admin/monitoring.html#exposing-jmx-via-http" rel="noopener noreferrer"&gt;Crate JMX HTTP Exporter&lt;/a&gt; and &lt;a href="https://github.com/justwatchcom/sql_exporter" rel="noopener noreferrer"&gt;Prometheus SQL Exporter&lt;/a&gt;. For what concerns OS metrics, in Linux environments, we recommend using the &lt;a href="https://prometheus.io/docs/guides/node-exporter/" rel="noopener noreferrer"&gt;Prometheus Node Exporter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Things are a bit different of course if you are using containers, or if you are using the fully-managed cloud-hosted &lt;a href="https://crate.io/products/cratedb-cloud" rel="noopener noreferrer"&gt;CrateDB Cloud&lt;/a&gt;, but let’s see how all this works on an on-premises installation by setting all this up together.&lt;/p&gt;

&lt;h2&gt;
  
  
  First we need a CrateDB cluster
&lt;/h2&gt;

&lt;p&gt;First things first, we will need a CrateDB cluster, you may have one already and that is great, but if you do not we can get one up quickly.&lt;/p&gt;

&lt;p&gt;You can review the install documentation at &lt;a href="https://crate.io/docs/crate/tutorials/en/latest/self-hosted/index.html" rel="noopener noreferrer"&gt;https://crate.io/docs/crate/tutorials/en/latest/self-hosted/index.html&lt;/a&gt; and &lt;a href="https://crate.io/docs/crate/howtos/en/latest/clustering/multi-node-setup.html" rel="noopener noreferrer"&gt;https://crate.io/docs/crate/howtos/en/latest/clustering/multi-node-setup.html&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In my case, I am using Ubuntu and I did it like this, first I ssh to the first machine and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nano /etc/default/crate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a configuration file that will be used by CrateDB, we only need one line to configure memory settings here (this is a required step otherwise we will fail bootstrap checks):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CRATE_HEAP_SIZE=4G
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to create another configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; /etc/crate
nano /etc/crate/crate.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my case I used the following values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;network.host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;_local_,_site_&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells CrateDB to respond to requests both from localhost and the local network.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;discovery.seed_hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ubuntuvm1:4300&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ubuntuvm2:4300&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lists all the machines that make up our cluster, here I only have 2, but for production use, we recommend having at least 3 nodes so that a quorum can be established in case of network partition to avoid split-brain scenarios.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;cluster.initial_master_nodes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ubuntuvm1&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ubuntuvm2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lists the nodes that are eligible to act as master nodes during bootstrap.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;auth.host_based.enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;host_based&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;crate&lt;/span&gt;
        &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;_local_&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trust&lt;/span&gt;
      &lt;span class="na"&gt;99&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This indicates that the &lt;code&gt;crate&lt;/code&gt; super user will work for local connections but connections from other machines will require a username and password.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;gateway.recover_after_data_nodes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;gateway.expected_data_nodes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this requires both nodes to be available for the cluster to operate in this case, but with more nodes, we could have set &lt;code&gt;recover_after_data_nodes&lt;/code&gt; to a value smaller than the total number of nodes.&lt;/p&gt;

&lt;p&gt;Now let’s install CrateDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://cdn.crate.io/downloads/deb/DEB-GPG-KEY-crate
apt-key add DEB-GPG-KEY-crate
add-apt-repository &lt;span class="s2"&gt;"deb https://cdn.crate.io/downloads/deb/stable/ &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;lsb_release &lt;span class="nt"&gt;-cs&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; main"&lt;/span&gt;
apt update
apt &lt;span class="nb"&gt;install &lt;/span&gt;crate &lt;span class="nt"&gt;-o&lt;/span&gt; Dpkg::Options::&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--force-confold"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;code&gt;force-confold&lt;/code&gt; is used to keep the configuration files we created earlier)&lt;/p&gt;

&lt;p&gt;Repeat the above steps on the other node.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup of the Crate JMX HTTP Exporter
&lt;/h2&gt;

&lt;p&gt;This is very simple, on each node run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /usr/share/crate/lib
wget https://repo1.maven.org/maven2/io/crate/crate-jmx-exporter/1.0.0/crate-jmx-exporter-1.0.0.jar
nano /etc/default/crate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then uncomment the &lt;code&gt;CRATE_JAVA_OPTS&lt;/code&gt; line and change its value to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CRATE_JAVA_OPTS="-javaagent:/usr/share/crate/lib/crate-jmx-exporter-1.0.0.jar=8080"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and restart the crate daemon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl restart crate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prometheus Node Exporter
&lt;/h2&gt;

&lt;p&gt;This can be set up with a one-liner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apt install prometheus-node-exporter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prometheus SQL Exporter
&lt;/h2&gt;

&lt;p&gt;The SQL Exporter allows running arbitrary SQL statements against a CrateDB cluster to retrieve additional information. As the cluster contains information from each node, we do not need to install the SQL Exporter on every node. Instead, we install it centrally on the same machine that also hosts Prometheus.&lt;/p&gt;

&lt;p&gt;Please note that it is not the same to set up a data source in Grafana pointing to CrateDB to display the output from queries in real-time as to use Prometheus to collect these values over time.&lt;/p&gt;

&lt;p&gt;Installing the package is straight-forward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;prometheus-sql-exporter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the SQL exporter to connect to the cluster, we need to create a new user &lt;code&gt;sql_exporter&lt;/code&gt;. We grant the user reading access to the &lt;code&gt;sys&lt;/code&gt; schema. Run the below commands on any CrateDB node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'http://localhost:4200/_sql'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"stmt":"CREATE USER sql_exporter WITH (password = '&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'insert_password'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;');"}'&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'http://localhost:4200/_sql'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"stmt":"GRANT DQL ON SCHEMA sys TO sql_exporter;"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then create a configuration file in &lt;code&gt;/etc/prometheus-sql-exporter.yml&lt;/code&gt; with a sample query that retrieves the number of shards per node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;global"&lt;/span&gt;
  &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;5m'&lt;/span&gt;
  &lt;span class="na"&gt;connections&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;postgres://sql_exporter:insert_password@ubuntuvm1:5433?sslmode=disable'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;queries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shard_distribution"&lt;/span&gt;
    &lt;span class="na"&gt;help&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Number&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;shards&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;per&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;node"&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node_name"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shards"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;SELECT node['name'] AS node_name, COUNT(*) AS shards&lt;/span&gt;
      &lt;span class="s"&gt;FROM sys.shards&lt;/span&gt;
      &lt;span class="s"&gt;GROUP BY 1;&lt;/span&gt;
    &lt;span class="na"&gt;allow_zero_rows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heap_usage"&lt;/span&gt;
    &lt;span class="na"&gt;help&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Used&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;heap&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;space&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;per&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;node"&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node_name"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heap_used"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;SELECT name AS node_name, heap['used'] / heap['max']::DOUBLE AS heap_used&lt;/span&gt;
      &lt;span class="s"&gt;FROM sys.nodes;&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;global_translog"&lt;/span&gt;
    &lt;span class="na"&gt;help&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Global&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;translog&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;statistics"&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;translog_uncommitted_size"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;SELECT COALESCE(SUM(translog_stats['uncommitted_size']), 0) AS translog_uncommitted_size&lt;/span&gt;
      &lt;span class="s"&gt;FROM sys.shards;&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;checkpoints"&lt;/span&gt;
    &lt;span class="na"&gt;help&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Maximum&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;global/local&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;checkpoint&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;delta"&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_checkpoint_delta"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;SELECT COALESCE(MAX(seq_no_stats['local_checkpoint'] - seq_no_stats['global_checkpoint']), 0) AS max_checkpoint_delta&lt;/span&gt;
      &lt;span class="s"&gt;FROM sys.shards;&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shard_allocation_issues"&lt;/span&gt;
    &lt;span class="na"&gt;help&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Shard&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;allocation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;issues"&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shard_type"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shards"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;SELECT IF(s.primary = TRUE, 'primary', 'replica') AS shard_type, COALESCE(shards, 0) AS shards&lt;/span&gt;
        &lt;span class="s"&gt;FROM UNNEST([true, false]) s(primary)&lt;/span&gt;
        &lt;span class="s"&gt;LEFT JOIN (&lt;/span&gt;
          &lt;span class="s"&gt;SELECT primary, COUNT(*) AS shards&lt;/span&gt;
          &lt;span class="s"&gt;FROM sys.allocations&lt;/span&gt;
          &lt;span class="s"&gt;WHERE current_state &amp;lt;&amp;gt; 'STARTED'&lt;/span&gt;
          &lt;span class="s"&gt;GROUP BY 1&lt;/span&gt;
        &lt;span class="s"&gt;) a ON s.primary = a.primary;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Please note: There exist two implementations of the SQL Exporter: &lt;a href="https://github.com/burningalchemist/sql_exporter" rel="noopener noreferrer"&gt;burningalchemist/sql_exporter&lt;/a&gt; and &lt;a href="https://github.com/justwatchcom/sql_exporter" rel="noopener noreferrer"&gt;justwatchcom/sql_exporter&lt;/a&gt;. They don't share the same configuration options.&lt;br&gt;
Our example is based on the implementation that is shipped with the Ubuntu package, which is justwatchcom/sql_exporter.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To apply the new configuration, we restart the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl restart prometheus-sql-exporter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SQL Export can also be used to monitor any business metrics as well, but be careful with regularly running expensive queries. Below are two more advanced monitoring queries of CrateDB that may be useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Time since the last successful snapshot (backup) */&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;started&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;MinutesSinceLastSuccessfulSnapshot&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snapshots&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"state"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SUCCESS'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prometheus setup
&lt;/h2&gt;

&lt;p&gt;You would run this on a machine that is not part of the CrateDB cluster and it can be installed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apt install prometheus --no-install-recommends
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that by default this will right away become available on port 9090 without authentication requirements, you can use &lt;code&gt;policy-rcd-declarative&lt;/code&gt; to prevent the service from starting immediately after installation and you can define a YAML web config file with &lt;code&gt;basic_auth_users&lt;/code&gt; and then refer to that file in &lt;code&gt;/etc/default/prometheus&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For a large deployment where you also use Prometheus to monitor other systems, you may also want to use a CrateDB cluster as the storage for all Prometheus metrics, you can read more about this at &lt;a href="https://github.com/crate/cratedb-prometheus-adapter" rel="noopener noreferrer"&gt;CrateDB Prometheus Adapter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we will configure Prometheus to scrape metrics from the node explorer from the CrateDB machines and also metrics from our Crate JMX HTTP Exporter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nano /etc/prometheus/prometheus.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where it says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;node'&lt;/span&gt;
  &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost:9100'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We replace this with the below configuration, which reflects port 8080 (Crate JMX Exporter), port 9100 (Prometheus Node Exporter), port 9237 (Prometheus SQL Exporter), as well as port 9100 (Prometheus Node Exporter).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;node'&lt;/span&gt;
  &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntuvm1:9100'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntuvm2:9100'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cratedb_jmx'&lt;/span&gt;
  &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntuvm1:8080'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntuvm2:8080'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sql_exporter'&lt;/span&gt;
  &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost:9237'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the &lt;code&gt;prometheus&lt;/code&gt; daemon if it was already started (&lt;code&gt;systemctl restart prometheus&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Grafana setup
&lt;/h2&gt;

&lt;p&gt;This can be installed on the same machine where you have Prometheus and can be installed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb https://packages.grafana.com/oss/deb stable main"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/apt/sources.list.d/grafana.list
wget &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; - https://packages.grafana.com/gpg.key | &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-key add -
apt update
apt &lt;span class="nb"&gt;install &lt;/span&gt;grafana
systemctl start grafana-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you now point your browser to &lt;em&gt;http://&amp;lt;Grafana host&amp;gt;:3000&lt;/em&gt; you will be welcomed by the Grafana login screen, the first time you can log in with admin as both the username and password, make sure to change this password right away.&lt;/p&gt;

&lt;p&gt;Click on "Add your first data source", then click on "Prometheus", and enter the URL &lt;em&gt;http://&amp;lt;Prometheus host&amp;gt;:9090&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If you had configured basic authentication for Prometheus this is where you would need to enter the credentials.&lt;/p&gt;

&lt;p&gt;Click "Save &amp;amp; test".&lt;/p&gt;

&lt;p&gt;An example dashboard based on the discussed setup is available for easy importing on &lt;a href="https://grafana.com/grafana/dashboards/17174-cratedb-monitoring/" rel="noopener noreferrer"&gt;grafana.com&lt;/a&gt;. In your Grafana installation, on the left-hand side, hover over the “Dashboards” icon and select “Import”. Specify the ID 17174 and load the dashboard. On the next screen, finalize the setup by selecting your previously created Prometheus data sources.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkg8rkfobxaidskovyvvq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkg8rkfobxaidskovyvvq.png" alt="Grafana dashboard" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternative implementations
&lt;/h2&gt;

&lt;p&gt;If you decide to build your own dashboard or use an entirely different monitoring approach, we recommend still covering similar metrics as discussed in this article. The list below is a good starting point for troubleshooting most operational issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CrateDB metrics (with example Prometheus queries based on the Crate JMX HTTP Exporter)

&lt;ul&gt;
&lt;li&gt;Thread pools rejected: &lt;code&gt;sum(rate(crate_threadpools{property="rejected"}[5m])) by (name)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Thread pool queue size: &lt;code&gt;sum(crate_threadpools{property="queueSize"}) by (name)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Thread pools active: &lt;code&gt;sum(crate_threadpools{property="active"}) by (name)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Queries per second: &lt;code&gt;sum(rate(crate_query_total_count[5m])) by (query)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Query error rate: &lt;code&gt;sum(rate(crate_query_failed_count[5m])) by (query)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Average Query Duration over the last 5 minutes: &lt;code&gt;sum(rate(crate_query_sum_of_durations_millis[5m])) by (query) / sum(rate(crate_query_total_count[5m])) by (query)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Circuit breaker memory in use: &lt;code&gt;sum(crate_circuitbreakers{property="used"}) by (name)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Number of shards: &lt;code&gt;crate_node{name="shard_stats",property="total"}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Garbage Collector rates: &lt;code&gt;sum(rate(jvm_gc_collection_seconds_count[5m])) by (gc)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Thread pool queue size: &lt;code&gt;crate_threadpools{property="queueSize"}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Thread pool rejected operations: &lt;code&gt;crate_threadpools{property="rejected"}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Operating system metrics

&lt;ul&gt;
&lt;li&gt;CPU utilization&lt;/li&gt;
&lt;li&gt;Memory usage&lt;/li&gt;
&lt;li&gt;Open file descriptors&lt;/li&gt;
&lt;li&gt;Disk usage&lt;/li&gt;
&lt;li&gt;Disk read/write operations and throughput&lt;/li&gt;
&lt;li&gt;Received and transmitted network traffic&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;We got a Grafana dashboard that allows us to check live and historical data around performance and capacity metrics in our CrateDB cluster, this illustrates one possible setup. You could use different tools depending on your environment and preferences. Still, we recommend you use the interface of the Crate JMX HTTP Exporter to collect CrateDB-specific metrics and that you always also monitor the health of the environment at the OS level as we have done here with the Prometheus Node Exporter.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>showdev</category>
      <category>indiegames</category>
      <category>retrogaming</category>
    </item>
    <item>
      <title>Advanced downsampling with the LTTB algorithm</title>
      <dc:creator>marijaselakovic</dc:creator>
      <pubDate>Thu, 19 Jan 2023 12:24:43 +0000</pubDate>
      <link>https://forem.com/crate/advanced-downsampling-with-the-lttb-algorithm-3okh</link>
      <guid>https://forem.com/crate/advanced-downsampling-with-the-lttb-algorithm-3okh</guid>
      <description>&lt;p&gt;Downsampling is the process of transforming data, reducing its resolution so that it requires less space, while preserving some of its basic characteristics so that it is still usable.&lt;/p&gt;

&lt;p&gt;There are a number of reasons why we may want to do this.&lt;/p&gt;

&lt;p&gt;We may for instance have sensors reporting readings on a high frequency, imagine every 5 seconds, and we may have accumulated historical data over a period of time. This may start to consume lots of disk space.&lt;/p&gt;

&lt;p&gt;Looking at data from last year there may never be a need to go down to the 5 seconds level of precision, but we may want to keep data at a lower level of granularity for future reference.&lt;/p&gt;

&lt;p&gt;There is another case where we may want to downsample data. Let’s consider again the same scenario with a reading every 5 seconds. A week of data would require 120,960 data points, but we may be presenting this information to users as a graph which may be only 800 pixels wide on the screen. It should be possible then to transfer just 800 points, saving network bandwidth and CPU cycles on the client side. The trick resides in getting the right points.&lt;/p&gt;

&lt;p&gt;There are many ways to tackle this problem, sometimes we can just do something like keep a reading per day and drop the rest (think stock prices at closing time), and sometimes we can get good results with averages, but there are cases where these simple approaches result on a distorted view of the data.&lt;/p&gt;

&lt;p&gt;Sveinn Steinarsson at the University of Iceland looked at this problem and came out with an algorithm called Largest-Triangle-Three-Buckets, or LTTB for short, and this is published under the MIT license in &lt;a href="https://github.com/sveinn-steinarsson/flot-downsample" rel="noopener noreferrer"&gt;sveinn-steinarsson/flot-downsample: Downsample plugin for Flot charts. (github.com)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s take a look at how we can use this with CrateDB and the kind of results we can get.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting some sample data
&lt;/h2&gt;

&lt;p&gt;I will be using Python and &lt;a href="https://jupyter.org/install" rel="noopener noreferrer"&gt;Jupyter Notebook&lt;/a&gt; for this demonstration, but you can use any language of your choice.&lt;/p&gt;

&lt;p&gt;I will start by installing a few dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;seaborn
pip &lt;span class="nb"&gt;install &lt;/span&gt;crate[sqlalchemy]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will now import the demo data from Sveinn Steinarsson’s GitHub repo into a table called &lt;code&gt;demo&lt;/code&gt; in my local CrateDB instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://raw.githubusercontent.com/sveinn-steinarsson/flot-downsample/8bb3db00f035dfab897e29d01fd7ae1d9dc999b2/demo_data.js&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reading&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;demo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;crate://localhost:4200&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;if_exists&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;append&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And taking advantage I already have this data set of 5000 points in a data frame I will plot it using &lt;code&gt;seaborn&lt;/code&gt; to see what it looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;seaborn&lt;/span&gt;
&lt;span class="n"&gt;seaborn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lineplot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reading&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0irfc1w470by8p2oip1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0irfc1w470by8p2oip1n.png" alt="Demo 1" width="574" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So this is the actual profile of this noisy-looking signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  A basic approach using averages
&lt;/h2&gt;

&lt;p&gt;Let’s try downsampling this down 50 times to 100 data points with a simple query in CrateDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;avg_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;reading&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;demo&lt;/span&gt; 
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
dfavg_data = pd.read_sql(avg_query, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;crate://localhost:4200&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)
seaborn.lineplot(dfavg_data,x=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,y=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reading&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)`
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2fdtjvu2bsuqo4z85dqv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2fdtjvu2bsuqo4z85dqv.png" alt="Demo 2" width="578" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is not wrong if we know we are looking at an average, but if we are doing this downsampling behind the scenes to save space, bandwidth, or CPU cycles we risk now giving a very wrong impression to the users, notice for instance that the range on the y-axis only goes as far as 1.5 instead of 10+.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter LTTB
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sveinn-steinarsson/flot-downsample" rel="noopener noreferrer"&gt;The reference implementation&lt;/a&gt; of the LTTB algorithm is written in JavaScript and this is very handy because CrateDB supports JavaScript &lt;a href="https://crate.io/docs/crate/reference/en/5.1/general/user-defined-functions.html" rel="noopener noreferrer"&gt;user-defined functions&lt;/a&gt; out of the box.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.cratedb.com/uploads/short-url/evM3SD7DKaHZ5Fjbnt3XDcGCDD2.sql" rel="noopener noreferrer"&gt;Take a look at the linked script&lt;/a&gt;, compared to the original JavaScript we have just made some very minor adjustments so that the variables in input and output are in a format that is easy to work with in SQL in CrateDB. This is using some very useful CrateDB features that are not available in other database systems, arrays and objects, this particular version requires CrateDB 5.1.1 but it is perfectly possible to make this work on earlier versions with some minor changes.&lt;/p&gt;

&lt;p&gt;In this case we are working with an x-axis with integers, but this approach works equally well using timestamps, just changing the script where it says &lt;code&gt;ARRAY(INTEGER)&lt;/code&gt; to &lt;code&gt;ARRAY(TIMESTAMP WITH TIME ZONE)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Another cool feature of CrateDB we can use here is the overloading of functions, we can deploy multiple versions of the same function, one accepting an &lt;code&gt;xarray&lt;/code&gt; which is an array of integers and another one which is an array of timestamps and both will be available to use on SQL queries.&lt;/p&gt;

&lt;p&gt;We can check the different versions deployed by querying the &lt;code&gt;routines&lt;/code&gt; tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;routine_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;specific_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routines&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;routine_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'lttb_with_parallel_arrays'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have our UDF ready let’s give it a try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;lttb_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;    &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;downsampleddata&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; 
     &lt;span class="p"&gt;(&lt;/span&gt;  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;lttb_with_parallel_arrays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;   
            &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;demo&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;                           
            &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;reading&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;demo&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;lttb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;unnest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lttb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;unnest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lttb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;reading&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;downsampleddata&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
dflttb_data = pd.read_sql(lttb_query, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;crate://localhost:4200&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)
seaborn.lineplot(dflttb_data,x=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,y=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reading&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd8hsujt9brjiittqqbrp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd8hsujt9brjiittqqbrp.png" alt="Demo 3" width="574" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see this dataset of just 100 points resembles much better the original profile of the signal, we are looking at a plot where the range is now -10 to 10 same as in the original graph, the noisy character of the curve has been preserved and we can see the spikes all along the curve.&lt;/p&gt;

&lt;p&gt;We hope you find this interesting, do not hesitate to let us know your thoughts.&lt;/p&gt;

</description>
      <category>welcome</category>
      <category>firstpost</category>
      <category>community</category>
    </item>
    <item>
      <title>Fetching large results sets from CrateDB</title>
      <dc:creator>marijaselakovic</dc:creator>
      <pubDate>Thu, 19 Jan 2023 09:49:26 +0000</pubDate>
      <link>https://forem.com/crate/fetching-large-results-sets-from-cratedb-3jen</link>
      <guid>https://forem.com/crate/fetching-large-results-sets-from-cratedb-3jen</guid>
      <description>&lt;p&gt;As a distributed database system with support for the standard SQL query language, CrateDB is great to run aggregations server-side, working on huge datasets, and getting summarized results back; there are however cases where we may still want to retrieve lots of data from CrateDB, to train a machine learning model for instance.&lt;/p&gt;

&lt;p&gt;CrateDB collects results in memory before sending them back to the clients, so trying to run a &lt;code&gt;SELECT&lt;/code&gt; statement that returns a very large result set in one go can trigger &lt;a href="https://crate.io/docs/crate/reference/en/5.0/config/cluster.html#query-circuit-breaker" rel="noopener noreferrer"&gt;circuit breakers&lt;/a&gt; or result in an &lt;code&gt;OutOfMemoryError&lt;/code&gt;, and getting all results in a single operation can also be a challenge client-side, so we need a mechanism to fetch results in manageable batches as we are ready to process them.&lt;/p&gt;

&lt;p&gt;One option, for cases where we are looking at a single table, and we know already that we need all the records that satisfy a condition, is to do a bulk export with the &lt;a href="https://crate.io/docs/crate/reference/en/5.0/sql/statements/copy-to.html" rel="noopener noreferrer"&gt;COPY TO&lt;/a&gt; command which accepts a &lt;code&gt;WHERE&lt;/code&gt; clause. It happens, however, that in many cases we may want to run more complex queries, or simply storing the results in files may not fit well into our application.&lt;/p&gt;

&lt;h2&gt;
  
  
  The case for pagination
&lt;/h2&gt;

&lt;p&gt;A common requirement is also what is called pagination, which is to present results to users with pages with a set number of results each, allowing them to move between these pages. In this case, it is common that many users will only look at the first few pages of results, so we want to implement that in the most efficient way possible.&lt;/p&gt;

&lt;p&gt;Let’s imagine we have a table called “observations” with the following data:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ts&lt;/th&gt;
&lt;th&gt;device&lt;/th&gt;
&lt;th&gt;reading&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2021-10-14T09:39:19&lt;/td&gt;
&lt;td&gt;dev2&lt;/td&gt;
&lt;td&gt;-1682&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2022-02-02T00:33:47&lt;/td&gt;
&lt;td&gt;dev1&lt;/td&gt;
&lt;td&gt;827&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2022-06-11T21:49:53&lt;/td&gt;
&lt;td&gt;dev2&lt;/td&gt;
&lt;td&gt;-713&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2022-06-29T23:23:28&lt;/td&gt;
&lt;td&gt;dev1&lt;/td&gt;
&lt;td&gt;1059&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2022-07-01T09:22:56&lt;/td&gt;
&lt;td&gt;dev2&lt;/td&gt;
&lt;td&gt;-689&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2022-07-10T02:43:36&lt;/td&gt;
&lt;td&gt;dev2&lt;/td&gt;
&lt;td&gt;-570&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2022-09-18T03:28:02&lt;/td&gt;
&lt;td&gt;dev1&lt;/td&gt;
&lt;td&gt;303&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2022-10-14T20:34:10&lt;/td&gt;
&lt;td&gt;dev1&lt;/td&gt;
&lt;td&gt;1901&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We will work with very small number of records here to visualize how different techniques work but imagine that we have thousands or even millions of records. In particular, I will show examples here of retrieving 2 rows at a time but depending on the use case you would probably be looking at retrieving 50, 1000, or even 5000 rows at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using &lt;code&gt;LIMIT&lt;/code&gt; + &lt;code&gt;OFFSET&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;date_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observations&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01 00:00'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="s1"&gt;'2022-10-01 00:00'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------------------+--------+---------+
| date_format(ts)             | device | reading |
+-----------------------------+--------+---------+
| 2022-02-02T00:33:47.000000Z | dev1   |     827 |
| 2022-06-11T21:49:53.000000Z | dev2   |    -713 |
+-----------------------------+--------+---------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could then re-issue the query with &lt;code&gt;LIMIT 2 OFFSET 2&lt;/code&gt; and we would get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------------------+--------+---------+
| date_format(ts)             | device | reading |
+-----------------------------+--------+---------+
| 2022-06-29T23:23:28.000000Z | dev1   |    1059 |
| 2022-07-01T09:22:56.000000Z | dev2   |    -689 |
+-----------------------------+--------+---------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a number of issues to be aware of with this approach.&lt;/p&gt;

&lt;p&gt;Each new query is considered a new request and looks at current data. Consider what happens if the observation for 11 June 2022 above were to be deleted after we run the first query, but before we run the second one with &lt;code&gt;OFFSET 2&lt;/code&gt;. Skipping 2 rows we are now skipping the observation from 29 June 2022, and the users will never see it.&lt;/p&gt;

&lt;p&gt;Another issue is that there is not always an efficient way for CrateDB to skip the rows so, for certain queries, as the &lt;code&gt;OFFSET&lt;/code&gt; value goes up, we may find that execution times grow larger and larger as the engine is actually going through the rows that need to be skipped and just discarding them server-side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using &lt;code&gt;LIMIT&lt;/code&gt; with a &lt;code&gt;WHERE&lt;/code&gt; clause on a watermark field
&lt;/h2&gt;

&lt;p&gt;Continuing from the example above, after we get the initial 2 rows, instead of using &lt;code&gt;OFFSET 2&lt;/code&gt; we could run a query like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;date_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observations&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2022-06-11T21:49:53.000000Z'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="s1"&gt;'2022-10-01 00:00'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That 11 June value is the last value we observed so far on the ts column that in this case, we know to be always increasing, this approach is very efficient, but it can only be used if there is a suitable field in the data which is not always the case.&lt;/p&gt;

&lt;p&gt;Also compared to the &lt;code&gt;LIMIT&lt;/code&gt; + &lt;code&gt;OFFSET&lt;/code&gt; approach we discussed earlier, we cannot use this to let the users jump to a given page of results without first having obtained all the results for the previous pages, we cannot for instance jump directly to page 10 as we do not know what is the last reading of ts at page 9.&lt;/p&gt;

&lt;p&gt;Some people call this approach above “cursor pagination”, but the most common concept behind “cursors” is something a bit different which we are going to discuss now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cursors
&lt;/h2&gt;

&lt;p&gt;A cursor is like having a bookmark pointing to a specific record in the result set of a query, this is a generic approach that is implemented efficiently and does not require us to have a special anchor/watermark column.&lt;/p&gt;

&lt;p&gt;In CrateDB we can use cursors at the protocol level or with SQL commands.&lt;/p&gt;

&lt;p&gt;Cursors in CrateDB are &lt;code&gt;INSENSITIVE&lt;/code&gt;, meaning that the client can take all the time it needs to retrieve the results, and the data will always reflect the status of the tables as it was at the time the cursor was declared, ignoring any records that were updated, deleted, or newly inserted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using cursors in Python
&lt;/h2&gt;

&lt;p&gt;In Python one way to work with cursors is with &lt;a href="https://magicstack.github.io/asyncpg/current/" rel="noopener noreferrer"&gt;asyncpg&lt;/a&gt;, taking advantage of CrateDB's compatibility with the &lt;a href="https://crate.io/docs/crate/reference/en/5.0/interfaces/postgres.html" rel="noopener noreferrer"&gt;PostgreSQL wire protocol&lt;/a&gt;.&lt;br&gt;
First, we need to install the library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;asyncpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can use it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncpg&lt;/span&gt;

&lt;span class="c1"&gt;# If you are using jupyter-notebook 
# remove this function definition line and the indentation in the block of code that follows
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# we will then establish a connection    
&lt;/span&gt;    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncpg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;crate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# and we need a “transaction” context, 
&lt;/span&gt;    &lt;span class="c1"&gt;# there are no transactions as such in CrateDB, 
&lt;/span&gt;    &lt;span class="c1"&gt;# but this gives a scope where the cursor lives:  
&lt;/span&gt;    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

        &lt;span class="c1"&gt;# and now we can declare the cursor 
&lt;/span&gt;        &lt;span class="c1"&gt;# specifying how many rows we want asyncpg to fetch at a time from CrateDB, 
&lt;/span&gt;        &lt;span class="c1"&gt;# and we can iterate over the results: 
&lt;/span&gt;        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT ts,device,reading FROM doc.observations WHERE ts BETWEEN &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2022-01-01 00:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; AND &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2022-10-01 00:00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefetch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Remove this line if you are using jupyter-notebook
&lt;/span&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just to clarify, our Python code works with one record at a time, but behind the scenes &lt;code&gt;asyncpg&lt;/code&gt; is requesting 1000 records at a time from CrateDB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using cursors in Java
&lt;/h2&gt;

&lt;p&gt;In Java, we can use the PostgreSQL JDBC driver.&lt;br&gt;
In a Maven project add this to your &lt;code&gt;pom.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.postgresql&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;postgresql&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;42.5.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can use it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.sql.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="cm"&gt;/* ... */&lt;/span&gt;   
&lt;span class="cm"&gt;/* we first establish a connection to CrateDB */&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DriverManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jdbc:postgresql://localhost/"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"crate"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Statement&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createStatement&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/* We will then open the cursor 
         * defining how many rows we want to retrieve at a time, 
         * in this case 1,000: 
         */&lt;/span&gt;
        &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setFetchSize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SELECT ts,device,reading "&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;"FROM doc.observations "&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;"WHERE ts BETWEEN '2022-01-01 00:00' AND '2022-10-01 00:00';"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; 
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ResultSet&lt;/span&gt; &lt;span class="n"&gt;resultSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="cm"&gt;/* and while there are rows available, we will iterate over them: */&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resultSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;                      
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resultSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;                     
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works like in the Python case above, in our Java code we see one row at a time, but the rows are retrieved from CrateDB 1,000 at a time and kept in memory on the client side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using cursors with SQL commands
&lt;/h2&gt;

&lt;p&gt;An approach that works with all clients is to use &lt;a href="https://crate.io/docs/crate/reference/en/5.1/sql/statements/declare.html" rel="noopener noreferrer"&gt;SQL commands&lt;/a&gt; supported since CrateDB 5.1.0.&lt;/p&gt;

&lt;p&gt;First, we need to issue this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a SQL command that would normally start a transaction, there are no transactions as such in CrateDB, but this will create a scope on which cursors can be created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="n"&gt;observations_cursor&lt;/span&gt; &lt;span class="k"&gt;NO&lt;/span&gt; &lt;span class="k"&gt;SCROLL&lt;/span&gt; &lt;span class="k"&gt;CURSOR&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; 
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt; 
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observations&lt;/span&gt; 
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01 00:00'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="s1"&gt;'2022-10-01 00:00'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This associates a cursor name with a query and determines the point in time at which data is “frozen” from the point of view of the cursor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;FETCH&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;observations_cursor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This retrieves 10 rows from the query, and when issued again it will retrieve the next 10 rows and so on. We can retrieve a different number of records each time and we know we have reached the end of the result set when &lt;code&gt;FETCH&lt;/code&gt; returns zero rows.&lt;/p&gt;

&lt;p&gt;Once the cursor is not needed anymore it can be closed with either &lt;code&gt;END;&lt;/code&gt;, &lt;code&gt;CLOSE ALL;&lt;/code&gt;, &lt;code&gt;CLOSE observations_cursor;&lt;/code&gt;, &lt;code&gt;COMMIT;&lt;/code&gt;, &lt;code&gt;COMMIT TRANSACTION;&lt;/code&gt;, or &lt;code&gt;COMMIT WORK;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Take a look at this short animation showing an example of how this works: &lt;a href="https://twitter.com/mfussenegger/status/1572256514798424066" rel="noopener noreferrer"&gt;Mathias Fußenegger on Twitter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We hope you find this useful, and we will be happy to hear about your experience in the &lt;a href="https://community.crate.io/" rel="noopener noreferrer"&gt;Community&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>gratitude</category>
    </item>
    <item>
      <title>Scaling CrateDB clusters up and down to cope with peaks in demand</title>
      <dc:creator>marijaselakovic</dc:creator>
      <pubDate>Thu, 19 Jan 2023 09:46:44 +0000</pubDate>
      <link>https://forem.com/crate/scaling-cratedb-clusters-up-and-down-to-cope-with-peaks-in-demand-2mn</link>
      <guid>https://forem.com/crate/scaling-cratedb-clusters-up-and-down-to-cope-with-peaks-in-demand-2mn</guid>
      <description>&lt;p&gt;Many organizations size their database infrastructure to handle the maximum level of load they can anticipate, but very often load is seasonal, in some cases around specific events on certain days of the year. Compromises are often made where infrastructure sits idle most of the year and performance is not as good as desired when requests peak.&lt;/p&gt;

&lt;p&gt;CrateDB allows clusters to be scaled both up and down by adding and removing nodes, this allows significant savings during quiet periods, and also the provisioning of extra capacity during particular periods of high activity to have optimal performance.&lt;/p&gt;

&lt;p&gt;When nodes are added or removed CrateDB automatically rebalances shards, but in cases where we have very large volumes of historical data and new nodes are only added for a short period of time, we may want to avoid any of the historical data being relocated to the temporary nodes. The approach described below explains how to achieve this using &lt;a href="https://crate.io/docs/crate/reference/en/5.1/general/ddl/shard-allocation.html" rel="noopener noreferrer"&gt;shard allocation filtering&lt;/a&gt; on a table that is partitioned by day, the idea is that the period of high activity can be foreseen, so the scaling up will take place the day before a big event, and the scaling down someday after the event has ended.&lt;/p&gt;

&lt;p&gt;This same approach can be applied to multiple tables. It is particularly relevant for the larger tables, and smaller tables can be kept on the baseline nodes, but it is always good to consider the impact on querying performance if the small tables will be queried during the big event &lt;code&gt;JOIN&lt;/code&gt;ed to the big tables that will have data on the temporary nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing the test environment
&lt;/h2&gt;

&lt;p&gt;In this example, we will imagine that the surge in demand we are preparing for is related to the 2022 FIFA Men’s World Cup running from 20/11/2022 to 18/12/2022.&lt;/p&gt;

&lt;p&gt;We will start with a 3 nodes cluster, on which we will create a test table and populate it with some pre-World Cup data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;recorddetails&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nv"&gt;"day"&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;date_trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'day'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;PARTITIONED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"day"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="n"&gt;CLUSTERED&lt;/span&gt;  &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;SHARDS&lt;/span&gt; 
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number_of_replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2022-11-18'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-11-19'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shards will initially look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0s8k1elhmnxxwetoca9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0s8k1elhmnxxwetoca9d.png" alt="Shards" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Before deploying extra nodes
&lt;/h2&gt;

&lt;p&gt;We want to make sure that the addition of the temporary nodes does not result on data from the large tables getting rebalanced to use these nodes.&lt;/p&gt;

&lt;p&gt;We will be able to identify the new nodes by using a custom attribute (&lt;code&gt;node.attr.storage=temporarynodes&lt;/code&gt;) (see further down for details on how to configure this), so the first step is to configure the existing partitions so that they do not consider the new nodes as suitable targets for shard allocation.&lt;/p&gt;

&lt;p&gt;In CrateDB 5.1.2 or higher we can achieve this with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* this applies the setting to all existing partitions and new partitions */&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"routing.allocation.exclude.storage"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'temporarynodes'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/* then we run this other command so that the setting does not apply to new partitions */&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;ONLY&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="k"&gt;RESET&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"routing.allocation.exclude.storage"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No data gets reallocated when running this, and there is no impact on querying or ingestion.&lt;/p&gt;

&lt;p&gt;Starting in CrateDB 5.2 this setting is visible in &lt;code&gt;settings['routing']&lt;/code&gt; in &lt;code&gt;information_schema.table_partitions&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying the extra nodes
&lt;/h2&gt;

&lt;p&gt;We want to deploy the new nodes setting a custom attribute.&lt;br&gt;
If using containers add a line to &lt;code&gt;args&lt;/code&gt; in your YAML file with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- -Cnode.attr.storage=temporarynodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise add this to &lt;code&gt;crate.yml&lt;/code&gt; (typically on &lt;code&gt;/etc/crate&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node.attr.storage=temporarynodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note the word &lt;code&gt;storage&lt;/code&gt; in this context does not have any special meaning for CrateDB, it is just a name that we have chosen in this case for the custom attribute.&lt;/p&gt;

&lt;p&gt;Starting with CrateDB 5.2 these node attributes will be visible in &lt;code&gt;sys.nodes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We need to calculate how many shards will be created each day (that is on each partition) during the special event, since our test table is &lt;code&gt;CLUSTERED INTO 4 SHARDS&lt;/code&gt; &lt;code&gt;WITH (number_of_replicas=1)&lt;/code&gt; we would have 4 (shards per partition) x 2 (primary + copy) = 8&lt;/p&gt;

&lt;p&gt;We then need to see what is the ceiling of the number we got (8) divided by the total number of nodes (baseline + temporary), if that is for instance 3+2=5 then we have ceiling(8/5)=2.&lt;/p&gt;

&lt;p&gt;That means that if a maximum of 2 of the new shards created each day during the event goes to each node then the new data will be balanced across all nodes.&lt;/p&gt;

&lt;p&gt;With the nodes ready, on the day before the event, we need to configure the special tables so that any new partitions follow this rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;ONLY&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"routing.allocation.total_shards_per_node"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setting (&lt;code&gt;total_shards_per_node&lt;/code&gt;) is visible at partition level in &lt;code&gt;settings['routing']&lt;/code&gt; in &lt;code&gt;information_schema.table_partitions&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No data gets reallocated when running this and there is no impact on querying or ingestion.&lt;/p&gt;

&lt;p&gt;This setting can be checked by using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  During the event
&lt;/h2&gt;

&lt;p&gt;Let’s now simulate the arrival of data during the event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; 
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2022-11-20'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-11-21'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-11-22'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-11-23'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2022-11-24'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-11-25'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-11-26'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-11-27'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2022-11-28'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-11-29'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-11-30'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-01'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-02'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-03'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-04'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-05'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-06'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-07'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-08'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-09'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-10'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-11'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-12'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-13'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-14'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-15'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-16'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-17'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-18'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that data from before the event stays on the baseline nodes while data for the days of the event gets distributed over all nodes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq7ar0xp6j60ztunz9lvh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq7ar0xp6j60ztunz9lvh.png" alt="Data1" width="800" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same can be checked programmatically with this query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;   &lt;span class="n"&gt;table_partitions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;table_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;table_partitions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;table_partitions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'day'&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;shards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;shards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shards&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;table_partitions&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;shards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partition_ident&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;table_partitions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partition_ident&lt;/span&gt; 
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The day the event ends
&lt;/h2&gt;

&lt;p&gt;On the last day of the event, we need to configure the table so that the next partition goes to the baseline nodes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;ONLY&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"routing.allocation.exclude.storage"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'temporarynodes'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;ONLY&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="k"&gt;RESET&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"routing.allocation.total_shards_per_node"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A day after the event has ended
&lt;/h2&gt;

&lt;p&gt;New data should now again go the baseline nodes only.&lt;/p&gt;

&lt;p&gt;Let’s confirm it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-19'&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="s1"&gt;'2022-12-20'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2x7t3lgm3iwlaiu1a6c7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2x7t3lgm3iwlaiu1a6c7.png" alt="Baseline node" width="800" height="85"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When we are ready to decommission the temporary nodes, we need to move the data collected during the days of the event.&lt;/p&gt;

&lt;p&gt;In CrateDB 5.1.2 or higher we can achieve this with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"routing.allocation.exclude.storage"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'temporarynodes'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="k"&gt;RESET&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"routing.allocation.total_shards_per_node"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The data movement takes place one replica at a time and there is no impact on querying of the event’s data while it is being moved, new data also continues to flow to the baseline nodes and its ingestion and querying are also not impacted.&lt;/p&gt;

&lt;p&gt;We can monitor the progress of the data relocation querying &lt;code&gt;sys.shards&lt;/code&gt; and &lt;code&gt;sys.allocations&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once all shards have moved away from the temporary nodes, we can decommission them gracefully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;CLUSTER&lt;/span&gt; &lt;span class="n"&gt;DECOMMISSION&lt;/span&gt; &lt;span class="s1"&gt;'nodename'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this is done, the machines can safely be shutdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the time comes for the next event
&lt;/h2&gt;

&lt;p&gt;If desired, new nodes can be deployed reusing the same names that were used for the temporary nodes before.&lt;/p&gt;

</description>
      <category>gratitude</category>
      <category>performance</category>
      <category>devops</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Ingesting with CrateDB</title>
      <dc:creator>marijaselakovic</dc:creator>
      <pubDate>Wed, 18 Jan 2023 13:58:08 +0000</pubDate>
      <link>https://forem.com/crate/ingesting-with-cratedb-9c8</link>
      <guid>https://forem.com/crate/ingesting-with-cratedb-9c8</guid>
      <description>&lt;p&gt;&lt;strong&gt;In this post, we would like to explore CrateDB’s writing performance through a series of batched insert benchmarks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We don’t discuss performance very often because we see a couple of issues in the benchmarking space: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Overall lack of transparency &lt;/li&gt;
&lt;li&gt;Reproducibility issues &lt;/li&gt;
&lt;li&gt;Use case issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To eliminate all of these issues, we would like to start a series where we &lt;strong&gt;transparently explore our own performance&lt;/strong&gt; limits and disclose all the used tools and infrastructure.  &lt;/p&gt;

&lt;p&gt;Let's start with &lt;strong&gt;writing performance&lt;/strong&gt;. It is one of CrateDB's biggest strengths when utilized correctly. We will demonstrate this through a series of batched insert benchmarks performed on different tiers of CrateDB Cloud clusters.&lt;/p&gt;

&lt;p&gt;We applied many learnings from our previous benchmarking adventure: &lt;a href="https://crate.io/blog/how-we-scaled-ingestion-to-one-million-rows-per-second"&gt;Scaling ingestion to one million rows per second&lt;/a&gt;. If you haven't read it yet, we encourage you to do so! &lt;/p&gt;

&lt;h2&gt;
  
  
  Content
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What we want to achieve&lt;/li&gt;
&lt;li&gt;Infrastructure&lt;/li&gt;
&lt;li&gt;CrateDB Clusters&lt;/li&gt;
&lt;li&gt;Benchmarking&lt;/li&gt;
&lt;li&gt;Results&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we want to achieve
&lt;/h2&gt;

&lt;p&gt;We like to be transparent with our customers and users about the performance they can expect from our product. We want to introduce a &lt;strong&gt;reliable and verifiable way of measuring performance&lt;/strong&gt; with a workload that suits CrateDB. We also want to make clear that this is only one use case and one type of workload.&lt;/p&gt;

&lt;p&gt;If you want to see how we can handle a different use case or workload, &lt;strong&gt;don’t hesitate to &lt;a href="https://crate.io/contact"&gt;contact&lt;/a&gt; and challenge us&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Additional steps we took to get as close as possible to the real-world use case: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The data generated by the benchmarking tool must be &lt;strong&gt;representative&lt;/strong&gt;, including numerical and textual columns in a non-trivial structure. &lt;/li&gt;
&lt;li&gt;All the components of the infrastructure we used are &lt;strong&gt;disclosed and easily replicable&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;We measured the performance with an &lt;strong&gt;out-of-the-box configuration&lt;/strong&gt; of CrateDB Cloud clusters. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Infrastructure
&lt;/h2&gt;

&lt;p&gt;For the ingesting tool/benchmark VM, we decided to go with the &lt;a href="https://aws.amazon.com/ec2/instance-types/c5/"&gt;c5.12xlarge&lt;/a&gt; EC2 instance. We don’t need all the 48 CPUs this instance offers, but it’s &lt;strong&gt;the most affordable instance&lt;/strong&gt; with 12 Gigabit networking, which can be necessary when &lt;strong&gt;ingesting into higher-tier&lt;/strong&gt; CrateDB Cloud clusters. &lt;/p&gt;

&lt;p&gt;As for the &lt;strong&gt;CrateDB Cloud clusters&lt;/strong&gt;, this benchmark was performed on AWS deployment. We also offer clusters running on Azure. The performance is comparable in both environments. These are the instances that we currently use: &lt;/p&gt;

&lt;p&gt;AWS - &lt;a href="https://aws.amazon.com/ec2/instance-types/m5/"&gt;m5.4xlarge&lt;/a&gt; &lt;br&gt;
Azure - &lt;a href="https://azure.microsoft.com/en-au/pricing/details/virtual-machines/linux/#ddv4-series"&gt;D16s v5&lt;/a&gt; &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%2Fqy7jb6bvr6z7g7cqmk8n.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%2Fqy7jb6bvr6z7g7cqmk8n.png" alt="AWS benchmarks setup" width="397" height="774"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  CrateDB Clusters
&lt;/h2&gt;

&lt;p&gt;For the &lt;strong&gt;CrateDB Clusters&lt;/strong&gt; we used our Cloud platform. It offers &lt;strong&gt;fully-managed clusters that can be deployed in a matter of seconds&lt;/strong&gt;. It comes with 4 tiers of clusters, depending on the workload that you’re expecting: &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%2Fglcolcnurqyj4m0zvxin.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%2Fglcolcnurqyj4m0zvxin.png" alt="CrateDB Cloud Tiers spec" width="800" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are &lt;strong&gt;per-node resources&lt;/strong&gt;. When deploying multi-node clusters, simply multiply these by the number of nodes. Storage is a big part of fast writing. Offered storage depends on the tier of the cluster, but in general, we offer storage of up to 8 TiB of enterprise-level SSD per node.&lt;/p&gt;

&lt;p&gt;If you want to follow this step of the setup, &lt;strong&gt;deploy your CrateDB Cloud cluster &lt;a href="https://console.cratedb.cloud/?utm_campaign=2022-Q3-WS-Developer-Motion&amp;amp;utm_source=website&amp;amp;utm_medium=ingesting-with-cratedb"&gt;here&lt;/a&gt;&lt;/strong&gt;. &lt;/p&gt;
&lt;h2&gt;
  
  
  Benchmarking
&lt;/h2&gt;

&lt;p&gt;We used the &lt;a href="https://github.com/proddata/nodeIngestBench"&gt;nodeIngestBench&lt;/a&gt; for all the benchmarking. It is a multi-process Node.js script that runs high-performance ingest benchmarks on CrateDB. It uses a data model that was adapted from &lt;a href="https://github.com/timescale/tsbs"&gt;Timescale’s Time Series Benchmark Suite (TSBS)&lt;/a&gt;. One thing that we want to make clear is that nodeIngestBench is a write benchmark. The data structure that it creates is unsuitable for any performance-indicative reading tests because of its high &lt;a href="https://en.wikipedia.org/wiki/Cardinality_(SQL_statements)"&gt;cardinality&lt;/a&gt; (due to random data) and no &lt;a href="https://crate.io/docs/crate/reference/en/5.1/general/ddl/partitioned-tables.html"&gt;partitioning&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpu&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; 
 &lt;span class="nv"&gt;"tags"&lt;/span&gt; &lt;span class="k"&gt;OBJECT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DYNAMIC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; 
   &lt;span class="nv"&gt;"arch"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
   &lt;span class="nv"&gt;"datacenter"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
   &lt;span class="nv"&gt;"hostname"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
   &lt;span class="nv"&gt;"os"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
   &lt;span class="nv"&gt;"rack"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
   &lt;span class="nv"&gt;"region"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
   &lt;span class="nv"&gt;"service"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
   &lt;span class="nv"&gt;"service_environment"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
   &lt;span class="nv"&gt;"service_version"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
   &lt;span class="nv"&gt;"team"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; 
 &lt;span class="p"&gt;),&lt;/span&gt; 
 &lt;span class="nv"&gt;"ts"&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
 &lt;span class="nv"&gt;"usage_user"&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
 &lt;span class="nv"&gt;"usage_system"&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
 &lt;span class="nv"&gt;"usage_idle"&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
 &lt;span class="nv"&gt;"usage_nice"&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
 &lt;span class="nv"&gt;"usage_iowait"&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
 &lt;span class="nv"&gt;"usage_irq"&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
 &lt;span class="nv"&gt;"usage_softirq"&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
 &lt;span class="nv"&gt;"usage_steal"&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
 &lt;span class="nv"&gt;"usage_guest"&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
 &lt;span class="nv"&gt;"usage_guest_nice"&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; 
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CLUSTERED&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;shards&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SHARDS&lt;/span&gt; 
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number_of_replicas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;replicas&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It creates a &lt;strong&gt;single doc.cpu table&lt;/strong&gt; where each row is made of 10 randomly generated numerical metrics. The rows get ingested in batched inserts. As mentioned previously, default settings are used, which also means that &lt;strong&gt;all columns are indexed automatically&lt;/strong&gt;. One notable setting is &lt;a href="https://crate.io/docs/crate/reference/en/5.1/general/ddl/replication.html"&gt;shard replication&lt;/a&gt;. It comes at a slight writing performance expense, but we &lt;strong&gt;encourage the usage of replication in every use case&lt;/strong&gt;, and practically all of our customers do use it.  &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%2Fwi940onvywq8nmll1waq.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%2Fwi940onvywq8nmll1waq.png" alt="Ingested data" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example of a single benchmark run on a 3-node CR4 cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node appCluster.js &lt;span class="se"&gt;\ &lt;/span&gt;
 &lt;span class="nt"&gt;--batchsize&lt;/span&gt; 10000 &lt;span class="se"&gt;\ &lt;/span&gt;
 &lt;span class="nt"&gt;--max_rows&lt;/span&gt; 200000000 &lt;span class="se"&gt;\ &lt;/span&gt;
 &lt;span class="nt"&gt;--shards&lt;/span&gt; 42 &lt;span class="se"&gt;\ &lt;/span&gt;
 &lt;span class="nt"&gt;--concurrent_requests&lt;/span&gt; 10 &lt;span class="se"&gt;\ &lt;/span&gt;
 &lt;span class="nt"&gt;--processes&lt;/span&gt; 3 &lt;span class="se"&gt;\ &lt;/span&gt;
 &lt;span class="nt"&gt;--replicas&lt;/span&gt; 1 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;batchsize&lt;/code&gt;: The number of rows that are passed in a single INSERT statement. The higher the number, the fewer inserts you need to perform to ingest all of the generated data. At the same time, there comes the point of diminishing returns. We found the best value at around 10 000 rows per INSERT.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;max_rows&lt;/code&gt;: The maximum number of rows that will be generated. This is the parameter that allows to control total runtime of the benchmark.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shards&lt;/code&gt;: The number of shards the table will be split into. Each shard can be written independently, so we aim for a number that allows for enough concurrency. As a rule of thumb, we create one shard per CPU.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;concurrent_requests&lt;/code&gt;: The number of INSERT statements that each child process will run concurrently as asynchronous operations. For single-node and 3-node clusters, we found the value of ten concurrent requests to be ideal&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;processes&lt;/code&gt;: The main node process will start this number of child processes (workers) that generate INSERT statements in parallel. Generally, we found the best performance with one process per CrateDB node.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;replicas&lt;/code&gt;: Replicas are pretty much pointless on single-node clusters. On multi-node clusters, however, they provide a great way to avoid data loss in case of node failure. For 3-node cluster we created one replica.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As for the single-node benchmarks, this is an example configuration for a CR4 single-node cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node appCluster.js &lt;span class="se"&gt;\ &lt;/span&gt;
 &lt;span class="nt"&gt;--batchsize&lt;/span&gt; 10000 &lt;span class="se"&gt;\ &lt;/span&gt;
 &lt;span class="nt"&gt;--max_rows&lt;/span&gt; 20000000 &lt;span class="se"&gt;\ &lt;/span&gt;
 &lt;span class="nt"&gt;--shards&lt;/span&gt; 14 &lt;span class="se"&gt;\ &lt;/span&gt;
 &lt;span class="nt"&gt;--concurrent_requests&lt;/span&gt; 10 &lt;span class="se"&gt;\ &lt;/span&gt;
 &lt;span class="nt"&gt;--processes&lt;/span&gt; 1 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;We ran the benchmark on both single-node and 3-node clusters to see how they compare. &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%2Fn4bo4ybb72r3jnho9v5g.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%2Fn4bo4ybb72r3jnho9v5g.png" alt="Horizontal and vertical scaling in CrateDB Cloud" width="556" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first thing we noticed is that lower-tier 3-node clusters (CR1 and CR2) offer a smaller improvement over their single-node counterpart than CR3 and CR4. This is because of the increased intra-cluster network traffic and overhead created by enabled replicas.&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%2Frkxhvhythsvm5xebcycn.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%2Frkxhvhythsvm5xebcycn.png" alt="horizontal scaling performance gain" width="638" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we have a comparison of &lt;strong&gt;the performance gained by scaling from one node to three nodes&lt;/strong&gt;. You can see that performance gain varies from around 30% in smaller clusters to around 60% in higher clusters. However, as mentioned before, &lt;strong&gt;performance isn’t the only thing you gain by scaling your cluster to multi-node&lt;/strong&gt;. Data loss prevention and high availability are big topics for many customers, and multi-node is the only way to get that. &lt;/p&gt;

&lt;p&gt;That being said, we also want to enable our customers to do whatever they want with their clusters. If you’re purely after performance and are willing to sacrifice some protective measures because of it, &lt;strong&gt;one step you can take is disabling replicas in multi-node clusters. We don’t encourage users to do this&lt;/strong&gt;, and haven’t encountered a production grade setup where this was necessary, but you’re free to do so.&lt;/p&gt;

&lt;p&gt;Disabling replicas improved the &lt;strong&gt;ingest performance by 50%&lt;/strong&gt; on average. Keep in mind that without replicas there is a possibility of data loss upon a non-recoverable node failure. As the data is split evenly among the nodes, &lt;strong&gt;the data loss&lt;/strong&gt; would be around 1/X, X being the number of nodes in a cluster. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vertical scaling&lt;/strong&gt; can, of course, also be useful. If you’re already scaled to multi-node and need a bit more performance, or &lt;strong&gt;you’re testing your application&lt;/strong&gt; and are not interested in multi-node benefits, increasing the tier of your cluster can also be helpful. It should, however, be secondary to horizontal scaling. We advise our customers to &lt;strong&gt;scale to a higher node count&lt;/strong&gt; as long as possible.&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%2Fcyrymkq7je6zp8nj77z9.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%2Fcyrymkq7je6zp8nj77z9.png" alt="Vertical scaling of 1 and 3 node cluster" width="708" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vertical scaling from CR1 offers the biggest improvement, as it's a very affordable tier suited for small applications and compatibility testing. In higher tiers, the performance gained by vertical scaling drops. The average improvement provided by vertical scaling is around 90%. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this post, we hoped to &lt;strong&gt;provide users with a relatively simple introduction to benchmarking their CrateDB clusters&lt;/strong&gt;. In our scenario, we showed what performance can be expected when using the CrateDB Cloud clusters, but this same process can be replicated on on-premise clusters using a diverse range of hardware. &lt;/p&gt;

&lt;p&gt;These benchmarks provide only a single-use case using batched inserts, and we hope to follow this post with others in the future. The &lt;strong&gt;next logical step would be looking into read performance&lt;/strong&gt; or maybe the write performance of the before mentioned on-premise hardware.  &lt;/p&gt;

&lt;p&gt;If you have a specific scenario or use a case in mind that you would like to see, &lt;strong&gt;don’t hesitate to &lt;a href="https://crate.io/contact"&gt;reach out&lt;/a&gt;&lt;/strong&gt;. &lt;/p&gt;

</description>
      <category>cratedb</category>
      <category>database</category>
      <category>performance</category>
      <category>sql</category>
    </item>
    <item>
      <title>Correlated sub-queries in CrateDB</title>
      <dc:creator>marijaselakovic</dc:creator>
      <pubDate>Wed, 18 Jan 2023 12:29:16 +0000</pubDate>
      <link>https://forem.com/crate/correlated-sub-queries-in-cratedb-31lg</link>
      <guid>https://forem.com/crate/correlated-sub-queries-in-cratedb-31lg</guid>
      <description>&lt;p&gt;Correlated sub-queries are a new feature that CrateDB 5.1 supports. This post will introduce you to the concept of a correlated sub-query, its usage, and how it's currently implemented in CrateDB!  &lt;/p&gt;

&lt;p&gt;Our main motivation for this implementation was to increase the compatibility to support tools using correlated sub-queries. &lt;a href="https://github.com/grafana/grafana" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt;, for example, uses correlated sub-queries for the auto-completion of tables and columns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction: Sub-query and Correlated sub-query
&lt;/h2&gt;

&lt;p&gt;Before we start with correlated sub-queries, we'll first have a look at how a sub-query works.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a sub-query?
&lt;/h3&gt;

&lt;p&gt;A sub-query also called an inner query or inner select, is a query embedded within another query. Let’s have a look at the following example:&lt;br&gt;&lt;br&gt;
Suppose we have a table with customers and a table that lists CrateDB clusters owned by those customers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----+-------+---------+
| id | name  | country |
+----+-------+---------+
|  1 | Anton | Austria |
|  2 | Maria | Germany |
|  3 | Anna  | Italy   |
+----+-------+---------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clusters&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----+-------------+-----------------+-------------+
| id | customer_id | number_of_nodes | name        |
+----+-------------+-----------------+-------------+
|  1 |           1 |              10 | cluster 1   |
|  2 |           2 |              15 | cluster 2   |
|  3 |           2 |               8 | cluster 3   |
|  4 |           3 |              12 | cluster 4   |
+----+-------------+-----------------+-------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want to find all the clusters owned by customers from Austria. We can first start by finding all customers from Austria:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Austria'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----+-------+
| id | name  |
+----+-------+
|  1 | Anton |
+----+-------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now for each row in clusters, we want to look up if the cluster’s &lt;code&gt;customer_id&lt;/code&gt; matches a customer where the country is Austria. Therefore we can include the first query as a sub-query into the where-clause of another query, a so-called outer-query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;clusters&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
                                &lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;customers&lt;/span&gt;
                                &lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Austria'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----+-------------------+
| id | name              |
+----+-------------------+
|  1 | cluster 1         |
+----+-------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can learn how the query is executed using the &lt;a href="https://crate.io/docs/crate/reference/en/5.1/sql/statements/explain.html" rel="noopener noreferrer"&gt;EXPLAIN&lt;/a&gt; command. With this command, we will retrieve the execution plan of the query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;clusters&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
                                &lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;customers&lt;/span&gt;
                                &lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Austria'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                        |
+------------------------------------------------------------------------------------------------+
| MultiPhase                                                                                     |
|   └ Collect[doc.clusters | [id, name] | (customer_id = ANY((SELECT id FROM (doc.customers))))] |
|   └ OrderBy[id ASC]                                                                            |
|     └ Collect[doc.customers | [id] | (country = 'Austria')]                                    |
+------------------------------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s dive into the details of the execution plan to understand what exactly is happening here. The execution plan is a tree of operators which gives us insights into how this query is handled internally. Each operator represents an operation the database will execute. We will be looking into each operation from the bottom to the top:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The operator &lt;code&gt;Collect[doc.customers]&lt;/code&gt; collects from the table &lt;code&gt;doc.customers&lt;/code&gt; the value &lt;code&gt;id&lt;/code&gt; where the query expression &lt;code&gt;(country = 'Austria')&lt;/code&gt; matches on its rows. It represents the sub-query.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The operator &lt;code&gt;OrderBy[id ASC]&lt;/code&gt; sorts the value &lt;code&gt;id&lt;/code&gt; ascending. This operator is added by the query optimizer to return the values sorted, because it make the outer-query’s filter faster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The operator &lt;code&gt;Collect[doc.clusters]&lt;/code&gt; collects the values &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt; from the table &lt;code&gt;doc.clusters&lt;/code&gt; where the &lt;code&gt;customer_id&lt;/code&gt; matches the result from the sub-query &lt;code&gt;select id from doc.customers&lt;/code&gt;. It represents the outer-query.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The operator &lt;code&gt;MultiPhase&lt;/code&gt; combines the operators &lt;code&gt;OrderBy[id ASC]&lt;/code&gt; and &lt;code&gt;Collect[doc.clusters]&lt;/code&gt;, executes them in order, and injects the result of the first operator into the second operator.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The order of execution is the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Collect all &lt;code&gt;ids&lt;/code&gt; from &lt;code&gt;doc.customers&lt;/code&gt; where the country is Austria ordered by &lt;code&gt;id&lt;/code&gt; ascending.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Collect the values &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; from &lt;code&gt;doc.cluster&lt;/code&gt; where the &lt;code&gt;customer_id&lt;/code&gt; matches the ids collected from the first operation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Return these values back to the client.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since the first operation, which represents the inner query, does not depend on any outer context, it can be executed once, standalone and the result can be injected into the outer-query.&lt;/p&gt;

&lt;h3&gt;
  
  
  So what is then a correlated sub-query?
&lt;/h3&gt;

&lt;p&gt;Imagine now the following use-case: We would like to know the total number of nodes each customer has in its clusters. Let’s try the same approach as before and break this down into multiple steps: We can first create a query retrieving the sum of all nodes for a single &lt;code&gt;customer_id&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number_of_nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clusters&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------------------+
| sum(number_of_nodes) |
+----------------------+
|                   23 |
+----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then we embed this query again as a sub-query into an outer-query to generalize it for all customers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number_of_nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;clusters&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;number_of_nodes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----+-------+-----------------+
| id | name  | number_of_nodes |
+----+-------+-----------------+
|  1 | Anton |              10 |
|  3 | Anna  |              12 |
|  2 | Maria |              23 |
+----+-------+-----------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that now the sub-query uses a reference &lt;code&gt;customers.id&lt;/code&gt; which correlates to the outer- query. That is why it's called a correlated sub-query. The result of the sub-query will be different for each value of &lt;code&gt;customers.id&lt;/code&gt; from the outer-query. Therefore, the inner-query needs to be executed for each value again. Consequently, the execution of correlated sub-queries in a database is not a straightforward task.&lt;/p&gt;

&lt;p&gt;Let’s now check the execution plan of the query using &lt;a href="https://crate.io/docs/crate/reference/en/5.1/sql/statements/explain.html" rel="noopener noreferrer"&gt;EXPLAIN&lt;/a&gt; command again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number_of_nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;clusters&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;number_of_nodes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------------------------------------------------------------------------------+
| EXPLAIN                                                                                 |
+-----------------------------------------------------------------------------------------+
| Eval[id, name, (SELECT sum(number_of_nodes) FROM (doc.clusters)) AS number_of_nodes]    |
|   └ CorrelatedJoin[id, name, (SELECT sum(number_of_nodes) FROM (doc.clusters))]         |
|     └ Collect[doc.customers | [id, name] | true]                                        |
|     └ SubPlan                                                                           |
|       └ Limit[2::bigint;0::bigint]                                                      |
|         └ HashAggregate[sum(number_of_nodes)]                                           |
|           └ Collect[doc.clusters | [number_of_nodes, customer_id] | (customer_id = id)] |
+-----------------------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s look into the execution plan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The operator &lt;code&gt;Collect[doc.clusters]&lt;/code&gt; collects from the table &lt;code&gt;doc.clusters&lt;/code&gt; the values &lt;code&gt;[number_of_nodes, customer_id]&lt;/code&gt; from the rows where &lt;code&gt;customer_id&lt;/code&gt;matches a given &lt;code&gt;id&lt;/code&gt;. It represents the data collection part of the sub-query.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The operator &lt;code&gt;HashAggregate[sum(number_of_nodes)]&lt;/code&gt; aggregates the values of &lt;code&gt;number of nodes&lt;/code&gt;. It is dependent on the previous operation. It represents the sum function of the sub-query.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The operator &lt;code&gt;Limit[2::bigint;0::bigint]&lt;/code&gt; is used as an assertion. A sub-query must return at most 1 result row, so we limit the query to 2, if there is no limit to avoid retrieval.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The operator&lt;code&gt;SubPlan&lt;/code&gt; marks the formerly described operator tree as a correlated sub-query.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The operator &lt;code&gt;Collect[doc.customers]&lt;/code&gt; collects the values &lt;code&gt;[id, name&lt;/code&gt;] from the table &lt;code&gt;doc.customers&lt;/code&gt;. It represents the outer query.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The operator&lt;code&gt;CorrelatedJoin&lt;/code&gt; performs the join between the outer-query results and the sub-query results. First, the outer query will be executed and then for each value of the result of the outer-query the sub-query will be executed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The operator&lt;code&gt;Eval[id, name]&lt;/code&gt; combines the results from the underlying &lt;code&gt;CorrelatedJoin&lt;/code&gt; and returns the values &lt;code&gt;[id, name,number_of_nodes]&lt;/code&gt; back to the client.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The order of execution is the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Collect all the values &lt;code&gt;[id, name]&lt;/code&gt; from &lt;code&gt;doc.customers&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For each of the id’s from the first operation, collect the values &lt;code&gt;[number_of_nodes, customer_id]&lt;/code&gt; where the &lt;code&gt;customer.id&lt;/code&gt; matches the &lt;code&gt;id&lt;/code&gt; values and aggregate &lt;code&gt;number_of_nodes&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Return the values &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt; and the result of the aggregation back to the client.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The inner-query is now depending on the outer context. Therefore, it has to be executed again for every single row of the outer-query. This is the state of the current implementation in CrateDB. It works, but the execution is not optimal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can we do better?
&lt;/h3&gt;

&lt;p&gt;The same query can also be expressed as a join. Relational databases are good at running joins because there is plenty of room for optimizations. The question is: How to convert a correlated-sub-query to a join?&lt;/p&gt;

&lt;p&gt;Let’s take our correlated sub-query example from the beginning again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number_of_nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;clusters&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;number_of_nodes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As mentioned above, the correlated sub-query has a reference &lt;code&gt;customers.id&lt;/code&gt; to the outer-query. Hence the query is executed each time for every row of the outer-query. Let’s have a closer look at the sub-query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number_of_nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;clusters&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sub-query selects data from the table &lt;code&gt;doc.clusters&lt;/code&gt;. So instead of utilizing the sub-query in the outer query, we can replace it with a join to the table &lt;code&gt;doc.clusters&lt;/code&gt;. The where-clause of the sub-query, which includes the reference &lt;code&gt;customers.id&lt;/code&gt; to the outer-query, becomes the join condition. We replace the sub-query in the outer-query with the aggregation function &lt;code&gt;Sum(number_of_nodes)&lt;/code&gt; from the sub-query and we finally have to add a group-by for the aggregation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;number_of_nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;number_of_nodes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;clusters&lt;/span&gt;
       &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;
         &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voilà, we converted the correlated sub-query into a join. Let’s run this query again to see if it yields the same result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----+-------+-----------------+
| id | name  | number_of_nodes |
+----+-------+-----------------+
|  3 | Anna  |              12 |
|  1 | Anton |              10 |
|  2 | Maria |              23 |
+----+-------+-----------------+

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks good. Let’s now check the logical plan using the &lt;a href="https://crate.io/docs/crate/reference/en/5.1/sql/statements/explain.html" rel="noopener noreferrer"&gt;EXPLAIN&lt;/a&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;number_of_nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;number_of_nodes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;clusters&lt;/span&gt;
       &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;
         &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------------------------------------------------------------+
| EXPLAIN                                                               |
+-----------------------------------------------------------------------+
| Eval[id, name, sum(number_of_nodes) AS number_of_nodes]               |
|   └ GroupHashAggregate[id, name | sum(number_of_nodes)]               |
|     └ HashJoin[(customer_id = id)]                                    |
|       ├ Collect[doc.clusters | [number_of_nodes, customer_id] | true] |
|       └ Collect[doc.customers | [id, name] | true]                    |
+-----------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s look into the execution plan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The operator &lt;code&gt;Collect[doc.customers]&lt;/code&gt; collects from the table &lt;code&gt;doc.customers&lt;/code&gt; the values &lt;code&gt;[id, name]&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The operator &lt;code&gt;Collect[doc.clusters]&lt;/code&gt; collects the values &lt;code&gt;[number_of_nodes, customer_id]&lt;/code&gt; from the table &lt;code&gt;doc.clusters&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The operator &lt;code&gt;HashJoin[(customer_id = id)]&lt;/code&gt; performs a hash-join on the output from the previous two operators with the join condition &lt;code&gt;customer_id = id&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The operator &lt;code&gt;GroupHashAggregate[id, name | sum(number_of_nodes)]&lt;/code&gt; aggregates the values of &lt;code&gt;number_of_nodes&lt;/code&gt; for each &lt;code&gt;[id, name]&lt;/code&gt; pair.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The operator&lt;code&gt;Eval[id, name, sum(number_of_nodes)]&lt;/code&gt; takes the results from the underlying operator tree and returns the values &lt;code&gt;[id, name,number_of_nodes]&lt;/code&gt; back to the client.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The order of execution is the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Collect the values &lt;code&gt;[id, name]&lt;/code&gt;from &lt;code&gt;doc.customers&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Collect the values &lt;code&gt;[number_of_nodes, customer_id]&lt;/code&gt; from &lt;code&gt;doc.clusters&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Join the previous two datasets together on &lt;code&gt;customer_id = id&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Aggregate the values of &lt;code&gt;number_of_nodes&lt;/code&gt; for each row together.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Return the values &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt; and the result of the aggregation back to the client.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The new execution plan is much more efficient than the previous one. The expensive execution of the correlated sub-query for each value of the outer-query is eliminated. This transformation from the correlated join to a regular join is called decorrelation. Of course, we expect that the query optimizer is clever enough to do the decorrelation for us automatically, so we don’t have to rearrange our query manually. In practice, there are more pitfalls to generalizing this concept to a broader range of queries because the queries can be much more complex. However, this gives you an idea of the concept and the further work we have to put in to integrate this into CrateDB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this post, we covered what a correlated sub-query is and how it is implemented in CrateDB 5.1. We also showed where the bottleneck and limitations are with the current approach and how we can improve this in the future.&lt;/p&gt;

&lt;p&gt;Are you interested in CrateDB? Have a look at the documentation &lt;a href="https://crate.io/docs/crate/reference/en/5.1/" rel="noopener noreferrer"&gt;CrateDB Reference&lt;/a&gt;. If you have questions, check out our &lt;a href="https://community.crate.io/" rel="noopener noreferrer"&gt;CrateDB Community&lt;/a&gt;. We're happy to help!&lt;/p&gt;

</description>
      <category>networking</category>
      <category>devops</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Guide to write operations in CrateDB</title>
      <dc:creator>marijaselakovic</dc:creator>
      <pubDate>Wed, 18 Jan 2023 12:08:03 +0000</pubDate>
      <link>https://forem.com/crate/guide-to-write-operations-in-cratedb-2jp0</link>
      <guid>https://forem.com/crate/guide-to-write-operations-in-cratedb-2jp0</guid>
      <description>&lt;p&gt;In our &lt;a href="https://crate.io/blog/indexing-and-storage-in-cratedb" rel="noopener noreferrer"&gt;previous article&lt;/a&gt;, we gave a general overview of the storage layer in CrateDB. In general, every shard in CrateDB represents a Lucene index that is broken down into segments and stored in the filesystem. A Lucene segment can be seen as a sub-index and can be searched independently.&lt;/p&gt;

&lt;p&gt;When new records are written in CrateDB, Lucene first creates in-memory segments before flushing them to the disk. In this article, our goal is to give you a throughout understanding of how CrateDB writes new records. With that in mind, we will go through the basic concepts of Lucene, such as Lucene segments, refresh and flush operation and introduce the concept of translog that guarantees that write operations are persistent to disk.&lt;/p&gt;

&lt;p&gt;Lucene segments&lt;br&gt;
The Lucene segment is a part of the logical Lucene index and the Lucene index maps 1-1 to a CrateDB shard. It is an independent index, that can contain inverted indexes, k-d trees, and doc values. A Lucene segment can be searched independently of other segments and documents in the segments are immutable. Every time a field of a document is updated, the document is flagged as deleted in the segment it belonged, and the updated document is added to a new segment. The same behavior applies when a document is deleted in CrateDB. All subsequent queries will skip all the documents that were previously marked as deleted.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1geglzl4dnfd2fwfs2cz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1geglzl4dnfd2fwfs2cz.png" alt="Lucene index and segments" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To keep the number of segments manageable, Lucene periodically merges them according to some merge policy. When segments are merged, documents that are marked as deleted are discarded and newly created segments will contain only valid, non-deleted documents from the original segments. Merge is triggered when new documents are added and maybe surprisingly, this can result in a smaller index size. To merge segments of a table or a partition on demand one can use the &lt;code&gt;OPTIMIZE TABLE&lt;/code&gt; command in CrateDB. When the &lt;code&gt;max_num_segments&lt;/code&gt; parameter is set to 1, CrateDB will fully merge the table or partition for optimal query performance. The more details on table optimization in CrateDB, check out our &lt;a href="https://crate.io/docs/crate/reference/en/5.1/sql/statements/optimize.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retention period
&lt;/h3&gt;

&lt;p&gt;During the recovery of nodes, to speed up the process, CrateDB needs to replay operations that were executed on one shard on other shards (replicas), residing on different nodes. To preserve the recent deletions within the Lucene index, CrateDB supports the concept of &lt;a href="https://crate.io/docs/crate/reference/en/5.1/sql/statements/create-table.html#soft-deletes-enabled" rel="noopener noreferrer"&gt;&lt;em&gt;soft delete&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt; Because of that, deleted documents still take up disk space, which is why CrateDB keeps only recently deleted documents. It is possible to customize the &lt;em&gt;retention lease period&lt;/em&gt; that says for how long the deleted documents have to be preserved. CrateDB will discard documents only after the expiration of this period. This means that if the merge operation takes place before the expiration, deleted documents will still remain physically available. The default retention lease period is 12 hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing data to CrateDB
&lt;/h2&gt;

&lt;p&gt;The following diagram illustrates how a new record is stored in CrateDB. When a new document arrives it is first committed to a &lt;em&gt;memory buffer&lt;/em&gt; and &lt;em&gt;translog&lt;/em&gt; (step 1). Documents in the memory buffer will become in-memory Lucene segments after &lt;em&gt;the refresh&lt;/em&gt; operation takes place (step 2). CrateDB executes &lt;a href="https://crate.io/docs/crate/reference/en/5.1/general/dql/refresh.html#introduction" rel="noopener noreferrer"&gt;a refresh operation&lt;/a&gt; every second if a search request is received in the last 30 seconds. Eventually, Lucene will commit new segments to the disk (step 3). Once the data are stored on the disk, the merge operation will get triggered and some of the segments will be merged.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5l60b68z287x1w8l6u2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5l60b68z287x1w8l6u2w.png" alt="Flow of write operation" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Translog
&lt;/h3&gt;

&lt;p&gt;Translog stores write operations for documents that are in memory and that have not been committed. A translog exists for each shard and it is stored in the physical disk. In case of a node failure, CrateDB can retrieve the potentially lost data by replaying the operations from the translog (step 4).&lt;/p&gt;

&lt;p&gt;The data in translog are persisted to disk when the translog is &lt;code&gt;fsynced&lt;/code&gt; and committed. The default behavior of CrateDB implies that the translog is flushed to the disk after every operation. Additionally, it is possible to flush the translog every &lt;code&gt;translog.sync_interval&lt;/code&gt; which can be controlled by &lt;code&gt;translog.durability&lt;/code&gt; parameter.&lt;/p&gt;

&lt;p&gt;The following parameters control the behavior of the translog:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;translog.flush_threshold_size&lt;/code&gt; sets the size of the transaction log containing the operations that are not yet safely persisted. This is done to prevent recoveries from taking too long.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;translog.interval&lt;/code&gt; sets the frequency of flush necessity check.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;translog.durability&lt;/code&gt; can be set as &lt;code&gt;ASYNC&lt;/code&gt; or &lt;code&gt;REQUEST&lt;/code&gt;. When set to &lt;code&gt;ASYNC&lt;/code&gt; the translog is flushed every &lt;code&gt;translog.sync_interval&lt;/code&gt;, and when set to &lt;code&gt;REQUEST&lt;/code&gt; the flush happens after every operation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;translog.sync_interval&lt;/code&gt; sets how often the translog is &lt;code&gt;fsynced&lt;/code&gt; to disk. The default value is 5s. The setting of this parameter takes effect only if the &lt;code&gt;translog.durability&lt;/code&gt; is set to &lt;code&gt;ASYNC&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Refresh
&lt;/h3&gt;

&lt;p&gt;Between CrateDB and the disk, there is RAM memory. New records are first buffered in memory before being written to a new segment. Refresh operation makes the in-memory segment available for search. Furthermore, a table can be refreshed explicitly to ensure that the latest state of the table is fetched. In CrateDB this is done with the &lt;code&gt;REFRESH TABLE&lt;/code&gt; command. However, the refresh operation doesn’t guarantee durability: to address the persistence issue, CrateDB relies on &lt;em&gt;translog&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If not done explicitly, the table is refreshed with a specified refresh interval. The default value is one second but the interval can be changed with the table parameter &lt;code&gt;refresh_interval&lt;/code&gt;. If no query accesses the table during the refresh interval, the table becomes &lt;em&gt;idle&lt;/em&gt;. When the table is in an idle state it will not be refreshed until the next query arrives which will first refresh the table and then execute. This will also enable the periodic refresh again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flush
&lt;/h3&gt;

&lt;p&gt;A flush operation triggers a Lucene commit: it writes the segments permanently on the disk and clears the transaction log. Writing segments to disk is expensive and takes place at less frequent intervals than the refresh operation. However, a flush happens automatically based on the translog configurations as illustrated in the previous section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;To summarize, this article provides an overview on how data is written to CrateDB. The following properties of CrateDB are important to be aware of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A Lucene index is organized in segments which are first built and kept in memory and later flushed to disk. This can influence the time when data becomes available on disk.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In-memory segments become searchable after the refresh operation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Documents are immutable and deleted documents are not discarded until a merge takes place.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Segments are occasionally merged and deleted documents are discarded.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If &lt;code&gt;translog.durability&lt;/code&gt; is set to &lt;code&gt;REQUEST&lt;/code&gt;, the translog is flushed after every operation. Otherwise, if set to &lt;code&gt;ASYNC&lt;/code&gt;, the translog is flushed when certain configurable conditions are met.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CrateDB maintains the transaction log of operations on each shard for recovery in case of a node crash.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We hope you found this topic interesting. Do you want to learn more about CrateDB? Take a look at our &lt;a href="https://crate.io/docs" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; and in case of any questions, check out our &lt;a href="https://community.crate.io/" rel="noopener noreferrer"&gt;CrateDB Community.&lt;/a&gt; We're happy to help!&lt;br&gt;
We hope you found this topic interesting. Do you want to learn more about CrateDB? Take a look at our documentation and in case of any questions, check out our CrateDB Community. We're happy to help!&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>career</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Working with cursors with SQL commands in CrateDB</title>
      <dc:creator>marijaselakovic</dc:creator>
      <pubDate>Wed, 18 Jan 2023 11:53:34 +0000</pubDate>
      <link>https://forem.com/crate/working-with-cursors-with-sql-commands-in-cratedb-33pl</link>
      <guid>https://forem.com/crate/working-with-cursors-with-sql-commands-in-cratedb-33pl</guid>
      <description>&lt;p&gt;As a distributed database system with support for the standard SQL query language, CrateDB is great to run aggregations server-side, working on huge datasets, and getting summarized results back; there are however cases where we may still want to retrieve lots of data from CrateDB.&lt;/p&gt;

&lt;p&gt;Getting a large number of results in a single operation can put stress on the network and other resources, both client and server-side, so we need a mechanism to fetch results in manageable batches as we are ready to process them.&lt;/p&gt;

&lt;p&gt;A common requirement is also what is called pagination, which is to present results to users with pages with a set number of results each, allowing them to move between these pages. In this case, it is common that many users will only look at the first few pages of results, so we want to implement that in the most efficient way possible.&lt;/p&gt;

&lt;p&gt;This can be done using cursors, a cursor is like having a bookmark pointing to a specific record in the result set of a query.&lt;/p&gt;

&lt;p&gt;In CrateDB we were already able to use cursors at the protocol level, but with version 5.1.1 we have introduced the ability to do it with standard SQL commands.&lt;/p&gt;

&lt;p&gt;First we issue this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a SQL command that would normally start a transaction, there are no transactions as such in CrateDB, but this will create a scope on which cursors can be created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="n"&gt;observations_cursor&lt;/span&gt; &lt;span class="k"&gt;NO&lt;/span&gt; &lt;span class="k"&gt;SCROLL&lt;/span&gt; &lt;span class="k"&gt;CURSOR&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; 
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;reading&lt;/span&gt; 
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observations&lt;/span&gt; 
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01 00:00'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="s1"&gt;'2022-10-01 00:00'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This associates a cursor name with a query and determines the point in time at which data is “frozen”, this is because cursors in CrateDB are &lt;code&gt;INSENSITIVE&lt;/code&gt;, meaning that the client can take all the time it needs to retrieve the results and the data will always reflect the status of the tables as it was at the time the cursor was declared, ignoring any records that are updated, deleted, or newly inserted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;FETCH&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;observations_cursor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This retrieves 10 rows from the query, and when issued again it will retrieve the next 10 rows and so on. We can retrieve different number of records each time and we know we have reached the end of the result set when &lt;code&gt;FETCH&lt;/code&gt; returns zero rows.&lt;/p&gt;

&lt;p&gt;Once the cursor is not needed anymore it can be closed with either &lt;code&gt;END&lt;/code&gt;, &lt;code&gt;CLOSE&lt;/code&gt;, or &lt;code&gt;COMMIT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Take a look at this short animation showing an example of how this works: &lt;iframe class="tweet-embed" id="tweet-1572256514798424066-541" src="https://platform.twitter.com/embed/Tweet.html?id=1572256514798424066"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1572256514798424066-541');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1572256514798424066&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;We hope you find this useful, and we will be happy to hear about your experience in the &lt;a href="https://community.crate.io/" rel="noopener noreferrer"&gt;Community&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>watercooler</category>
    </item>
  </channel>
</rss>
