DEV Community

Shrijith Venkatramana
Shrijith Venkatramana

Posted on • Edited on

Crafting a High-Performance Dashboard with PostgreSQL Caching and Golang Scheduled Routines

Hello, I'm Shrijith. I'm building git-lrc, an AI code reviewer that runs on every commit. It is free, unlimited, and source-available on Github. Star Us to help devs discover the project. Do give it a try and share your feedback for improving the product.

Dashboards are the heartbeat of data-driven apps, but slow queries can kill the vibe. Let’s build a fast, scalable dashboard using PostgreSQL caching and Golang scheduled routines to keep things snappy. This guide dives into practical steps, code, and tips to make your dashboard fly. We’ll cover setting up a PostgreSQL cache, scheduling data updates with Go, and wiring it all into a sleek dashboard. Ready to code?

Why Caching and Scheduling Matter for Dashboards

Dashboards often pull data from complex queries, and hitting the database every time a user refreshes is a recipe for lag. Caching stores precomputed results, slashing response times. Scheduled routines in Go keep that cache fresh without manual intervention. Together, they ensure your dashboard is fast and up-to-date. We’ll use PostgreSQL’s materialized views for caching and Go’s time.Ticker for scheduling.

Key benefit: Users get instant data, and your database doesn’t cry under pressure.

Setting Up Your PostgreSQL Database

First, you need a PostgreSQL database. I’m assuming you’ve got PostgreSQL installed (if not, grab it from postgresql.org). Let’s create a sample database for a sales dashboard with orders data.

-- Create database and table
CREATE DATABASE sales_dashboard;
\c sales_dashboard

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    customer_id INTEGER,
    amount DECIMAL(10, 2),
    order_date TIMESTAMP
);

-- Insert sample data
INSERT INTO orders (customer_id, amount, order_date) VALUES
(1, 99.50, '2025-07-01 10:00:00'),
(2, 150.75, '2025-07-02 12:30:00'),
(1, 45.25, '2025-07-03 09:15:00');
Enter fullscreen mode Exit fullscreen mode

Output: A sales_dashboard database with an orders table containing sample data.

This sets the stage for our cache and dashboard queries.

Creating a Materialized View for Caching

PostgreSQL’s materialized views are perfect for caching. Unlike regular views, they store query results physically, so you don’t rerun expensive queries. Let’s create a materialized view to summarize daily sales.

CREATE MATERIALIZED VIEW daily_sales_cache AS
SELECT 
    DATE(order_date) AS sale_date,
    COUNT(*) AS order_count,
    SUM(amount) AS total_amount
FROM orders
GROUP BY DATE(order_date)
WITH DATA;

-- Refresh the view (run this to update cache)
REFRESH MATERIALIZED VIEW daily_sales_cache;
Enter fullscreen mode Exit fullscreen mode

Output: A daily_sales_cache view with columns sale_date, order_count, and total_amount.

Why this rocks: Queries hit the cached view instead of scanning the entire orders table. Use REFRESH MATERIALIZED VIEW to update it when data changes. We’ll automate this later with Go.

Building the Golang Backend

Now, let’s set up a Golang backend to serve the dashboard and manage the cache. You’ll need Go installed (get it at golang.org). We’ll use the net/http package for the API and database/sql with pq for PostgreSQL.

package main

import (
    "database/sql"
    "encoding/json"
    "log"
    "net/http"

    _ "github.com/lib/pq"
)

const (
    dbHost     = "localhost"
    dbPort     = 5432
    dbUser     = "your_user"
    dbPassword = "your_password"
    dbName     = "sales_dashboard"
)

func main() {
    // Connect to PostgreSQL
    connStr := "host=" + dbHost + " port=" + string(dbPort) + " user=" + dbUser + " password=" + dbPassword + " dbname=" + dbName + " sslmode=disable"
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // API endpoint for dashboard data
    http.HandleFunc("/api/sales", func(w http.ResponseWriter, r *http.Request) {
        rows, err := db.Query("SELECT sale_date, order_count, total_amount FROM daily_sales_cache")
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        defer rows.Close()

        type Sale struct {
            Date        string  `json:"date"`
            OrderCount  int     `json:"order_count"`
            TotalAmount float64 `json:"total_amount"`
        }

        var sales []Sale
        for rows.Next() {
            var s Sale
            if err := rows.Scan(&s.Date, &s.OrderCount, &s.TotalAmount); err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            sales = append(sales, s)
        }

        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(sales)
    })

    log.Fatal(http.ListenAndServe(":8080", nil))
}
Enter fullscreen mode Exit fullscreen mode

Output: A Go server running on localhost:8080 with an /api/sales endpoint returning JSON from the daily_sales_cache view.

Pro tip: Replace your_user and your_password with your PostgreSQL credentials. Install the pq driver with go get github.com/lib/pq.

Scheduling Cache Refreshes with Go

To keep the cache fresh, we’ll use a Go routine with time.Ticker to refresh the materialized view periodically. This runs in the background, so your API stays responsive.

package main

import (
    "database/sql"
    "log"
    "time"

    _ "github.com/lib/pq"
)

func refreshCache(db *sql.DB) {
    ticker := time.NewTicker(1 * time.Hour)
    for range ticker.C {
        _, err := db.Exec("REFRESH MATERIALIZED VIEW daily_sales_cache")
        if err != nil {
            log.Printf("Error refreshing cache: %v", err)
        } else {
            log.Println("Cache refreshed successfully")
        }
    }
}

