<?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: Sagyam Thapa</title>
    <description>The latest articles on DEV Community by Sagyam Thapa (@sagyam).</description>
    <link>https://dev.to/sagyam</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%2F3207400%2F94a41674-527d-4fbd-b221-12a0b635b1db.jpg</url>
      <title>DEV Community: Sagyam Thapa</title>
      <link>https://dev.to/sagyam</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sagyam"/>
    <language>en</language>
    <item>
      <title>Interactive Guide To Rate Limiting</title>
      <dc:creator>Sagyam Thapa</dc:creator>
      <pubDate>Wed, 04 Jun 2025 23:03:47 +0000</pubDate>
      <link>https://dev.to/sagyam/interactive-guide-to-rate-limiting-58p3</link>
      <guid>https://dev.to/sagyam/interactive-guide-to-rate-limiting-58p3</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Rate limiting is a must have strategy in every back-end app. It prevent one user from overusing a resource and degrading the quality of service for other users. Here are some benefits of rate limiting&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It presents resource starvation&lt;/li&gt;
&lt;li&gt;Reduces server hosting cost&lt;/li&gt;
&lt;li&gt;Provides basic protection against DDoS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have made four interactive app that let’s you play around with common rate limiting algorithms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Token bucket
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Working:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;A bucket holds fixed number tokens&lt;/li&gt;
&lt;li&gt;Tokens are added to bucket at fixed rate&lt;/li&gt;
&lt;li&gt;When a request comes in:&lt;/li&gt;
&lt;li&gt;If a token is available, it’s removed from the bucket and the request is allowed.&lt;/li&gt;
&lt;li&gt;If no tokens are available, the request is rejected or delayed.&lt;/li&gt;
&lt;li&gt;Allows for occasional short burst if tokens are available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have created an &lt;a href="https://tools.sagyamthapa.com.np/token-bucket" rel="noopener noreferrer"&gt;app&lt;/a&gt; that let’s you play with leaky bucket algorithm.&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%2Fm4q7246dy7kcy7di92jn.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%2Fm4q7246dy7kcy7di92jn.png" alt="Image description" width="800" height="812"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Leaky bucket
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Working:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Think of it as a bucket leaking at a fixed rate&lt;/li&gt;
&lt;li&gt;Incoming requests are added to the bucket&lt;/li&gt;
&lt;li&gt;Requests are processed (or “leak”) at a constant rate&lt;/li&gt;
&lt;li&gt;If the bucket is full when a new request arrives, the request is dropped&lt;/li&gt;
&lt;li&gt;Smooths out bursts; outputs requests at a steady rate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have created an &lt;a href="https://tools.sagyamthapa.com.np/leaky-bucket" rel="noopener noreferrer"&gt;app&lt;/a&gt; that let’s you play with leaky bucket algorithm.&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%2Foomcpeg7th0es51rl0tw.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%2Foomcpeg7th0es51rl0tw.png" alt="Image description" width="800" height="1110"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixed window counter
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Working:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Time is divided into fixed-size windows (e.g., 1 minute)
A counter tracks the number of requests per client/IP in the current window&lt;/li&gt;
&lt;li&gt;If the count exceeds the limit, further requests are rejected until the next window&lt;/li&gt;
&lt;li&gt;Simple and efficient, but allows burst traffic spike at end/start&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have created an &lt;a href="https://tools.sagyamthapa.com.np/fixed-window" rel="noopener noreferrer"&gt;app&lt;/a&gt; that let’s you play with fixed bucket algorithm.&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%2Fctzat12btzybyhafjh7n.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%2Fctzat12btzybyhafjh7n.png" alt="Image description" width="800" height="744"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sliding window counter
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Working:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Keeps a timestamped log of each request&lt;/li&gt;
&lt;li&gt;When a request comes in, logs are checked to count how many requests were made in the last X seconds&lt;/li&gt;
&lt;li&gt;If under the limit, the request is allowed and logged; otherwise, it’s rejected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have created an &lt;a href="https://tools.sagyamthapa.com.np/sliding-window" rel="noopener noreferrer"&gt;app&lt;/a&gt; that let’s you play with fixed bucket algorithm.&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%2Fqsb87a8xl70utp3dmst3.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%2Fqsb87a8xl70utp3dmst3.png" alt="Image description" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Scaling PostgreSQL with Kubernetes</title>
      <dc:creator>Sagyam Thapa</dc:creator>
      <pubDate>Tue, 27 May 2025 17:24:51 +0000</pubDate>
      <link>https://dev.to/sagyam/scaling-postgresql-with-kubernetes-dpf</link>
      <guid>https://dev.to/sagyam/scaling-postgresql-with-kubernetes-dpf</guid>
      <description>&lt;h2&gt;
  
  
  A case for vertical scaling
&lt;/h2&gt;

