<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Angelico</title>
    <description>The latest articles on DEV Community by Angelico (@dragarcia).</description>
    <link>https://dev.to/dragarcia</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%2F362763%2F76d5c07f-5139-428c-bebd-1fd7dbc39bcf.jpeg</url>
      <title>DEV Community: Angelico</title>
      <link>https://dev.to/dragarcia</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dragarcia"/>
    <language>en</language>
    <item>
      <title>TIL Postgres: Views &amp; Materialized Views</title>
      <dc:creator>Angelico</dc:creator>
      <pubDate>Wed, 18 Nov 2020 05:06:19 +0000</pubDate>
      <link>https://dev.to/supabase/til-postgres-views-materialized-views-317o</link>
      <guid>https://dev.to/supabase/til-postgres-views-materialized-views-317o</guid>
      <description>&lt;h2&gt;
  
  
  What is a View?
&lt;/h2&gt;

&lt;p&gt;To put it simply, a &lt;a href="https://www.postgresql.org/docs/12/sql-createview.html" rel="noopener noreferrer"&gt;view&lt;/a&gt; is a convenient shortcut to a query. Creating a view does not involve any new tables or data. When ran, the underlying query is executed, returning its results to the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Example
&lt;/h3&gt;

&lt;p&gt;Say we have the following tables from a database of a university:&lt;/p&gt;

&lt;h4&gt;
  
  
  students
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Arun&lt;/td&gt;
&lt;td&gt;undergraduate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Zack&lt;/td&gt;
&lt;td&gt;graduate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Joy&lt;/td&gt;
&lt;td&gt;graduate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  courses
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;title&lt;/th&gt;
&lt;th&gt;code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Introduction to Postgres&lt;/td&gt;
&lt;td&gt;PG101&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Authentication Theories&lt;/td&gt;
&lt;td&gt;AUTH205&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Fundamentals of Supabase&lt;/td&gt;
&lt;td&gt;SUP412&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  grades
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;student_id&lt;/th&gt;
&lt;th&gt;course_id&lt;/th&gt;
&lt;th&gt;result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;B+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;A+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;A-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;B-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Creating a view consisting of all the three tables will look 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;CREATE&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;transcripts&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;students&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;students&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;courses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;courses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;grades&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;grades&lt;/span&gt;
    &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;grades&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;students&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;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;courses&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;grades&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;courses&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once done, we can now easily access the underlying query 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;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;transcripts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For additional parameters or options, refer &lt;a href="https://www.postgresql.org/docs/12/sql-createview.html#:~:text=parameters" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should we use it?
&lt;/h2&gt;

&lt;p&gt;Views are highly useful for reading data based on the following aspects:&lt;/p&gt;

&lt;h3&gt;
  
  
  Simplicity
&lt;/h3&gt;

&lt;p&gt;As a query become complex and begins to occupy multiple lines, it becomes a hassle to call it. It becomes even more apparent when we run it at regular intervals. In the example above, instead of repeatedly running:&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;students&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;students&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;courses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;courses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;grades&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;result&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;grades&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;grades&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;students&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;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;courses&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;grades&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;courses&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can run this instead:&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;transcripts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, a view behaves like a typical table. As such, we can safely use it for any subsequent &lt;code&gt;JOIN&lt;/code&gt;s or even create a view from a query that already involves another view.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consistency
&lt;/h3&gt;

&lt;p&gt;Along with its simplicity, a view brings along consistency that ensures that the likelihood of mistakes decreases when repeatedly executing a query. With reference to the query above, it could be a part of other queries. One day, we may decide that we want to exclude the course &lt;em&gt;Introduction to Postgres&lt;/em&gt;. The above query would become:&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;students&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;students&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;courses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;courses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;grades&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;result&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;grades&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;grades&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;students&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;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;courses&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;grades&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;courses&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;WHERE&lt;/span&gt; &lt;span class="n"&gt;courses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'PG101'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without a view, we would need to go into every single dependent query to add the new rule. By doing this, there will be an increase in the likelihood of errors and inconsistencies arising from typos and missing out on dependent queries. With views, we would need to just alter the underlying query in the view &lt;strong&gt;transcripts&lt;/strong&gt;. The change will be applied to any other queries using this view.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logical Organization
&lt;/h3&gt;

