<?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: Amree Zaid</title>
    <description>The latest articles on DEV Community by Amree Zaid (@amree).</description>
    <link>https://dev.to/amree</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%2F47406%2F11ad0fe8-d2e7-4ca4-ab36-d6e5a55fa112.jpg</url>
      <title>DEV Community: Amree Zaid</title>
      <link>https://dev.to/amree</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amree"/>
    <language>en</language>
    <item>
      <title>Scaling PostgreSQL Performance with Table Partitioning</title>
      <dc:creator>Amree Zaid</dc:creator>
      <pubDate>Fri, 13 Jun 2025 01:25:06 +0000</pubDate>
      <link>https://dev.to/coingecko/scaling-postgresql-performance-with-table-partitioning-136o</link>
      <guid>https://dev.to/coingecko/scaling-postgresql-performance-with-table-partitioning-136o</guid>
      <description>&lt;p&gt;Table of contents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The background&lt;/li&gt;
&lt;li&gt;The investigation&lt;/li&gt;
&lt;li&gt;The execution&lt;/li&gt;
&lt;li&gt;The result&lt;/li&gt;
&lt;li&gt;What we would do differently&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The background
&lt;/h2&gt;

&lt;p&gt;In CoinGecko we have multiple tables that we use to store crypto prices for various purposes. However, after over 8 years of data, one of the tables we store hourly data in grew over 1TB to a point it took over 30 seconds on average to query.&lt;/p&gt;

&lt;p&gt;We started to see higher IOPS usage whenever there are more requests hitting price endpoints. Requests queues started to increase and our Apdex score started to go down. For a short term fix, we increased the IOPS up to 24K. However, the IOPS keeps on getting breached causing alerts everyday.&lt;/p&gt;

&lt;p&gt;In order to ensure this situation doesn’t affect our SLO and eventually our SLA, we started to look into what we can do to improve the situation.&lt;/p&gt;

&lt;p&gt;FYI, we are using PostgreSQL RDS as our main database.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The investigation
&lt;/h2&gt;

&lt;p&gt;Adding indexes was initially considered the quickest solution, but this approach was unsuccessful. The query utilizes a JSONB column with keys based on supported currencies, presenting an additional challenge. Indexing different keys for various applications was deemed excessive, as an added index might only benefit a single application.&lt;/p&gt;

&lt;p&gt;Ultimately, table partitioning was chosen as the solution most likely to yield the greatest returns, despite its complexity.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What is table partitioning?
&lt;/h3&gt;

&lt;p&gt;Table partitioning involves dividing a large table into smaller, more manageable pieces called partitions. These partitions share the same logical structure as the original table but are physically stored as separate tables.&lt;/p&gt;

&lt;p&gt;This allows queries to operate on only the relevant partitions, improving performance by reducing the amount of data scanned.&lt;/p&gt;

&lt;p&gt;There are three methods of partitions which are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Range:&lt;/strong&gt; Partitions data based on a range of values (e.g., dates, numerical ranges).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;List:&lt;/strong&gt; Partitions data based on specific list values (e.g., countries, categories).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hash:&lt;/strong&gt; Partitions data by applying a hash function to a column's value, distributing data evenly across partitions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The question is, which partitioning method should we use? As usual, the answer is: it depends. We need to analyze our query patterns to determine which method will provide the greatest benefit. The key is to select the method that minimizes the amount of data our queries need to read every time it runs.&lt;/p&gt;

&lt;p&gt;In our case, range partitioning is the optimal choice. This is due to the fact that almost all of our queries on this table incorporate a timestamp range in the &lt;code&gt;WHERE&lt;/code&gt; clause. Moreover, we know that we generally only require data for a few months at a time, with a maximum of four. As a result, partitioning the table by month will guarantee that our queries only access up to four partitions (most of the time).&lt;/p&gt;

&lt;p&gt;IF for some reason, we are not limiting the read based on the timestamp, we may need to use the Hash method as that will limit the read based on a foreign key. Again, it depends on the use case.&lt;/p&gt;

&lt;p&gt;What would the code look like?&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;orders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;customer_id&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;order_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&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;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&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="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;RANGE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Create partitions for different amount ranges&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;orders_small&lt;/span&gt;
   &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;OF&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
   &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;TO&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;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;orders_medium&lt;/span&gt;
   &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;OF&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
   &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="k"&gt;FROM&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;TO&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&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;orders_large&lt;/span&gt;
   &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;OF&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
   &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&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;orders_extra_large&lt;/span&gt;
   &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;OF&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
   &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAXVALUE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Insert sample data&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;orders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2024-01-15'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;    &lt;span class="c1"&gt;-- Goes to orders_small&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;'2024-01-15'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;-- Goes to orders_medium&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;'2024-01-15'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;-- Goes to orders_large&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="s1"&gt;'2024-01-15'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;-- Goes to orders_extra_large&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;span class="s1"&gt;'2024-01-15'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;    &lt;span class="c1"&gt;-- Goes to orders_small&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2024-01-15'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;450&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;-- Goes to orders_medium&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my case, I just name the partition based on this format: table_YYYYMM.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What did I learn during the investigation?
&lt;/h3&gt;

&lt;p&gt;If someone had to do this again, these are the info that I will pass on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We can’t change the table from an unpartitioned table to a partitioned table. We need to create a new table and copy the data into it before making the switch.&lt;/li&gt;
&lt;li&gt;The partitioned table needs to have the partition key as part of the primary key. If we have ID as the original primary key, then, we need to use composite keys on the new table.&lt;/li&gt;
&lt;li&gt;In order to use partitioned tables in Ruby on Rails, we need to change the schema format from &lt;code&gt;schema.rb&lt;/code&gt; to &lt;code&gt;schema.sql&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We need to figure out how to have both tables running at the same time if we can’t afford downtime.&lt;/li&gt;
&lt;li&gt;Since we will be creating a new table, we have to be careful about the cache. Technically, the new table doesn’t have cache at all and the performance will be very bad. We have to figure out how to “warm” up the new table. I am referring to the cache in PostgreSQL itself.&lt;/li&gt;
&lt;li&gt;To warm up the table, please learn about pg_prewarm.&lt;/li&gt;
&lt;li&gt;Copying data as big as 1.2TB would require bigger resources such as IOPS. We need to take that into account.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With all the info that we had, we created a Release Plan document outlining when and what is going to happen. We used that document as our main reference point for everyone to see. The document contains these info:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Date:&lt;/strong&gt; When it is going to happen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prerequisites:&lt;/strong&gt; Before executing the todos, we may need to do other tasks first. They will be listed in this section ensuring we do not start the todos without completing them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risks:&lt;/strong&gt; For every risk, we will list down what could happen and what are the mitigation plans.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Todos:&lt;/strong&gt; This section will list down what needs to be done and once it is done, we will tick them off from the list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The execution
&lt;/h2&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dry Run
&lt;/h3&gt;

&lt;p&gt;Our customers are very particular about the uptime of our services, hence, we proactively conduct dry runs to safeguard our SLO/SLA commitments. Before we start our dry run, we will list down what we want to do and what kind of statistics that we want to collect.&lt;/p&gt;

&lt;p&gt;For this project, we spinned up another database identical to our production. Then, we ran all the commands or scripts that we will run on the production instance later. In our case, we were looking for these data:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Before and after query performance.&lt;/li&gt;
&lt;li&gt;How long will it actually take to copy the data?&lt;/li&gt;
&lt;li&gt;How long does it take to warm up the table partitions?&lt;/li&gt;
&lt;li&gt;What does the CPU and IOPS look like for every action that we did?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Avoid operating in the dark.. Be prepared so that we won’t miss our objectives. Remember, using a database similar to our production is going to cost a lot of money.&lt;/p&gt;

&lt;p&gt;What we found out during the dry run:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The original table was too slow at first. But, this is expected as without any cache, we won’t be able to work on it.&lt;/li&gt;
&lt;li&gt;It took us 10 hours to just warm up the original table before we could start trying out our commands.&lt;/li&gt;
&lt;li&gt;It took us 3 days+ to finish copying the data.&lt;/li&gt;
&lt;li&gt;Total IOPS can spike up to 6,000 during this operation, even when running in isolation without any other database workload. To put this in perspective, 6,000 IOPS for the read is virtually identical to what our production database handles under normal operating conditions.&lt;/li&gt;
&lt;li&gt;We can get 6-8x performance based on the same query that we had when we switched to partitioned tables.&lt;/li&gt;
&lt;li&gt;Prewarming the partitioned table only took 3 hours compared to the original table which was 10 hours.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once we have the right statistics, we made the necessary arrangements such as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Announcing the day and time where we will do this on the production database.&lt;/li&gt;
&lt;li&gt;Increase the storage capacity to ensure our database can fit the new table and still have extra spaces left until we drop the original table. We also need to consider the amount of storage needed for everyday tasks.&lt;/li&gt;
&lt;li&gt;Increase the IOPS so that the Primary and the Replicas can handle the load due to the data copy process.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Go-Live Day
&lt;/h3&gt;

&lt;p&gt;The work will start at the beginning of the week to minimize weekend work and ensure our engineers have a peaceful weekend. We also have a backup engineer and SRE support.&lt;/p&gt;

&lt;p&gt;It’s quite normal that things didn’t go as planned, but the first challenge was something that I didn’t expect at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge #1: Our original table was so bad that I couldn’t even complete copying one day's worth of data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Based on what we did during the dry run, we should be able to copy one day of data within 2-3 minutes. However, when it comes to production, I didn’t expect it was going to be much worse. Technically, the production data should have sufficient cache as it is actively being used. I didn’t spend too much time looking into it, but I know I can’t warm up the table as we won’t have enough IOPS for it.&lt;/p&gt;

&lt;p&gt;We have 8 years of data that we need to copy. So, imagine waiting 15 minutes for 1 day of data. Actually, I don’t know how long it would take as I just kill the query after 15 minutes.&lt;/p&gt;

&lt;p&gt;What was the solution? Well, we know for a fact from our dry run, if we warm up the table, it will only take 2-3 minutes for one day of data. But, we cannot warm up our production’s table. So, what can we do?&lt;/p&gt;

&lt;p&gt;I remember a Postgres feature called &lt;a href="https://www.postgresql.org/docs/current/ddl-foreign-data.html" rel="noopener noreferrer"&gt;Foreign data wrappers&lt;/a&gt;. Basically, we will read from another host and write to the partitioned table in the production’s host. This way, we don’t have to warm up the table in the production and we also won’t use too much IOPS as well. This seems like a win to us.&lt;/p&gt;

&lt;p&gt;Based on that idea, we improvised our plan for a little bit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Provision another production grade database.&lt;/li&gt;
&lt;li&gt;Prewarm the table.&lt;/li&gt;
&lt;li&gt;Setup Foreign data wrapper.&lt;/li&gt;
&lt;li&gt;Update our copy script to read from the new host and write to the current production’s database.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This whole process set us back by two to three days. But, it’s something that we cannot avoid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge #2: Warming up all databases, including replicas, was necessary&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We didn't realize we needed to warm up our replicas until the go-live day began. We had only been focusing on the primary database. This oversight added extra work to the process but at least we are not so worried about the partitions not warmed up enough in the replicas.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  The final query
&lt;/h4&gt;

&lt;p&gt;Once we have gone through all of the tasks, we just flip the switch by renaming the 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;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Remove existing trigger&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;

&lt;span class="c1"&gt;-- THE IMPORTANT BITS&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;prices&lt;/span&gt; &lt;span class="k"&gt;RENAME&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;prices_old&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;prices_partitioned&lt;/span&gt; &lt;span class="k"&gt;RENAME&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Create the trigger function&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;sync_prices_changes_v2&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="c1"&gt;-- ...&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Attach the trigger on the new table so that prices_old will get the changes&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;sync_to_partitioned_table&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;prices&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;sync_prices_changes_v2&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, we are using triggers to copy the data from and to the new and the old table. We still need the old table. Remember, things could go wrong and we need our Plan B, C and so on.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What We Did Right
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Table warm-ups:&lt;/strong&gt; Based on past experience from the database upgrade, we made the right call to warm up the partitions. This ensured that query time didn't increase when we switched from the unpartitioned table to the partitioned table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scripted the tasks:&lt;/strong&gt; We prepared scripts for every task and developed a small Go app to manage data copying. The app included essential features like timestamps, the ability to specify the year for data copying, and the time taken to copy data. We also created an app for warming up the table, allowing us to carefully manage CPU and IOPS usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Used CloudWatch Dashboard:&lt;/strong&gt; We decided to fully utilize CloudWatch Dashboard for this project, and it proved invaluable for monitoring IOPS, CPU, Replica Lag, and other metrics across multiple replicas. Learning to set up the vertical line feature was particularly helpful for visualizing before-and-after comparisons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team Backup:&lt;/strong&gt; Having backup from another person or team was beneficial. They helped identify things we might have missed and provided a sounding board for ideas during planning and execution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;Now let’s go to the fun part. However, there was also a regression after we switched to the partitioned table. Details are in the “What we would do differently” section.&lt;/p&gt;

&lt;p&gt;When we are talking about the result, we should return back to why we are doing this in the first place. On the micro level, we want to reduce the IOPS for certain queries. On the macro level, we want our endpoints to be faster and more resilient towards requests spikes. Severe high IOPS can cause replica lags as well.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  IOPS
&lt;/h3&gt;

&lt;p&gt;The IOPS was reduced by 20% right after this exercise. Since this table was being used extensively across all of our applications, we can reduce the maximum IOPS thus allowing us to save our costs further. To be clear, we are running multiple replicas so the cost savings are multiplied by the number of replicas that we have.&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%2F293xfzj2gueosw2lhnzb.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%2F293xfzj2gueosw2lhnzb.png" alt="IOPS" width="800" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Response Time
&lt;/h3&gt;

&lt;p&gt;This was quite significant as we managed to reduce the p99 from 4.13s to 578ms. That is a about 86% reduction in terms of the response time. You can see how flat the chart is right after we made the switch.&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%2Fmz7z3wez7v5upmnyatle.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%2Fmz7z3wez7v5upmnyatle.png" alt="Response Time" width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Replica Lag
&lt;/h3&gt;

&lt;p&gt;Right before we made the table switch, we had increased usage of the affected endpoints causing higher IOPS which in result caused us replica lags. But, it went away the moment we flipped to the partitioned table.&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%2Fandcu2ufh770yo4qx3gd.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%2Fandcu2ufh770yo4qx3gd.png" alt="Replica Lag" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What we would do differently
&lt;/h2&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Block the deployments
&lt;/h3&gt;

&lt;p&gt;The biggest mistake in our planning was not blocking deployments on the day of the switch. While the switch itself wouldn't disrupt others' work, we overlooked the fact that two deployments were related to the table we were optimizing. This caused confusion about the impact of the table partitioning.&lt;/p&gt;

&lt;p&gt;The higher CPU and IOPS utilization observed after the switch put the exercise at risk of rollback. We eventually identified that an earlier deployment caused the problem. However, pinpointing the cause required rolling back the changes and extensive discussion. This situation could have been avoided by blocking deployments for a day to clearly assess the impact of our changes.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Too focused on the replicas
&lt;/h3&gt;

&lt;p&gt;Our focus on resolving the replica issue led us to overlook the impact on the primary database. We failed to identify which queries would be affected, and one query, in particular, performed worse after the switch. This query triggered scans across all of the table partitions, increasing IOPS and CPU usage. By modifying the query, we managed to resolve it.&lt;/p&gt;

&lt;p&gt;This experience highlighted that without the correct query, table partitioning can be detrimental. In this instance, the query lacked a lower limit for the date range, resulting in all partitions being scanned unnecessarily. Interestingly, the same query performed well on the unpartitioned table.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Extra partitions
&lt;/h3&gt;

