ENGINEERING WORK 01 / LARAVEL CRM PERFORMANCE

~17 seconds to ~1 second without rewriting the workflow.

The visible screen stayed familiar. The data path underneath it changed substantially.

PAGE LOAD ~17s → ~1s same user workflow
01
CONTEXT

A mature CRM grid had become slow enough to disrupt normal work.

The original path leaned on large database views, derived fields and relationship-heavy retrieval. Those abstractions were convenient when the system was smaller, but the request eventually accumulated several seconds of database work before PHP could even render the grid.

In the observed slow path, one counter/view branch consumed roughly seven seconds and another relationship-heavy branch around hotels consumed roughly nine seconds. The target was therefore not “optimize Blade.” The expensive work happened earlier.

The requirement was strict: preserve the existing filters, default sorting, result shape and workflow. Users should experience a faster screen, not a redesigned feature.

BEFORE / EXPENSIVE PATH
Grid request
Large vi_* database views
Broad joins + derived fields
Hydrate more than grid needs
→
AFTER / FOCUSED PATH
Grid request
Direct application model query
Focused relationships + counters
Existing grid contract
02
CODE / RETRIEVAL SHAPE

Stop asking one giant view to answer every question on the screen.

The implementation moved the main result set back to the application models and kept expensive counters as separate focused work. Existing filter logic was reused rather than reimplemented.

SIMPLIFIED / ANONYMIZED EXCERPTFocused grid retrieval
public function getGridData(array $filters)
{
    $query = Record::query()
        ->select([
            'record_id',
            'title',
            'start_at',
            'end_at',
            'status_id',
        ])
        ->with([
            'client',
            'owner',
            'membersSummary',
        ]);

    $this->applyRecordTypeFilter($query, $filters['type'] ?? null);

    $records = $query
        ->orderBy($this->defaultSortColumn(), 'desc')
        ->get();

    $counters = $this->loadGridCounters(
        $records->pluck('record_id')
    );

    return $this->mergeGridCounters($records, $counters);
}

The excerpt is intentionally simplified and all domain-specific identifiers are generalized. The important implementation decision is real: preserve the caller contract, reuse existing filter behavior, and stop making the broad view carry unrelated aggregation work.

03
RISK CONTROL

The old path stayed available while the new one proved itself.

Performance rewrites are easy to celebrate and easy to get subtly wrong. The legacy retrieval method was retained during transition so output and behavior could be compared and rollback remained cheap.

01

Kept existing type/filter logic rather than creating a parallel interpretation of the business rules.

02

Preserved default sorting and the caller-visible result contract.

03

Moved the grid away from the view-heavy retrieval path.

04

Used model relationships where they were a clearer source of truth.

05

Separated counters from broad grid retrieval rather than forcing one query to do everything.

06

Kept the previous method available during transition to reduce deployment risk.

RESULT

The same business screen became roughly an order of magnitude faster.

BEFORE~17stypical slow load
→
AFTER~1ssame workflow
ABOUT THIS ENGINEERING EXAMPLE

This example is based on production Laravel performance work performed outside a named NoRewrite client engagement. Employer/client identifiers, model names, field names and method names are removed or generalized. Timings and the architectural change are retained to show the diagnostic and implementation method.