&lt;p&gt;With views, we can give our query a name. Doing this is extremely useful in teams working on the same database. Instead of trying to guess what a query is meant to do, having it as a well-named view can easily explain it. For example, by looking at the name of the view &lt;strong&gt;transcripts&lt;/strong&gt;, we can infer that the underlying query could involve the &lt;strong&gt;students&lt;/strong&gt;, &lt;strong&gt;courses&lt;/strong&gt;, and &lt;strong&gt;grades&lt;/strong&gt; tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;p&gt;Using views can also restrict the amount and type of data presented to a user. Instead of allowing a user direct access to a set of tables, we provide them a view instead. With it, we can prevent them from reading sensitive columns by not including them in the underlying query.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Materialized View?
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://www.postgresql.org/docs/12/rules-materializedviews.html" rel="noopener noreferrer"&gt;materialized view&lt;/a&gt; is a form of view but with the added feature of physically storing its resultant data into storage. Given the same underlying query, in subsequent reads of a materialized view, the time taken to return its results would be much faster than that of the conventional view. And this is because the data is readily available for a materialized view while the typical view only executes the underlying query on the spot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Example
&lt;/h3&gt;

&lt;p&gt;Using the same set of tables and underlying query as the above, a new materialized view will look 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;CREATE&lt;/span&gt; &lt;span class="n"&gt;MATERIALIZED&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;transcripts&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;students&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;students&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;courses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;courses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;grades&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;grades&lt;/span&gt;
    &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;students&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;grades&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;student_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;students&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;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;courses&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;grades&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;course_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;courses&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Afterward, reading the materialized view can be done as such:&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;transcripts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For additional parameters or options, refer &lt;a href="https://www.postgresql.org/docs/12/sql-creatematerializedview.html#:~:text=parameters" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Refreshing
&lt;/h3&gt;

&lt;p&gt;Unfortunately, there is a trade-off - data in materialized views are not always up to date. We would need to refresh it regularly to prevent the data from becoming too stale. To do 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="n"&gt;REFRESH&lt;/span&gt; &lt;span class="n"&gt;MATERIALIZED&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;transcripts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When should we use this over the conventional view?
&lt;/h2&gt;

&lt;p&gt;Materialized views come in handy when execution times for queries or views become unbearable or exceed the service level agreements of a business. These could likely occur in views or queries involving multiple tables and hundreds of thousands of rows. When using such a view, however, there should be tolerance towards data being not up to date. We should schedule refreshes regularly to ensure that data does not become too outdated over time.&lt;/p&gt;

&lt;p&gt;It is to note that creating a materialized view is not a solution to inefficient queries. All options to optimize a slow running query should be exhausted before implementing a materialized view.&lt;/p&gt;

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

&lt;p&gt;Postgres views and materialized views are a great way to organize and view results from commonly used queries. Although highly similar to one another, each has its purpose. Views simplify the process of running queries. Materialized views add on to this by speeding up the process of accessing slower running queries at the trade-off of having stale or not up-to-date data.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TIL Postgres&lt;/strong&gt; is an ongoing series by &lt;a href="https://supabase.io" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt; that aims to regularly share snippets of information about PostgreSQL and hopefully provide you with something new to learn. 😉&lt;/p&gt;

&lt;p&gt;Any new posts or announcements of our future features and freebies will be made here on DEV first. Follow us so that you don't miss out.&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%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--uyA__P-6--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_66%252Cw_880%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjf7adc5e3kbdu4luxvt4.gif" 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%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--uyA__P-6--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_66%252Cw_880%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjf7adc5e3kbdu4luxvt4.gif" alt="follow gif" width="800" height="400"&gt;&lt;/a&gt; &lt;/p&gt;