&lt;p&gt;If you have read any article or a book on system design then you probably know what vertical and horizontal scaling is and benefits of horizontal scaling. Before I explain how to setup proper horizontal scaling with Postgres let me make a case when you should not try this.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Simplicity: Single node database means you can run your database out of the box. Although I recommend you run &lt;a href="https://pgtune.leopard.in.ua" rel="noopener noreferrer"&gt;PGTune&lt;/a&gt; for a quick preset or visit &lt;a href="http://postgresqlco.nf/" rel="noopener noreferrer"&gt;postconf&lt;/a&gt; a full breakdown&lt;/li&gt;
&lt;li&gt;Easier backup and recovery: No need to think about state across replicas when creating backups or applying a backup.&lt;/li&gt;
&lt;li&gt;No network overhead especially with write heavy operations.&lt;/li&gt;
&lt;li&gt;A temporary fix: If need a fix right now, this will provide an instant relief.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisite
&lt;/h2&gt;

&lt;p&gt;Make sure you have following tools installed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/reference/kubectl/" rel="noopener noreferrer"&gt;kubectl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://helm.sh/" rel="noopener noreferrer"&gt;helm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://minikube.sigs.k8s.io/docs/start/?arch=%2Flinux%2Fx86-64%2Fstable%2Fbinary+download" rel="noopener noreferrer"&gt;minikube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k9scli.io/" rel="noopener noreferrer"&gt;k9s&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Following the guide requires you have basic understanding of Kubernetes, &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources" rel="noopener noreferrer"&gt;CRD&lt;/a&gt;, Helm. Nothing deep a quick AI summary will suffice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replication
&lt;/h2&gt;