&lt;p&gt;This was another instance where we noticed a regression on one endpoint that helped us realize the mistake.&lt;/p&gt;

&lt;p&gt;To reduce the future workload of creating partitions, we initially created all the partitions for 2025. We discovered that one query was structured like  'created_at &amp;gt; ?' without an upper limit. It caused the query to scan future partitions that were empty. By removing these partitions, we fixed the issue.&lt;/p&gt;

&lt;p&gt;Going forward, we need to determine a better strategy for when to create future partitions.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Incremental release
&lt;/h3&gt;

&lt;p&gt;We shouldn’t have waited for the entire table to be migrated before starting to use it. Since most users only access data from the last four partitions (or months), we could have implemented a feature toggle to direct queries for data after a specific date to the already migrated partitioned tables.&lt;/p&gt;

&lt;p&gt;This approach would have allowed us to start using the partitioned tables sooner and reduced the risks and potential negative impact of any issues that might arise. As it stands, rolling back our changes would be costly in terms of IOPS, as we would need to prewarm the old table again to avoid production downtime due to cold cache.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;This is just the beginning. With this experience under our belt, we can start exploring the possibility of implementing table partitions on other tables as well. Of course, table partitioning isn't a one-size-fits-all solution. We need to diagnose the issue before proceeding. Sometimes, something as simple as adding an index can resolve the problem.&lt;/p&gt;

&lt;p&gt;In conclusion, while partitioning the 1TB+ "prices" table presented some challenges, especially during the go-live phase, the overall outcome was substantial performance improvements. This initiative aligns with the API Team's ongoing goal: providing the best possible experience to better serve our customers. Our API is now more stable and resilient against sudden spikes in requests during peak hours.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>rails</category>
    </item>
    <item>
      <title>Caching Basics</title>
      <dc:creator>Amree Zaid</dc:creator>
      <pubDate>Mon, 01 Jan 2024 03:10:10 +0000</pubDate>
      <link>https://dev.to/amree/caching-basics-1g6i</link>
      <guid>https://dev.to/amree/caching-basics-1g6i</guid>
      <description>&lt;p&gt;Caching is something we use to reduce the access to the original source. To do that, we store the data temporarily on another medium. We can use various tech to achieve that, but whatever we choose should be faster or lighter than the original source. This will help our users to retrieve our data faster than before. It will also help lower the initial resource utilization. Depending on what we choose, we can save some costs.&lt;/p&gt;

&lt;p&gt;What kind of data can we cache? I am unsure if there's a limitation, but anything retrieved can be cached. The only question is, where do you store them? That highly depends on what kind of data that we have.&lt;/p&gt;

&lt;p&gt;Let's start with something basic first. When we retrieve data from our database, we might not realize there's a cache happening there, too. But is it good enough? Depending on your use case, it won't since the cache can easily get busted due to changes in the data. You should retrieve the data and cache it on the application layer to have a proper cache. But where would you store it? This is the part where we choose something faster than the database itself because if we don't, there's no point in caching, right?&lt;/p&gt;

&lt;p&gt;To make it short, I suggest storing it on an in-memory database such as Redis. Consequently, the web server doesn't have to contact the database whenever someone requests the endpoint that calls for the query. The response time will be faster since the data is stored in the memory instead of files. The endpoint doesn't have to compute or do additional work to get the data. We are supposed to store precomputed data close to the final form anyway. As you can see, we have freed the database to work on something else. It is also a technique we can use to avoid expensive computations on the database.&lt;/p&gt;

&lt;p&gt;With the previous implementation, we have reduced the requests that go to the database, but you might have noticed that the requests will still reach our servers. It's good to stop here for most use cases. However, that is different with high-traffic websites. The key to handling many requests from your websites is shifting the responsibility to something else. It's quite similar to how we avoid access to the database by asking Redis to store the data. What kind of options do we have?&lt;/p&gt;

&lt;p&gt;There are various ways to do this, such as HAProxy or Nginx. However, I would like to discuss how we can use a Content Delivery Network (CDN) such as CloudFlare (CF) to do this.&lt;/p&gt;

&lt;p&gt;"A CDN, or Content Delivery Network, is a system of distributed servers that deliver web content and other web services to users based on their geographic location, the origin of the web page, and a content delivery server. The primary purpose of a CDN is to improve access speed and efficiency by reducing the distance between the user and the content." - ChatGPT.&lt;/p&gt;

&lt;p&gt;We can do so many things with CDN, but in this context, we would like to temporarily store the cache on the CDN itself. When we put the content on the CDN, depending on how we configure the cache, the CDN will serve the requests instead of the "origin" servers (our servers). As you might have guessed, we just freed the servers from serving those requests, which, as a result, reduced the server utilizations. So, now, the CDN servers, which are strategically located around the world, will serve the data. Normally, this is done by setting the proper "Cache-Control" header.&lt;/p&gt;

&lt;p&gt;Everything sounds so good, but remember, our decisions always have trade-offs, and these are some of them:&lt;/p&gt;

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

&lt;p&gt;When we temporarily store the data, we must determine how long it will be considered fresh. Once we have all the right requirements, we must figure out how to invalidate the data. The methods depend highly on the stack that you are using. Some frameworks made it easy by specifying the Time to Live (TTL). We can also use the Observer pattern to invalidate the data when an event happens, but we need to know the key to store the cache. We can even configure the cache TTL on the server level. For example, Redis has eviction policies such as Least Recently Used (LRU), Least Frequently Used (LFU) and others. What about the caches on the CDN? The basic is still the same: we either remove it based on the TTL or force it by triggering an API call to the CDN.&lt;/p&gt;

&lt;p&gt;One big disadvantage I want to point out when caching on the CDN is how we need to use JavaScript to handle the dynamic portion of the cached page. Caching means the CDN will save a snapshot of the page on its servers for a certain period, which means everything that was rendered that time won't change any more.&lt;/p&gt;

&lt;p&gt;For example, we put a time on the page based on when the request comes in after the cache has expired. The CDN will then cache that time based on that particular request. Since the time code is coming from our server side, it won't change anymore, as the next request will hit the CDN instead of our servers. Remember, CDN is serving what has been saved. They won't re-render the page from scratch as that's the job of our server. So, what do we do? We had to move some code to JavaScript (JS) since JS is dynamic and can run on the user's machine. Depending on the requirements, we can have the JS asynchronously load some of the pages. At the same time, the CDN will handle the initial page load. So, we can still cache the page on the CDN and update some of the content anytime we want.&lt;/p&gt;

&lt;p&gt;Alright, that's it for now. There is still more to discuss, but I will stop here first. There are also more ways to approach the cache invalidation. Maybe next time, it will be about the Dogpile Effect. Till then, happy New Year, everyone!&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>Tiny Guide to Webscaling</title>
      <dc:creator>Amree Zaid</dc:creator>
      <pubDate>Sun, 26 Nov 2023 10:38:49 +0000</pubDate>
      <link>https://dev.to/amree/tiny-guide-to-webscaling-5eem</link>
      <guid>https://dev.to/amree/tiny-guide-to-webscaling-5eem</guid>
      <description>&lt;p&gt;Someone on Twitter asked what if Khairul Aming wanted to set up his own website for his sambal? For those who may not know, his product has gained fame and typically sells out quickly once he opens orders. At present, he utilizes Shopee.&lt;/p&gt;

&lt;p&gt;From a business standpoint, it's advisable for him to stay with Shopee. My post is primarily for educational purposes.&lt;/p&gt;

&lt;p&gt;Disclaimer: I am not an SRE/DevOps professional, but rather someone eager to share insights that might broaden understanding of web scalability, drawn from my limited experiences. Therefore, there may be inaccuracies, though I hope none too significant.&lt;/p&gt;

&lt;p&gt;Expect some odd structuring in the paragraphs, as the content was initially made for Twitter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Firstly, traffic won’t spike up on day one. Even if the founder is a marketing genius, he might prefer an established app over starting from scratch. However, if he's starting anew, what’s the accepted baseline for stress tests?&lt;/p&gt;

&lt;p&gt;If he already has good sales and no problems, what’s the business decision on moving to a completely new platform?&lt;/p&gt;

&lt;p&gt;Then, it circles back to the possibility of scaling what the owner currently has, which is most likely his own custom platform. That means legacy baggage.&lt;/p&gt;

&lt;p&gt;A small tip for those interviewing: don't simply throw tons of cloud jargon at your interviewer. First, ask about any constraints. Your system design answer will be more relatable.&lt;/p&gt;

&lt;p&gt;The solution should be about improving the existing problem. Yes, it's not cool and easy to improve old stuff. But we are not in school anymore. We rarely start with something 100% new.&lt;/p&gt;

&lt;p&gt;Ask and investigate the existing stack. Then, we can start working on it.&lt;/p&gt;

&lt;p&gt;This article explained it best: &lt;a href="https://mensurdurakovic.com/hard-to-swallow-truths-they-wont-tell-you-about-software-engineer-job/"&gt;https://mensurdurakovic.com/hard-to-swallow-truths-they-wont-tell-you-about-software-engineer-job/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Understanding the process from the user typing the address to getting the response back is important. I'd say we can simplify it to:&lt;/p&gt;

&lt;p&gt;User -&amp;gt; Server -&amp;gt; Data Storage (DS)&lt;br&gt;
User -&amp;gt; DNS -&amp;gt; Load Balancer (LB) -&amp;gt; Servers -&amp;gt; DS&lt;br&gt;
User -&amp;gt; DNS -&amp;gt; CDN -&amp;gt; LB -&amp;gt; Servers -&amp;gt; DS&lt;/p&gt;

&lt;p&gt;It is important because we want to know which part of the section to look at and fix when there is a problem.&lt;/p&gt;

&lt;p&gt;Developers may not care as there are DevOps or someone else to take care of it, but, in my humble opinion, it's good to understand the stack even at the surface level to help the team.&lt;/p&gt;

&lt;h2&gt;
  
  
  DNS
&lt;/h2&gt;

&lt;p&gt;Let’s start with the DNS. Every part of the architecture is important, but DNS is the first layer of the request that will be touched before it goes somewhere else. This means you can do lots of interesting things here.&lt;/p&gt;

&lt;p&gt;See the traffic sequence image if you use CloudFlare (CF).&lt;/p&gt;

&lt;p&gt;From the image, you can see that CF can do lots of things such as DDoS protection, redirections, caches, firewall (WAF), and others.&lt;/p&gt;

&lt;p&gt;CF is not the only option. There's CloudFront as well. But I'm more used to CF than CloudFront, so I'm going to talk more about this service.&lt;/p&gt;

&lt;p&gt;Let's start from the top: DDoS. You can’t have a popular website without DDoS protection. If you are big enough, that means you are popular enough for others to do bad stuff, like bots, attacks, fake traffic, and so on.&lt;/p&gt;

&lt;p&gt;There’s always a limit to what you can handle.&lt;/p&gt;

&lt;p&gt;To protect from those attacks, we need to stop them before they reach your servers. If they manage to get through, then you'll have bigger problems.&lt;/p&gt;

&lt;p&gt;CF can help mitigate the attack. But not all of them. You have to get used to tuning the knobs manually when needed.&lt;/p&gt;

&lt;p&gt;So, remember, don’t trust the advertisements from the service providers. Test them out, get attacked, gain experience in looking at the traffic, see the anomalies, separate them, and control them by banning or throttling them so that your servers can handle the incoming traffic.&lt;/p&gt;

&lt;p&gt;Page Rules - You can override the behavior of certain pages without code. For example, you might set cache-control to cache at CF for 5 minutes from the code, then, for whatever reason, you need to make the cache stay longer, and that can be done from this page.&lt;/p&gt;

&lt;p&gt;WAF - My favorite. This is where you can ban, throttle the traffic based on IP/ASN/country/custom matchers/etc. This page has saved me from sleepless nights countless times.&lt;/p&gt;

&lt;p&gt;Imagine having a sale and getting attacked at the same time?&lt;/p&gt;

&lt;p&gt;Before moving on to the next layer, it's important to note that this is where we point &lt;a href="http://domain.com"&gt;http://domain.com&lt;/a&gt; to an ALB / Load Balancer address / your server. It's how CF will know how to route the request to the next step.&lt;/p&gt;

&lt;p&gt;It will also go through the traffic sequence mentioned above.&lt;/p&gt;

&lt;p&gt;I didn’t talk much about Content Delivery Networks (CDN). I think that’s the lowest hanging fruit that one can do to ensure your assets are served by the CDN servers located nearest to your visitors.&lt;/p&gt;

&lt;p&gt;Remember, the less traffic goes to the origin, the better.&lt;/p&gt;

&lt;p&gt;This is also why some people tweak their WordPress/Web to be served by the CDN as much as possible.&lt;/p&gt;

&lt;p&gt;What’s the catch? Once cached, how do you expire the cache? Cache is also less usable if your traffic is too random. You have to understand your traffic before reaching for it.&lt;/p&gt;

&lt;p&gt;Before moving to the next layer, learn about the Cache-Control header as much as you can. Learn to leverage it not just for your assets, but also for your normal pages. Figure out how to get the most out of it and understand the downside as well, then you'll have a very fast website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Load Balancer
&lt;/h2&gt;

&lt;p&gt;Let's move on to the Load Balancer (LB). Its purpose is to distribute incoming traffic across various servers, typically using a round-robin algorithm. In our example, traffic comes from CF and goes to the LB. In AWS, this is known as the Application Load Balancer (ALB).&lt;/p&gt;

&lt;p&gt;Focusing on the ALB, which operates at the Layer 7 or application layer (HTTP/HTTPS), it represents the last point before traffic is distributed to the servers. This is why the ALB address is entered into CF.&lt;/p&gt;

&lt;p&gt;Securing the ALB address is critical because attackers might launch direct attacks, bypassing the protections set up in CF. Normally, traffic is only allowed from trusted sources like CF, with all other traffic denied.&lt;/p&gt;

&lt;p&gt;ALB is just one of AWS's services; you can also use other open-source software (OSS) as your LB. Even nginx can handle load balancing, but whether it can withstand the traffic is another matter. AWS ALB, in my experience, is very reliable.&lt;/p&gt;

&lt;p&gt;Discussing scalability leads us to Auto Scaling Groups (ASG) and Target Groups (TG). Some believe these services will solve all scaling problems, but it's not that straightforward. They are helpful, but endless scaling isn't the goal, right?&lt;/p&gt;

&lt;p&gt;In summary: Traffic from the ALB is forwarded to the TG. The ASG, using a Launch Template (LT), manages the addition of servers as needed—this much I'm sure of.&lt;/p&gt;

&lt;p&gt;The criteria for adding servers, such as a server's CPU usage hitting 60% for 10 minutes, can be based on various factors. When triggered, AWS launches more servers according to the LT specifications. To reduce costs, many opt for auto spot instances, which are notably cheaper. New servers are registered to the TG for the ALB's utilization.&lt;/p&gt;

&lt;p&gt;The servers will be terminated once the preset conditions are no longer met, allowing resources to fluctuate based on demand. This is the essence of 'autoscaling.'&lt;/p&gt;

&lt;p&gt;To be clear, autoscaling isn't mandatory. It's for teams who anticipate sudden spikes in traffic that require additional server capacity. The thought of managing this manually is daunting. It's wise to research the pros and cons of auto spot instances as well.&lt;/p&gt;

&lt;p&gt;But it's not a magic solution; there are always trade-offs. Adding servers impacts other resources—nothing comes without cost.&lt;/p&gt;

&lt;p&gt;Specifically, there are implications for storage, but let's discuss the servers and application first, or perhaps address them simultaneously since they're interconnected.&lt;/p&gt;