</description>
      <category>todayilearned</category>
      <category>postgres</category>
      <category>supabase</category>
    </item>
    <item>
      <title>TIL Postgres: Setting up WAL-G</title>
      <dc:creator>Angelico</dc:creator>
      <pubDate>Tue, 04 Aug 2020 12:45:45 +0000</pubDate>
      <link>https://dev.to/supabase/til-postgres-setting-up-wal-g-1i0g</link>
      <guid>https://dev.to/supabase/til-postgres-setting-up-wal-g-1i0g</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/wal-g/wal-g" rel="noopener noreferrer"&gt;WAL-G&lt;/a&gt; is an &lt;a href="https://www.citusdata.com/blog/2017/08/18/introducing-wal-g-faster-restores-for-postgres/" rel="noopener noreferrer"&gt;open-source continuous archiving tool&lt;/a&gt; used to easily set up and recover from &lt;a href="https://dev.to/supabase/til-postgres-logical-vs-physical-backups-218b"&gt;physical backups&lt;/a&gt; in Postgres. It mainly handles the storage and retrieval of physical backups and WAL archives to and from a chosen cloud storage provider. In this week's edition of &lt;strong&gt;TIL Postgres&lt;/strong&gt;, we will walk you through on how to effortlessly set up WAL-G for your database as well as guide you on what to do if and when disaster strikes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;For this tutorial, we will be using two instances running Postgres databases on &lt;a href="https://releases.ubuntu.com/18.04/" rel="noopener noreferrer"&gt;Ubuntu 18.04&lt;/a&gt;. One instance will act as your main database, the other is your recovery database. Do note that, if you’re using another operating system some file paths may vary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installations
&lt;/h3&gt;

&lt;p&gt;Make sure the below packages are installed in your instances. Alternatively, you can spin up the &lt;a href="https://github.com/supabase/postgres/releases/tag/v0.13.0" rel="noopener noreferrer"&gt;latest version&lt;/a&gt; of &lt;a href="https://github.com/supabase/postgres" rel="noopener noreferrer"&gt;Supabase Postgres&lt;/a&gt; which would already have everything configured and installed, along with other &lt;a href="https://github.com/supabase/postgres#features" rel="noopener noreferrer"&gt;goodies&lt;/a&gt;. It is readily available in either the &lt;a href="https://aws.amazon.com/marketplace/pp/B08915TCJ2?qid=1595854723755&amp;amp;sr=0-1&amp;amp;ref_=srh_res_product_title" rel="noopener noreferrer"&gt;AWS&lt;/a&gt; or &lt;a href="https://marketplace.digitalocean.com/apps/supabase-postgres" rel="noopener noreferrer"&gt;Digital Ocean&lt;/a&gt; marketplaces and only takes &lt;a href="https://dev.to/supabase/getting-a-postgresql-server-up-and-running-in-digital-ocean-within-minutes-8kd"&gt;a few minutes&lt;/a&gt; to get running.&lt;/p&gt;

&lt;h4&gt;
  
  
  Postgres 12
&lt;/h4&gt;

&lt;p&gt;A quick installation guide can be found &lt;a href="https://www.postgresql.org/download/linux/ubuntu/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  envdir
&lt;/h4&gt;

&lt;p&gt;&lt;a href="http://manpages.ubuntu.com/manpages/bionic/man8/envdir.8.html" rel="noopener noreferrer"&gt;envdir&lt;/a&gt; allows us to run other programs with a modified environment based on the files in the provided directory. This can be installed through the &lt;a href="https://cr.yp.to/daemontools.html" rel="noopener noreferrer"&gt;daemontools&lt;/a&gt; package:&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; daemontools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  WAL-G
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;wget https://github.com/wal-g/wal-g/releases/download/v0.2.15/wal-g.linux-amd64.tar.gz
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-zxvf&lt;/span&gt; wal-g.linux-amd64.tar.gz
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mv &lt;/span&gt;wal-g /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AWS credentials and resources
&lt;/h3&gt;

&lt;p&gt;When storing backups, WAL-G has numerous &lt;a href="https://github.com/wal-g/wal-g#configuration" rel="noopener noreferrer"&gt;cloud storage provider options&lt;/a&gt; for us to choose from. For this tutorial, we will be using AWS. Have the following prepared:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Access &amp;amp; Secret keys.&lt;/li&gt;
&lt;li&gt;An S3 bucket.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  1. Configure environment variables
&lt;/h3&gt;