&lt;p&gt;Replication means keeping multiple copies of data on multiple machines connected via network. Here is why you might want to do that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It keeps you data close to your users.&lt;/li&gt;
&lt;li&gt;It acts as a hot backup of a follower goes down.&lt;/li&gt;
&lt;li&gt;It helps with scaling if most of your workload is read operation (which is the case for most &lt;a href="https://www.wikiwand.com/en/articles/Online_transaction_processing" rel="noopener noreferrer"&gt;OLTP&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&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%2Fgqpxvdk8oacu0jz38ja5.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%2Fgqpxvdk8oacu0jz38ja5.png" alt="Diagram depicting a database architecture with a leader and two followers. The leader handles create, delete, and update queries, while followers handle read queries. Data synchronization is done through WAL sync. User queries are directed through a pg-pool component." width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here &lt;a href="https://www.pgpool.net/docs/46/en/html/intro-whatis.html" rel="noopener noreferrer"&gt;pg-pool&lt;/a&gt; acts as load balancer, it distributes read request evenly among followers and mutation request to the leader. Notice that Leader periodically syncs it WAL with it’s followers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Setup StackGres and enable load balancer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;minikube addons enable metallb
minikube tunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm install stackgres-operator stackgres-charts/stackgres-operator\               --namespace stackgres-operator \
--create-namespace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Define CRD for replicated cluster
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# replication.yaml

apiVersion: stackgres.io/v1
kind: SGCluster
metadata:
  name: cluster

spec:
  instances: 3 # 1 primary + 2 replicas

  postgres:
    version: "15"

  pods:
    persistentVolume:
      size: "1Gi"

  profile: development

  postgresServices:
    primary:
      type: LoadBalancer
    replicas:
      type: LoadBalancer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Apply the CRD
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f ./replication.yaml
kubectl get pods -w
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Get credentials
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PG_PASSWORD=$(kubectl -n default get secret cluster --template '{{ printf "%s" (index .data "superuser-password" | base64decode) }}')
echo "The superuser password is: $PG_PASSWORD"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  See who is who
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -it cluster-0  -c patroni -- patronictl list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Kill the primary
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -it cluster-0  -c patroni -- patronictl list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  See who is in charge now
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://patroni.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;Patroni&lt;/a&gt; should have elected a new leader by now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -it cluster-1 -c patroni -- patronictl list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tell something only to the primary
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PRIMARY=$(kubectl exec -it cluster-1 -c patroni -- patronictl list | grep Leader | awk '{print $2}')
kubectl exec -it $PRIMARY -c patroni -- psql -U postgres -c "CREATE TABLE replication_test_table (id SERIAL PRIMARY KEY, data TEXT);"
kubectl exec -it $PRIMARY -c patroni -- psql -U postgres -c "INSERT INTO replication_test_table (data) VALUES ('Spread the word about our lord savior PostgreSQL!');"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Primary tell his followers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -it cluster-0 -c patroni -- psql -U postgres -c "SELECT * FROM replication_test_table;"
kubectl exec -it cluster-1 -c patroni -- psql -U postgres -c "SELECT * FROM replication_test_table;"
kubectl exec -it cluster-2 -c patroni -- psql -U postgres -c "SELECT * FROM replication_test_table;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see how quickly the word has spread. This is possible because StackGres uses Patroni under the hood to coordinate all the replication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partitioning
&lt;/h2&gt;

&lt;p&gt;Partitioning splits the data (table in our case) into smaller, more manageable parts. &lt;strong&gt;This is done within a single database instance.&lt;/strong&gt; Postgres supports this out of the box. It is defined in data definition layer and having multiple replicas for makes a partition highly available. It works best for time-series data, logs, or region-based segmentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Types of Partitioning
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Range Partitioning – Data is partitioned based on value ranges (e.g., date ranges).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;List Partitioning – Partitioning based on a list of values (e.g., regions or categories).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hash Partitioning – Data is distributed using a hash function (e.g., &lt;em&gt;MOD(user_id, 4)&lt;/em&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Following code create a table orders and derives three tables from it using range, list and hash based partition in a hierarchical way. Order table is split by year, year is further split into regions and region is finally split by hash.&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%2Fyx1f4l52ho0g6qnfafs0.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%2Fyx1f4l52ho0g6qnfafs0.png" alt="Image description" width="800" height="646"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice that only hash based partition grantees that all partition are of same size.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Setup StackGres and enable load balancer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm install stackgres-operator stackgres-charts/stackgres-operator \
    --namespace stackgres-operator \
    --create-namespace

minikube addons enable metallb
minikube tunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Get credentials
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PG_PASSWORD=$(kubectl -n default get secret cluster --template '{{ printf "%s" (index .data "superuser-password" | base64decode) }}')
echo "The superuser password is: $PG_PASSWORD"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Your database should now be available at &lt;code&gt;postgresql://postgres:&amp;lt;password&amp;gt;:localhost:5432&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now open an SQL Editor like &lt;a href="https://www.pgadmin.org/" rel="noopener noreferrer"&gt;pgAdmin&lt;/a&gt;, and run the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- Parent table
CREATE TABLE orders (
    order_id    INT,
    customer_id INT,
    order_date  DATE,
    region      TEXT,
    amount      INT,
    PRIMARY KEY (order_id, order_date, region, customer_id)
) PARTITION BY RANGE (order_date);


-- Range: Year 2024
CREATE TABLE orders_2024 PARTITION OF orders
    FOR VALUES FROM ('2024-01-01') TO ('2025-01-01')
    PARTITION BY LIST (region);

-- Range: Year 2025
CREATE TABLE orders_2025 PARTITION OF orders
    FOR VALUES FROM ('2025-01-01') TO ('2026-01-01')
    PARTITION BY LIST (region);

-- 2024 - US region
CREATE TABLE orders_2024_us PARTITION OF orders_2024
    FOR VALUES IN ('US')
    PARTITION BY HASH (customer_id);

-- 2024 - EU region
CREATE TABLE orders_2024_eu PARTITION OF orders_2024
    FOR VALUES IN ('EU')
    PARTITION BY HASH (customer_id);

-- 2024 - US - Hash partitions
CREATE TABLE orders_2024_us_0 PARTITION OF orders_2024_us FOR VALUES WITH (MODULUS 2, REMAINDER 0);
CREATE TABLE orders_2024_us_1 PARTITION OF orders_2024_us FOR VALUES WITH (MODULUS 2, REMAINDER 1);

-- 2024 - EU - Hash partitions
CREATE TABLE orders_2024_eu_0 PARTITION OF orders_2024_eu FOR VALUES WITH (MODULUS 2, REMAINDER 0);
CREATE TABLE orders_2024_eu_1 PARTITION OF orders_2024_eu FOR VALUES WITH (MODULUS 2, REMAINDER 1);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bulk insert synthetic data
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- Generate 1000 random orders
INSERT INTO orders (order_id, customer_id, order_date, region, amount)
SELECT 
    -- Generate order IDs between 1000 and 9999
    1000 + floor(random() * 9000)::int AS order_id,
    -- Generate customer IDs between 1000 and 9999
    1000 + floor(random() * 9000)::int AS customer_id,    
    -- Generate dates in 2024 (to fit the 2024 partition)
    DATE '2024-01-01' + (floor(random() * 366)::int * INTERVAL '1 day') AS order_date,    
    -- Randomly select region
    (ARRAY['US', 'EU'])[1 + floor(random() * 2)::int] AS region,    
    -- Generate random amounts between 10 and 1000
    10 + floor(random() * 990)::int AS amount
FROM 
    generate_series(1, 1000) AS i;            -- 1k rows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM orders
WHERE order_date = '2024-06-10'
  AND region = 'US'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Querying the orders does not require you to know the partition&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Sharding with replication
&lt;/h2&gt;

&lt;p&gt;Sharding splits a large database into small pieces called shards. Each shard is then split among multiple machines so that our database can continue to function even if we lose a few machines. Routing of queries to the proper is done by a coordinator, and just like with the replication example we will have &lt;code&gt;pg-pool&lt;/code&gt; doing load balancing within a shard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Types of sharding
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Row based: Think of it like splitting a very thick book into many volumes (shards) based on and creating a new volumes just to keep track of table of content (coordinator). Think of a table where the schema of the table is simple but amount of rows and amount write operation has gone crazy. With this method both read/write operation for every shard can scale as needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Schema based: Just like last time we are still splitting the book but this time we are taking a few chapters that are related and turning it into a book about a sub topic. Think of how a very thick physics textbook can be split into Optics, Thermodynamics, Quantum Mechanics. Think of a table to large number of columns, but you don’t need the all the columns every time a query is made. So you split the table into shards such that related columns get placed together.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2F6a38k7ruruho4x1r798r.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%2F6a38k7ruruho4x1r798r.png" alt="Image description" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice the resiliency of this architecture, not only we have multiple replicas for shards but also for the coordinator. As long as we have a minimum of 3 machines to run our sharded cluster, failure of single machine will not bring the down our database.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Setup StackGres and enable load balancer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm install stackgres-operator stackgres-charts/stackgres-operator \
    --namespace stackgres-operator \
    --create-namespace

minikube addons enable metallb
minikube tunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Define CRD for Sharded Cluster
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# shard.yaml
apiVersion: stackgres.io/v1alpha1
kind: SGShardedCluster
metadata:
  name: cluster
spec:
  type: citus
  database: mydatabase
  postgres:
    version: 'latest'
  coordinator:
    instances: 2 # Number of coordinator instances
    pods:
      persistentVolume:
        size: '1Gi'
  shards:
    clusters: 3 # Number of shards
    instancesPerCluster: 3 # 1 primary and 2 replicas
    pods:
      persistentVolume:
        size: '1Gi'
  postgresServices:
    coordinator:
      primary:
        type: LoadBalancer

  profile: development
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Apply Citus CRD
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f ./shard.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Get credentials
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PG_PASSWORD=$(kubectl -n default get secret cluster --template '{{ printf "%s" (index .data "superuser-password" | base64decode) }}')
echo "The superuser password is: $PG_PASSWORD"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Your database should now be available at &lt;code&gt;postgresql://postgres:&amp;lt;password&amp;gt;:localhost:5432&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now open a app like SQL Editor like pgAdmin, and run the following.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create some distributed table
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    name TEXT
);
SELECT create_distributed_table('users', 'id');

CREATE TABLE orders (
    id BIGINT,
    user_id BIGINT,
    product_id BIGINT,
    amount INTEGER,
    PRIMARY KEY (user_id, id)
);
SELECT create_distributed_table('orders', 'user_id');

CREATE TABLE products (
    id BIGINT PRIMARY KEY,
    name TEXT,
    price NUMERIC
);
SELECT create_reference_table('products');

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Insert some data
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO users (id, name) VALUES
(1, 'Alice'),
(2, 'Bob'),
(3, 'Charlie');

INSERT INTO orders (id, user_id, product_id, amount) VALUES
(1, 1, 1, 2),
(2, 1, 2, 3),
(3, 2, 1, 1),
(4, 3, 3, 5);
INSERT INTO products (id, name, price) VALUES
(1, 'Product A', 10.00),
(2, 'Product B', 20.00),
(3, 'Product C', 30.00);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  See how shards are spread
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM citus_shards
WHERE table_name = 'orders'::regclass;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Find which node host which shard
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
  s.shardid,
  n.nodename,
  n.nodeport
FROM pg_dist_shard s
JOIN pg_dist_shard_placement p ON s.shardid = p.shardid
JOIN pg_dist_node n ON p.nodename = n.nodename
WHERE s.logicalrelid = 'orders'::regclass;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Find which has a specific row
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT get_shard_id_for_distribution_column('orders', 1);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Join distributed-distributed (co-located)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
    o.id AS order_id,
    u.name AS customer,
    o.amount
FROM orders o
JOIN users u ON o.user_id = u.id;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is efficient because orders and users are sharded using the same key (user_id and id)&lt;/p&gt;

&lt;h3&gt;
  
  
  Join distributed-reference
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
    o.id AS order_id,
    u.name AS customer,
    p.name AS product,
    o.amount
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN products p ON o.product_id = p.id;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well because products is replicated across all nodes.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/ddl-partitioning.html" rel="noopener noreferrer"&gt;Postgres partitioning docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackgres.io/doc/1.2/reference/crd/sgcluster/" rel="noopener noreferrer"&gt;StackGres docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.citusdata.com/blog/2023/08/04/understanding-partitioning-and-sharding-in-postgres-and-citus/" rel="noopener noreferrer"&gt;Citus data&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>postgres</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