&lt;p&gt;And if anyone is still reading this, kudos to you lol 🤷&lt;/p&gt;

&lt;h2&gt;
  
  
  Servers
&lt;/h2&gt;

&lt;p&gt;After the ALB routes the data, it's up to the servers to push it to the application layer. 'Servers' could refer to either physical hardware or application servers. My expertise lies with EC2 rather than serverless, so that's where I'll concentrate.&lt;/p&gt;

&lt;p&gt;EC2 is a service from AWS where you deploy your application. AWS offers various server types optimized for compute, memory, storage, etc. The best choice depends on your workload. Understanding your server's specs is crucial.&lt;/p&gt;

&lt;p&gt;Without knowing a server's limits, we may inadvertently overuse it. Recognizing when a server is at capacity is a skill in itself.&lt;/p&gt;

&lt;p&gt;Cost is a significant factor. AWS provides regular, spot, and dedicated instances, among others. Upfront payment can also offer cost savings. Tools like spot.io assist in cost optimization, but understanding your workload is fundamental.&lt;/p&gt;

&lt;p&gt;Spot instances are economical but can be terminated unexpectedly, so it might be prudent to start with dedicated instances and then transition to spot instances based on usage and requirements. These concepts warrant further research for a comprehensive understanding.&lt;/p&gt;

&lt;p&gt;Cost considerations become even more critical when auto-scaling because you could potentially spin up many servers. It's essential to determine a reasonable limit. I haven't even begun to discuss the impact on other resources.&lt;/p&gt;

&lt;p&gt;Now, let's delve into the server itself. The software stack depends on your application. For Ruby on Rails, a classic setup might include nginx, puma, and then your code. Understanding this flow is vital.&lt;/p&gt;

&lt;p&gt;You should consider how your application server uses resources, as this will dictate the necessary memory and CPU. Optimizing a single server might mean you don't need to spin up ten servers for the same load.&lt;/p&gt;

&lt;p&gt;I recall a 'fixer' at a seminar who explained how he calculated the required RAM based on active processes during a nationwide application outage years ago, using just basic Linux tools, without AWS.&lt;/p&gt;

&lt;p&gt;Web scaling isn't merely about adding more servers. It's about understanding why resources are being strained. At my work, the recent incident made me question if the resource usage was high because we couldn't pinpoint the underlying issue.&lt;/p&gt;

&lt;p&gt;I highly recommend this article from Judoscale, &lt;a href="https://bit.ly/46yg8hF"&gt;https://bit.ly/46yg8hF&lt;/a&gt;. It offers excellent visuals on data flow from the ALB to the application server. It's a valuable read, even for those not using Ruby on Rails, as the principles apply broadly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application
&lt;/h2&gt;

&lt;p&gt;At the application layer, it’s clear that handling substantial traffic isn’t solely the server team's responsibility. Developers must ensure that the code is optimized to handle the load, necessitating close collaboration across the team to minimize or eliminate downtime.&lt;/p&gt;

&lt;p&gt;The infrastructure team can do a lot, but if developers consistently push two-second queries, the servers will inevitably struggle. We must acknowledge limits, particularly financial ones. Short-term solutions might work, but the key question is: How short is our short-term?&lt;/p&gt;

&lt;p&gt;Applications vary, but certain best practices apply universally, such as deferring non-critical tasks to background jobs. Even simple actions like voting can be processed in the background, leading to quicker responses.&lt;/p&gt;

&lt;p&gt;As previously stated, use a CDN whenever possible to serve assets and cache pages. Understanding and leveraging cache-control can significantly reduce the load on your servers.&lt;/p&gt;

&lt;p&gt;For heavy database queries, consider caching results in faster storage solutions like Redis. The goal is to minimize the workload during the main request. Fewer tasks during the request equate to quicker response times.&lt;/p&gt;

&lt;p&gt;Beyond simply caching, it's crucial to understand how caching works. For instance, what happens if a cache expires just as a thousand requests per second hit? Strategizing for such scenarios is key to reducing origin server load.&lt;/p&gt;

&lt;p&gt;Addressing the original scalability question, particularly in e-commerce, one of the biggest challenges is managing stock availability during high demand, akin to new iPhone releases or limited event tickets.&lt;/p&gt;

&lt;p&gt;Locking stock without causing database lockups is a delicate process. I’ve implemented a solution for this, but I cannot guarantee it could handle the intense traffic like @khairulaming's sales events.&lt;/p&gt;

&lt;p&gt;In a Malaysian context, holding stock for a brief period is essential to allow users to complete transactions, especially when they must navigate to their banks’ payment interfaces.&lt;/p&gt;

&lt;p&gt;When payment failures occur, it's necessary to return the unclaimed stock to the pool for others to purchase. This often involves coupon systems and requires updating user wallets concurrently, which is resource-intensive due to the need for transactions and row locking.&lt;/p&gt;

&lt;p&gt;These operations are costly, and while they may handle a substantial number of requests, the scalability to the level of something like @khairulaming’s order volume or COVID-19 appointment systems remains uncertain.&lt;/p&gt;

&lt;p&gt;Rapid deployment capability is also critical, particularly for CI/CD processes. This ensures that any issues can be addressed quickly, which is vital during high-traffic events to avoid frustrating users.&lt;/p&gt;

&lt;p&gt;From my experience, database optimization is frequently the bottleneck. Developers unfamiliar with tuning and structuring their databases will encounter issues well before traffic peaks. With an adequately optimized database, excessive caching might be unnecessary.&lt;/p&gt;

&lt;p&gt;At a minimum, developers should utilize tools like EXPLAIN to diagnose slow queries. Eliminating N+1 queries and applying appropriate indexing are fundamental skills that remain vital across all database platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database
&lt;/h2&gt;

&lt;p&gt;The database is often the most challenging aspect, not because other areas are problem-free, but due to its complexity and the impact of its performance. Let's explore some straightforward infrastructural optimizations.&lt;/p&gt;

&lt;p&gt;Implement connection pooling, whether it's HA Proxy, RDS Proxy, pgPool, pgBouncer, or similar. It's crucial to comprehend the nuances between application and server-side pooling. Connections are costly in terms of memory, so monitor usage and set appropriate quotas.&lt;/p&gt;

&lt;p&gt;Pooling allows your application to reuse open connections, which can be more memory-efficient. However, you'll need to understand how these applications work and may need to adjust them based on your specific application needs.&lt;/p&gt;

&lt;p&gt;Employ replicas and balance the load between them. It's ideal to dedicate a server to a single application and split read operations based on usage. Adjusting the master server for writes is more complex; I've seen professionals split a primary server to support an increased number of write operations.&lt;/p&gt;

&lt;p&gt;Splitting the master server and data partitioning or sharding are advanced solutions that require careful configuration. These methods are complex and should not be your first line of approach—start with simpler solutions.&lt;/p&gt;

&lt;p&gt;Avoid default configurations. Pinpoint the issues you're facing, then focus on the relevant settings. Be mindful of potential cascading effects when changing configurations. Always monitor the changes and revert if they don't yield improvements.&lt;/p&gt;

&lt;p&gt;Understand your database's strengths and limitations. Your technology choice may not be the best, but proficiency can help resolve most issues. For instance, Uber switched to MySQL, although PostgreSQL advocates might disagree with their reasons (&lt;a href="https://bit.ly/3sG6I5S"&gt;https://bit.ly/3sG6I5S&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Utilize raw queries when necessary. ORMs are convenient but come with overhead, often requiring more memory. Don't hesitate to write direct queries to leverage your database's full capabilities.&lt;/p&gt;

&lt;p&gt;Like with EC2, don't focus solely on CPU usage. Consider other metrics like memory and IOPS, which can limit your database depending on the specifications you choose.&lt;/p&gt;

&lt;p&gt;Conduct schema and data migrations cautiously. Aim for zero downtime, even if it means multiple stages or phases.&lt;/p&gt;

&lt;p&gt;While proper database normalization is common, there are times when denormalization is necessary to enhance performance. Step outside the standard practices if needed, but ensure you understand the trade-offs.&lt;/p&gt;

&lt;p&gt;Choose the right database for your needs, not just what's trendy. If a part of your app benefits from key-value storage, consider Redis. Mixing and matching technologies is fine, but be cognizant of the time and money costs for maintenance.&lt;/p&gt;

&lt;p&gt;I won't delve deeply into Redis. The principles are similar to other technologies: use replicas, connection pooling, understand the tech and its uses. Asking why Redis is faster can lead to a deeper understanding—it's not just a key-value store; it offers much more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability
&lt;/h2&gt;

&lt;p&gt;Let's approach the final chapter I hadn't planned on writing: Observability.&lt;/p&gt;

&lt;p&gt;I believe that's the term, though I’m not a DevOps/SRE—so that's my disclaimer for any inaccuracies, lols.&lt;/p&gt;

&lt;p&gt;Assuming we've optimized our code and servers, there's still one crucial element to consider, and it should be addressed concurrently: monitoring tools. It's essential to have a system that alerts you to problems.&lt;/p&gt;

&lt;p&gt;I'll share tools I'm familiar with, acknowledging my preference for paid services—though I wasn't always like this. Discussing their use might lead to identifying similar, free alternatives.&lt;/p&gt;

&lt;p&gt;By combining these tools, we can gain the most benefit.&lt;/p&gt;

&lt;p&gt;Application Performance Management (APM) is one category. I use @newrelic, which offers a comprehensive suite for monitoring request queues, searching logs, tracing details, historical performance, external call performance, among other metrics.&lt;/p&gt;

&lt;p&gt;For real-time server stats like CPU usage, network, and IOPS, I turn to AWS CloudWatch. AWS Performance Insights is also a go-to as it provides a real-time overview of database performance.&lt;/p&gt;

&lt;p&gt;I’ve started using dashboards to consolidate AWS information, allowing me to view everything, including ASG and ALB metrics, on one screen.&lt;/p&gt;

&lt;p&gt;@pganalyze is another invaluable tool for identifying slow queries, underused indexes, bloat stats, idle connections, and more. It offers insights into query throughput and IOPS, though there's some overlap with Performance Insights. One downside is its data refresh rate, which is every 10 minutes by default but might be adjustable.&lt;/p&gt;

&lt;p&gt;Error tracking is another critical area. While New Relic handles this, dedicated error tracking services like Bugsnag and Sentry offer specialized capabilities, though they can become costly with increased traffic.&lt;/p&gt;

&lt;p&gt;Integration with notification services is also crucial. PagerDuty or even Slack can be used to alert engineers of issues.&lt;/p&gt;

&lt;p&gt;The goal of these tools is to provide immediate insights into what's happening, enabling quick identification and response to issues for both short-term fixes and long-term solutions.&lt;/p&gt;

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

&lt;p&gt;I think I have covered most of what I want to talk about. I would love to elaborate more on some of them, but this is Twitter. So, let's go to the summary.&lt;/p&gt;

&lt;p&gt;I can't stress enough that what works at another place will not necessarily work the same for you. Learn from others and use them as guidance, but don't expect the same result 100%. This is the main reason I keep on using "it depends". Same with this thread, it may work for you or it may not 🤷.&lt;/p&gt;

&lt;p&gt;Keep on learning. Don't simply say "auto-scale" without understanding the consequences and, of course, be humble. There are still too many things to learn. I've made mistakes by thinking my way is the only way before, and once I learned more, I realized the answer can be different.&lt;/p&gt;

&lt;p&gt;Change the mindset. Most of the time, the solution is not that clear. There is always a trade-off between them. But which one is I'm ok to go with? Win some, lose some. It doesn't have to be perfect, but it has to work, if possible so that we can return to our sleep lol.&lt;/p&gt;

&lt;p&gt;Hopefully, this will help someone. I know it would have helped me three years ago.&lt;/p&gt;

&lt;p&gt;Thanks for reading and THE END.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>aws</category>
      <category>postgres</category>
    </item>
    <item>
      <title>How to do big upgrades with small changes</title>
      <dc:creator>Amree Zaid</dc:creator>
      <pubDate>Fri, 18 Nov 2022 10:06:35 +0000</pubDate>
      <link>https://dev.to/amree/how-to-do-big-upgrades-with-small-changes-55op</link>
      <guid>https://dev.to/amree/how-to-do-big-upgrades-with-small-changes-55op</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I'd like to share how I manage multiple library/gem/package update at the same time when I'm working on big upgrades. But before we start, it's important to know why.&lt;/p&gt;

&lt;p&gt;Big upgrades require bigger change of files. The amount of files that you need to change can get really out of hand. You may have to replace some code due to the deprecations or maybe you have to deploy in multiple stages in order to not break the production.&lt;/p&gt;

&lt;p&gt;Which is why I strive to have smaller changes that can help reduce the final changes. In the end, the final Pull Request will only have small changes that would help your colleagues to review them safely. Yeah, no one is gonna review 50+ changed files.&lt;/p&gt;

&lt;p&gt;It's very common for me to have more than 30 PR opened (not at the same time). In fact, it's not that weird to have 10+ PR(s) opened waiting for approval or deployments. So, it can be very confusing to maintain all those PR(s).&lt;/p&gt;

&lt;p&gt;It's not just about having small changes, it's also about having the ability to get into the future where you are in a state where you have everything merged so that you can work on the future upgrade at the same time.&lt;/p&gt;

&lt;p&gt;In my case, I was working on upgrading v5.2.7 -&amp;gt; v5.2.8 -&amp;gt; v6.1.7 -&amp;gt; v7.0.4 (I'm also upgrading Ruby at the same time, but won't go into that to simplify the explanation). The good thing is that I don't have to wait for everything to be ready before even working on the next upgrade. When I was working on v5.2.8, I have already started to work on v7.0.4 at the same time while waiting for code review and deployments. To me, that's a BIG ADVANTAGE.&lt;/p&gt;

&lt;h2&gt;
  
  
  How am I doing it
&lt;/h2&gt;

&lt;p&gt;Let's talk about how I'm doing it. Pretty sure someone else has done this before. It doesn't require tools (you can create a script if you want to), just need some basic git commands and some small notes. It's a little bit hard to explain this without a visualization, so, I've created one:&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%2Fkres49ptvd3oaduajqqo.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%2Fkres49ptvd3oaduajqqo.png" alt="Image description" width="800" height="580"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every PR must be based on the master (master &amp;lt;- pkg-upgrade-1 )&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means, you can ensure you won't break production with this particular change and you can always rebase from master whenever needed to. If things went wrong when you deployed, you will immediately know the problem compared to having multiple upgrades in one PR. If you have one PR with multiple upgrades, you'd have to hunt down the part that is causing the problem&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If the change requires another change, use a different base (master &amp;lt;- pkg-upgrade-1 &amp;lt;- pkg-upgrade-2)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The world is never that simple, there's always a chance where you'd have to depend on a different package before you can work on another upgrade. Obviously you can wait for the first changes to be deployed first, but why should you?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use a 'before' branch before the big upgrade (master &amp;lt;- before-big-upgrade (upgrade1, upgrade2, ..)  &amp;lt;- the-big-upgrade)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The big upgrades usually requires multiple changes. We can't use 2nd technique here as that is only suitable for one or two changes. We handle this by creating a branch that will combine every small changes that hasn't been merged to master.&lt;/p&gt;

&lt;p&gt;That 'before' branch will be used as the base for the big upgrade PR. This is HOW WE TRAVEL INTO THE FUTURE. You can even see if your changes actually work here. The final branch/PR should pass your CI&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use the combination of those techniques to work on another major upgrade&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We can combine them and the result would look like this: master &amp;lt;- major-upgrade-1 &amp;lt;- major-upgrade-2 &amp;lt;- major-upgrade-3&lt;/p&gt;