&lt;p&gt;The directory &lt;code&gt;/etc/wal-g.d/env&lt;/code&gt; is created and contains files that stores environment variables. It would later be used in WAL-G commands via envdir.&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;umask &lt;/span&gt;&lt;span class="nv"&gt;u&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rwx,g&lt;span class="o"&gt;=&lt;/span&gt;rx,o&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /etc/wal-g.d/env
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'secret-key-content'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/wal-g.d/env/AWS_SECRET_ACCESS_KEY
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'access-key'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/wal-g.d/env/AWS_ACCESS_KEY_ID
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'s3://backup-bucket/project-directory'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/wal-g.d/env/WALG_S3_PREFIX
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'db password'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/wal-g.d/env/PGPASSWORD
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; root:postgres /etc/wal-g.d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Enable WAL archiving
&lt;/h3&gt;

&lt;p&gt;Here, we enable &lt;a href="https://www.postgresql.org/docs/12/continuous-archiving.html" rel="noopener noreferrer"&gt;WAL archiving&lt;/a&gt; and instruct Postgres to store the archives in the specified S3 bucket via WAL-G.&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"archive_mode = yes"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/postgresql/12/main/postgresql.conf
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"archive_command = 'envdir /etc/wal-g.d/env /usr/local/bin/wal-g wal-push %p'"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/postgresql/12/main/postgresql.conf
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"archive_timeout = 60"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/postgresql/12/main/postgresql.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Restart the database
&lt;/h3&gt;

&lt;p&gt;The database is restarted to let the changes in the configuration to take effect.&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; /etc/init.d/postgresql restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Create your first physical backup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-su&lt;/span&gt; postgres envdir /etc/wal-g.d/env /usr/local/bin/wal-g backup-push /var/lib/postgresql/12/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, if you were to check the S3 path that you provided, the following two newly created and populated directories would be observed:&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%2Fi%2Flai1mxg62kffyd2khmtm.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%2Fi%2Flai1mxg62kffyd2khmtm.png" alt="Alt Text" width="486" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From then on, subsequent physical backups would be found in the directory &lt;code&gt;basebackups_005&lt;/code&gt; and any WAL archives would be sent to the directory &lt;code&gt;wal_005&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. [Optional] Schedule regular physical backups
&lt;/h3&gt;

&lt;p&gt;A CRON job can then be set to schedule physical backups to be performed everyday:&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"0 0 * * * postgres /usr/bin/envdir /etc/wal-g.d/env /usr/local/bin/wal-g backup-push /var/lib/postgresql/12/main"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/cron.d/pg_backup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the instance has been instructed to back up the database at the start of each day at midnight. By physically backing up your instance regularly, overall recovery time could be faster. Restoring from a physical backup from yesterday would lead to fewer WAL archive files to be replayed as compared to restoring from one from a month ago.&lt;/p&gt;




&lt;h2&gt;
  
  
  Disaster strikes
&lt;/h2&gt;

&lt;p&gt;Something goes wrong with the database or instance. We will now use what available physical backups we have in the S3 bucket to recover and restore all of our data on to a new instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Configure environment variables
&lt;/h3&gt;

&lt;p&gt;The configuration should be the &lt;strong&gt;same&lt;/strong&gt; as the original instance. For recovery and restoration, we would not need the variable &lt;code&gt;PGPASSWORD&lt;/code&gt;.&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;umask &lt;/span&gt;&lt;span class="nv"&gt;u&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rwx,g&lt;span class="o"&gt;=&lt;/span&gt;rx,o&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /etc/wal-g.d/env
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'secret-key-content'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/wal-g.d/env/AWS_SECRET_ACCESS_KEY
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'access-key'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/wal-g.d/env/AWS_ACCESS_KEY_ID
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'s3://backup-bucket/project-directory'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/wal-g.d/env/WALG_S3_PREFIX
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; root:postgres /etc/wal-g.d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Stop the database
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; /etc/init.d/postgresql stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Switch to the user &lt;code&gt;postgres&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-su&lt;/span&gt; postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Prepare the database for recovery
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Set restore_command
&lt;/h4&gt;

&lt;p&gt;Through &lt;a href="https://www.postgresql.org/docs/12/continuous-archiving.html#:~:text=must%20specify%20is%20the%20restore_command,%20which%20tells%20PostgreSQL%20how%20to%20retrieve%20archived%20WAL%20file%20segments" rel="noopener noreferrer"&gt;restore_command&lt;/a&gt;, we instruct Postgres to pull all WAL archives from our S3 bucket to use during recovery.&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"restore_command = '/usr/bin/envdir /etc/wal-g.d/env /usr/local/bin/wal-g wal-fetch &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;%f&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;%p&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt;&amp;gt; /tmp/wal.log 2&amp;gt;&amp;amp;1'"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/postgresql/12/main/postgresql.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  [Optional] Achieve Point in Time Recovery (PITR)
&lt;/h4&gt;

