Available from May 2026 — Currently Interviewing
Back to Portfolio
Technical BlogRead time: 4 mins

How I Handled 1M Users on Laravel using SQL Server

RA
Reyyan Alam
Backend Engineer @ PomPak Project

When you're building a B2G (Business to Government) educational platform endorsed by the State Bank of Pakistan, failure is not an option. You cannot afford an error screen when entire districts of students log into the portal simultaneously at 9:00 AM on a Monday.

The scaling challenge for PomPak—a financial literacy application that quickly accrued over 1 million users and 750,000 active students—required a specialized architectural approach. The stack? Laravel/PHP running against SQL Server.

The ORM Bottleneck

Initially, the platform leaned heavily on Laravel's Eloquent ORM. For standard CRUD operations covering single users, Eloquent is incredible. But when generating complex state-wide analytics, grading millions of exam attempts, and tracking multi-tenant organization access... Eloquent started showing its limits.

We noticed query execution times drifting into the multi-second territory during peak N+1 query traps. Wait logic and transaction limits started rejecting new student signups.

The Architecture Pivot

To solve this without massive infrastructure costs, we implemented a Command Query Responsibility Segregation (CQRS) lite approach. We kept Eloquent for writes (to maintain business logic and validation) but moved complex read-aggregations to raw SQL engine primitives.

-- Moving heavy aggregation to the database layer
CREATE PROCEDURE [dbo].[GetDistrictAnalytics]
    @DistrictID Int,
    @StartDate DateTime,
    @EndDate DateTime
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        S.SchoolName,
        COUNT(DISTINCT ST.StudentID) TotalStudents,
        AVG(CAST(E.FinalScore AS FLOAT)) AveragePerformance,
        SUM(CASE WHEN E.Status = 'Certified' THEN 1 ELSE 0 END) Certifications
    FROM Schools S
    JOIN Students ST ON S.SchoolID = ST.SchoolID
    LEFT JOIN Exams E ON ST.StudentID = E.StudentID
    WHERE S.DistrictID = @DistrictID 
      AND E.CompletedAt BETWEEN @StartDate AND @EndDate
    GROUP BY S.SchoolName
    OPTION (RECOMPILE); -- Optimize for varying parameter ranges
END

The Implementation Layer

By executing these procedures directly via the DB::statement() or DB::select() facade, we bypassed the entire Eloquent object hydration cycle. Instantiating 10,000 PHP objects in memory just to get a single "Average" number was the primary cause of our memory leaks.

Combined with Redis Tags for intelligent cache invalidation, the performance gains were immediate. We could now handle 10k+ concurrent exam submissions without the CPU usage crossing 30%.

Quantifiable Results

MetricBefore (Eloquent)After (Stored Procs)
Aggregated Report Query4,200ms85ms
Peak Concurrent Capacity~1,500 Users10,000+ Users
Memory Usage / Request128MB+~12MB

The lesson remains clear: An ORM is a tool, not a religion. When you hit a million users, knowing how to write pure SQL and architecting directly on engine primitives is the difference between a system crash and a high-performance deployment.