&lt;p&gt;It may look simple, but 'major-upgrade-1' PR is a combination of techniques from 1 to 3. A little bit hard to wrap your mind when you read it for the first time, but you guys can take a look at the next image. Hopefully, it will help.&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%2Fqcvrvcln6dzxabjr65xz.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%2Fqcvrvcln6dzxabjr65xz.png" alt="Image description" width="300" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are some git command that I use to handle everything that I mentioned:&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="c"&gt;# Sometimes, I need to update a branch that depends on another branch&lt;/span&gt;
&lt;span class="c"&gt;# This has to be done before we work on others&lt;/span&gt;
&lt;span class="c"&gt;# Usually, I squashed and keep one commit&lt;/span&gt;
git checkout upgrade-pkg-1&lt;span class="p"&gt;;&lt;/span&gt; git rebase &lt;span class="nt"&gt;--onto&lt;/span&gt; master HEAD~1
git checkout upgrade-pkg-2&lt;span class="p"&gt;;&lt;/span&gt; git rebase &lt;span class="nt"&gt;--onto&lt;/span&gt; upgrade-pkg-2 HEAD~1

&lt;span class="c"&gt;# Reset to master&lt;/span&gt;
&lt;span class="c"&gt;# 'before' branch should be refreshed periodically to ensure it won't break production&lt;/span&gt;
&lt;span class="c"&gt;# It's ok to reset since we are not creating a PR for it&lt;/span&gt;
git checkout before-rails7_0_4 &lt;span class="p"&gt;;&lt;/span&gt; git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; master


&lt;span class="c"&gt;# Merge those branch that hasn't been approved / wip&lt;/span&gt;
&lt;span class="c"&gt;# Normally, I have close to 10 package that needs to merged&lt;/span&gt;
git merge upgrade-pkg-2 &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# I'm skipping pkg-1 because this pkg-2 has the changes&lt;/span&gt;
upgrade-pkg-3 &lt;span class="se"&gt;\&lt;/span&gt;
upgrade-pkg-4

&lt;span class="c"&gt;# Sometimes, not all packages can just be merged&lt;/span&gt;
&lt;span class="c"&gt;# This requires manual merge due to the conflicts&lt;/span&gt;
git merge pkg-5 &lt;span class="c"&gt;# resolve conflict&lt;/span&gt;

&lt;span class="c"&gt;# The straightforward verion&lt;/span&gt;
&lt;span class="c"&gt;# Rebase final branch to the 'before' branch&lt;/span&gt;
git checkout am-upgrade-rails7_0_4&lt;span class="p"&gt;;&lt;/span&gt; git rebase &lt;span class="nt"&gt;--onto&lt;/span&gt; am-before-rails7_0_4 HEAD~1

&lt;span class="c"&gt;# This can happen:&lt;/span&gt;
&lt;span class="c"&gt;# I had to do something like this when the upgrade has major conflict&lt;/span&gt;
git checkout am-upgrade-rails7_0_4 &lt;span class="p"&gt;;&lt;/span&gt; git reset &lt;span class="nt"&gt;--soft&lt;/span&gt; HEAD~1
git reset
git checkout Gemfile.lock
git add .&lt;span class="p"&gt;;&lt;/span&gt; git stash&lt;span class="p"&gt;;&lt;/span&gt; git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; am-before-rails7_0_4
git stash pop &lt;span class="c"&gt;# fix conflicts&lt;/span&gt;
bundle update rails rails-i18n
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s1"&gt;'Upgrade Rails from v6.1.7 to v7.0.4'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is this the best way to do this? I'm not sure, but it certainly helped me to keep on working without being blocked by pending PR or deployments. Do remember, I don't deploy immediately to ensure I can monitor for regressions. Usually, I'd put some gaps between deployments.&lt;/p&gt;

&lt;p&gt;I highly doubt people will read this far lol, but if you did, thanks! I'm planning to do a presentation on this in the future and writing this will certainly help me to explain the method better.&lt;/p&gt;

</description>
      <category>github</category>
    </item>
    <item>
      <title>Rails Connection Pool vs PgBouncer</title>
      <dc:creator>Amree Zaid</dc:creator>
      <pubDate>Wed, 31 Aug 2022 04:56:17 +0000</pubDate>
      <link>https://dev.to/amree/rails-connection-pool-vs-pgbouncer-2map</link>
      <guid>https://dev.to/amree/rails-connection-pool-vs-pgbouncer-2map</guid>
      <description>&lt;p&gt;Rails by default comes with connection pooler on the application side but I always wonder what is the difference if we use another connection pooler such as PgBouncer. So, here is some notes on trying to understand &lt;em&gt;some&lt;/em&gt; of it.&lt;/p&gt;

&lt;p&gt;To try this out, I'm using docker so that I don't have to install extra application:&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;mkdir &lt;/span&gt;conn-poc&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;conn-poc
&lt;span class="nv"&gt;$ &lt;/span&gt;rails new blog &lt;span class="nt"&gt;--api&lt;/span&gt; &lt;span class="nt"&gt;-T&lt;/span&gt; &lt;span class="nt"&gt;--database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgresql

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;bouncer
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;db

&lt;span class="c"&gt;# create docker network so that PgBouncer and PostgreSQL can communicate with eacher other&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker network create conn-poc-1-net

&lt;span class="c"&gt;# start postgresql&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;db
&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--net&lt;/span&gt; conn-poc-1-net &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; conn-poc-1-pg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;blog_development &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/var/lib/postgresql/data  &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 6432:5432 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-it&lt;/span&gt; postgres:13.8-alpine

&lt;span class="c"&gt;# start pgbouncer&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;bouncer
&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--net&lt;/span&gt; conn-poc-1-net &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; conn-poc-1-bouncer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"postgres://postgres:postgres@conn-poc-1-pg/blog_development"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/etc/pgbouncer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 7432:5432 &lt;span class="se"&gt;\&lt;/span&gt;
  edoburu/pgbouncer


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

&lt;/div&gt;

&lt;p&gt;PgBouncer config:&lt;/p&gt;

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

&lt;span class="nn"&gt;[databases]&lt;/span&gt;
&lt;span class="py"&gt;blog_development&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;host=conn-poc-1-pg port=5432 user=postgres&lt;/span&gt;

&lt;span class="nn"&gt;[pgbouncer]&lt;/span&gt;
&lt;span class="py"&gt;listen_addr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0&lt;/span&gt;
&lt;span class="py"&gt;listen_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;5432&lt;/span&gt;
&lt;span class="py"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
&lt;span class="py"&gt;auth_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/etc/pgbouncer/userlist.txt&lt;/span&gt;
&lt;span class="py"&gt;auth_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;md5&lt;/span&gt;
&lt;span class="py"&gt;ignore_startup_parameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;extra_float_digits&lt;/span&gt;
&lt;span class="py"&gt;pool_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;transaction&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Update some of the code:&lt;/p&gt;

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

&lt;span class="c1"&gt;# config/database.yml&lt;/span&gt;
&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;default&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql&lt;/span&gt;
  &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unicode&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres"&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres"&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("DB_PORT") %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
  &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("RAILS_MAX_THREAD") %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;checkout_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
  &lt;span class="na"&gt;idle_timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;prepared_statements&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;


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

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

&lt;span class="c1"&gt;# config/puma.rb&lt;/span&gt;
&lt;span class="n"&gt;workers&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"WEB_CONCURRENCY"&lt;/span&gt;&lt;span class="p"&gt;)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Setup the DB and create a table:&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;rails g model User name
&lt;span class="nv"&gt;$ &lt;/span&gt;rails db:create db:migrate


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

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

&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="s2"&gt;"home#index"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# app/controllers/home_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# config/environments/development.rb&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;We can connect to the PostgreSQL with:&lt;/p&gt;

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

&lt;span class="c"&gt;# 6432 = Connect to PostgreSQL directly&lt;/span&gt;
&lt;span class="c"&gt;# 7432 = Connect throught PgBouncer&lt;/span&gt;
psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-h&lt;/span&gt; localhost &lt;span class="nt"&gt;-d&lt;/span&gt; blog_development &lt;span class="nt"&gt;-p&lt;/span&gt; 6432


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

&lt;/div&gt;

&lt;p&gt;We can use this SQL to check the amount of connections:&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_stat_activity&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;datname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'blog_development'&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;To run the server, I use this command (values will be changed depending on what I want to try):&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;DB_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7432 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;RAILS_MAX_THREAD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;WEB_CONCURRENCY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3 &lt;span class="se"&gt;\&lt;/span&gt;
  rails s &lt;span class="nt"&gt;-b&lt;/span&gt; 0.0.0.0


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

&lt;/div&gt;

&lt;p&gt;To send request, we can use this command:&lt;/p&gt;

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

&lt;span class="c"&gt;# Apache benchmark&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; jordi/ab &lt;span class="nt"&gt;-c&lt;/span&gt; 500 &lt;span class="nt"&gt;-n&lt;/span&gt; 500 http://host.docker.internal:3000/


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

&lt;/div&gt;

&lt;p&gt;The result:&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%2Fua2aua1h956nadtaj9v9.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%2Fua2aua1h956nadtaj9v9.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculating the amount of database needed
&lt;/h2&gt;

&lt;p&gt;The first part is to figure out the max connections per process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The pool size in &lt;code&gt;database.yml&lt;/code&gt; depends on how many threads/concurrency that you set&lt;/li&gt;
&lt;li&gt;If you set &lt;code&gt;RAILS_MAX_THREAD&lt;/code&gt; to 10, then, that's the amount the pool size needed&lt;/li&gt;
&lt;li&gt;But you might different value when you have a background job, e.g: Sidekiq&lt;/li&gt;
&lt;li&gt;Sidekiq might have different concurrency, so, if the concurrency is set to 20, then you need to increase the pool size to accomodate that. So, there's a chance the web might have bigger value unless you can specify different config between the worker and the web server (assuming they live in different server)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once we have figured that out, we need to think about the amount of process that we will have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A process is something like how many puma/sidekiq process that you have&lt;/li&gt;
&lt;li&gt;In this exercise, I'm using &lt;code&gt;WEB_CONCURRENCY&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Puma: WEB_CONCURRENCY=2&lt;/li&gt;
&lt;li&gt;Puma: RAILS_MAX_THREAD=5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means, we need to have at least 2x5 = 10 connections. We also need to set the DB pool size to 5.&lt;/p&gt;

&lt;p&gt;Let's add Sidekiq to the mix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bundle exec sidekiq -C config/sidekiq/payment.yml&lt;/li&gt;
&lt;li&gt;bundle exec sidekiq -C config/sidekiq/data.yml&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Assuming &lt;code&gt;concurrency&lt;/code&gt; set to 20 each in those configs, we need to have 2 x 20 = 40 connections. DB pool should be set to 20 in this case.&lt;/p&gt;

&lt;p&gt;So, we need a total of 10 + 40 = 50 connections.&lt;/p&gt;

&lt;p&gt;Again, we need to ensure proper DB pool size is set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;p&gt;These are some the notes based on my observations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Opening rails console won't immediately open a connection&lt;/li&gt;
&lt;li&gt;Without PgBouncer, Rails will immediately open all possible connections&lt;/li&gt;
&lt;li&gt;PgBouncer will increase the connections after I ran the benchmark couple of times, but never reached the max&lt;/li&gt;
&lt;li&gt;Both Rails and PgBouncer has an option to disconnect idle connections&lt;/li&gt;
&lt;li&gt;Without the right pool/thread size, Rails will throw &lt;code&gt;ActiveRecord::ConnectionTimeoutError&lt;/code&gt; error&lt;/li&gt;
&lt;li&gt;I had to use transaction mode with &lt;code&gt;prepared_statement&lt;/code&gt; option disabled. Need to read more about this&lt;/li&gt;
&lt;li&gt;I'm not splitting the read and write&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;I assumed PgBouncer will always use less connections which is kind of true but I'm not sure if more requests comes in, will the amount of connections keeps on increasing to the max?&lt;/li&gt;
&lt;li&gt;We can rely on the timeout to remove the idle connections for both Rails and PgBouncer&lt;/li&gt;
&lt;li&gt;PgBouncer is definitely a must if we are connecting to the DB from various applications and one of them might not have their own pool manager&lt;/li&gt;
&lt;li&gt;Based on some searching, it's not possible to disable Rails connection pooler and just use PgBouncer&lt;/li&gt;
&lt;li&gt;I think PgBouncer is capable of processing multiple SQL using one connection because it has the multiplex feature, but I can't confirm this, yet&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devcenter.heroku.com/articles/best-practices-pgbouncer-configuration#pgbouncer-s-connection-pooling-modes" rel="noopener noreferrer"&gt;https://devcenter.heroku.com/articles/best-practices-pgbouncer-configuration#pgbouncer-s-connection-pooling-modes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pgbouncer.org/config.html" rel="noopener noreferrer"&gt;https://www.pgbouncer.org/config.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://makandracards.com/makandra/45360-using-activerecord-with-threads-might-use-more-database-connections-than-you-think" rel="noopener noreferrer"&gt;https://makandracards.com/makandra/45360-using-activerecord-with-threads-might-use-more-database-connections-than-you-think&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://maxencemalbois.medium.com/the-ruby-on-rails-database-connections-pool-4ce1099a9e9f" rel="noopener noreferrer"&gt;https://maxencemalbois.medium.com/the-ruby-on-rails-database-connections-pool-4ce1099a9e9f&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hopsoft/optimizing-rails-connections-4gkd"&gt;https://dev.to/hopsoft/optimizing-rails-connections-4gkd&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rails</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Ruby on Rails and Docker for Testing</title>
      <dc:creator>Amree Zaid</dc:creator>
      <pubDate>Thu, 13 Jan 2022 02:46:10 +0000</pubDate>
      <link>https://dev.to/amree/ruby-on-rails-and-docker-for-testing-4n8a</link>
      <guid>https://dev.to/amree/ruby-on-rails-and-docker-for-testing-4n8a</guid>
      <description>&lt;p&gt;In the previous &lt;a href="https://dev.to/amree/ruby-on-rails-development-using-docker-o1d"&gt;post&lt;/a&gt;, we managed to figure out a way to make our Docker setup work for Development. It’s time to figure out how we can run our tests with it. In the end, we should be able to run single and multiple tests. This also includes Capybara tests using headless Chrome.&lt;/p&gt;

&lt;p&gt;We will also look into how to use multiple docker-compose files to override what we have based on the environment. But we’ll start with something simple first.&lt;/p&gt;

&lt;p&gt;Let us install RSpec first but I will skip this part and refer you guys to this &lt;a href="https://relishapp.com/rspec/rspec-rails/docs/gettingstarted"&gt;guide&lt;/a&gt;. However, we need to update &lt;code&gt;.rspec&lt;/code&gt; to be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--require rails_helper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without that, we will get an uninitialized constant error.&lt;/p&gt;

&lt;p&gt;The first thing we need to do is to prepare the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose run &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"RAILS_ENV=test"&lt;/span&gt; web rails db:create db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is quite straightforward as we just override the &lt;code&gt;RAILS_ENV&lt;/code&gt; to &lt;code&gt;test&lt;/code&gt; and prepare the database based on that environment. However, I had problems because I was using &lt;code&gt;DATABASE_URL&lt;/code&gt; from &lt;code&gt;docker-compose&lt;/code&gt; and that will override the database name. &lt;/p&gt;

&lt;p&gt;Maybe there are better ways to do this, but this is how I fixed it:&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="c1"&gt;# config/database.yml&lt;/span&gt;
&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;default&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql&lt;/span&gt;
  &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unicode&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV['DATABASE_USERNAME'] %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV['DATABASE_PASSWORD'] %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV['DATABASE_HOST'] %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %&amp;gt;&lt;/span&gt;