&lt;p&gt;If we want to restore the database only up to a certain point in time (eg. right before the disaster), we can do so by setting both &lt;a href="https://www.postgresql.org/docs/12/runtime-config-wal.html#:~:text=recovery_target_time%20(timestamp)" rel="noopener noreferrer"&gt;recovery_target_time&lt;/a&gt; and &lt;a href="https://www.postgresql.org/docs/12/runtime-config-wal.html#:~:text=recovery_target_action%20(enum)" rel="noopener noreferrer"&gt;recovery_target_action&lt;/a&gt;. Do note that the timezone would need to match that of the original instance. This is usually at the UTC (+00) timezone.&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"recovery_target_time = '2020-07-27 01:23:00.000000+00'"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/postgresql/12/main/postgresql.conf
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"recovery_target_action = 'promote'"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/postgresql/12/main/postgresql.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Restore from physical backup
&lt;/h3&gt;

&lt;p&gt;The current data directory is deleted and is replaced with the latest version of the physical backup from the S3 bucket.&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/postgresql/12/main
&lt;span class="nv"&gt;$ &lt;/span&gt;envdir /etc/wal-g.d/env /usr/local/bin/wal-g backup-fetch /var/lib/postgresql/12/main LATEST
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Create a &lt;code&gt;recovery.signal&lt;/code&gt; file
&lt;/h3&gt;

&lt;p&gt;This file &lt;a href="https://www.postgresql.org/docs/12/continuous-archiving.html#:~:text=Set%20recovery%20configuration%20settings%20in%20postgresql.conf%20(see%20Section%2019.5.4)%20and%20create%20a%20file%20recovery.signal%20in%20the%20cluster%20data%20directory" rel="noopener noreferrer"&gt;instructs&lt;/a&gt; Postgres that the database should undergo recovery mode upon start.&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;touch&lt;/span&gt; /var/lib/postgresql/12/main/recovery.signal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Log out of &lt;code&gt;postgres&lt;/code&gt; and start the database
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; /etc/init.d/postgresql start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once Postgres finishes starting up and completes recovery mode, all data or data up to the specified point in time would have been successfully restored on to the new instance. Disaster averted.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TIL Postgres&lt;/strong&gt; is an ongoing series by &lt;a href="https://supabase.io" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt; that aims to regularly share snippets of information about PostgreSQL and hopefully provide you with something new to learn. 😉&lt;/p&gt;

&lt;p&gt;Any new posts or announcements of our future features and freebies will be made here on DEV first. Follow us so that you don't miss out.&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%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--uyA__P-6--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_66%252Cw_880%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjf7adc5e3kbdu4luxvt4.gif" 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%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--uyA__P-6--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_66%252Cw_880%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjf7adc5e3kbdu4luxvt4.gif" alt="follow gif" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>todayilearned</category>
      <category>postgres</category>
      <category>supabase</category>
    </item>
    <item>
      <title>TIL Postgres: Logical vs. Physical Backups</title>
      <dc:creator>Angelico</dc:creator>
      <pubDate>Fri, 17 Jul 2020 09:12:30 +0000</pubDate>
      <link>https://dev.to/supabase/til-postgres-logical-vs-physical-backups-218b</link>
      <guid>https://dev.to/supabase/til-postgres-logical-vs-physical-backups-218b</guid>
      <description>&lt;p&gt;As anyone who has been put in charge of a database knows, setting up backups and recovering from them are perhaps essential components of one's skillset. You probably would have already tried one or more of the many tools and ways in Postgres to achieve them, some of which we will cover in future posts. All can be categorized into two types of backups: &lt;em&gt;logical&lt;/em&gt; and &lt;em&gt;physical&lt;/em&gt;. In this week's edition of &lt;strong&gt;TIL Postgres&lt;/strong&gt;, we will take a quick look at each one and discuss the situations in which they are best used for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logical Backups
