<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Ali Sepehri on Medium]]></title>
        <description><![CDATA[Stories by Ali Sepehri on Medium]]></description>
        <link>https://medium.com/@alisepehri?source=rss-99ea8e6f88d1------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*lt_i6G1m_LDEJKDOoOGspA.jpeg</url>
            <title>Stories by Ali Sepehri on Medium</title>
            <link>https://medium.com/@alisepehri?source=rss-99ea8e6f88d1------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 10 Aug 2026 23:10:06 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@alisepehri/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[What I Learned from Digging into the SolidCache Gem]]></title>
            <link>https://itnext.io/what-i-learned-from-digging-into-the-solidcache-gem-ebcaf782bab3?source=rss-99ea8e6f88d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/ebcaf782bab3</guid>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[sharding]]></category>
            <category><![CDATA[caching]]></category>
            <category><![CDATA[database]]></category>
            <dc:creator><![CDATA[Ali Sepehri]]></dc:creator>
            <pubDate>Sun, 05 Oct 2025 09:56:22 GMT</pubDate>
            <atom:updated>2025-10-18T21:39:29.616Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*D5BdOhL7PPLmmq3sRO8RaQ.png" /></figure><p>This article isn’t specifically about Solid Cache or encouraging you to use it. Recently, I read the source code of the <a href="https://github.com/rails/solid_cache">solid_cache</a> gem and learned some interesting things that I’m going to share here.</p><p>Solid Cache is a database-backed cache storage system. In simple words, it enables you to use any database for caching, rather than relying solely on RAM-based stores. Solid Cache was introduced at the RailsWorld 2023 Conference’s Keynote, and <a href="https://github.com/djmb">Donal McBreen</a>, the main contributor, provided more details about it. I highly recommend watching <a href="https://www.youtube.com/watch?v=wYeVne3aRow">the presentation</a> if you want to understand the reasoning behind this paradigm shift.</p><blockquote><strong>I’d like to thank Donal for reviewing this article and providing feedback, which I have incorporated into the current version.</strong></blockquote><p>Before exploring how Solid Cache works under the hood, it’s useful to imagine what a <em>basic cache table</em> might look like. A minimal database-backed cache could simply have two columns — key and value — where each key uniquely identifies a cached object, and the value stores the serialized data. Optionally, a created_at or expires_at column can be added to help the system identify and remove stale entries over time. In such a design, cache lookups are done with a straightforward SELECT value FROM cache WHERE key = ?, and new entries are written with an INSERT or UPSERT. While simple and easy to implement, this naïve approach doesn’t look enough.</p><p>Now let’s see what other details have been considered by Solid Cache and what was the rationality behind them. I’ve split the article into sections to make it easier to read and to let you skip each part if you want.</p><h3>“byte_size” column</h3><p>The byte_size column is an estimation of the size of each record in the table, including the attributes and any related overhead. You might expect the size to be similar for all records, but in reality, the value can vary greatly from row to row. Solid Cache uses this column to estimate the total size of the cache (I’ll cover that later).</p><h3>“key_hash” column</h3><p>The key_hash column stores a 64-bit hash of the key. There are a few reasons for this: since the index is built on this column, and its size is always 8 bytes, the index size stays small regardless of how long the actual key is (even with considering the overhead of having a new column). Integer-based indexes are also faster in most databases. There’s another use case for this column: selecting random rows for size estimation using a sampling algorithm (I’ll cover that later).</p><h3>Why no Index for “created_at” column?</h3><p>Since Solid Cache is a First In, First Out (FIFO) cache, it needs an efficient way to remove the oldest rows. The query to find the oldest rows involves filtering by the created_at column. Without an index, this query could be slow. However, adding a new index isn’t free—it comes with storage and write costs. Since we’re using an auto-incrementing primary key, the row with the highest id was created most recently and has the highest created_at value. So, to find the oldest rows, we can just look for the rows with the smallest ids.</p><h3>Why “key_hash” is part of two indices?</h3><p>If you look at the indices, you’ll see two that involve the key_hash column:</p><pre>index [&quot;key_hash&quot;, &quot;byte_size&quot;], name: &quot;index_solid_cache_entries_on_key_hash_and_byte_size&quot;<br>index [&quot;key_hash&quot;], name: &quot;index_solid_cache_entries_on_key_hash&quot;, unique: true</pre><p>You might ask: if we have a multi-column index on key_hash, byte_size, why do we also need a single-column index on key_hash? Or vice versa?</p><p>The single-column index on key_hash enforces a uniqueness constraint. Because Solid Cache uses upserts to write entries, most databases require a uniqueness constraint on the target column(s) specified in the ON CONFLICT clause.</p><p>The multi-column index helps queries like the following, which estimate the table size, run more efficiently.</p><pre>SELECT SUM(&quot;byte_size&quot;)<br>FROM &quot;solid_cache_entries&quot;<br>WHERE &quot;key_hash&quot; &gt;= ?AND &quot;key_hash&quot; &lt; ? AND (byte_size &lt;= ?)</pre><p>When an index covers all the columns a query needs, the database can answer the query entirely from the index, without reading the actual table rows. This is called a covering index (Index-only Scan) and it improves performance by avoiding extra I/O. To learn more, check out the article below:</p><p><a href="https://medium.com/doctolib/how-we-optimized-a-query-to-run-1-billion-times-a-day-in-doctolib-beb2272b93c9">How We Optimized a Query to Run 1 Billion Times a Day at Doctolib</a></p><h3>Calculate(estimate) the cache size</h3><p>Solid Cache uses sampling to estimate the total cache size. The sample size can be set by size_estimate_samples in the cache.yml file.</p><p>Before diving into how estimation is done, let’s look at some alternative solutions that could be considered:</p><ul><li>Using Database Statistics: Some databases like Postgres and MySQL store table and index sizes in their metadata. But these queries are different for each database, and some databases might not support this at all. Since Solid Cache aims to be compatible with any database Rails can use, this isn’t a practical solution.</li><li>SQL SUM() function: You could sum up the byte_size column with SELECT sum(byte_size) FROM solid_cache_entries;, which works in all relational databases. However, this would require a sequential scan of the table, which is expensive if the table is large. Since we only need an estimate, running an expensive query frequently isn’t worth it.</li><li>Estimate size by row count: One simple solution is to estimate the table size based on the number of rows. It seems this was actually the initial approach in Solid Cache. I can guess why it’s dropped, it’s hard to pick an average row size that works for everyone, especially since cache entries can differ widely in size.</li></ul><p>Now let’s look at the current implementation. Ignoring some details for clarity, the high-level algorithm is:</p><pre>def size<br>  outliers_size + non_outlier_estimated_size<br>end</pre><p>outliers_size calculates the size of the N largest rows in the table, where N is the sample size. non_outlier_estimated_size picks N random rows (excluding the outliers) and sums their byte_size values. With this, the algorithm gets a more meaningful average row size and estimates the total table size by multiplying this average by the row count.</p><p>To improve accuracy, Solid Cache can keep some of the previous estimates and average them with the new estimate.</p><p>Selecting random N rows efficiently is tricky, and I’ll explain that later in this article.</p><h3>Random Selection for Size Estimation</h3><p>To select random rows from a table, you might first think of using the database’s built-in random functions. For example, in Postgres:</p><pre>SELECT * FROM solid_cache_entries ORDER BY RANDOM() LIMIT 10;</pre><p>Postgres requires a sequential scan of the whole table for this, which is very expensive for large tables. There are some other functions that don’t have this problem, but different databases have different solutions. We want a method that works everywhere.</p><p>Earlier, we saw that key_hash is a 64-bit integer, meaning its values range from -2⁶³ to 2⁶³-1. If the hash function is good and the keys are well-distributed, these values should be spread fairly evenly across this range. To select random rows, we can pick a random sub-range within the key_hash interval and select rows whose key_hash falls into that range.</p><p>For example, if our sample size is 1000 and there are 2000 rows in the table, to select 50% of the rows randomly, we could select all rows with key_hash between 0 and (2⁶³ - 1). This technique lets us efficiently sample rows for estimation.</p><h3>How Sharding works in Solid Cache?</h3><p>Solid Cache also supports sharding, which means using multiple databases to store the cache. For this, we need an algorithm that deterministically assigns a key to a database node. The same key should always map to the same node, even if multiple instances of your app are running at the same time or restarted.</p><p>Solid Cache builds an array of length TABLE_SIZE (2053 by default), where each cell points to one of the database nodes. For example, with 3 databases, each cell holds 0, 1, or 2, and each node’s number appears about equally often.</p><p>To find out which node to use for a given key, we use:</p><pre>lookup[quick_hash(key) % TABLE_SIZE]</pre><p>Here, lookup is the array, and the hash of the key determines which cell to check, which in turn tells us which database node to use.</p><h3>How lookup table is built?</h3><p>At first glance, you might try to build a lookup array by simply shuffling the node numbers, like this:</p><pre>lookup = [0, 0, ..., 1, 1, ..., 2, 2, ...].shuffle</pre><p>Why cann’t we just use this? Because this isn’t deterministic — every app instance might generate a different lookup table. We could persist the array somewhere, but that’s cumbersome and it only solve this issue. Also, this approach isn’t consistent: if we add or remove a node, nearly every key would be reassigned to a different node.</p><p>Solid Cache uses the <a href="https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/44824.pdf">Maglev</a> algorithm to generate a consistent lookup table. Maglev ensures that if nodes are added or removed, most keys continue to map to the same node as before, minimizing cache misses.</p><p><strong>How does Maglev solve the problem?</strong></p><ol><li>Start with an empty array.</li><li>Each node gets to pick which slot it wants, in turn. This is repeated until all slots are filled.</li><li>Each node’s choice is determined by a deterministic formula, so every app instance generates the same table.</li></ol><p>The following diagram shows how the algorithm works in a graphical way. The numbers on the arrows indicate the order of actions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WsU206KpYra7kTNCfypPjg.png" /></figure><p>Easy! Now the only thing that we need is a deterministic pick from every node for step 2. Here is my favorite part of the algorithm:</p><pre>preferred_slots = TABLE_SIZE.times.map { |i| (i * skip) % TABLE_SIZE }</pre><p>It’s mathematically provable that with this algorithm, if TABLE_SIZE is a prime number and skip is an integer such that 0 &lt; skip &lt;= TABLE_SIZE, then the preferred_slots array will contain each number from 0 to TABLE_SIZE - 1 exactly once, but in a pseudo-random order. This means every slot is used only once, and the order is determined by the algorithm.</p><p>Each node uses its own preferred_slots array when it’s their turn to choose a slot. Also, each node needs to track which index in their preferred_slots array they last used, so that they can continue where they left off during the next round of selection.</p><p>To ensure that every node’s preferred_slots array is unique, we introduce another variable, offset, into the algorithm. The final code looks like this:</p><pre>offset = md5(node_name, :offset) % TABLE_SIZE<br>skip = md5(node_name, :skip) % (TABLE_SIZE - 1) + 1<br><br>preferred_slots = TABLE_SIZE.times.map { |i| (offset + i * skip) % TABLE_SIZE }</pre><p>With this algorithm, we can build a consistent lookup table. The diagram below shows what happens when a new sharding node is added:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y_JrZwgJEBvoJcwMrjZZkg.png" /></figure><p>After adding node 2, some slots have changed to 2. The diagram also shows that some inconsistencies can occur (for example, a 1 has become 0). This means the algorithm is not perfect, but small changes in other slots are acceptable. After adding the new node, the frequency of node numbers in the lookup array becomes almost balanced again.</p><h3>How to delete the expired rows?</h3><p>As mentioned earlier, Solid Cache doesn’t define a database index on the created_at column, relying instead on the id column. Suppose cache entries expire after 2 months — rows older than that should be removed.</p><p>Since smaller ids are the oldest, we can look for rows with small ids and check if their created_at is older than 2 months. However, the check for created_at is not part of the SQL command and it’s done in memory after fetching the rows. Why is this?</p><p>If we add a WHERE created_at &lt; ? clause to the SQL statement, like so:</p><pre>SELECT id FROM solid_cache_entries<br>WHERE created_at &lt; ?<br>ORDER BY id<br>LIMIT ?</pre><p>the database will likely use the index on id to fetch the required number of rows as specified by LIMIT. However, if there aren’t enough qualifying rows to satisfy the LIMIT, the database may need to scan the entire table to perform the created_at check, which can be inefficient.</p><h3>Conclusion</h3><p>This article isn’t tied to any particular technology stack, and it doesn’t matter whether or not you use or will use Solid Cache. I found some very cool ideas in the Solid Cache source code and thought they were worth sharing.</p><p>I’ve found that reading source code and sharing what I learn is both useful and interesting, and I hope to continue doing it.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ebcaf782bab3" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/what-i-learned-from-digging-into-the-solidcache-gem-ebcaf782bab3">What I Learned from Digging into the SolidCache Gem</a> was originally published in <a href="https://itnext.io">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How We Optimized a Query to Run 1 Billion Times a Day at Doctolib]]></title>
            <link>https://medium.com/doctolib/how-we-optimized-a-query-to-run-1-billion-times-a-day-in-doctolib-beb2272b93c9?source=rss-99ea8e6f88d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/beb2272b93c9</guid>
            <category><![CDATA[postgresql]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[index]]></category>
            <category><![CDATA[query-optimization]]></category>
            <category><![CDATA[cache]]></category>
            <dc:creator><![CDATA[Ali Sepehri]]></dc:creator>
            <pubDate>Sun, 30 Mar 2025 17:44:22 GMT</pubDate>
            <atom:updated>2025-04-14T17:59:18.173Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/974/1*7WXo9EL-cUDkYTI7405C2A.png" /></figure><p>Over the past few months, our team at Doctolib has been working on migrating authorization data to a centralized table within its own database. This authorization database is powered by PostgreSQL. As part of the migration, we needed to transform the schema, which necessitated rewriting the queries. Given that authorization data is frequently queried, it’s crucial that these new queries are both fast and efficient.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nVJtEoV28MHsyJNl5FJwWA.png" /><figcaption>Migration of Authorization Data to a Centralized Table/Database</figcaption></figure><p>In this article, we won’t be discussing the migration process itself; instead, we’ll focus on how we enhanced the performance of queries in the new table.</p><p>The following code retrieves the access grants for a single account:</p><pre>Grant.where(<br>  role_id: [<br>    &#39;96761ffd-ccb9-46c8-abb5-5810d0d202bb&#39;,<br>    &#39;06fa4f0b-24de-4f3d-8803-a917606a0fb3&#39;,<br>    &#39;17b47072-8218-4b8c-afc9-f9fcddf1bffe&#39;, <br>    &#39;62bae27a-4b7f-4999-ac3b-de73e75e8273&#39;, <br>    &#39;74d99f9a-a04e-408e-a8c7-b1f7c9da4280&#39;,<br>  ],<br>  account_id: [&#39;123456&#39;]<br>)</pre><p>The code above generates the following SQL query to retrieve records from the grants table.</p><pre>SELECT *<br>FROM &quot;grants&quot; <br>WHERE &quot;role_id&quot; IN (<br> &#39;96761ffd-ccb9-46c8-abb5-5810d0d202bb&#39;,<br> &#39;06fa4f0b-24de-4f3d-8803-a917606a0fb3&#39;,<br> &#39;17b47072-8218-4b8c-afc9-f9fcddf1bffe&#39;, <br> &#39;62bae27a-4b7f-4999-ac3b-de73e75e8273&#39;, <br> &#39;74d99f9a-a04e-408e-a8c7-b1f7c9da4280&#39;<br>) AND &quot;account_id&quot; IN (&#39;123456&#39;)<br>-- AND &quot;expires_at&quot; IS NULL OR &quot;expires_at&quot; &gt; NOW();</pre><blockquote><strong>Note:</strong> The actual query also includes a condition for the expires_at column, which we have omitted from our analysis for the sake of simplicity.</blockquote><h3>EXPLAIN the query</h3><p>We begin by using the EXPLAIN command. EXPLAIN is a PostgreSQL command that displays the execution plan selected by the query planner for a given SQL statement, without executing it.</p><pre>EXPLAIN<br>SELECT * FROM ...</pre><p>The command above provides the following result:</p><pre>[<br>  {<br>    &quot;Plan&quot;: {<br>      &quot;Node Type&quot;: &quot;Index Scan&quot;,<br>      ...<br>      &quot;Index Name&quot;: &quot;index_grants_on_account_id&quot;,<br>      &quot;Relation Name&quot;: &quot;grants&quot;,<br>      &quot;Alias&quot;: &quot;grants&quot;,<br>      &quot;Index Cond&quot;: &quot;((account_id)::text = ANY (&#39;{123456}&#39;::text[]))&quot;,<br>      &quot;Filter&quot;: &quot;(role_id = ANY (&#39;{96761ffd-ccb9-46c8-abb5-5810d0d202bb,...}&#39;::uuid[]))&quot;<br>    }<br>  }<br>]</pre><p>As we can see, PostgreSQL uses the index_grants_on_account_id index to locate records with the specified account ID, ensuring that the database uses an index scan rather than a sequential scan.</p><h3>Reduce the Data Transferred over the Network</h3><p>Fetching records from the database involves more than just executing the query itself. Each query execution also includes waiting for a connection, transmitting the SQL command over the network, receiving records over the network, and more.<br>Therefore, retrieving large volumes of data from the database can be slow. By reducing the response size, we can enhance query performance.</p><p>To achieve this, we need to benchmark the query on the application side. The code below measures the execution time of our query.</p><pre>time = Benchmark.measure do<br>  Grant.where(<br>    role_id: [<br>      &#39;96761ffd-ccb9-46c8-abb5-5810d0d202bb&#39;,<br>      ...<br>    ],<br>    account_id: [&#39;123456&#39;]<br>  ).load<br>end<br><br>puts &quot;Time elapsed: #{time.real} seconds&quot;<br>  # =&gt; Time elapsed: ~0.2 seconds</pre><p>To reduce the size of the retrieved data, we can fetch only the columns that are necessary. To do this, we specify the required columns in the query.</p><pre>time = Benchmark.measure do<br>  Grant.where(<br>    role_id: [<br>      &#39;96761ffd-ccb9-46c8-abb5-5810d0d202bb&#39;,<br>      ...<br>    ],<br>    account_id: [&#39;123456&#39;]<br>  )<br>  .pluck(:account_id, :resource_id, :resource_type, :role_id)<br>  .load<br>end<br><br>puts &quot;Time elapsed: #{time.real} seconds&quot;<br>  # =&gt; Time elapsed: ~0.1 seconds</pre><blockquote><strong>Note: </strong>If you’re using an ORM, you can save time by avoiding the initialization of model objects. For instance, in ActiveRecord, using pluck instead of select can prevent unnecessary model object initializations.</blockquote><h3>More Advanced Aspect of Query Plan</h3><p>Now, let’s dive into the query plan and explore more advanced features of the EXPLAIN command. We can enhance EXPLAIN to obtain a more detailed query plan.</p><pre>set track_io_timing = on;<br>EXPLAIN(ANALYZE, TIMING, BUFFERS)<br><br>SELECT &quot;account_id&quot;, &quot;resource_id&quot;, &quot;resource_type&quot;, &quot;role_id&quot;<br>FROM &quot;grants&quot; <br>WHERE &quot;role_id&quot; IN (<br>  &#39;96761ffd-ccb9-46c8-abb5-5810d0d202bb&#39;,<br>  &#39;06fa4f0b-24de-4f3d-8803-a917606a0fb3&#39;,<br>  &#39;17b47072-8218-4b8c-afc9-f9fcddf1bffe&#39;, <br>  &#39;62bae27a-4b7f-4999-ac3b-de73e75e8273&#39;, <br>  &#39;74d99f9a-a04e-408e-a8c7-b1f7c9da4280&#39;<br>) AND &quot;account_id&quot; IN (&#39;123456&#39;)</pre><p>Let’s take a quick look at the settings we’ve added to the EXPLAIN command:</p><ul><li>ANALYZE Actually executes the query instead of just showing the plan, which shows real execution time and row counts (Total Cost, Actual Total Time, Actual Rows).</li><li>TIMING Shows the time spent in each node of the query plan by measuring CPU time for operations.</li><li>BUFFERS Shows buffer usage statistics by displaying Shared, Local, and Temp buffer Hits and Reads (Shared Hit Blocks, Shared Read Blocks).</li><li>TIMING Seeing execution time for each block could give a good sense to the execution speed for the query [List of the fields that is being added].</li><li>track_io_timing = on Enables tracking of I/O timing statistics by measuring actual time spent on disk I/O operations. It helps us to understand how much time we spent to read non-cached records (I/O Read Time)</li></ul><pre>[<br>  {<br>    &quot;Plan&quot;: {<br>      &quot;Node Type&quot;: &quot;Index Scan&quot;,<br>      ...<br>      &quot;Index Name&quot;: &quot;index_grants_on_account_id&quot;,<br>      &quot;Relation Name&quot;: &quot;grants&quot;,<br>      &quot;Startup Cost&quot;: 0.43,<br>      &quot;Total Cost&quot;: 696.92,<br>      &quot;Actual Startup Time&quot;: 2.824,<br>      &quot;Actual Total Time&quot;: 95.762,<br>      &quot;Actual Rows&quot;: 2882,<br>      &quot;Actual Loops&quot;: 1,<br>      &quot;Index Cond&quot;: &quot;((account_id)::text = &#39;123456&#39;::text)&quot;,<br>      &quot;Rows Removed by Index Recheck&quot;: 0,<br>      &quot;Filter&quot;: &quot;(role_id = ANY (&#39;{96761ffd-ccb9-46c8-abb5-5810d0d202bb,...}&#39;::uuid[]))&quot;,<br>      &quot;Rows Removed by Filter&quot;: 1,<br>      &quot;Shared Hit Blocks&quot;: 1,<br>      &quot;Shared Read Blocks&quot;: 765,<br>      &quot;I/O Read Time&quot;: 93.775,<br>      &quot;I/O Write Time&quot;: 0.000,<br>      ...<br>    },<br>    &quot;Planning&quot;: {<br>      ...<br>    },<br>    &quot;Planning Time&quot;: 0.438,<br>    &quot;Triggers&quot;: [<br>    ],<br>    &quot;Execution Time&quot;: 96.019<br>  }<br>]</pre><blockquote><strong>Note:</strong> The attributes not discussed in this article have been removed from the query plan above.</blockquote><p>As we can easily recognize, most of the time is spent on I/O Read Time. When the required blocks are not found in the cache, PostgreSQL fetches them from the disk. In other words, Shared Read Blocks and I/O Read Time are directly correlated. Each Shared Read Block typically requires an I/O operation, and more Shared Read Blocks generally result in more I/O Read Time.</p><p><strong>How can we reduce </strong><strong>Shared Read Blocks?<br></strong>Shared Hit Blocks refers to the number of blocks read from the cache. The total number of required blocks for our query is 766 (Shared Hit Blocks + Shared Read Blocks). It&#39;s important to note that PostgreSQL&#39;s &quot;Shared Buffer Cache&quot; operates based on blocks (defaulting to 8KB pages). Therefore, by increasing the cache hit ratio, the total number of read blocks remains the same and if Shared Hit Blocks is increased, Shared Read Blocks will be reduced.</p><p>If we execute the EXPLAIN command a second time, Shared Read Blocks and I/O Read Time typically decrease, leading to a significantly reduced Execution Time (which explains why the query runs faster on subsequent executions). When Shared Hit Blocks is much greater than Shared Read Blocks, we describe this situation as having a high cache hit ratio.</p><p>If Shared Read Blocks changes with each query execution, we need a method to determine the cache hit ratio for our query in production. To assess the cache hit ratio, we used the Datadog | Database Monitoring tool. In the &quot;Query Metrics&quot; section, we searched for our query and examined the Shared Blocks Hit Ratio column in the table.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1012/1*LjBIe1ESHgYFhnDTqiaO5w.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/976/1*SUcJTgSgWhMgYluqirrK-w.png" /><figcaption>Cache Hit Ratio for Queries in Production</figcaption></figure><p>After examining the cache hit ratio, we realized it was very low for our new query in the new table. We used two strategies to improve the ratio for our query.</p><p><strong>Warming-up the Cache<br></strong>Warming up the cache involves executing the query more frequently in your database. Since the old query was heavily used in the application, immediately switching to the new query in the new table and database could cause issues due to the cache not being warmed up yet. Fortunately, we were using experimentation, which allowed us to warm up the cache by slowly increasing the experimentation percentage.</p><p><strong>Improve Cache Hit Ratio<br></strong>As mentioned earlier, in our example, the query needs to fetch 766 blocks. We understand that the cache size is limited and cannot hold the entire database in memory. By reducing the number of required blocks, we can improve the cache hit ratio, as fewer blocks need to be cached.</p><p>Let’s try retrieving fewer columns from the database by removing resource_type and executing the query again:</p><pre>set track_io_timing = on;<br>EXPLAIN(ANALYZE, TIMING, BUFFERS)<br><br>SELECT &quot;account_id&quot;, &quot;resource_id&quot;, &quot;role_id&quot;<br>FROM &quot;grants&quot; ...</pre><p>If we execute the EXPLAIN command, we will see that the total number of blocks remains exactly 766, just as before?! 🤔</p><p><em>Why?</em> This makes sense when we consider how records are stored and cached. Each record, along with its attributes (though this can differ for wide records), is stored in blocks or pages. When PostgreSQL needs to read data from a block, it reads the entire block, not just the specific data or columns requested.</p><p><strong>What is “Index Only Scan”?</strong><br>When PostgreSQL can retrieve all required data directly from the index without accessing the actual table, it uses an “Index Only Scan” instead of an Index Scan. This is the most efficient type of scan when applicable. Therefore, we need to define an index that includes all required data. “All required data” means both the selected columns and the columns that are part of the conditions. Before proceeding to define the index, let’s take a look at the complete query, including the expires_at condition that we previously omitted for simplicity.</p><pre>SELECT &quot;account_id&quot;, &quot;resource_id&quot;, &quot;resource_type&quot;, &quot;role_id&quot;<br>FROM &quot;grants&quot; <br>WHERE &quot;role_id&quot; IN (<br>  &#39;96761ffd-ccb9-46c8-abb5-5810d0d202bb&#39;,<br>  &#39;06fa4f0b-24de-4f3d-8803-a917606a0fb3&#39;,<br>  &#39;17b47072-8218-4b8c-afc9-f9fcddf1bffe&#39;, <br>  &#39;62bae27a-4b7f-4999-ac3b-de73e75e8273&#39;, <br>  &#39;74d99f9a-a04e-408e-a8c7-b1f7c9da4280&#39;<br>) AND &quot;account_id&quot; IN (&#39;123456&#39;)<br>  AND expires_at IS NULL OR expires_at &gt; NOW()</pre><p>According to the query, “all required data” refers to five columns: account_id, resource_id, resource_type, role_id, and expires_at. An index with five columns could result in a large index. Let’s test this locally. I inserted approximately 11 million records into my grants table, which has 10 columns. I checked the table size, and it&#39;s 1.27 GB. Now, I want to try the following index with these five columns:</p><pre>CREATE INDEX index_grants_on_five_columns<br>  ON grants<br>  USING btree (account_id, expires_at, role_id, resource_id, resource_type);</pre><p>The new index’s size is approximately 700 MB. While indices provide benefits, they also come with costs — the larger the index, the higher the cost. If this index were the only one for the table, it might be worthwhile to keep it, but that’s usually not the case.<br>We were able to remove two columns from the query; let’s see how!</p><p><strong>Remove </strong><strong>resource_type column</strong><br>In our application, we know that resource_type can be derived from role_id, meaning we don&#39;t need to retrieve it from the database; instead, it can be calculated and appended to the result in memory. This is possible because each role_id corresponds to a single resource_type.</p><p><strong>Remove </strong><strong>expires_at from the condition<br></strong>The expires_at column specifies the expiration time for a record when set. We also had a nightly background job to clean up records that expired the previous day. Our application logic can tolerate returning a recently expired record, so this condition doesn&#39;t need to be very strict. Therefore, we decided to remove expires_at from the query and run the cleanup background job more frequently. Since it&#39;s a very lightweight job, it can even run every minute.</p><p>After reducing the number of required columns to three, we defined the new B-tree index on account_id, role_id, and resource_id:</p><pre>CREATE INDEX index_grants_on_account_role_resource_id <br>  ON grants <br>  USING btree (account_id, role_id, resource_id);</pre><blockquote><strong>Note:</strong> Since the query condition is based on role_id and account_id, these columns should come first in the index; otherwise, PostgreSQL won&#39;t be able to use the index effectively. The order of columns is important when creating a B-tree index.</blockquote><p><strong>Update: </strong>At the time of implementation, our team was not aware of PostgreSQL’s INCLUDE parameter for indexes. In our example, since resource_id is not used in the query condition, it can be included in the index as a <em>non-key</em> column.</p><pre>CREATE INDEX index_grants_on_account_role_resource_id <br>  ON grants <br>  USING btree (account_id, role_id)<br>  INCLUDE (resource_id);</pre><p>Using INCLUDE can reduce the index size. I tested it locally with approximately 11 million records, and the index size decreased from 700MB to 500MB.</p><p>Here is what the new query looks like:</p><pre>SELECT &quot;account_id&quot;, &quot;resource_id&quot;, &quot;role_id&quot;<br>FROM &quot;grants&quot; <br>WHERE &quot;role_id&quot; IN (<br>  &#39;96761ffd-ccb9-46c8-abb5-5810d0d202bb&#39;,<br>  ...<br>) AND &quot;account_id&quot; IN (&#39;123456&#39;)</pre><p>And let’s run EXPLAIN on our new query:</p><pre>[<br>  {<br>    &quot;Plan&quot;: {<br>      &quot;Node Type&quot;: &quot;Index Only Scan&quot;,<br>      ...<br>      &quot;Index Name&quot;: &quot;index_grants_on_account_role_resource_id&quot;,<br>      &quot;Relation Name&quot;: &quot;grants&quot;,<br>      &quot;Total Cost&quot;: 13.14,<br>      &quot;Plan Rows&quot;: 183,<br>      &quot;Plan Width&quot;: 31,<br>      &quot;Actual Total Time&quot;: 0.875,<br>      &quot;Actual Rows&quot;: 2882,<br>      &quot;Actual Loops&quot;: 1,<br>      &quot;Index Cond&quot;: &quot;(account_id = &#39;123456&#39;::text)&quot;,<br>      &quot;Rows Removed by Index Recheck&quot;: 0,<br>      &quot;Filter&quot;: &quot;(role_id = ANY (&#39;{96761ffd-ccb9-46c8-abb5-5810d0d202bb,...}&#39;::uuid[]))&quot;,<br>      &quot;Rows Removed by Filter&quot;: 1,<br>      &quot;Shared Hit Blocks&quot;: 183,<br>      &quot;Shared Read Blocks&quot;: 0,<br>      &quot;I/O Read Time&quot;: 0.000,<br>      ...<br>    },<br>    &quot;Planning&quot;: {<br>      ...<br>    },<br>    &quot;Planning Time&quot;: 0.200,<br>    &quot;Triggers&quot;: [<br>    ],<br>    &quot;Execution Time&quot;: 1.109<br>  }<br>]</pre><p>With the new index, only 183 blocks were read, and the &quot;Node Type&quot; changed to &quot;Index Only Scan&quot; 🎉. Additionally, the Execution Time has significantly decreased for this specific query. Before celebrating our achievement, let&#39;s check the average (or even better, the p99) execution time for this query in production.</p><p>After using the new query and index for a while in production, we checked Datadog monitoring.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nviUj2ohpMgTh6CPFGUQng.png" /><figcaption>Query metrics in Datadog for the new query and index</figcaption></figure><p>The Shared Blocks Hit Ratio is 100% 👌 and the Average Duration is 44 μs 🤯. It&#39;s time to celebrate!</p><h3><strong>Conclusion</strong></h3><p>In this article, we explored how to analyze a query to understand its performance and cost, and we learned several techniques to enhance them.</p><p><strong><em>Is it worth optimizing a query to this extent, or is it overkill?<br></em></strong>It depends(as usual)! Are you optimizing the query to improve the response time for the end-user, or do you want to reduce the load on your valuable database? Usually, the end-user cannot perceive a difference of a few milliseconds in response time, so saving milliseconds doesn’t necessarily enhance user experience. However, if the query is frequently executed, saving milliseconds becomes very valuable for the database. In some cases, you might even make your query faster at the database level but slower at the application level because application-level scaling is typically easier to achieve horizontally than database scaling. Scaling a database is more complex, expensive, and comes with certain limitations.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=beb2272b93c9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/doctolib/how-we-optimized-a-query-to-run-1-billion-times-a-day-in-doctolib-beb2272b93c9">How We Optimized a Query to Run 1 Billion Times a Day at Doctolib</a> was originally published in <a href="https://medium.com/doctolib">Doctolib</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to create Database Views in Ruby on Rails?]]></title>
            <link>https://alisepehri.medium.com/how-to-create-database-views-in-ruby-on-rails-537f1a981e3d?source=rss-99ea8e6f88d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/537f1a981e3d</guid>
            <dc:creator><![CDATA[Ali Sepehri]]></dc:creator>
            <pubDate>Sat, 10 Aug 2024 11:21:44 GMT</pubDate>
            <atom:updated>2024-08-10T11:21:44.523Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pabMs7oDgFCZhTj9UCzPww.jpeg" /><figcaption>View of Zugspitze mountain — Germany [Photo by me]</figcaption></figure><p>This article explains how we can create database views in Rails applications. To see a practical usage of database views, please refer to the article below:</p><p><a href="https://alisepehri.medium.com/database-view-in-ruby-on-rails-7331d2ee9784">Database View in Ruby on Rails</a></p><p>Suppose we have Post model in our application and the table looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/275/1*nGaxAtjdHCTnY5bVq9Ejcg.png" /><figcaption>“posts” table</figcaption></figure><p>And we want to create a Database view that expose only id and body columns. This can be achieved through 3 simple steps:</p><p><strong>1. </strong>First, we generate a migration file:</p><pre>rails g migration SimplifiedPosts</pre><p><strong>2. </strong>Add up and down methods to the migration file:</p><pre>class SimplifiedPosts &lt; ActiveRecord::Migration[7.0]<br>  def up<br>    execute &lt;&lt;~SQL.squish<br>      CREATE OR REPLACE VIEW simplified_posts AS<br>        SELECT<br>            &quot;id&quot;,<br>            &quot;body&quot;<br>        FROM public.posts;<br>    SQL<br>  end<br><br>  def down<br>    execute &lt;&lt;~SQL.squish<br>      DROP VIEW IF EXISTS simplified_posts;<br>    SQL<br>  end<br>end</pre><p>In this migration file, up method creates the view. If a view with the same name exists CREATE OR REPLACE VIEW replaces the existing one with the new one (it can also be used to update an existing database view). down method is used for rolling back the migration and in this case it drops the created view.</p><p><strong>3. </strong>Now by running rails db:migrate the view will be created in the database.</p><h3>Use ActiveRecord to query the View</h3><p>Fortunately, we can utilize ActiveRecord to query the view, which makes our life much easier. To achieve this, we just need to create a regular model class:</p><pre>class SimplifiedPost &lt; ApplicationRecord<br>end</pre><p>Now we can query the view by ActiveRecord model class:</p><pre>SimplifiedPost.find_by(id: 1)<br>SimplifiedPost.where(&#39;id = ?&#39;, 1)</pre><blockquote>Database Views — under particular conditions — are updatable!!! That means <em>DELETE FROM simplified_posts</em> deletes all the records from <em>posts</em> table. For Postgres, look at <a href="https://www.postgresql.org/docs/9.3/sql-createview.html">Updatable Views</a> section for more details.</blockquote><p>If you want to prevent manipulation through the model, the simplest approach in Rails is to define (or override) the readonly? method for the model.</p><pre>class SimplifiedPost &lt; ApplicationRecord<br>  def readonly?<br>    true<br>  end<br>end</pre><p>When you mark the model as read-only, it prohibits any actions such as create/update/delete/destroy as well as any methods that modify data:</p><pre># ERROR: It prohibits creation of a new record<br><br>SimplifiedPost.create body: &#39;Body for read-only model&#39;<br># =&gt; SimplifiedPost is marked as readonly (ActiveRecord::ReadOnlyRecord)</pre><blockquote>WARNING: Since readonly? is an instance method, The class-methods like delete_alland <em>update_all</em> will bypass the read-only check!</blockquote><h3>Switch schema format to SQL</h3><p>If you’re using schema.rb format to represent your database schema in your codebase, the created View will not be stored in schema.rb file. Therefore, if you initialize the database using rails db:schema:load, the View will not be created. To have the view in database, we will need to run that specific migration again!</p><p>If we change the schema format to SQL, the structure.sql file can accurately represent all aspects of the real database. To switch the format, you only need to follow two simple steps. First, add the following line to the application.rb file:</p><pre>module RailsApplication<br>  class Application &lt; Rails::Application<br>    ...<br><br>    config.active_record.schema_format = :sql<br>  end<br>end</pre><p>Afterward you should run rails db:schema:dump to generate structure.sql file, which will be based on the current state of the database. At this point schema.rb file can be removed.</p><p>Take a look at the generated file — structure.sql. It should represent the database in SQL format.</p><h3><strong>Another way: </strong>Using scenic</h3><p><a href="https://github.com/scenic-views/scenic"><strong><em>scenic</em></strong></a><strong><em> </em></strong>is a gem that lets you to create and mange database views using Ruby methods. It also allows you to keep your schema file in Ruby format (schema.rb). Rather than repeating <em>scenic’s</em> documentation here, I encourage you to check out its <a href="https://github.com/scenic-views/scenic">Github page</a>.</p><h3>Conclusion</h3><p>In this article, we learned how to create a database view in Rails applications, how to query it using ActiveRecord, and how to regenerate the database, including the views.</p><p>Is it better to use a gem to manage views? As always, it depends! Adding a gem to your project is not free, it requires ongoing maintenance (like updating it). Or If you’re going to have more complex schema, with things like <em>Database</em> <em>Constraints </em>or database specific configurations, switching to SQL format might be a better option. However, if you’re comfortable with <em>scenic </em>and want to use its additional features, then go for it</p><p><a href="https://alisepehri.medium.com/database-view-in-ruby-on-rails-7331d2ee9784">Database View in Ruby on Rails</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=537f1a981e3d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Simple Solution for Zero Downtime on Deployments with Docker]]></title>
            <link>https://itnext.io/a-simple-solution-for-zero-downtime-on-deployments-with-docker-bdb71f3101d0?source=rss-99ea8e6f88d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/bdb71f3101d0</guid>
            <category><![CDATA[deployment]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[capistrano]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[zero-downtime]]></category>
            <dc:creator><![CDATA[Ali Sepehri]]></dc:creator>
            <pubDate>Wed, 11 Nov 2020 17:32:28 GMT</pubDate>
            <atom:updated>2020-11-17T18:39:02.918Z</atom:updated>
            <content:encoded><![CDATA[<h3>Zero Downtime on Deployments with Docker</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3vuWiCTZVQ4NOgNLKNbVHw.jpeg" /><figcaption>Build wonderful architectures with simple pieces</figcaption></figure><p>Two months ago after dockerizing our applications, I got a task to implement zero downtime for our deployments. When you don’t use Docker, in order to have 0-downtime, you can use phased-restart feature of Puma HTTP server. With Docker infrastructure, also you can use Swarm or Kubernetes, but most of the times, Swarm or Kubernetes are overkill for us.</p><p>In this article, we’re going to implement 0-downtime deployment with some simple tools that you can substitute them with other alternatives, so we don’t want to stick to some specific tools. Keep in mind that, implementing a proper way for container deployment, needs a good knowledge of Docker technology, so before reading this article ensure that you have enough knowledge in Docker.</p><p>At first, we will implement a simple Rails application with a single endpoint, and after dockerizing it, we will use Capistrano to have a simple deployment based on the docker containers. Then we will setup Nginx reverse-proxy and in order to do that, we will use nginx-proxy docker image. In the last two steps, we will improve our deployment scripts to support zero-downtime, and finally, we will test zero-downtime on the deployments with ApacheBench tool.</p><p>I’ve also prepared a sample project according to this article and you can check it out through the link below:</p><p><a href="https://github.com/AliSepehri/zero_downtime">AliSepehri/zero_downtime</a></p><h3>Initialize application and implement a simple endpoint</h3><p>In order to have a simple application, we want to use Ruby-on-Rails framework. If you’re using other languages/frameworks, the only thing we need to have for this section is a /ping endpoint which returns a response with 200 HTTP status.</p><p>In this article, we don’t want to address installing Ruby, Ruby on Rails, and the other prerequisites. I assume that we’ve installed Ruby on Rails on our machines and for initializing a Rails application, we execute the command below:</p><pre>rails new zero-downtime --api</pre><p>For implementing /ping endpoint, generate ping controller with Rails generators:</p><pre>rails g controller ping</pre><p>Add a simple test to test/controllers/ping_controller_test.rb file, add the route to config/routes.rb file, and the related action to app/controllers/ping_controller.rb :</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f5d75a6f85baae1db50be4a127a55175/href">https://medium.com/media/f5d75a6f85baae1db50be4a127a55175/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0e000f12b6d96677e407a1acc8a71b06/href">https://medium.com/media/0e000f12b6d96677e407a1acc8a71b06/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a8adbd7c675245d9c240daeac9a0b228/href">https://medium.com/media/a8adbd7c675245d9c240daeac9a0b228/href</a></iframe><p>In the end, run the test to be sure that everything is working correctly:</p><pre>rails test test/controllers/ping_controller_test.rb</pre><h3>Dockerize the application</h3><p>Now we’re going to write a simple Dockerfile for our Rails application. If you’re using another framework, for sure you can find many articles on the Internet about the way that you can dockerize it.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1274d5e5ade1f8f9d28969c1a08a071a/href">https://medium.com/media/1274d5e5ade1f8f9d28969c1a08a071a/href</a></iframe><p>Now build your Docker image by running the docker build command:</p><pre>docker build -t zero-downtime .</pre><p>Create and run a container based on our Docker image:</p><pre>docker run --rm --name zero-downtime-app -p 3000:3000 zero-downtime</pre><p>And test it:</p><pre>curl <a href="http://localhost:3000/ping">http://localhost:3000/ping</a></pre><pre># ===&gt; {&quot;message&quot;:&quot;pong!&quot;}</pre><p>If you’re getting the {&quot;message&quot;:&quot;pong!&quot;} , it means your container works; stop the running container(Ctrl+C) and follow the article. In the next section, we’re going to deploy the container to our local machine by the Capistrano tool.</p><h3>Simple deployment with Capistrano</h3><p>Now we want to use Capistrano to implement a simple deployment flow for our application. For sure there are many alternatives for the Capistrano and you can use them easily because the commands which we have used in Capistrano tasks are very close to the raw shell commands.</p><p>At first, we’re going to add Capistrano to our Gemfile, install it and initialize it. In order to that we can execute the following commands:</p><pre><em># Add and install the Gem</em><br><strong>bundle add capistrano</strong></pre><pre><em># Generate default config files</em><br><strong>bundle exec cap install</strong></pre><p>I’ve shown my Capfile after removing unnecessary lines:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1682ffb527d789e632c716e1b20ddab7/href">https://medium.com/media/1682ffb527d789e632c716e1b20ddab7/href</a></iframe><p>Into the ./config/deploy.rb file, we need to set repo_url to refer to our code. For this, you should use a git server. For example, you can use a Github repository and then copy the ssh remote URL from the repository page to our deploy.rb file.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/77e1d46025fb584d8828749bea50e742/href">https://medium.com/media/77e1d46025fb584d8828749bea50e742/href</a></iframe><p>As you can see we also have some tasks which will be executed after the default deployment steps. We will implement the tasks inside the docker.rake file.</p><p>With Capistrano, you can have different configurations for the different stages. I’ve shown our configuration for the development stage.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9180b0564ea01dad2510e64a2250bf8f/href">https://medium.com/media/9180b0564ea01dad2510e64a2250bf8f/href</a></iframe><p>As you can see, we’ve set localhost for the server attribute and that means we will use our local machine for the deployment and we don’t need to use virtual-machines or a real server.</p><p>As we mentioned before, we have some custom tasks to be run after the default steps of the Capistrano to do the main part of the deployment flow.</p><p>We’ve put these custom tasks inside the ./lib/capistrano/tasks/docker.rake file.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/afa1cb609129ed74141ffb87bfeb06e8/href">https://medium.com/media/afa1cb609129ed74141ffb87bfeb06e8/href</a></iframe><p>I’m going to explain each task briefly and for sure we should have some experience with Docker to find out what’s happening in these tasks exactly. create_network creates backend network and we will use it to run all containers in the same network so they will be able to access each other. create_volumes creates prerequisite Docker volumes. db_setup runs rails db:setup to initialize the database (for sure for a real application you should not execute database-setup on each deployment). And finally start_container will run the application container with the specified environment variables.</p><p>Now we’re ready to deploy our application. bundle exec cap development deploy command runs Capistrano deployment flow for the development stage. After running the deployment command you should get the error below:</p><pre>...</pre><pre>Errno::ECONNREFUSED: Connection refused — connect(2) for 127.0.0.1:22</pre><pre>...</pre><p>We don’t have any ssh-server on our local machine and that’s the reason we’re getting this error. To install ssh-server and configure the ssh-key pair on the Ubuntu machines you can follow the commands below:</p><pre><em># ------- Install ssh-server, enable, and run its servic -------<br></em><strong>sudo apt-get install openssh-server<br>sudo systemctl enable ssh<br>sudo systemctl start ssh</strong></pre><pre><em># ------- Create a new key-pair -------</em><br><strong>ssh-keygen -t rsa -b 4096 -C &quot;Capistrano Local&quot; -f ~/.ssh/capistrano_local</strong></pre><pre><em># ------- Add the new private-key for ssh-client -------</em><br><strong>ssh-add ~/.ssh/capistrano_local</strong></pre><pre><em># ------- Add the new public-key for ssh-server -------</em><br><strong>cat ~/.ssh/capistrano_local.pub &gt;&gt; ~/.ssh/authorized_keys</strong></pre><p>So we will have ssh-server and ssh-client simultaneously on our machine. Now we’re ready to deploy:</p><pre>bundle exec cap development deploy</pre><p>When it finished successfully the application container should be running and responsible for the curl request:</p><pre><strong>curl </strong><a href="http://localhost:3000/ping"><strong>http://localhost:3000/ping</strong></a></pre><pre><em># ===&gt; {&quot;message&quot;:&quot;pong!&quot;}</em></pre><p>If we get {&quot;message&quot;:&quot;pong!&quot;} response, it means your application container is up and running.</p><h3>Add Nginx</h3><p>In our existing structure, the Puma which is running inside the application container receives the requests directly. In this section we’re going to setup an Nginx container and put the application container behind it. For this purpose, we will use <a href="https://hub.docker.com/r/jwilder/nginx-proxy">jwilder/nginx-proxy</a> image instead of the <a href="https://hub.docker.com/_/nginx">official nginx</a> image. When a new container is run, nginx-proxy will recognize it and will generate reverse-proxy configs and will reload the Nginx process automatically.</p><p>We need to run nginx-proxy only once and it’s better to keep it out of your deployment flow, but for the sake of simplicity, we will use Capistrano to run nginx-proxy container. Also, we need to add a new environment variable(VIRTUAL_HOST) to the application container. The new container will be recognized automatically because of the VIRTUAL_HOST environment variable and it will be used as server-name in the Nginx configurations.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dc67bd3ec669243ea0e5d5e4254c7817/href">https://medium.com/media/dc67bd3ec669243ea0e5d5e4254c7817/href</a></iframe><p>start_nginx checks the running containers and run the nginx-proxy container if it’s not running. We also have two small changes for the start_container task and as you can see, we don’t need port mapping anymore and instead of that, we’re passing VIRTUAL_HOST env to the application container — as we described before.</p><p>And we need to update config/deploy.rb file to add the new task to our deployment flow :</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/71610a550a5dd22f299624b40374fcbf/href">https://medium.com/media/71610a550a5dd22f299624b40374fcbf/href</a></iframe><p>Now we need to run Capistrano deployment command again to see our changes:</p><pre>bundle exec cap development deploy</pre><p>For testing keep in mind that, the nginx-proxy navigate the requests to our application container if they come for the specified domain which is specified via the VIRTUAL_HOST environment variable. So we need to fake the domain by setting Host header for the curl request:</p><pre><strong>curl --header &quot;Host: app-domain.test:8080&quot; localhost:8080/ping</strong></pre><pre><em># ===&gt; {&quot;message&quot;:&quot;pong!&quot;}</em></pre><h3>Implement zero-downtime with Blue-green deployment (the moment of truth)</h3><p>The idea is to add a health check to our application container, run the new version of the container without stopping the old one, keep running the old and new versions of the application simultaneously until the new version being responsible for the requests and finally stop the old container.</p><p>By adding a healthy check to the application container, we will be able to find out that the container is responsible for the requests or not. An easy way to implement a healthy check for our container is sending curl request to the /ping endpoint — which we implemented in the first section.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/38cc718215add754d0b8d938a84c003f/href">https://medium.com/media/38cc718215add754d0b8d938a84c003f/href</a></iframe><p>So according to our idea, we need to add healthy-check to the start_container task. curl -f localhost:3000/ping will execute every 10 seconds and will update the status of the container. wait_for_container will wait for the new container(zero-downtime-app-new) to be healthy and after that the stop_container will be executed to stop the old container and finally rename_container task will rename the name of the new container from zero-downtime-app-new to zero-downtime-app.</p><p>Ensure that you’ve added the new steps to the config/deploy.rb file:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b17963621d272b77fa88063cd20a3b34/href">https://medium.com/media/b17963621d272b77fa88063cd20a3b34/href</a></iframe><h3>Test zero-downtime with ApacheBench</h3><p>We want to use <a href="https://httpd.apache.org/docs/2.4/programs/ab.html">ApacheBench tool</a> to test our implementation for the zero-downtime deployment. We will use it to send a lot of requests in the specified time limitation and run the deployment simultaneously. ApacheBench will tell us the count of the Non-2xx responses and this value should be ZERO for us to confirm our solution.</p><p>Open two separate terminals on your machine. Run deployment in the first terminal:</p><pre>bundle exec cap development deploy</pre><p>and run the ApacheBench in the second one — if this is the first time that you’re running this command, it will take some times to get the ab image from the registry:</p><pre>docker run --rm --network=backend httpd ab -s5000 -t50 -n1000000 -H &quot;Host: app-domain.test&quot; -c1 <a href="http://nginx/ping">http://nginx/ping</a></pre><p>After finishing the deployment you can stop the ApacheBench container(Ctrl + C) and it will generate the report:</p><pre>This is ApacheBench, Version 2.3 &lt;$Revision: 1879490 $&gt;<br>Copyright 1996 Adam Twiss, Zeus Technology Ltd, <a href="http://www.zeustech.net/">http://www.zeustech.net/</a><br>Licensed to The Apache Software Foundation, <a href="http://www.apache.org/">http://www.apache.org/</a></pre><pre>Benchmarking nginx (be patient)</pre><pre>Server Software:        nginx/1.19.3<br>Server Hostname:        nginx<br>Server Port:            80</pre><pre>Document Path:          /ping<br>Document Length:        19 bytes</pre><pre>Concurrency Level:      1<br>Time taken for tests:   52.124 seconds<br><strong>Complete requests:      24705</strong><br><strong>Failed requests:        0</strong><br>Total transferred:      13488930 bytes<br>HTML transferred:       469395 bytes<br>Requests per second:    473.97 [#/sec] (mean)<br>Time per request:       2.110 [ms] (mean)<br>Time per request:       2.110 [ms] (mean, across all concurrent requests)<br>Transfer rate:          252.72 [Kbytes/sec] received</pre><pre>Connection Times (ms)<br>              min  mean[+/-sd] median   max<br>Connect:        0    0   0.1      0       9<br>Processing:     1    1   0.3      1      13<br>Waiting:        1    1   0.3      1      13<br>Total:          1    1   0.3      1      13</pre><pre>Percentage of the requests served within a certain time (ms)<br>  50%      1<br>  66%      1<br>  75%      2<br>  80%      2<br>  90%      2<br>  95%      2<br>  98%      2<br>  99%      3<br> 100%     13 (longest request)</pre><p>The Failed requests should be 0 and also you should not see Non-2xx responses section at the report.</p><h3>Todos</h3><ul><li>Build application docker-image with Capistrano on the server or CI</li><li>Configure and run nginx-proxy out of the deployment workflow</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bdb71f3101d0" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/a-simple-solution-for-zero-downtime-on-deployments-with-docker-bdb71f3101d0">A Simple Solution for Zero Downtime on Deployments with Docker</a> was originally published in <a href="https://itnext.io">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to speed up assets precompile for Ruby on Rails apps]]></title>
            <link>https://itnext.io/how-to-speed-up-assets-precompile-for-ruby-on-rails-apps-e0338d8d7301?source=rss-99ea8e6f88d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/e0338d8d7301</guid>
            <category><![CDATA[deployment]]></category>
            <category><![CDATA[webpacker]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[webpack]]></category>
            <dc:creator><![CDATA[Ali Sepehri]]></dc:creator>
            <pubDate>Fri, 10 Jul 2020 09:11:58 GMT</pubDate>
            <atom:updated>2020-07-22T15:15:36.017Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="&lt;span&gt;Photo by &lt;a href=”https://unsplash.com/@rodlong?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyT" src="https://cdn-images-1.medium.com/max/1024/1*EZgvVKlxWF0auQuQ_taXUA.jpeg" /></figure><p>You spend too much time to deploy your rails project especially on the assets:precompile step, or maybe sometimes you see the following error during the assets precompilation:</p><pre>FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory</pre><p>So for sure, this short article will help you to have 50% improvement only with some minor changes.</p><p>If you’re using webpacker in your project, the assets:precompile consists of two stages; we will call these two stages, sprockets and webpacker. Now we want to execute these two stages separately to find out which one is your problem.</p><p>Run sprockets stage:</p><pre>WEBPACKER_PRECOMPILE=false rake assets:precompile</pre><p>and then run webpacker building stage:</p><pre>rake webpacker:compile</pre><blockquote><strong>NOTE:</strong> If you want to analyze the performance of the above commands in production mode, pass RAILS_ENV=production as an environment variable to both of them.</blockquote><p>If the webpacker is your main problem, keep reading and we are going to investigate and resolve the issue. There is a way to see the details of webpacker compile command, in order to that we should use webpack itself instead of webpacker:</p><pre>./bin/webpack --progress</pre><blockquote>NOTE: If you want to run webpackcommand in production mode, you need to pass NODE_ENV=production as an environment variable to this command. For example NODE_ENV=production ./bin/webpack</blockquote><p>There should be a time/memory consuming step to generate source-map files.</p><pre>**% after chunk asset optimization SourceMapDevToolPlugin js/**-**.js generate SourceMap</pre><p>Source-map files help to map a combined/minified file back to an unbuilt state. If you don’t need source-map files on the production server, you can skip generating these files by setting devtool for the webpack/webpacker. I didn’t find already exist, implemented an environment variable to set this feature, but don’t worry, we will do it manually.</p><p>Open environment.js file and add the following two lines before the last line(module.exports = environment; ):</p><pre>...</pre><pre>const devtool = process.env.DEVTOOL;<br>if (devtool) environment.config.merge({ devtool });</pre><pre>...</pre><pre>module.exports = environment; // this line already exists</pre><p>Then test the implemented code:</p><pre>DEVTOOL=none ./bin/webpack --progress</pre><p>You should not see the source-map generating step anymore.</p><p>Now you will be able to pass DEVTOOL ENV to assets:precompile command:</p><pre>DEVTOOL=none rake assets:precompile</pre><p>That’s it. You should have a huge improvement!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e0338d8d7301" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/how-to-speed-up-assets-precompile-for-ruby-on-rails-apps-e0338d8d7301">How to speed up assets precompile for Ruby on Rails apps</a> was originally published in <a href="https://itnext.io">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Preview for Amazon S3 Client-Side Encrypted Active Storage files]]></title>
            <link>https://alisepehri.medium.com/preview-for-amazon-s3-client-side-encrypted-active-storage-files-8e3ba55accb8?source=rss-99ea8e6f88d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/8e3ba55accb8</guid>
            <category><![CDATA[preview]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[active-storage]]></category>
            <category><![CDATA[client-side-encryption]]></category>
            <category><![CDATA[s3]]></category>
            <dc:creator><![CDATA[Ali Sepehri]]></dc:creator>
            <pubDate>Thu, 25 Jul 2019 19:36:37 GMT</pubDate>
            <atom:updated>2019-07-25T19:36:37.057Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PyWc-ErpByB8E9ZeTLm2Ng.jpeg" /></figure><p>In our project, we are using Active Storage as our upload solution, and Amazon S3 for Storage service. Recently because of a requirement in the project, we decided to encrypt files before uploading to S3 and decrypting them after downloading (client-side encryption). <a href="https://github.com/aws/aws-sdk-ruby/tree/master/gems/aws-sdk-s3">Amazon S3 Gem</a> meets this requirement and we can find the <a href="https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/s3-example-client-encryption.html">documentation through this link</a>.</p><p>And the following greate article shows how we can implement it in a transparent way:</p><p><a href="https://ankane.org/activestorage-s3-encryption">Active Storage S3 Client-Side Encryption</a></p><p>This blog post has described what you need, <strong>BUT</strong> if you want to have a <a href="https://edgeapi.rubyonrails.org/classes/ActiveStorage/Preview.html">preview</a> for the uploaded files, so you are the one who should read this article.</p><h3>Preview for Uploaded Files (image, PDF, movie, …)</h3><p>Unfortunately, Active Storage does not completely support preview for client-side encrypted files, if we use the default previewer path which is created by Active Storage itself, it will give us a URL that refers directly to the encrypted thumbnail image on the S3, and it’s not previewable by an HTML image tag.</p><p><strong><em>NOTE:</em></strong> We should not forget that we need a transparent way that works with any configuration of Active Storage. For example, config.active_storage.service = :local should work properly in your development environment while config.active_storage.service = :amazon works on the production server.</p><p>Assume that we have an Attachment model that is associated with on document:</p><pre>class Attachment &lt; ApplicationRecord<br>  has_one_attached :document<br>end</pre><p>Now, we will go through the following steps:</p><ol><li>The blog post in the previous section has missed an important line of code that is used in the Active Storage Preview, and we need to change the download method like the following code:</li></ol><pre>def download(key, &amp;block)<br>  binary_data = instrument :download, key: key do<br>    encryption_client.get_object(<br>      bucket: bucket.name,<br>      key: key<br>    ).body.string.force_encoding(Encoding::BINARY)<br>  end</pre><pre><strong>  yield binary_data if block</strong></pre><pre>  binary_data<br>end</pre><p>The bold line is the missing line. If you want to know why we need to add this, check out <a href="https://github.com/rails/rails/blob/876548a7e7307fc7d4c22fb6560df1474353d0cf/activestorage/lib/active_storage/downloading.rb#L37">this line</a> of the Active Storage source code.</p><p>2. Then we are going to create a service object in order to process and download preview blob:</p><pre><strong>module</strong> Attachments<br>  <strong>class</strong> Preview<br>    <strong>def</strong> initialize(attachment)<br>      @attachment = attachment<br>    <strong>end</strong></pre><pre>    <strong>def</strong> call<br>      document_preview = <br>        @attachment.document<br>                   .blob<br>                   .representation(resize: &#39;200x200&#39;)<br>                   .processed</pre><pre>      <strong>if</strong> document_preview.is_a?(ActiveStorage::Variant)<br>        variant = document_preview<br>      <strong>else</strong><br>        variant = ActiveStorage::Variant.new(<br>          document_preview.image,<br>          document_preview.variation<br>        )<br>      <strong>end</strong><br>      variant_preview = variant.processed<br>      ActiveStorage::Blob.service.download(variant_preview.key)<br>    <strong>end</strong><br>  <strong>end</strong><br><strong>end</strong></pre><p>3. Now we need to create a controller to serve preview file:</p><pre><strong>class</strong> AttachmentsController &lt; ApplicationController<br>  <strong>def</strong> preview<br>    preview_data = ::Attachments::Preview.new(attachment).call<br>    send_data(<br>      preview_data,<br>      filename: attachment.filename,<br>      type: &#39;image/png&#39;<br>    )<br>  <strong>end</strong></pre><pre>  <strong>private</strong></pre><pre>  <strong>def</strong> attachment<br>    @attachment ||= ::Attachment.find params[:id]<br>  <strong>end</strong><br><strong>end</strong></pre><p>4. And that’s it, now you just need to use the proper route to implemented action in your &lt;img /&gt; tags:</p><pre>&lt;img src=<strong>&lt;%= preview_attachment_path(attachment) %&gt;</strong> /&gt;</pre><h3>Wrapping Up</h3><p>If you want to use the Preview feature of the Active Storage you need to know that it generates a thumbnail beside the original file, but when you are using client-side encryption, you can not directly refer to the thumbnail file (because it’s encrypted too). In this case, you need to decrypt the thumbnail file, before previewing. We have explained how you can meet this requirement by creating a custom controller and use it in your image tag instead of the URL which is generated by the Active Storage.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8e3ba55accb8" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A practical Example of Database View in Ruby on Rails]]></title>
            <link>https://alisepehri.medium.com/database-view-in-ruby-on-rails-7331d2ee9784?source=rss-99ea8e6f88d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/7331d2ee9784</guid>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <dc:creator><![CDATA[Ali Sepehri]]></dc:creator>
            <pubDate>Tue, 08 May 2018 19:25:31 GMT</pubDate>
            <atom:updated>2024-08-10T11:30:10.831Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Database View | Photo by Paul Skorupskas on Unsplash" src="https://cdn-images-1.medium.com/max/1024/1*QMMRrHM1TfqAV6MJ-GbeCA.jpeg" /></figure><p>To learn how to create a database view in Ruby on Rails, start by reading the following article:</p><p><a href="https://alisepehri.medium.com/how-to-create-database-views-in-ruby-on-rails-537f1a981e3d">How to create Database Views in Ruby on Rails?</a></p><h3>1. Concatenation methods</h3><p>Combine ActiveRecord result by the plus(+) operator (or any similar methods):</p><pre>photo_posts = PhotoPost.all<br>text_posts = TextPost.all</pre><pre>result = photo_posts + text_posts</pre><p>There are some drawbacks with this solution:</p><ul><li>Performance issue (Ram based process)</li><li>Really hard to sort and pagination</li></ul><h3>2. Polymorphic</h3><p>In this solution, we will create a model (table) with a polymorphic association to PhotoPost and TextPost:</p><pre>class Post &lt; ApplicationRecord<br>  belongs_to :postable, polymorphic: true<br>end</pre><p>and associate your models (PhotoPost and TextPost) to Post model:</p><pre>class PhotoPost &lt; ApplicationRecord<br>  has_one :posts, as: :postable<br>end</pre><pre>class TextPost &lt; ApplicationRecord<br>  has_one :posts, as: :postable<br>end</pre><p>Note that you need to create Post object manually after creating every PhotoPost or TextPost.</p><p>This technique is called multi-table inheritance. There are some gems to simulate MTI for ActiveRecord models (e.g. <a href="https://github.com/krautcomputing/active_record-acts_as">active_record-acts_as</a>).</p><p>Drawbacks with this solution:</p><ul><li>N+1 query problem, Because you can’t join Post model with PhotoPost &amp; TextPost</li><li>Additional model (Post)</li><li>Hard to sort by attributes which are not a member of Post model</li></ul><h3>3. Database View</h3><p>Describing Database View is beyond the scope of this article. We just will know how we should use View in Ruby on Rails.</p><p>Create a migration to create Database View:</p><pre>class CreatePostView &lt; ActiveRecord::Migration<br>  def up<br>    execute &lt;&lt;-SQL<br>      CREATE VIEW posts AS<br>      SELECT<br>        id AS indentifier,<br>        title,<br>        updated_at,<br>        created_at<br>      FROM<br>        photo_posts<br>      UNION ALL<br>      SELECT<br>        id AS identifier,<br>        title,<br>        updated_at,<br>        created_at<br>      FROM<br>        text_posts<br>    SQL<br>  end</pre><pre>  def down<br>    execute &lt;&lt;-SQL<br>      DROP VIEW IF EXISTS posts<br>    end<br>  end<br>end</pre><p>Rails behaves in the same way with tables &amp; views and you only need to create Post model like the other ones:</p><pre>class Post &lt; ApplicationRecord<br>end</pre><p>Now, All SELECT kind of queries are available by Post model, e.g. Post.first, Post.all, Post.where() .</p><h3>Wrapping up</h3><p>In this I’ve listed the advantages and disadvantages of each solution:</p><h4>Concatenation methods</h4><p>✓ Easy to read and write<br>✗ Hard to sort<br>✗ Hard to paginate<br>✗ Performance issue (Ram based process)</p><h4>Polymorphic</h4><p>✓ Ability to generalize attributes, associations, and methods<br>✓ Easy pagination<br>✓ Transparency between the models which have <em>is-a</em> relationship together<br>✗ Performance issue (N+1 query problem)<br>✗ Additional model (table)<br>✗ Hard to sort by attributes which are not a member of Post model</p><h4>Database View</h4><p>✓ High performance<br>✗ Hard to maintain</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7331d2ee9784" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My Experiences in Live Rails Application]]></title>
            <link>https://medium.com/salamcinema/my-experiences-in-live-rails-application-150fa2072b53?source=rss-99ea8e6f88d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/150fa2072b53</guid>
            <category><![CDATA[ruby-on-rails]]></category>
            <dc:creator><![CDATA[Ali Sepehri]]></dc:creator>
            <pubDate>Sat, 17 Feb 2018 18:16:44 GMT</pubDate>
            <atom:updated>2018-02-17T18:16:44.379Z</atom:updated>
            <content:encoded><![CDATA[<ol><li>Idempotency for seed files, rake tasks and background jobs.</li><li>Do not ignore schema.rb file in Git. Use rake db:schema:load instead of rake db:migrate to initialize database (for example when you clone a new project).</li><li>Use just a single way to define environment variables in your app.</li><li>Don’t ignore Gemfile.lock file.</li><li>You need both of application layer validations and database layer constraints. For example if you’re using multi process/thread app-servers(like unicorn, puma, …) in addtion to model uniqueness validation, also define database uniqueness constraint to protect againist race condition. Read <a href="https://robots.thoughtbot.com/validation-database-constraint-or-both">this</a> great article.</li><li>As far as possible don’t use Rails.env.production?, Rails.env.development? or any same conditions in your code. Sometimes you need them in config files.</li><li>Put your data changes in migration files. For example if you want to add a column and store concatenation of two another columns into it, in this case after creation of new column iterate all rows and update it in your new migration file (and then delete old columns). Also before goieng live, test your data migration functions with your existing live database in your development environment (if it is possible).</li><li>Don’t bypass model validations to save specific data by update_attribute method or manually from database.</li><li>After any change in validations, validate existing values stored in your database.</li><li>Dockerfile is the best runnable documentation for your app. Even if you don’t use docker to deploy your application write a Dockerfile.</li><li>Use <a href="https://datasift.github.io/gitflow/IntroducingGitFlow.html">gitflow</a> branching model (master, develop, release/*, feature/*, hotfix/*).</li><li>Use Continuous Integration, at least to run your tests.</li><li>Use error monitoring systems (like <a href="https://github.com/errbit/errbit">errbit</a>).</li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=150fa2072b53" width="1" height="1" alt=""><hr><p><a href="https://medium.com/salamcinema/my-experiences-in-live-rails-application-150fa2072b53">My Experiences in Live Rails Application</a> was originally published in <a href="https://medium.com/salamcinema">salamcinema</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Hadir]]></title>
            <link>https://medium.com/salamcinema/hadir-81e027a0d0d6?source=rss-99ea8e6f88d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/81e027a0d0d6</guid>
            <category><![CDATA[pundits]]></category>
            <category><![CDATA[hadir]]></category>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <dc:creator><![CDATA[Ali Sepehri]]></dc:creator>
            <pubDate>Wed, 17 Jan 2018 17:18:17 GMT</pubDate>
            <atom:updated>2020-07-10T11:58:21.349Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I0ECZwWikSTEKLikDdfVMA.jpeg" /></figure><p><a href="https://github.com/AliSepehri/hadir">Hadir</a> is a gem which provides an authorization system like Pundit. With Hadir you separate policy logics and put them in policy classes based on each controller.</p><h3>Installation</h3><p>All you need is adding hadir to your Gemfile:</p><pre>gem &#39;hadir&#39;</pre><h3>Getting Started</h3><p>Hadir is focused on policy classes. You need a policy class for each controller and a method into it for each action (It’s possible to use same method for multiple actions).</p><p>In the following controller we prevent to update or delete unpublished post, also in delete action we use custom method and messsage:</p><pre>class Api::V1::PostsController &lt; ActionController::Base<br>  def update<br>    post = Post.find(params[:id])<br>    authorize post<br>  end<br><br>  def delete<br>    post = Post.find(params[:id])<br>    authorize post, &#39;update?&#39;, message: &#39;You are not allowed to delete unpublished post.&#39;<br>  end<br><br>  private<br><br>  def current_user<br>    # retrieve current-user and return it<br>  end<br>end</pre><p>Following policy class will allow updating a post if it is not unpublished:</p><pre>class Api::V1::PostsPolicy<br>  attr_reader :user, :record<br><br>  def initialize(user, record)<br>    @user = user<br>    @record = record<br>  end<br><br>  def update?<br>    record.published?<br>  end<br>end</pre><p>Hadir makes following assumptions about your policy classes:</p><ul><li>Policy class name is the same as resource part of controller with Policy suffix.</li><li>Policy class has the same namespace as contoller class.</li><li>The first argument is a user. In your controller, Hadir will call the current_user method to retrieve what to send into this class. (null is acceptable)</li><li>The second argument is an object which you want to check its authorization. It can be any object you want.</li><li>As a default behaviour Hadir maps your action to the method with the same name and question mark (?) as a suffix. For example Hadir maps update action to update? method in your policy class. It is also possible to send your desired method name as second argument of authorize method.</li></ul><h3>Hadir vs Pundit</h3><p>In Pundit you create a policy class for each object’s class(most of the time a model) and put methods into it for each controller’s action, there are some disadvantages:</p><ul><li>You have only one policy class for different API versions or admin APIs(different controllers) for the same model(class). You will have a messy class!</li><li>You are not able to have policy if you don’t pass a specific object as argument of authorize metho</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=81e027a0d0d6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/salamcinema/hadir-81e027a0d0d6">Hadir</a> was originally published in <a href="https://medium.com/salamcinema">salamcinema</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Running advanced object/file storage server in less than 1 hour with “Minio”]]></title>
            <link>https://medium.com/salamcinema/running-advanced-object-file-storage-server-in-less-than-1-hour-with-minio-d55270b9d7a5?source=rss-99ea8e6f88d1------2</link>
            <guid isPermaLink="false">https://medium.com/p/d55270b9d7a5</guid>
            <category><![CDATA[s3]]></category>
            <category><![CDATA[minio]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[docker]]></category>
            <dc:creator><![CDATA[Ali Sepehri]]></dc:creator>
            <pubDate>Tue, 07 Nov 2017 18:50:29 GMT</pubDate>
            <atom:updated>2017-11-07T18:50:29.540Z</atom:updated>
            <content:encoded><![CDATA[<p>We wanted to prevent user from downloading files before paying for them. At first attempt I created a temporary link per each user and each file and stored it in database. After that I just need a get API and redirect request to original file in server. That was really slow, CPU intensive process and insecure. Trust me, that wasn’t proper for big files.</p><h4><strong>Why should I use Minio?</strong></h4><p>As I mentioned before we needed one-time URL of files per each user and I couldn’t use Nginx itself to provide files.</p><h4><strong>Run Minio using docker</strong></h4><p>That is really simple to run Minio by its docker image. Install docker and run following commands:‍‍</p><pre><strong>$</strong> docker pull minio/minio</pre><pre><strong>$</strong> docker run -p 9000:9000 --name minio \<br>    -v /home/username/<em>_minio_</em>volume:/export \<br>    minio/minio server /export</pre><p>After running successfully, you will see Minio credentials (AccessKey, SecretKey), write them down; Or you can pass them by yourself:</p><pre><strong>$</strong> docker run -d -p 9000:9000 --name minio \<br>    -v /home/username/<em>_minio_</em>volume:/export \<br>    -e &quot;MINIO<em>_ACCESS_</em>KEY=XXXXXXXXXXXX&quot; \<br>    -e &quot;MINIO<em>_SECRET_</em>KEY=XXXXXXXXXXXXX&quot; minio/minio server /export</pre><p>Open your browser and enter localhost:9000, if every things go right, you will see Minio login page.</p><h4><strong>Upload file to Minio by GUI</strong></h4><p>If you see login page, login with your credentials. At first, create a bucketand upload your file to specified bucket:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/359/1*Cuaw7XZN_tCY03Fp_tD_lQ.png" /><figcaption>Create &amp; Upload file buttons at down-right side of the page</figcaption></figure><p>Create temporary link to your file by clicking on three dot icon in the front of uploaded file; then click on copy icon and finally click on Copy Link button. As you can see in following picture you can change temporary link expiration time:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rE_GoDlVCy1DbQhweQZ71Q.png" /><figcaption>Create temporary link</figcaption></figure><h4><strong>Using Ruby on Rails to make temporary URL</strong></h4><p>After uploading a file to Minio you can create temporary link to the file programmatically. Minio APIs are completely compatible with Amazon S3. That means you can use S3 gem. You can use following three steps to create temporary link:</p><ol><li>Add AWS gem to Gemfile:</li></ol><pre>gem &#39;aws-sdk&#39;, &#39;~&gt; 2&#39;</pre><p>2. Creaet initializers/aws.rb file:</p><pre>Aws.config.update(<br>  region: &#39;us-east-1&#39;,<br>  endpoint: ENV[&#39;minio_endpoint&#39;],<br>  force_path_style: true,<br>  credentials: Aws::Credentials.new(<br>    ENV[&#39;minio_access_key&#39;],<br>    ENV[&#39;minio_secret_key&#39;]<br>  )<br>)</pre><p>3. And finally create temporary link:</p><pre>s3_res = Aws::S3::Resource.new</pre><pre>tmp_url = s3_res.bucket(&#39;movies&#39;)<br>                .object(&#39;filename&#39;)<br>                .presigned_url(:get, expires_in: 10.days.from_now)</pre><h4>How to configure Carrierwave gem in Ruby on Rails to use Minio</h4><p>For example in some use-cases you need to upload your files to Minio through carrierwave. In order to this purpose you can use <a href="https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Use-minio-with-Carrierwave">this link</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d55270b9d7a5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/salamcinema/running-advanced-object-file-storage-server-in-less-than-1-hour-with-minio-d55270b9d7a5">Running advanced object/file storage server in less than 1 hour with “Minio”</a> was originally published in <a href="https://medium.com/salamcinema">salamcinema</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>