&lt;span class="na"&gt;development&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*default&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;blog_development&lt;/span&gt;

&lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*default&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;blog_test&lt;/span&gt;

&lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*default&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_production&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV['APP_DATABASE_PASSWORD'] %&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_USERNAME=postgres&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_PASSWORD=password&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_HOST=db&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, we only provide the username, password and host. We will let the database name change based on the environment it is being run.&lt;/p&gt;

&lt;p&gt;Add a simple spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/models/post_spec.rb&lt;/span&gt;
&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :model&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"can be created successfully"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

    &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persisted?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then run the spec by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"RAILS_ENV=test"&lt;/span&gt; web bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rspec spec/models/post_spec.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multiple Compose Files
&lt;/h3&gt;

&lt;p&gt;As you can see from the previous guide, we had to override the environment variable using &lt;code&gt;-e&lt;/code&gt;. This is ok for one or two variables but it is not scalable when we have more than that. docker-compose has &lt;a href="https://docs.docker.com/compose/extends/"&gt;extend feature&lt;/a&gt; that would allow us to use a base compose file and override with another one.&lt;/p&gt;

&lt;p&gt;Using the same example from the above, we can create a new docker-compose file:&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="c1"&gt;# docker-compose.test.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RAILS_ENV=test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, we can run the spec with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.test.yml &lt;span class="se"&gt;\&lt;/span&gt;
  run &lt;span class="nt"&gt;--rm&lt;/span&gt; web bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rspec spec/models/post_spec.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means we can separate different configs depending on the environment. We just need to a base config and override with the file we specified.&lt;/p&gt;

&lt;p&gt;Do remember not to commit the &lt;code&gt;docker-compose.*.yml&lt;/code&gt; as it might contain sensitive information. Create a template file such as &lt;code&gt;docker-compose.test.template.yml&lt;/code&gt; for others to copy and change accordingly.&lt;/p&gt;

&lt;p&gt;Another important note is if we have &lt;code&gt;docker-compose.override.yml&lt;/code&gt;, we don’t have to specify &lt;code&gt;-f&lt;/code&gt; to override the compose config as docker will do that automatically. &lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;docker-compose config&lt;/code&gt; to see your final config. Use &lt;code&gt;-f override_file&lt;/code&gt; if needed. That might be helpful in debugging complex configurations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser Testing
&lt;/h3&gt;

&lt;p&gt;Add a simple spec for feature testing first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/features/user_creates_post_spec.rb&lt;/span&gt;
&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"User creates post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;js: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="s2"&gt;"successfully"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;posts_path&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"New Post"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Headless&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add these files first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/support/capybara.rb&lt;/span&gt;
&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before&lt;/span&gt; &lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;js: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'SELENIUM_REMOTE_HOST'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:4444/wd/hub"&lt;/span&gt;

    &lt;span class="n"&gt;driven_by&lt;/span&gt; &lt;span class="ss"&gt;:selenium&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :chrome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;options: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;browser: :remote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;desired_capabilities: :chrome&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`/sbin/ip route|awk '/scope/ { print $9 }'`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;
    &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"43447"&lt;/span&gt;
    &lt;span class="n"&gt;session_server&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server&lt;/span&gt;
    &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app_host&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;session_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;session_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;port&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.test.yml&lt;/span&gt;
&lt;span class="ss"&gt;services:
  web:
    environment:
      &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="no"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
      &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="no"&gt;SELENIUM_REMOTE_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;selenium&lt;/span&gt;
    &lt;span class="ss"&gt;depends_on:
      &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;selenium&lt;/span&gt;

  &lt;span class="ss"&gt;selenium:
    image: &lt;/span&gt;&lt;span class="n"&gt;selenium&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;standalone&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;chrome&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, we are going to use a selenium container for the test to run. The &lt;code&gt;web&lt;/code&gt; container will access it at port &lt;code&gt;4444&lt;/code&gt; and we do not need to open it as they communicated with each other within docker’s network itself. We will also open Capybara at port &lt;code&gt;43447&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-headless&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TBD - This is something that I haven’t been able to figure out. I will definitely update it once I managed to solve it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallel Testing
&lt;/h3&gt;

&lt;p&gt;TBD - More on this once I’ve figured the production part.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cloudbees.com/blog/testing-rails-application-docker"&gt;https://www.cloudbees.com/blog/testing-rails-application-docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/compose/extends/"&gt;https://docs.docker.com/compose/extends/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rails</category>
      <category>docker</category>
    </item>
    <item>
      <title>Ruby on Rails Development using Docker</title>
      <dc:creator>Amree Zaid</dc:creator>
      <pubDate>Fri, 31 Dec 2021 16:30:25 +0000</pubDate>
      <link>https://dev.to/amree/ruby-on-rails-development-using-docker-o1d</link>
      <guid>https://dev.to/amree/ruby-on-rails-development-using-docker-o1d</guid>
      <description>&lt;p&gt;Check out my previous &lt;a href="https://dev.to/amree/introduction-to-ruby-on-rails-and-dockerfile-5a29"&gt;post&lt;/a&gt; if you want to start learning from just a Dockerfile. As you may have realized, it's not scalable if we keep on using the long command to manage our containers. We haven't even started talking about different services such as PostgreSQL and Redis, yet.&lt;/p&gt;

&lt;p&gt;In this post, we are aiming to use &lt;code&gt;docker-compose&lt;/code&gt; to make our development experience easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prepare our application
&lt;/h3&gt;

&lt;p&gt;I'm assuming we are starting from fresh where we don't have any Ruby on Rails application ready, yet. I'll put a note later if you are starting with an existing application.&lt;/p&gt;

&lt;p&gt;The files that we need in order to dockerize our application are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gemfile - List of gems that are going to be installed&lt;/li&gt;
&lt;li&gt;Gemfile.lock - Locked version of &lt;code&gt;Gemfile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;package.json - List of npm packages that are going to be installed&lt;/li&gt;
&lt;li&gt;yarn.lock - Locked version of &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why these files? They are the ones that will determine what packages will be installed in order for us to have a working Rails application.&lt;/p&gt;

&lt;p&gt;How do we get them without installing the Rails application in our system? Similar to what we did before, we need to create a temporary application to copy them.&lt;/p&gt;