&lt;/h2&gt;

&lt;p&gt;This form of backup is typically achieved by &lt;a href="https://www.postgresql.org/docs/current/backup-dump.html" rel="noopener noreferrer"&gt;translating all the data into a set of SQL commands and writing it into a single file&lt;/a&gt;. This can then be fed to any database cluster to recreate everything. In your CLI, performing logical backups can be as easy as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pg_dump db_name &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; file_name.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for a single database, and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pg_dumpall &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; file_name.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for an entire database cluster. Both the &lt;a href="https://www.postgresql.org/docs/current/app-pgdump.html" rel="noopener noreferrer"&gt;pg_dump&lt;/a&gt; and &lt;a href="https://www.postgresql.org/docs/current/app-pg-dumpall.html" rel="noopener noreferrer"&gt;pg_dumpall&lt;/a&gt; utilities have their respective additional options for you to choose from and set up your desired logical backup setting. Recovering from them is comparably as simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-d&lt;/span&gt; db_name &lt;span class="nt"&gt;-f&lt;/span&gt; file_name.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What is it good for?
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Simpler and quicker way of performing backups
&lt;/h4&gt;

&lt;p&gt;As shown above, a single command is enough to perform a logical backup, and another to recover from it. As a novice with databases, this would be an ideal and non-intimidating start in making sure that your database is backed up at all times.&lt;/p&gt;

&lt;h4&gt;
  
  
  Migration between different major versions of Postgres
&lt;/h4&gt;

&lt;p&gt;If you are planning to migrate to a different major version of Postgres (for eg. from Postgres 11 to Postgres 12), logical backups via &lt;code&gt;pg_dumpall&lt;/code&gt; would surely be your &lt;a href="https://www.postgresql.org/docs/current/upgrading.html" rel="noopener noreferrer"&gt;tool of choice&lt;/a&gt;. This is mainly because internal data storage formats may differ between major versions. This is the basis of physical backups, hence eliminating it as an option. We'll go deeper into migrations and how to perform them in another post.&lt;/p&gt;

&lt;h4&gt;
  
  
  Backing up a single specific database
&lt;/h4&gt;

&lt;p&gt;With &lt;code&gt;pg_dump&lt;/code&gt;, you can constantly just back up the database of your choice without having to think about the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Physical Backups
&lt;/h2&gt;

&lt;p&gt;Physical backups pertain more to the actual set of files or file systems where all your data are stored. Performing a backup can just involve &lt;a href="https://www.postgresql.org/docs/current/backup-file.html" rel="noopener noreferrer"&gt;taking a snapshot&lt;/a&gt; of all the files involved by making a copy of them and storing it somewhere safe.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is it good for?
&lt;/h3&gt;

&lt;h4&gt;
  
  
  More ideal for larger databases
&lt;/h4&gt;

&lt;p&gt;As your database grows to the size of a few gigabytes, backing it up through physical backups is more ideal than through logical backups. As explained &lt;a href="https://devcenter.heroku.com/articles/heroku-postgres-data-safety-and-continuous-protection#the-performance-impact-of-logical-backups" rel="noopener noreferrer"&gt;here&lt;/a&gt;, over time, performing logical backups in large databases could lead to degraded performance for other queries. Given the long run time as well to successfully perform a logical backup on a large database, errors have a higher chance of occurring, making the eventual backup unusable.&lt;/p&gt;

&lt;h4&gt;
  
  
  Achieving Point in Time Recovery
&lt;/h4&gt;

&lt;p&gt;Another form of physical backup called &lt;a href="https://www.postgresql.org/docs/current/wal-intro.html" rel="noopener noreferrer"&gt;Write Ahead Log&lt;/a&gt; (WAL) files can be used together with a backed-up file system to &lt;a href="https://www.postgresql.org/docs/current/continuous-archiving.html" rel="noopener noreferrer"&gt;recover a database up to any chosen point in time&lt;/a&gt;. When disaster strikes, this would be one of the best options in recreating a database up to the point right before the unfortunate happens. This greatly minimizes &lt;a href="https://www.ibm.com/services/business-continuity/rpo" rel="noopener noreferrer"&gt;Recovery Point Objective (RPO)&lt;/a&gt; along the way. Even better, a lot of tools such as &lt;a href="https://github.com/wal-g/wal-g" rel="noopener noreferrer"&gt;WAL-G&lt;/a&gt; are readily available to simplify the steps involved in setting everything up.&lt;/p&gt;

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

