Scaling a Transport Management System to a Fleet of 500+ Vehicles
A transport management system scales to hundreds of vehicles by separating real-time tracking data from transactional business data — live GPS positions don't belong in the same write path as dispatch, billing, and driver records. That separation is the core reason the Transport Management System (TMS) I built handles a fleet of 500+ vehicles without dispatch screens lagging or billing records getting corrupted under load.
The Problem: One System, Three Very Different Data Patterns
Logistics companies running their own fleet need three things that behave completely differently under the hood: dispatch (who's assigned to what, updated occasionally, needs strong consistency), billing (invoices and rates, needs auditability and correctness above all), and live tracking (GPS position updates, arriving constantly, tolerant of the occasional dropped update but intolerant of lag). Building all three as if they were the same kind of data — the mistake I see most often in fleet software — is what causes dispatch dashboards to freeze under load or tracking to fall minutes behind reality.
The Architecture: SignalR for Live State, MSSQL for Everything That Must Be Correct
ASP.NET Core backs the whole system, but the tracking layer runs on SignalR for real-time, push-based updates to dispatch dashboards — when a vehicle's position updates, connected dashboards receive it immediately over a persistent WebSocket connection instead of polling an API every few seconds and hammering the database with read queries that don't need to hit disk at that frequency.
MSSQL handles dispatch assignments, billing records, and driver data — the parts of the system where a wrong number is a real business problem, not just a stale map pin. Keeping GPS position streams out of this transactional store, and instead pushing them through SignalR to connected clients (with only periodic snapshots persisted for historical route playback), keeps the database's write load proportional to actual business events rather than to GPS ping frequency across 500 vehicles.
Key Features
Live fleet map — Dispatchers see every active vehicle's position update in real time on a single dashboard, without a manual refresh, using SignalR's push model instead of client-side polling.
Dispatch and assignment workflow — Jobs get assigned to drivers and vehicles with status tracking (assigned, in progress, delivered), giving dispatchers a clear queue instead of a spreadsheet of who's doing what.
Automated billing generation — Completed jobs roll up into invoices automatically based on rate rules per client or route, removing the manual invoice-building step that used to eat a meaningful chunk of the back office's week.
Driver mobile check-ins — Drivers update job status and log delivery confirmations from a mobile-friendly interface, feeding the same dispatch pipeline dispatchers see in real time.
Historical route playback — Periodic position snapshots are persisted so a specific vehicle's route on a specific day can be reviewed later — useful for both customer disputes and internal route-efficiency analysis.
The Decision That Mattered Most: Not Treating GPS Data Like Transactional Data
The single highest-leverage architectural call was refusing to write every GPS ping straight into the same relational tables as dispatch and billing records. Early fleet systems that make this mistake tend to work fine in a pilot with 10-20 vehicles and then degrade badly once real fleet-scale volume hits — hundreds of vehicles pinging every few seconds is a write volume that a transactional schema built for dispatch and billing correctness was never designed to absorb gracefully. Routing live position data through SignalR's push model, and persisting only periodic snapshots, kept the transactional database doing what it's actually good at.
Results
The system currently supports a fleet of 500+ vehicles with dispatchers seeing live position updates without the lag that plagued the client's previous polling-based tracking tool. Billing generation that used to require manual invoice assembly at the end of each billing cycle now rolls up automatically from completed job records, and historical route playback has become a routine tool for resolving delivery disputes rather than something the office dreads digging up.
FAQ
Why not just poll the database for vehicle positions every few seconds?
At small scale, polling works fine. At hundreds of vehicles, polling every client dashboard against the database multiple times a minute creates read load that scales with dashboard count times vehicle count — a push-based model like SignalR scales with actual position updates instead.
Does this architecture work for smaller fleets too?
Yes — the same pattern (SignalR for live state, relational storage for transactional data) works fine at 20 vehicles or 2,000. It's less about fleet size and more about keeping fast-moving, loss-tolerant data separate from slow-moving, correctness-critical data from day one.
What's the hardest part of building fleet dispatch software?
Usually not the tracking map — it's designing the billing and rate-rule engine to handle the real-world exceptions (partial deliveries, multi-stop routes, custom client rates) without turning into a pile of special-case code.
If you're running a fleet on spreadsheets or a tracking tool that can't keep up as you scale, get in touch — this is exactly the kind of system I build.
Found this useful? Share it with your network.
Comments
Leave a comment