&lt;p&gt;Create a directory and put this &lt;code&gt;Dockerfile&lt;/code&gt; inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ruby:2.6.3&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  curl build-essential libpq-dev &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  curl &lt;span class="nt"&gt;-sL&lt;/span&gt; https://deb.nodesource.com/setup_16.x | bash - &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb https://dl.yarnpkg.com/debian/ stable main"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; /etc/apt/sources.list.d/yarn.list &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nodejs
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; yarn
&lt;span class="k"&gt;RUN &lt;/span&gt;gem &lt;span class="nb"&gt;install &lt;/span&gt;rails:6.1.4.4 bundler:2.3.4

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["rails", "server", "-b", "0.0.0.0"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, run these commands to generate a new rails application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; blog

docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; bundle-2.6.3:/bundle &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; node_modules-rails-6.1.4.1:/app/node_modules &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;BUNDLE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bundle &lt;span class="se"&gt;\&lt;/span&gt;
  blog &lt;span class="se"&gt;\&lt;/span&gt;
  bash

&lt;span class="c"&gt;# run this in the container&lt;/span&gt;
rails new &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgresql 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that is done, we will have all of the necessary files to really start our new application. &lt;/p&gt;

&lt;p&gt;Our next target is to run the basic application successfully. Replace the existing &lt;code&gt;Dockerfile&lt;/code&gt; with this content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ruby:2.6.3&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl &lt;span class="se"&gt;\
&lt;/span&gt;    build-essential &lt;span class="se"&gt;\
&lt;/span&gt;    libpq-dev &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl &lt;span class="nt"&gt;-sL&lt;/span&gt; https://deb.nodesource.com/setup_16.x | bash - &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb https://dl.yarnpkg.com/debian/ stable main"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; /etc/apt/sources.list.d/yarn.list &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nodejs &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;gem &lt;span class="nb"&gt;install &lt;/span&gt;rails:6.1.4.4 bundler:2.3.4
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Gemfile* /app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--jobs&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;nproc&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; yarn
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; yarn.lock /app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; . /app&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["rails", "server", "-b", "0.0.0.0"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create another file &lt;code&gt;docker-compose.yml&lt;/code&gt;&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:14.1-alpine&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=password&lt;/span&gt;

  &lt;span class="na"&gt;webpacker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash -c "bin/webpack-dev-server"&lt;/span&gt;
    &lt;span class="na"&gt;ports&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;3035:3035"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle:/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules:/app/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUNDLE_PATH=/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;WEBPACKER_DEV_SERVER_HOST=0.0.0.0&lt;/span&gt;

  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash -c "rm -f /app/tmp/pids/server.pid &amp;amp;&amp;amp; rails s -b 0.0.0.0"&lt;/span&gt;
    &lt;span class="na"&gt;ports&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;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle:/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules:/app/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUNDLE_PATH=/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgres://postgres:password@db/blog_development&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;WEBPACKER_DEV_SERVER_HOST=webpacker&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;webpacker&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;bundle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;node_modules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run these commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose build
docker-compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; web rails db:create db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;docker-compose up&lt;/code&gt; to see if it works. You can verify all containers are running by issuing &lt;code&gt;docker container ls&lt;/code&gt;. There should be three containers: &lt;code&gt;web&lt;/code&gt;, &lt;code&gt;webpacker&lt;/code&gt; and the &lt;code&gt;db&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Open &lt;a href="http://localhost:3000/"&gt;http://localhost:3000/&lt;/a&gt; to ensure everything is good.&lt;/p&gt;

&lt;p&gt;Generate some resources so that we can have something that we can try on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose run web rails g scaffold Post title body:text
docker-compose run web rails db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Things to try out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new Post and see if you can persist it&lt;/li&gt;
&lt;li&gt;Change the &lt;code&gt;application.js&lt;/code&gt; and see if your webpacker will compile the changes&lt;/li&gt;
&lt;li&gt;See if your hot reload works when your js file changed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cool, now we have a running local rails application using Docker. We will start tackling some normal workflows one by one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Base Image
&lt;/h3&gt;

&lt;p&gt;This is actually something I realized later, but I’m adding it to the top to help us save some time when trying out other stuff mentioned below. Normally, I would use this config whenever I wanted to add a new service in our &lt;code&gt;docker-compose&lt;/code&gt;:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;console&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash -c "rails console"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle:/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules:/app/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUNDLE_PATH=/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgres://postgres:password@db/blog_development&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;WEBPACKER_DEV_SERVER_HOST=webpacker&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That will actually rebuild the image again which means installing all the apt, gems, npm, etc.  You can try &lt;code&gt;docker-compose up&lt;/code&gt; and see what happened. I’ll wait.&lt;/p&gt;

&lt;p&gt;To prevent this, we can build an image first and use the same to all of our services:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;base_web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;base_web&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/true&lt;/span&gt;

  &lt;span class="na"&gt;webpacker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# build: . # remove&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;base_web&lt;/span&gt;

  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# build: . # remove&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;base_web&lt;/span&gt;

  &lt;span class="na"&gt;worker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# build: . # remove&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;base_web&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding a new gem
&lt;/h3&gt;

&lt;p&gt;Actually, we can just add into the &lt;code&gt;Gemfile&lt;/code&gt; just like we normally do and run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; web bundle &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, I do notice that running &lt;code&gt;docker-compose build&lt;/code&gt; will re-install everything again. I’m not sure yet how to handle this and it doesn’t make sense to keep on installing all gems every time we change something in &lt;code&gt;Gemfile&lt;/code&gt;. I’ll just defer this to later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessing pry console
&lt;/h3&gt;

&lt;p&gt;The first thing that I noticed is &lt;code&gt;docker-compose up&lt;/code&gt; will launch all services together and you won’t be able to access the prompt when you load the page. It will just go past the console as if nothing happened:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;web_1        | Processing by PostsController#index as HTML
web_1        |
web_1        | From: /app/controllers/posts_controller.rb:8 PostsController#index:
web_1        |
web_1        |      5: def index
web_1        |      6:   @posts &lt;span class="o"&gt;=&lt;/span&gt; Post.all
web_1        |      7:
web_1        |  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;  8:   require &lt;span class="s1"&gt;'pry'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; binding.pry
web_1        |      9:
web_1        |     10:   puts &lt;span class="s2"&gt;"a"&lt;/span&gt;
web_1        |     11: end
web_1        |
&lt;span class="o"&gt;[&lt;/span&gt;1] pry&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="c"&gt;#&amp;lt;PostsController&amp;gt;)&amp;gt;&lt;/span&gt;
web_1        |   Rendering layout layouts/application.html.erb
web_1        |   Rendering layout layouts/application.html.erb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to catch/access the prompt, we need to update the &lt;code&gt;docker-compose.yml&lt;/code&gt; for the web part:&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;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;stdin_open&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;docker-compose up&lt;/code&gt; again and load the page with &lt;code&gt;pry&lt;/code&gt; code. Once it’s stopped, open a new terminal and run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker attach container_name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can get the right container name by running &lt;code&gt;docker container ls&lt;/code&gt;. Make sure we choose the web’s container name as that is where the prompt is.&lt;/p&gt;

&lt;p&gt;Once we ran that command, we will notice nothing happened. We are actually already in the prompt itself. Just press a key, e.g: enter and we will get the prompt. Once we have exited the prompt, the would still be in the container itself. To detach, just press &lt;code&gt;CTRL-p CTRL-q&lt;/code&gt; key sequence.&lt;/p&gt;

&lt;p&gt;By the way, we can tail the log from another window by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose logs &lt;span class="nt"&gt;--follow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sidekiq
&lt;/h3&gt;

&lt;p&gt;It is pretty common to use background job processing in our app and one of the most popular services out there would be Sidekiq. Add &lt;code&gt;sidekiq&lt;/code&gt; gem to our &lt;code&gt;Gemfile&lt;/code&gt; and install it using the steps mentioned above.&lt;/p&gt;

&lt;p&gt;We need to make some adjustments to existing configurations:&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="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:6.2-alpine&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-server&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis:/data&lt;/span&gt;

  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://redis:6379&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;

  &lt;span class="na"&gt;worker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash -c "bundle exec sidekiq"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle:/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules:/app/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUNDLE_PATH=/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgres://postgres:password@db/blog_development&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://redis:6379&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a simple worker to test it out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/workers/test_worker.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestWorker&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;
    &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"Blogging at &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do a &lt;code&gt;docker-compose down&lt;/code&gt; and then &lt;code&gt;docker-compose up&lt;/code&gt; to restart everything. Run console to manually execute the worker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; web rails console

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; TestWorker.perform_async
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will notice the worker will be processed from the server log. It will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;worker_1  | 2021-12-31T03:49:18.807Z &lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;tid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gpmykgmlp &lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TestWorker &lt;span class="nv"&gt;jid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1f261ce8ab3bdb0b7c11d2e2 INFO: start
worker_1  | 2021-12-31T03:49:19.137Z &lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;tid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gpmykgmlp &lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TestWorker &lt;span class="nv"&gt;jid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1f261ce8ab3bdb0b7c11d2e2 &lt;span class="nv"&gt;elapsed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.329 INFO: &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do note that the worker will be run automatically whenever we use &lt;code&gt;docker-compose up&lt;/code&gt;. We can always run it manually using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; web bundle &lt;span class="nb"&gt;exec &lt;/span&gt;sidekiq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically use the existing container and do make sure we already set the right Redis connection for Sidekiq to work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Development ENV(s)
&lt;/h3&gt;

&lt;p&gt;It is pretty common to have environment variables loaded with different values based on the environment. It could also be different because it is unique to the developer himself. So, how do we handle this? Let us assume we want to load &lt;code&gt;FOO=bar&lt;/code&gt; in every service that we created.&lt;/p&gt;

&lt;p&gt;Just add this config in our services, e.g:&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="c1"&gt;# .env.dev&lt;/span&gt;
&lt;span class="s"&gt;FOO=bar&lt;/span&gt;

&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# ...&lt;/span&gt;
        &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env.dev&lt;/span&gt;

    &lt;span class="c1"&gt;# do the same for worker, webpacker, etc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can test it out with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; web bash

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$FOO&lt;/span&gt;
bar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might be asking, wouldn’t this mean we are stuck with the same development variable for all environments? Yes, that would be a problem, but we will solve it in the next chapter. Right now, we just need to worry about our development environment to simplify learning.&lt;/p&gt;

&lt;p&gt;With the addition of &lt;code&gt;.env*&lt;/code&gt;, we need to ensure it won’t be persisted into the docker image for security reasons. I know we can do this with &lt;code&gt;.dockerignore&lt;/code&gt;, but I’m not sure how to verify or validate it. I’ll keep that in mind first and return to this later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Private or Commercial gems
&lt;/h3&gt;

&lt;p&gt;This actually took me the longest to understand how it works and I don’t think I have the best solution yet, but it is working with some caveats. Let’s get on to it.&lt;/p&gt;

&lt;p&gt;I am using Sidekiq paid version in one of my projects but this problem can be applied to private gems hosted in GitLab as well. Let us tackle Sidekiq’s gem first.&lt;/p&gt;

&lt;p&gt;Sidekiq required us to supply a username and password. We need to figure out how to make it work during the build and runtime. We will talk about the disadvantages of this approach later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Gemfile
gem 'sidekiq-ent', source: "https://enterprise.contribsys.com/"

## .dockerignore
/.env.development
/build_credentials
/docker-compose.override.yml

## .gitignore
/.env.development
/build_credentials
/docker-compose.override.yml

## Dockerfile
# ..
RUN gem install rails:6.1.4.4 bundler:2.3.4
COPY Gemfile* /app
COPY bundle_install.sh .
RUN --mount=type=secret,id=bundle_credentials ./bundle_install.sh
# ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# bundle_install.sh&lt;/span&gt;
&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="c"&gt;# Pre-installation&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /run/secrets/bundle_credentials &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s1"&gt;'^#'&lt;/span&gt; /run/secrets/bundle_credentials | xargs&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Run installation&lt;/span&gt;
bundle &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--jobs&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;nproc&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Cleanup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# bundle_credentials&lt;/span&gt;
&lt;span class="nv"&gt;BUNDLE_ENTERPRISE__CONTRIBSYS__COM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;username:password
&lt;span class="nv"&gt;BUNDLE_GITLAB__COM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amree:personal_token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:14.1-alpine&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=password&lt;/span&gt;

  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:6.2-alpine&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-server&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis:/data&lt;/span&gt;

  &lt;span class="na"&gt;webpacker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;blog_base&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash -c "bin/webpack-dev-server"&lt;/span&gt;
    &lt;span class="na"&gt;ports&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;3035:3035"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle:/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules:/app/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUNDLE_PATH=/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;WEBPACKER_DEV_SERVER_HOST=0.0.0.0&lt;/span&gt;

  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;blog_base&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash -c "rm -f /app/tmp/pids/server.pid &amp;amp;&amp;amp; rails s -b 0.0.0.0"&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;stdin_open&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;ports&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;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle:/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules:/app/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUNDLE_PATH=/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgres://postgres:password@db/blog_development&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;WEBPACKER_DEV_SERVER_HOST=webpacker&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://redis:6379&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;webpacker&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;

  &lt;span class="na"&gt;worker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;blog_base&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash -c "bundle exec sidekiq"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle:/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules:/app/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUNDLE_PATH=/bundle&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgres://postgres:password@db/blog_development&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://redis:6379&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;bundle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;node_modules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&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 yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.override.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;webpacker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env.development&lt;/span&gt;

  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env.development&lt;/span&gt;

  &lt;span class="na"&gt;worker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env.development&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is quite some changes 😅  But this is needed for security purposes. We don’t want our image to contain sensitive information. It is OK if we are not going to push it to production, but for best practice, we will just try this out first even though it’s a little bit complicated. Things may change once I started to look into the deployment part, but let’s focus on what we have first.&lt;/p&gt;

&lt;p&gt;We are using a couple of features from Docker here, mainly secret and override. Credit to this blog &lt;a href="https://pythonspeed.com/articles/build-secrets-docker-compose/"&gt;post&lt;/a&gt; that solves most of the problems. We just need to adapt what was written to our problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docker secret&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the secure way to build our image without persisting the secret in the image itself. From what I understand, the secret won’t be saved in the image as it’s mounted temporarily, which is why we need to run the bundle install the script instead of outside.&lt;/p&gt;

&lt;p&gt;We can’t even export the value out and we also do not want to save the value somewhere where people can access it once they managed to get our image. This is also the reason why we move the &lt;code&gt;bundle install&lt;/code&gt; into the script as that is where the required credentials are available without being exposed to the image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docker override&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This feature would allow us to specify different values for the variables based on different environments. A small tip: Use &lt;code&gt;docker-compose config&lt;/code&gt; to check your final output.&lt;/p&gt;

&lt;p&gt;Do note we need these two features for different reasons. docker secret is going to be used when we build the image and docker override is being used when we are running the services from &lt;code&gt;docker-compose&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since we can’t use the secret feature from &lt;code&gt;docker-compose&lt;/code&gt;, we will just build the image using &lt;code&gt;docker&lt;/code&gt; from now on. The image generated will be used in &lt;code&gt;docker-compose.yml&lt;/code&gt; by specifying the tag name.&lt;/p&gt;

&lt;p&gt;To build the image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; blog_base &lt;span class="nt"&gt;--progress&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;plain &lt;span class="nt"&gt;--secret&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bundle_credentials,src&lt;span class="o"&gt;=&lt;/span&gt;.env.development &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, we tag it as &lt;code&gt;blog_base&lt;/code&gt; and use the same name in &lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If we want to verify whether the credential was leaked, we can use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;history &lt;/span&gt;blog_base &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"table{{.ID}}, {{.CreatedBy}}"&lt;/span&gt; &lt;span class="nt"&gt;--no-trunc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Random Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Volumes were created with a namespace, most likely based on the directory name. I had to reinstall the gems because of this (&lt;a href="https://stackoverflow.com/a/41222926"&gt;ref&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;The default username for &lt;code&gt;postgres&lt;/code&gt; image is &lt;code&gt;postgres&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/docker/compose/issues/4560"&gt;https://github.com/docker/compose/issues/4560&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/briankung/ebfb567d149209d2d308576a6a34e5d8"&gt;https://gist.github.com/briankung/ebfb567d149209d2d308576a6a34e5d8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/compose/environment-variables/"&gt;https://docs.docker.com/compose/environment-variables/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/engine/reference/builder/#dockerignore-file"&gt;https://docs.docker.com/engine/reference/builder/#dockerignore-file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pythonspeed.com/articles/build-secrets-docker-compose/"&gt;https://pythonspeed.com/articles/build-secrets-docker-compose/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/develop/develop-images/build_enhancements/#new-docker-build-secret-information"&gt;https://docs.docker.com/develop/develop-images/build_enhancements/#new-docker-build-secret-information&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nickjj/docker-rails-example"&gt;https://github.com/nickjj/docker-rails-example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/19331497/set-environment-variables-from-file-of-key-value-pairs"&gt;https://stackoverflow.com/questions/19331497/set-environment-variables-from-file-of-key-value-pairs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rails</category>
      <category>docker</category>
    </item>
    <item>
      <title>Introduction to Ruby on Rails and Dockerfile</title>
      <dc:creator>Amree Zaid</dc:creator>
      <pubDate>Sat, 04 Dec 2021 09:51:12 +0000</pubDate>
      <link>https://dev.to/amree/introduction-to-ruby-on-rails-and-dockerfile-5a29</link>
      <guid>https://dev.to/amree/introduction-to-ruby-on-rails-and-dockerfile-5a29</guid>
      <description>&lt;p&gt;I think everyone knows by now how good Docker is in making sure we have &lt;em&gt;almost&lt;/em&gt; the same setup everywhere. However, I think it is not easy as everyone thought for people new to it. There are just so many questions that I have to the point they discouraged me from using it as my daily driver.&lt;/p&gt;

&lt;p&gt;I decided to spend some time to look into this and document this journey through this blog post. I hope this will help me and you in learning this awesome service. There will be more after this, but we will start with this one first.&lt;/p&gt;

&lt;p&gt;At the end of this blog post, you will be able to run a Ruby on Rails application with a working asset compilation using just a &lt;code&gt;Dockerfile&lt;/code&gt;. We will be using SQLite first for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Dockerfile
&lt;/h2&gt;

&lt;p&gt;Let us start with a simple &lt;code&gt;Dockerfile&lt;/code&gt;. You can create a directory and place this file there. Right now, we just want to create a new Rails application without installing the rails gem on our local machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ruby:2.6.3&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  curl build-essential libpq-dev &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  curl &lt;span class="nt"&gt;-sL&lt;/span&gt; https://deb.nodesource.com/setup_10.x | bash - &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb https://dl.yarnpkg.com/debian/ stable main"&lt;/span&gt; | &lt;span class="nb"&gt;tee&lt;/span&gt; /etc/apt/sources.list.d/yarn.list &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nodejs yar

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;gem &lt;span class="nb"&gt;install &lt;/span&gt;rails bundler

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/bin/bash"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will go through this file line by line&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ruby:2.6.3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will pull an image from &lt;a href="https://hub.docker.com/_/ruby"&gt;DockerHub&lt;/a&gt; and use it as the base. This particular line will pull the image tagged as &lt;code&gt;2.6.3&lt;/code&gt;, which is not listed on the main page. However, you can always look it up from the Tags page.&lt;/p&gt;

&lt;p&gt;There are still many variants that you can choose from. That will determine the size of your local image and the library that is loaded with it.&lt;/p&gt;

&lt;p&gt;I also learnt that once you build the image using that base, you can only clear it using &lt;code&gt;docker builder prune&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line will install all of our required libraries in order to make sure we can run what we want. Try not to install unnecessary applications/services to reduce the image size.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will set &lt;code&gt;/app&lt;/code&gt; as our default directory. Every subsequent command will be run from that directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/bin/bash"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the command that will be executed when we run the container. In this case, we need access to &lt;code&gt;bash&lt;/code&gt; first so that we can create a new rails application and install all the required gems.&lt;/p&gt;

&lt;p&gt;Let's build the container for the first time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever we build the image, we need to tag it using &lt;code&gt;-t&lt;/code&gt;. We also need to supply the &lt;code&gt;Dockerfile&lt;/code&gt; but we can just &lt;code&gt;.&lt;/code&gt; and docker will find the file on its own.&lt;/p&gt;

&lt;p&gt;We will see the base image is downloaded and cached. It may take some time for the first time, but it will be faster once everything is cached.&lt;/p&gt;

&lt;p&gt;Since we need to create a new Rails application, we will interact with the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--rm&lt;/code&gt; will ensure there will be no unused container once we exit from it. We can verify it using &lt;code&gt;docker container ls&lt;/code&gt;. &lt;code&gt;-it&lt;/code&gt; is actually &lt;code&gt;--interactive + --tty&lt;/code&gt; and it is for to interact with the container. The explanation is a bit long, but we can read it from these pages (&lt;a href="https://stackoverflow.com/a/59965320/113573"&gt;1&lt;/a&gt;, &lt;a href="https://stackoverflow.com/a/22287905/113573"&gt;2&lt;/a&gt;). The &lt;code&gt;blog&lt;/code&gt; param is just the image name that was created using &lt;code&gt;docker build&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once we got it, we realize we don't have anything yet, so, we need to create the Rails application with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails new &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once everything is installed and you exit the container, you will realize the Rails application you generated is not available in your local copy. It is also not available in your docker container if you use the &lt;code&gt;docker run&lt;/code&gt; again. Obviously, we don't want to keep on creating a new Rails application. &lt;/p&gt;

&lt;p&gt;Create another directory in the same level as the &lt;code&gt;Dockerfile&lt;/code&gt; called &lt;code&gt;blog&lt;/code&gt;. This directory will be mapped to &lt;code&gt;/app&lt;/code&gt; in our container.&lt;/p&gt;

&lt;p&gt;We can use volume for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; /local/path/to/blog:/app blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time, we can create a Rails application with &lt;code&gt;rails new .&lt;/code&gt; from within the container itself and it will be persisted in our local copy as well. Any changes made from our machine (the host) and the container will be reflected on both sides.&lt;/p&gt;

&lt;p&gt;Once that is done, try exiting and entering your container again. We will realize &lt;code&gt;rails -v&lt;/code&gt; will throw us an error saying we are missing lots of gems. This is happening because our gem installation wasn't persisted. In order to fix this, we can use the volume feature again. &lt;/p&gt;

&lt;p&gt;This time, we won't be mounting from our local directory just like we did with our Rails application, instead, it will be created first and then we will supply it as one of the arguments:&lt;/p&gt;

&lt;p&gt;We just need to specify a new volume name and it will be created automatically and in this case, we are using &lt;code&gt;bundle&lt;/code&gt; as the volume name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /local/path/to/blog:/app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; bundle:/bundle &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;BUNDLE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bundle &lt;span class="se"&gt;\&lt;/span&gt;
  blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;BUNDLE_PATH&lt;/code&gt; is just an environment variable that is used by &lt;code&gt;ruby&lt;/code&gt; to install the bundled gems. Just do &lt;code&gt;bundle&lt;/code&gt; and exit once it's done. Run the container with the same command and &lt;code&gt;rails -v&lt;/code&gt; won't give us any error.&lt;/p&gt;

&lt;p&gt;Run this command to finish setting up our Rails application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails db:create db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally we can just use &lt;code&gt;rails s&lt;/code&gt; to access our welcome page, but we can't yet as there is no way we can access it from the host. In order to solve this, we need to expose a port when we run the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /local/path/to/blog:/app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; bundle:/bundle &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;BUNDLE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bundle &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000
  blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to start our server using &lt;code&gt;rails -b 0.0.0.0&lt;/code&gt; as we want it to be accessible to the host as well. You should be able to see the Welcome page now when you open &lt;a href="http://localhost:3000/"&gt;http://localhost:3000/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to use this line in order to get the &lt;code&gt;docker run&lt;/code&gt; to run the rails server automatically without going into the &lt;code&gt;bash&lt;/code&gt; first anymore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ENTRYPONT &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/bin/bash"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="c"&gt;# vs&lt;/span&gt;
CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"rails"&lt;/span&gt;, &lt;span class="s2"&gt;"server"&lt;/span&gt;, &lt;span class="s2"&gt;"-b"&lt;/span&gt;, &lt;span class="s2"&gt;"0.0.0.0"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I think this &lt;a href="https://stackoverflow.com/a/34245657/113573"&gt;answer&lt;/a&gt; explained about &lt;code&gt;ENTRYPOINT&lt;/code&gt; vs &lt;code&gt;CMD&lt;/code&gt; pretty well.&lt;/p&gt;

&lt;h3&gt;
  
  
  webpacker
&lt;/h3&gt;

&lt;p&gt;I didn't manage to get this working on the first try due to the lack of knowledge on how docker and webpacker works. But, you guys are lucky because I have the summary on how to get it to work.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./bin/webpack-dev/server&lt;/code&gt; that compiles our asset is serving the files from the memory, not from our file system. However, it writes to &lt;code&gt;public/packs/manifest.json&lt;/code&gt; if it doesn't exist or maybe during the update as well.&lt;/p&gt;

&lt;p&gt;Without a working &lt;code&gt;webpacker&lt;/code&gt;, you might run into a situation where the &lt;code&gt;rails server&lt;/code&gt; is the one doing the compilation which is slow and wrong.&lt;/p&gt;

&lt;p&gt;Normally, both of them would run in the same host, but with docker, you are running them in different containers. So, we need to ensure the webpacker and web containers can connect with each other.&lt;/p&gt;

&lt;p&gt;If we don't specify any network when we run our docker container, it will be connected to the &lt;code&gt;bridge&lt;/code&gt; network by default. You can connect to another container using the IP but not through the container name. To fix this, you need to create a different network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker network create blog-net &lt;span class="c"&gt;# bridge driver by default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that is done, we need to update our &lt;code&gt;docker run commands&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="c"&gt;# for webpacker&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /local/path/to/blog:/app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; bundle:/bundle &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;BUNDLE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bundle &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;WEBPACKER_DEV_SERVER_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3035:3035 &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# needed for auto reload page&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; blog-net &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; webpacker &lt;span class="se"&gt;\&lt;/span&gt;
  blog &lt;span class="se"&gt;\&lt;/span&gt;
  bin/webpack-dev-server

&lt;span class="c"&gt;# for rails&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /local/path/to/blog:/app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; bundle:/bundle &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;BUNDLE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bundle &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;WEBPACKER_DEV_SERVER_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;webpacker &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; blog-net &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; web &lt;span class="se"&gt;\&lt;/span&gt;
  blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main reason for these changes is to ensure our &lt;code&gt;rails&lt;/code&gt; container can access &lt;code&gt;webpacker&lt;/code&gt; container. Confused about the network part? I did and this &lt;a href="https://docs.docker.com/network/network-tutorial-standalone/"&gt;tutorial&lt;/a&gt; helped me a lot.&lt;/p&gt;

&lt;p&gt;Basically, the Rails container need to know where it can find the assets, which is from the &lt;code&gt;webpacker&lt;/code&gt; host. The webpacker itself need to ensure that the assets can be accessed by anyone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tips
&lt;/h3&gt;

&lt;p&gt;Some other tips that I discovered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rails&lt;/code&gt; is not in &lt;code&gt;/bundle&lt;/code&gt; but it is in the original directory which is &lt;code&gt;/usr/local/bundle&lt;/code&gt;. I think this is because of the base image. Then, this raises another question which is how do we upgrade rails themselves?&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We can override the &lt;code&gt;ENTRYPOINT&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; blog command-to-override
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This also means we can use it to run other rails command such as &lt;code&gt;rails db:migrate&lt;/code&gt;, &lt;code&gt;rails g scaffold&lt;/code&gt; and so on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you don't want to kill the container when exiting it, you can use &lt;code&gt;Ctrl + P, Ctrl + Q&lt;/code&gt;. After that, you can use &lt;code&gt;docker attach container-name&lt;/code&gt; to get in again.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  docker-compose
&lt;/h2&gt;

&lt;p&gt;As we have noticed, the &lt;code&gt;docker run&lt;/code&gt; command is getting longer. It doesn't make sense to keep on passing that command to everyone. What about the database? Webpack? Etc. As the title said, we can use &lt;code&gt;docker-compose&lt;/code&gt; to improve our docker experience. &lt;/p&gt;

&lt;p&gt;I will talk about it in the next post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/tsftech/how-to-fully-utilise-docker-compose-during-development-4b723caed798"&gt;https://medium.com/tsftech/how-to-fully-utilise-docker-compose-during-development-4b723caed798&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/swlh/docker-caching-introduction-to-docker-layers-84f20c48060a"&gt;https://medium.com/swlh/docker-caching-introduction-to-docker-layers-84f20c48060a&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.freecodecamp.org/news/painless-rails-development-environment-setup-with-docker/"&gt;https://www.freecodecamp.org/news/painless-rails-development-environment-setup-with-docker/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.plymouthsoftware.com/articles/dockerising-webpacker"&gt;https://www.plymouthsoftware.com/articles/dockerising-webpacker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/webpacker/issues/1019#issuecomment-351066969"&gt;https://github.com/rails/webpacker/issues/1019#issuecomment-351066969&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/network/network-tutorial-standalone/"&gt;https://docs.docker.com/network/network-tutorial-standalone/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/webpacker/issues/863#issuecomment-346081995"&gt;https://github.com/rails/webpacker/issues/863#issuecomment-346081995&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>rails</category>
    </item>
    <item>
      <title>Speed Up your Ruby on Rails Development Environment</title>
      <dc:creator>Amree Zaid</dc:creator>
      <pubDate>Sat, 08 May 2021 23:55:30 +0000</pubDate>
      <link>https://dev.to/amree/speed-up-your-ruby-on-rails-development-environment-1k8m</link>
      <guid>https://dev.to/amree/speed-up-your-ruby-on-rails-development-environment-1k8m</guid>
      <description>&lt;p&gt;There will be a time when your Rails development environment started to become very slow due to multiple reasons, but mostly because your codebase is very big and the monolith architecture is just too sweet for you to pass on.&lt;/p&gt;

&lt;p&gt;An example of this is you have to wait like 10 seconds (or worse) when you change something in your Rails code and hit that refresh button. Normally, starting Rails console/server also can be affected. &lt;/p&gt;

&lt;p&gt;What works for me might not work for you, but it should help you to get started. BTW, I'm optimizing 7 years old codebase. So here are some tips for speed improvements. &lt;/p&gt;

&lt;p&gt;Get a baseline first on how slow it is so that you know you are actually improving it instead of just using your feeling:&lt;/p&gt;

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

$ time bundle exec rake environment


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

&lt;/div&gt;

&lt;p&gt;This should show you the load time without spring. Any changes we make should make the numbers better than before. &lt;br&gt;
Alright, now we need to identify what's causing the slowness. We can use &lt;a href="https://github.com/nevir/Bumbler" rel="noopener noreferrer"&gt;https://github.com/nevir/Bumbler&lt;/a&gt; gem for this. Just run this command first:&lt;/p&gt;

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

$ bumbler


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

&lt;/div&gt;

&lt;p&gt;It will show you gems that are taking time to be loaded/require.&lt;/p&gt;

&lt;p&gt;But what can you do with that output? Start from the bottom and see if you put your gem in the right development. You don't have to put your server monitoring gem in the development environment, right? &lt;/p&gt;

&lt;p&gt;You can also do &lt;code&gt;require: false&lt;/code&gt; in your Gemfile for certain gems that are being used rarely, but you need to &lt;code&gt;require&lt;/code&gt; it manually when you want to use it.&lt;/p&gt;

&lt;p&gt;Use your judgment wisely. Check out how &lt;a href="https://github.com/discourse/discourse/blob/master/Gemfile" rel="noopener noreferrer"&gt;https://github.com/discourse/discourse/blob/master/Gemfile&lt;/a&gt; is doing it&lt;/p&gt;

&lt;p&gt;Next, run:&lt;/p&gt;

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

$ bumbler --initializers


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

&lt;/div&gt;

&lt;p&gt;This will show you the load time of initializers.&lt;/p&gt;

&lt;p&gt;In my case, it was the routes (we have thousands of routes due to our support of multiple languages). So, reducing the routes will help but we can't remove the routes, it's one of our best features.&lt;/p&gt;

&lt;p&gt;Since you won't always be using ALL of them in the development, so you can do this:&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%2F8a9y6oxsomlfp8yndaz7.jpeg" 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%2F8a9y6oxsomlfp8yndaz7.jpeg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What does it do? By default, it will use all of the languages except when you have that &lt;code&gt;MIN_LOCALE&lt;/code&gt; environment variable which you would only put in your local development. &lt;/p&gt;

&lt;p&gt;Another place to check would be your Admin routes. Again, you are not accessing your Admin routes all the time, so, you can use the same trick in your &lt;code&gt;routes.rb&lt;/code&gt;. Try optimizing your codebase based on what bumbler told you. &lt;/p&gt;

&lt;p&gt;One last tip would be to do some cleanup on your gems. Remove gems that are not being used. Sometimes we just forgot that we don't need it anymore. &lt;/p&gt;

&lt;p&gt;That's it, folks. Here is my result from this exercise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce slow requires by 41%&lt;/li&gt;
&lt;li&gt;Reduce Initialize require by 72%&lt;/li&gt;
&lt;li&gt;Reduce the number of routes by 91%&lt;/li&gt;
&lt;li&gt;Reduce rake time by 55%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading &lt;/p&gt;

</description>
      <category>rails</category>
    </item>
    <item>
      <title>How to Integrate TradingView's HTML5 Charting Library with Ruby on Rails v6</title>
      <dc:creator>Amree Zaid</dc:creator>
      <pubDate>Mon, 23 Nov 2020 06:59:37 +0000</pubDate>
      <link>https://dev.to/amree/how-to-integrate-tradingview-s-html5-charting-library-with-ruby-on-rails-v6-13be</link>
      <guid>https://dev.to/amree/how-to-integrate-tradingview-s-html5-charting-library-with-ruby-on-rails-v6-13be</guid>
      <description>&lt;p&gt;As you probably know, the charting library is not accessible publicly. You need to request access from them. So, I can't really give a complete repo as an example. I did however open a PR at &lt;a href="https://github.com/tradingview/charting-library-examples/pull/197"&gt;https://github.com/tradingview/charting-library-examples/pull/197&lt;/a&gt;, but I'm not sure if it's going to be accepted.&lt;/p&gt;

&lt;p&gt;Anyway, the given example is using asset pipeline and the modern Ruby on Rails application is using webpacker. So after trying out a few times, I figured a working way to load the sample chart. &lt;/p&gt;

&lt;p&gt;It's not straight forward for me, so maybe this will help someone else in the future. However, I'm not sure if it's the best way to load it, however, it works 😁&lt;/p&gt;

&lt;p&gt;I'm going to assume you're using the Ruby on Rails v6.0.3.4. Once you've cloned the library into &lt;code&gt;charting_library&lt;/code&gt; directory, you can do these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copy &lt;code&gt;charting_library/charting_library.js&lt;/code&gt; into &lt;code&gt;app/javascript/packs/charting_library/charting_library.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Copy &lt;code&gt;datafeeds/udf/dist/*.js&lt;/code&gt; into &lt;code&gt;app/javascript/packs/datafeeds/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Copy &lt;code&gt;charting_library/*.html&lt;/code&gt; into &lt;code&gt;public/charting_library/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Copy &lt;code&gt;charting_library/bundles&lt;/code&gt; into &lt;code&gt;public/charting_library/bundles&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't worry about serving outdated files just because you put it in the public directory as the charting library will use a new hash on the files every time there's a new update.&lt;/p&gt;

&lt;p&gt;Once we got the files in the correct places, we can use this code to load the sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/packs/application.js&lt;/span&gt;
&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@rails/ujs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;turbolinks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;channels&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;packs/datafeeds/polyfills&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Datafeeds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;packs/datafeeds/bundle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TradingView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;packs/charting_library/charting_library&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getLanguageFromURL&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;?&amp;amp;]lang=([^&amp;amp;#]*)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;decodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&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="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\+&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;initOnReady&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tvWidget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;TradingView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AAPL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;datafeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Datafeeds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UDFCompatibleDatafeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://demo_feed.tradingview.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;D&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;container_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tv_chart_container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;library_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/charting_library/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getLanguageFromURL&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;disabled_features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use_localstorage_for_settings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;enabled_features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;study_templates&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;charts_storage_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://saveload.tradingview.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;charts_storage_api_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tradingview.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public_user_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fullscreen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;autosize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;studies_overrides&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onChartReady&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headerReady&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createButton&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Click to show a notification popup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apply-common-tooltip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showNoticeDialog&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Notification&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TradingView Charting Library API works correctly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Noticed!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;}));&lt;/span&gt;

      &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Check API&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initOnReady&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&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;The code is actually coming from the sample with slight modifications.&lt;/p&gt;

&lt;p&gt;Create a view and put this HTML in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"page-tv-chart-container"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tv_chart_container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TradingView will use that ID to load the chart.&lt;/p&gt;

&lt;p&gt;Just start your server and that's it. You should have a working TradingView chart by now 👍&lt;/p&gt;

</description>
      <category>rails</category>
      <category>tradingview</category>
    </item>
    <item>
      <title>Exporting data from RDS to S3 using AWS Glue</title>
      <dc:creator>Amree Zaid</dc:creator>
      <pubDate>Mon, 05 Oct 2020 09:37:43 +0000</pubDate>
      <link>https://dev.to/amree/exporting-data-from-rds-to-s3-using-aws-glue-mai</link>
      <guid>https://dev.to/amree/exporting-data-from-rds-to-s3-using-aws-glue-mai</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Why would we even want to do this? Just imagine if you have data that are infrequently accessed. Keeping it in your RDS might costs you more than it should. Besides, having big table is gonna cause you a bigger headache with your database maintenance (indexing, auto vacuum, etc).&lt;/p&gt;

&lt;p&gt;What if we can store those data somewhere where it's gonna cost less and it's in an even smaller size? You can read more about the cost-saving part &lt;a href="https://dev.to/cloudforecast/using-parquet-on-athena-to-save-money-on-aws-3fac"&gt;here&lt;/a&gt;. I'll be focusing on the how and not the why in this post.&lt;/p&gt;

&lt;p&gt;Exporting data from RDS to S3 through AWS Glue and viewing it through AWS Athena requires a lot of steps. But it’s important to understand the process from the higher level.&lt;/p&gt;

&lt;p&gt;IMHO, I think we can visualize the whole process as two parts, which are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input: This is the process where we’ll get the data from RDS into S3 using AWS Glue&lt;/li&gt;
&lt;li&gt;Output: This is where we’ll use AWS Athena to view the data in S3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s important to note both processes require almost similar steps. We need to specify the database and table for both of them.&lt;/p&gt;

&lt;p&gt;Database and table don’t exactly carry the same meaning as our normal PostgreSQL. The database in this context is more like containers for the tables and doesn’t really have any extra configurations. &lt;/p&gt;

&lt;p&gt;The table is a little bit different as it has a schema attached to it. The table in AWS Glue is just the metadata definition that represents your data and it doesn’t have data inside it. The data is available somewhere else. It can be in RDS/S3/other places.&lt;/p&gt;

&lt;p&gt;How do we create a table? We can either create it manually or use Crawlers in AWS Glue for that. We can also create a table from AWS Athena itself.&lt;/p&gt;

&lt;p&gt;The database and tables that you see in AWS Glue will also be available in AWS Athena.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;In order not to confuse ourselves, I think it’d better if we use different database names for the input and output. We need to differentiate between what’s the input and output for easier reference when we set up the AWS Glue Job.&lt;/li&gt;
&lt;li&gt;More will be added&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisite
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Security
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;These security configurations are required to prevent errors when we ran AWS Glue&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Amazon VPC Endpoints for Amazon S3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to VPC &amp;gt; Endpoints&lt;/li&gt;
&lt;li&gt;Create Endpoint&lt;/li&gt;
&lt;li&gt;Search by Services: S3 (com.amazonaws.ap-southeast-1.s3)&lt;/li&gt;
&lt;li&gt;Select your VPC&lt;/li&gt;
&lt;li&gt;Tick a Route Table ID&lt;/li&gt;
&lt;li&gt;Choose Full Access&lt;/li&gt;
&lt;li&gt;Create Endpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result will be shown in the “Route Tables &amp;gt; Routes” page. There’ll be a new route added with VPC as the target and S3 service as the destination&lt;/p&gt;

&lt;p&gt;Reference: &lt;a href="https://docs.aws.amazon.com/glue/latest/dg/vpc-endpoints-s3.html"&gt;https://docs.aws.amazon.com/glue/latest/dg/vpc-endpoints-s3.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;RDS Security Group:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select the security group of the database that you want to use&lt;/li&gt;
&lt;li&gt;Edit inbound rules&lt;/li&gt;
&lt;li&gt;Add rule&lt;/li&gt;
&lt;li&gt;Type: All TCP&lt;/li&gt;
&lt;li&gt;Source: Custom and search for the security group name itself&lt;/li&gt;
&lt;li&gt;Save rules&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Roles
&lt;/h4&gt;

&lt;p&gt;This will allow Glue to call AWS service on our behalf&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to IAM &amp;gt; Roles &amp;gt; Create role&lt;/li&gt;
&lt;li&gt;Type of trusted identity: AWS Service&lt;/li&gt;
&lt;li&gt;Service: Glue&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Search and select AWSGlueServiceRole&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;We can skip adding tags&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Roles: AWSGlueServiceDefault (can be anything)&lt;/li&gt;
&lt;li&gt;Create Role&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Add Database Connections (for Input)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Go to AWS Glue &amp;gt; Databases &amp;gt; Connections&lt;/li&gt;
&lt;li&gt;Click “Add Connection”&lt;/li&gt;
&lt;li&gt;Connection type: Amazon RDS&lt;/li&gt;
&lt;li&gt;Database Engine: PostgreSQL&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Instance: Choose an RDS&lt;/li&gt;
&lt;li&gt;Put the database details: name, username, and password&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Review and click Finish&lt;/li&gt;
&lt;li&gt;Use “Test Connection” in the “Connections” page to test it out (this might take a while)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Setup S3 access (for Output)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Go to AWS Glue &amp;gt; Databases &amp;gt; Connections&lt;/li&gt;
&lt;li&gt;Click “Add Connection”&lt;/li&gt;
&lt;li&gt;Connection type: Network&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;VPC: Select the same one as the RDS*&lt;/li&gt;
&lt;li&gt;Subnet: Select the same one as the RDS*&lt;/li&gt;
&lt;li&gt;Security Group: default*&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Review and click Finish&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(*) Other options might work too, but I didn’t try them out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Databases
&lt;/h3&gt;

&lt;p&gt;This will be the parent/container for the table. A table might come from the input and output.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to AWS Glue &amp;gt; Databases &amp;gt; Add database&lt;/li&gt;
&lt;li&gt;Database name: anything will do&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I created for both. E.g: &lt;code&gt;myapp_input&lt;/code&gt; and &lt;code&gt;myapp_output&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Crawlers
&lt;/h3&gt;

&lt;p&gt;Before we create a job to import the data, we need to set up our input table’s schema. This schema will be used for the data input in the Job later.&lt;/p&gt;

&lt;p&gt;Naming is hard. I decided to go with this format: &lt;code&gt;rds_db_name_env_table_name_crawler&lt;/code&gt;. It’s easier if we can grasp what the crawler does from the name even though we can have a shorter name and put the details in the description.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to AWS Glue &amp;gt; Tables &amp;gt; Add tables &amp;gt; Add tables using a crawler&lt;/li&gt;
&lt;li&gt;Crawler name: Anything&lt;/li&gt;
&lt;li&gt;Crawler source type: Data stores&lt;/li&gt;
&lt;li&gt;Next &lt;/li&gt;
&lt;li&gt;Choose a data store: JDBC&lt;/li&gt;
&lt;li&gt;Connection: Choose the one we created above&lt;/li&gt;
&lt;li&gt;Include path: &lt;code&gt;db_name/public/table_name&lt;/code&gt; (assuming we want to take data from table table_nameas we can use % as the wild card)&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Add another data store: No&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;IAM role: Choose the one we created above (AWSGlueServiceDefault)&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Frequency&lt;/li&gt;
&lt;li&gt;Use Run on demand&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Configure the crawler’s output&lt;/li&gt;
&lt;li&gt;Database: Choose database for the crawler output (this will be the source for our Job later)&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Review and click Finish&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The crawler that we’ve just defined is just to create a table with a schema based on the RDS’s table that we just specified.&lt;/p&gt;

&lt;p&gt;Let’s run it to see the output. Just go to the Crawlers page and select “Run crawler”. It’ll take a moment before it starts and there’s no log when it’s running (or at least I can’t find it yet). However, there’ll be a log once it’s done. The only thing you can do to monitor its progress is to keep clicking the Refresh icon on the Crawlers page.&lt;/p&gt;

&lt;p&gt;Once it’s done, you’ll see the table created automatically in the Tables section. You can filter out the list of the tables by going through Databases first. You should see a table with defined schema similar to what you have in RDS.&lt;/p&gt;

&lt;p&gt;We need to add another crawler that will define the schema of our output. But this time it’ll be for our S3 (which is the output)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to AWS Glue &amp;gt; Tables &amp;gt; Add tables &amp;gt; Add tables using a crawler&lt;/li&gt;
&lt;li&gt;Crawler name: Anything (I chose &lt;code&gt;s3_db_name_env_table_name_crawler&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Crawler source type: Data stores&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Choose a data store: S3&lt;/li&gt;
&lt;li&gt;Connection: Use connection declared before for S3 access&lt;/li&gt;
&lt;li&gt;Crawl data in: Specified path in my account&lt;/li&gt;
&lt;li&gt;Include path: &lt;code&gt;s3://you-data-path/&lt;/code&gt;. This will be the path where you’ll store the output from Job that you’ll create later. The output here means the Apache Parquet files. I chose: &lt;code&gt;s3://glue-dir/env/database_name/table_name/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Add another data store: No&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Choose IAM role&lt;/li&gt;
&lt;li&gt;Choose an existing IAM role&lt;/li&gt;
&lt;li&gt;IAM role: AWSGlueServiceRoleDefault&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Create a schedule for this crawler:&lt;/li&gt;
&lt;li&gt;Frequency: Run on demand&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Configure the crawler’s output:&lt;/li&gt;
&lt;li&gt;Database: A database where you’ll store the output from S3&lt;/li&gt;
&lt;li&gt;Next&lt;/li&gt;
&lt;li&gt;Review and click Finish&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’re not going to run this crawler yet as the S3 directory is empty. We’ll run it once we’ve exported RDS data to S3.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a job
&lt;/h3&gt;

&lt;p&gt;For some unknown reason, I couldn’t get this to work without using AWS Glue Studio. Maybe I’ll figure it out once I have more time. But I’ll just use AWS Glue Studio for now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open AWS Glue Studio in ETL section&lt;/li&gt;
&lt;li&gt;Choose "Create and manage jobs"&lt;/li&gt;
&lt;li&gt;Source: RDS&lt;/li&gt;
&lt;li&gt;Target: S3&lt;/li&gt;
&lt;li&gt;Click Create&lt;/li&gt;
&lt;li&gt;Click on the “Data source - JDBC” node&lt;/li&gt;
&lt;li&gt;Database: Use the database that we defined earlier for the input&lt;/li&gt;
&lt;li&gt;Table: Choose the input table (should be coming from the same database)&lt;/li&gt;
&lt;li&gt;You’ll notice that the node will now have a green check&lt;/li&gt;
&lt;li&gt;Click on the “Data target - S3 bucket” node&lt;/li&gt;
&lt;li&gt;Format: Glue Parquet&lt;/li&gt;
&lt;li&gt;Compression type: Snappy&lt;/li&gt;
&lt;li&gt;S3 Target location: This will the place where the parquet files will be generated. This path should also be the same as what we defined in our crawler for the output before. Remember, this is for the data, not for the schema. The crawler is responsible for the schema. I chose to use &lt;code&gt;s3://glue-dir/env/database_name/table_name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You’ll notice that the node will now have a green check&lt;/li&gt;
&lt;li&gt;Now go to “Job details” tab&lt;/li&gt;
&lt;li&gt;Name: Can be anything, I chose &lt;code&gt;rds_to_s3_db_name_env_table_name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;IAM Role: Choose the role that we created before - AWSGlueServiceRoleDefault&lt;/li&gt;
&lt;li&gt;Expand “Advanced Properties”&lt;/li&gt;
&lt;li&gt;We’re going to specify some paths so that it won’t litter our top-level s3&lt;/li&gt;
&lt;li&gt;Script path: &lt;code&gt;s3://glue-dir/scripts/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Spark UI logs path: &lt;code&gt;s3://glue-dir/sparkHistoryLogs/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Temporary path: &lt;code&gt;s3://glue-dir/temporary/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click Save&lt;/li&gt;
&lt;li&gt;Click “Run” to run the script. You can see the log in “Run details” tab. If everything is working as expected, you should files generated in &lt;code&gt;s3://glue-dir/env/database_name/table_name&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Viewing the record using AWS Athena
&lt;/h3&gt;

&lt;p&gt;Before we can view the output, we need to create a table/schema for those parquet files. This is the job for the crawler (you can also create the table manually if you want to). That’s what the second crawler does. Just run the crawler and you’ll get a new table created if it’s new. Refer to previous steps on how we run the first crawler.&lt;/p&gt;

&lt;p&gt;To confirm the table has been created, just go to the database for the output and then click on the “Tables ..” link. You should see it there. There’s also an alert at the top of the Crawler index page once it has finished the job.&lt;/p&gt;

&lt;p&gt;To view the record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to AWS Athena&lt;/li&gt;
&lt;li&gt;Select your database and table.&lt;/li&gt;
&lt;li&gt;Click on the three dots on the right side of the table name and choose Preview Table&lt;/li&gt;
&lt;li&gt;You’ll see some data in the Results&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/aws-samples/aws-glue-samples/blob/master/FAQ_and_How_to.md"&gt;https://github.com/aws-samples/aws-glue-samples/blob/master/FAQ_and_How_to.md&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://spark.apache.org/docs/2.1.0/sql-programming-guide.html"&gt;https://spark.apache.org/docs/2.1.0/sql-programming-guide.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/how-to-access-and-analyze-on-premises-data-stores-using-aws-glue/"&gt;https://aws.amazon.com/blogs/big-data/how-to-access-and-analyze-on-premises-data-stores-using-aws-glue/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/34948296/using-pyspark-to-connect-to-postgresql"&gt;https://stackoverflow.com/questions/34948296/using-pyspark-to-connect-to-postgresql&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/cloudforecast/watch-out-for-unexpected-s3-cost-when-using-athena-5hdm"&gt;https://dev.to/cloudforecast/watch-out-for-unexpected-s3-cost-when-using-athena-5hdm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>rds</category>
      <category>s3</category>
    </item>
    <item>
      <title>Order Update with Two-Step Payment</title>
      <dc:creator>Amree Zaid</dc:creator>
      <pubDate>Sat, 09 May 2020 00:36:26 +0000</pubDate>
      <link>https://dev.to/amree/order-update-with-two-step-payment-2n64</link>
      <guid>https://dev.to/amree/order-update-with-two-step-payment-2n64</guid>
      <description>&lt;p&gt;I was asked about this from my latest job application. Didn't realize it's going to be this long so I thought I should share it publicly, easier for me to show it to the next interviewer. The exact question was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Describe a recent technical solution or achievement that you are proud of. Anything goes, from a tiny one hour ticket to a large system, we are just interested in how you think.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer:&lt;/p&gt;

&lt;p&gt;It's just order updating, how complex can it be? I realized I was wrong about the complexity when I started my investigation. The complexity comes from the two-step payment system that we're going to implement to make sure the whole order editing work smoothly. It was actually my first time hearing the two-step payment words.&lt;/p&gt;

&lt;p&gt;In case you didn't know: A two-step payment system is where you hold a certain amount of money on someone's credit card. Depending on the requirement, you'll charge the credit card later. We're using Stripe for our payment system.&lt;/p&gt;

&lt;p&gt;A little bit of background: We don't want to charge the credit card until the cut-off date of the food delivery, which will then allow the customer to change their order online without contacting us. So, a customer can keep on changing the menu (which will affect the price) as much as he wanted without us having to deal with the credit card refund and charge manually.&lt;/p&gt;

&lt;p&gt;The simplest workflow would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customer checkout from our websites with a date far in the future&lt;/li&gt;
&lt;li&gt;We'll queue the card authorization 7 days before the cut off date&lt;/li&gt;
&lt;li&gt;Customer didn't make any changes&lt;/li&gt;
&lt;li&gt;When the time comes, we authorized the amount, queue another job for the card capture process&lt;/li&gt;
&lt;li&gt;On the day of the cut off date, we'll capture the amount automatically&lt;/li&gt;
&lt;li&gt;No order update from the web will be allowed at this point. They need to contact our customer support for this&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The complexity keeps on compounding as you need to think about these scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;During the checkout we need to know whether we should authorize, capture, or process the card immediately (one-step payment).&lt;/li&gt;
&lt;li&gt;The biggest one would be the editing part. We need to think what's the current state of the order and what action that was made. Is the order in the authorized state? Capture state? When is the cut off date? Do we need to refund everything? Do we need to do a partial refund? Do we need to refund and charge a new amount?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, I was given this task alone (we don't have many devs last time). There were too many things that need to be done, so I had to break it up by phases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update existing checkout to support two-step payment system (this is when the order was created)&lt;/li&gt;
&lt;li&gt;I need to update/add the code to handle cancel, refund, authorize and capture the card. Each action has its own complexity, but that's the high overview.&lt;/li&gt;
&lt;li&gt;Alter the database to support the new payment state&lt;/li&gt;
&lt;li&gt;Figure out the best time to capture the payment (e.g: cut off date - weekend). I also need to give some buffer for the customer support to handle if there's an error during any of the processes above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the end of the project, I got helps from my awesome team for things like mailers and other kinds of updates. So, I still need to work on the core parts. I don't think I can meet the deadline without those helps lol.&lt;/p&gt;

&lt;p&gt;I'd love to tell you more about the whole process, but it was pretty long. But you can see the branch conditions that I've drawn here: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fAvhRilK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/uouBdhG.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fAvhRilK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/uouBdhG.jpg" alt="diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;P = Pending Authorization&lt;br&gt;
A = Authorized&lt;br&gt;
C = Capture&lt;br&gt;
R = Refund&lt;br&gt;
H = Amount higher&lt;br&gt;
L = Amount lower&lt;/p&gt;

&lt;p&gt;But the biggest thing I learned with this project is that visualization will help a lot. It doesn't have to be in a standard format like when you study in the university. Just draw however you want as long it can help you see the problem and possible blockers.&lt;/p&gt;

&lt;p&gt;In terms of the code itself, I had to dive into React and Redux to implement the whole update (we have complex menu selections). Of course, testing is very important. With lots of new and updated code, I need to make sure none were broken every time I added/updated new ones. At first, I mocked a lot of API requests, but it doesn't feel safe, so I used VCR library to record the interactions and the result feels more accurate and safe. For the front end part, I used Capybara/Chrome for the feature tests.&lt;/p&gt;

&lt;p&gt;Together with a feature flag in place, I can safely deploy the changes every day without having to do one big rollout. In terms of the backend code, I used a lot of service objects to keep the classes small. It's also easier to read and find, e.g: ChargeProcessor, AuthorizeProcessor, etc. Everything was also namespaced to ensure I don't pollute the main service directory.&lt;/p&gt;

&lt;p&gt;With this feature implemented, we improved further with other features as well such as the ability to save and delete credit cards. The checkout is also easier as the customer can just select from the previous credit card. The support couldn't be happier as well as they don't have to handle manual order updates.&lt;/p&gt;

&lt;p&gt;I think I better stop here lol&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>react</category>
      <category>redux</category>
      <category>stripe</category>
    </item>
  </channel>
</rss>