&lt;p&gt;All in all, logical and physical backups are generated differently from one another. Neither has an advantage over the other. Depending on your needs, each brings unique uses to the table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Logical&lt;/th&gt;
&lt;th&gt;Physical&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simpler way of getting started with backups.&lt;/td&gt;
&lt;td&gt;Better way of handling backups for larger database clusters.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Using it to migrate between different major versions of Postgres.&lt;/td&gt;
&lt;td&gt;Using it for Point in Time Recovery.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Having the option to back up a single database.&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;strong&gt;TIL Postgres&lt;/strong&gt; is an ongoing series by &lt;a href="https://supabase.io" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt; that aims to regularly share snippets of information about PostgreSQL and hopefully provide you with something new to learn. 😉&lt;/p&gt;

&lt;p&gt;Any new posts or announcements of our future features and freebies will be made here on DEV first. Follow us so that you don't miss out.&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%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--uyA__P-6--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_66%252Cw_880%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjf7adc5e3kbdu4luxvt4.gif" 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%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--uyA__P-6--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_66%252Cw_880%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjf7adc5e3kbdu4luxvt4.gif" alt="follow gif" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>todayilearned</category>
      <category>postgres</category>
      <category>supabase</category>
    </item>
    <item>
      <title>TIL Postgres: Template Databases</title>
      <dc:creator>Angelico</dc:creator>
      <pubDate>Thu, 09 Jul 2020 13:09:14 +0000</pubDate>
      <link>https://dev.to/supabase/til-postgres-template-databases-3mo8</link>
      <guid>https://dev.to/supabase/til-postgres-template-databases-3mo8</guid>
      <description>&lt;p&gt;Whenever you create a new database in Postgres, you are actually &lt;a href="https://www.postgresql.org/docs/current/manage-ag-templatedbs.html" rel="noopener noreferrer"&gt;basing it off an already present database&lt;/a&gt; in your cluster. This database, &lt;code&gt;template1&lt;/code&gt;, and another, called &lt;code&gt;template0&lt;/code&gt;, are standard system databases that exist in every newly created database cluster. Don't believe me? Why not quickly &lt;a href="https://dev.to/supabase/getting-a-postgresql-server-up-and-running-in-digital-ocean-within-minutes-8kd"&gt;spin up a database&lt;/a&gt; and see it for yourself 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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_database&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 post, we'll explore these template databases and see how we can make full use of their potential. We'll even look into creating a template database of our own.&lt;/p&gt;

&lt;h2&gt;
  
  
  template1
&lt;/h2&gt;

&lt;p&gt;By default, running:&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;DATABASE&lt;/span&gt; &lt;span class="n"&gt;new_db_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;simply copies everything from the database &lt;code&gt;template1&lt;/code&gt;. We can modify this template database in any way: add a table, insert some data, create new extensions, or install procedural languages. Any of these actions would be propagated to subsequently created databases.&lt;/p&gt;

&lt;p&gt;This, however, is &lt;strong&gt;not&lt;/strong&gt; advisable. Removing any one of these modifications would need you to manually uninstall or drop these changes from &lt;code&gt;template1&lt;/code&gt;. You do have the option to drop and recreate the entire &lt;code&gt;template1&lt;/code&gt; database altogether. This unfortunately comes at the risk of committing a mistake along the way, effectively breaking &lt;code&gt;CREATE DATABASE&lt;/code&gt;. It would be better to leave &lt;code&gt;template1&lt;/code&gt; alone and create a template database of your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Template Databases
&lt;/h2&gt;

&lt;p&gt;To set an existing database as a template database:&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;DATABASE&lt;/span&gt; &lt;span class="n"&gt;template_db_name&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;is_template&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doing this allows any user or role with the &lt;code&gt;CREATEDB&lt;/code&gt; privilege to utilize it as a template. If not, only superusers or owners of the database would be allowed to do so.&lt;/p&gt;