func main() {
    connStr := "host=localhost port=5432 user=your_user password=your_password dbname=sales_dashboard sslmode=disable"
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Start cache refresh routine
    go refreshCache(db)

    // Rest of the server code (same as above)
    // ...
}
Enter fullscreen mode Exit fullscreen mode

Output: Logs “Cache refreshed successfully” every hour, or an error if the refresh fails.

Why this works: The go keyword runs refreshCache in a separate goroutine, so it doesn’t block the main server. Adjust the time.Hour interval as needed.

Wiring Up a Simple Dashboard Frontend

Let’s create a basic HTML dashboard to display the data. We’ll use plain JavaScript to fetch data from the Go API and render it in a table. Host this in the same Go server by adding a file server.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Sales Dashboard</title>
    <style>
        table { border-collapse: collapse; width: 100%; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
    </style>
</head>
<body>
    <h1>Sales Dashboard</h1>
    <table id="salesTable">
        <thead>
            <tr>
                <th>Date</th>
                <th>Order Count</th>
                <th>Total Amount</th>
            </tr>
        </thead>
        <tbody id="salesBody"></tbody>
    </table>

    <script>
        fetch('/api/sales')
            .then(response => response.json())
            .then(data => {
                const tbody = document.getElementById('salesBody');
                data.forEach(sale => {
                    const row = document.createElement('tr');
                    row.innerHTML = `
                        <td>${sale.date}</td>
                        <td>${sale.order_count}</td>
                        <td>$${sale.total_amount.toFixed(2)}</td>
                    `;
                    tbody.appendChild(row);
                });
            })
            .catch(error => console.error('Error:', error));
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Output: A webpage at localhost:8080 showing a table with sales data.

Add this to your Go server:

http.Handle("/", http.FileServer(http.Dir("static")))
Enter fullscreen mode Exit fullscreen mode

Save the HTML as static/index.html. The server now serves both the API and the dashboard.

Optimizing Cache Performance

Materialized views are great, but they can bloat if your data grows. Index your materialized view to speed up queries, and use CONCURRENTLY for refreshes to avoid locking the view.

-- Add index on sale_date
CREATE INDEX idx_daily_sales_date ON daily_sales_cache (sale_date);

-- Refresh without locking
REFRESH MATERIALIZED VIEW CONCURRENTLY daily_sales_cache;
Enter fullscreen mode Exit fullscreen mode

Update the Go refresh function to use CONCURRENTLY:

_, err := db.Exec("REFRESH MATERIALIZED VIEW CONCURRENTLY daily_sales_cache")
Enter fullscreen mode Exit fullscreen mode

Why this matters: CONCURRENTLY lets users query the view during refreshes, keeping the dashboard responsive. The index speeds up queries on sale_date.

Optimization Benefit
Index on sale_date Faster filtering/sorting
CONCURRENTLY No downtime during refresh

Handling Errors and Monitoring

Things can go wrong—database outages, network issues, or query failures. Add error handling and logging to your Go code to catch issues early. Let’s enhance the refresh function with retries.

func refreshCache(db *sql.DB) {
    ticker := time.NewTicker(1 * time.Hour)
    for range ticker.C {
        for retries := 3; retries > 0; retries-- {
            _, err := db.Exec("REFRESH MATERIALIZED VIEW CONCURRENTLY daily_sales_cache")
            if err == nil {
                log.Println("Cache refreshed successfully")
                break
            }
            log.Printf("Error refreshing cache (retries left: %d): %v", retries-1, err)
            time.Sleep(5 * time.Second)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Output: Logs success or errors with retry attempts.

Pro tip: Use a monitoring tool like Prometheus to track refresh times and API response times. Check out Prometheus for setup details.

Next Steps for Scaling Your Dashboard

You’ve got a solid dashboard, but there’s room to grow. Add authentication to secure your API (try JWT). Scale the cache by partitioning the orders table for huge datasets. Enhance the frontend with a framework like React for interactivity. Finally, consider deploying to a cloud provider like AWS or Heroku, and use a connection pooler like PgBouncer for high traffic.

Next Step Why Do It
Add JWT Secure API access
Partition tables Handle massive data
Use React Dynamic UI
Deploy with PgBouncer Manage connections

This setup gives you a fast, reliable dashboard that’s ready to scale. Keep tweaking based on your app’s needs, and you’ll have users raving about the speed.

git-lrc
*AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs -- without telling you. You often find out in production.

git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.*

Any feedback or contributors are welcome! It's online, source-available, and ready for anyone to use.

⭐ Star it on GitHub:

GitHub logo HexmosTech / git-lrc

Free, Unlimited AI Code Reviews That Run on Commit

git-lrc logo

git-lrc

Free, Unlimited AI Code Reviews That Run on Commit


git-lrc - Free, unlimited AI code reviews that run on commit | Product Hunt

AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs -- without telling you. You often find out in production.

git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.

See It In Action

See git-lrc catch serious security issues such as leaked credentials, expensive cloud operations, and sensitive material in log statements

git-lrc-intro-60s.mp4

Why

  • 🤖 AI agents silently break things. Code removed. Logic changed. Edge cases gone. You won't notice until production.
  • 🔍 Catch it before it ships. AI-powered inline comments show you exactly what changed and what looks wrong.
  • 🔁 Build a habit, ship better code. Regular review → fewer bugs → more robust code → better results in your team.
  • 🔗 Why git? Git is universal. Every editor, every IDE, every AI…




Top comments (0)