&lt;p&gt;To create a new database with this template:&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;DATABASE&lt;/span&gt; &lt;span class="n"&gt;new_db_name&lt;/span&gt; &lt;span class="k"&gt;TEMPLATE&lt;/span&gt; &lt;span class="n"&gt;template_db_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;With this, you can now have customized templates without the need to worry about polluting &lt;code&gt;template1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You can safely drop the entire custom template database without the risk of breaking &lt;code&gt;CREATE DATABASE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If you wish, you can create multiple template databases for various use cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;To properly create a database from a custom template database, there should be no other connections present. &lt;code&gt;CREATE DATABASE&lt;/code&gt; immediately fails if any connections exist at the start of the query.&lt;/li&gt;
&lt;li&gt;As such, if you are looking to replicate a database while maintaining your connections (eg. a production database), it would be more ideal to use the Postgres utility &lt;a href="https://www.postgresql.org/docs/12/app-pgdump.html" rel="noopener noreferrer"&gt;pg_dump&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  template0
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;template0&lt;/code&gt; contains the same data as &lt;code&gt;template1&lt;/code&gt;. We could think of this template database as a fallback if anything irreversible happens to &lt;code&gt;template1&lt;/code&gt;. As such, this template database should never be modified in any way as soon as the database cluster has been initialized. To create a database with &lt;code&gt;template0&lt;/code&gt; as the template database:&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;DATABASE&lt;/span&gt; &lt;span class="n"&gt;new_db_name&lt;/span&gt; &lt;span class="k"&gt;TEMPLATE&lt;/span&gt; &lt;span class="n"&gt;template0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Applications
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;If anything goes wrong with &lt;code&gt;template1&lt;/code&gt;, It can be dropped and recreated with &lt;code&gt;template0&lt;/code&gt; as the template database.&lt;/li&gt;
&lt;li&gt;We can also create a clean database that does not contain any modifications present in &lt;code&gt;template1&lt;/code&gt;. This would be useful when restoring from pg_dump. Any conflicts brought about by modifications not present in the dump are eliminated.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;template0&lt;/code&gt; can be used to specify new encodings. As pointed out in this &lt;a href="https://hashrocket.com/blog/posts/exploring-the-default-postgres-template-databases#:~:text=we%20can't%20edit%20the%20locale%20or%20encoding%20of%20a%20database%20copied%20from%20template1" rel="noopener noreferrer"&gt;article&lt;/a&gt;, creating a new database with &lt;code&gt;template1&lt;/code&gt; and new encodings would result in an error.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Will succeed&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;new_db_name&lt;/span&gt; &lt;span class="k"&gt;TEMPLATE&lt;/span&gt; &lt;span class="n"&gt;template0&lt;/span&gt; &lt;span class="k"&gt;ENCODING&lt;/span&gt; &lt;span class="s1"&gt;'SQL_ASCII'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Will return an error&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;new_db_name&lt;/span&gt; &lt;span class="k"&gt;ENCODING&lt;/span&gt; &lt;span class="s1"&gt;'SQL_ASCII'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;To quickly sum things up, we found out that new databases are, by default, created from a template database called &lt;code&gt;template1&lt;/code&gt;. &lt;code&gt;template1&lt;/code&gt; can be modified in any way we please and the changes would be present in any database created afterward. We can also create custom template databases and base new databases from them instead. If things go awry, &lt;code&gt;template0&lt;/code&gt; is always there to help.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TIL Postgres&lt;/strong&gt; is an ongoing series by &lt;a href="https://supabase.io" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt; that aims to regularly share snippets of information about PostgreSQL and hopefully provide you with something new to learn. 😉&lt;/p&gt;

&lt;p&gt;Any new posts or announcements of our future features and freebies will be made here on DEV first. Follow us so that you don't miss out.&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%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--uyA__P-6--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_66%252Cw_880%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjf7adc5e3kbdu4luxvt4.gif" 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%2Fres.cloudinary.com%2Fpracticaldev%2Fimage%2Ffetch%2Fs--uyA__P-6--%2Fc_limit%252Cf_auto%252Cfl_progressive%252Cq_66%252Cw_880%2Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjf7adc5e3kbdu4luxvt4.gif" alt="follow gif" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>todayilearned</category>
      <category>postgres</category>
      <category>supabase</category>
    </item>
  </channel>
</rss>
