← Back
Home › Systems › Transport, logistics and mobility

car-rental

rental reservation/fleet platforms, mostly internal

Enterprise, Hertz, Avis Budget; peer-to-peer Turo

Open the car-rental pagecar-rental Sharded countersconcurrency: Split one hot counter into per-core or per-thread cells on separate cache lines; increments stay local and reads sum the cells.›Interval treedata-structures: Index of ranges that answers "which intervals overlap this point or range?" in O(log n + k); static sorted-array form is the compact, cache-friendly choice.›Leasesdistributed: A lock that expires on its own: bounded-time ownership that survives crashes and partitions, given bounded clock drift and fencing tokens on writes.›Saga patterndistributed: Split a cross-service operation into local transactions, each paired with an undo; on failure run the undos in reverse, or retry forward.›Can bus framingembedded: CAN / CAN FD frames, bitwise arbitration, bit stuffing, CRC and error confinementIdempotency keysfinance: Client-chosen unique key per logical operation, stored with a request fingerprint and the final response, so retries replay the first result instead of acting twice.›Inventory reservationfinance: Reserving stock with TTL holds, release on timeout and no overselling under contentionMoney representationfinance: Store money as integer minor units with an explicit currency exponent, compute in wider integers, and round once with a named rounding mode.›Payment state machinefinance: Persisted state machine for a payment lifecycle (authorize, capture, void, refund) that treats timeouts as unknown, retries with the same key and applies webhooks idempotently.›Revenue management emsrfinance: EMSR-b booking limits and bid prices for perishable capacity (seats, rooms)Geodesic distance (haversine,Vincenty, Karney)geometry: Shortest-path distance between two lat/lon points on the Earth: haversine on a sphere (about 0.5% error), Vincenty or Karney on the WGS84 ellipsoid (sub-millimetre).›Point in polygongeometry: Ray-casting/winding tests and indexed polygon lookup for reverse geocodingMin cost flowgraphs: Minimum-cost flow (successive shortest paths, network simplex) for distribution and rebalancingGradient-boosted decision trees(GBDT)machine-learning: Add small regression trees one at a time, each fitted to the loss gradient; histogram splits over 256 bins make it the strongest tabular default.›Linear and logistic regressionmachine-learning: Fit a weighted sum of features to a number (least squares) or a probability (logistic); the baseline every model must beat.›Object detectionmachine-learning: A network turns an image into scored, classified boxes in one pass; anchor-based, anchor-free and set-prediction heads differ in how they assign labels and remove duplicates.›Mqtt protocolnetworking: MQTT pub/sub: topics, QoS 0/1/2, retained messages, sessions and keepaliveSimplex methodoptimization: Solves linear programs by walking along the edges of the feasible polytope from vertex to vertex, one basis swap (pivot) at a time; the workhorse behind MIP solvers.›Ble gattsignal-processing: Bluetooth LE link layer, ATT/GATT services and characteristics, connection intervals and pairingTime-series forecasting(baselines, model families, ba…statistics: The end-to-end recipe: fix horizon and uncertainty, beat naive baselines, choose a model family, and validate with rolling-origin backtests and scale-free metrics.›Optimistic concurrency controlstorage: Run transactions without locks, record what they read, and validate at commit that nothing changed; fastest when conflicts are rare.›Calendar date arithmeticsystems: Civil dates, time zones (tzdb), DST gaps, month-end anchoring and duration mathFalse sharingconcurrency: Independent variables written by different cores on one cache line force the line to bounce between cores; pad or regroup hot data to fix it.›Memory orderingconcurrency: What Relaxed, Acquire, Release and SeqCst atomics actually guarantee, what they cost on x86 and ARM, and which one each pattern needs.›Fencing tokensdistributed: A number that grows with every lock grant and is checked by the protected resource, so a paused ex-holder's late write is rejected.›Durable job queuedistributed: Persisted tasks that workers lease for a visibility timeout, ack on success and retry with backoff; at-least-once execution that survives worker and queue crashes.›Transactional outboxdistributed: Write the event as a row in the same local transaction as the state change; a relay publishes it later at-least-once, so state and events never diverge.›Cryptographic hashes (SHA-2,BLAKE3)security: Collision-resistant fingerprints of data; SHA-256 for interoperability, BLAKE3 for speed, HMAC/HKDF for keyed uses.›Conditional writesdistributed: Put-if-absent / if-match compare-and-swap on objects or keys as a commit primitiveHierarchical timing wheelsystems: Timers bucketed by expiry into wheels of slots, coarser wheels for far deadlines: O(1) insert and cancel for millions of timeouts.›Exponential backoff with jittersystems: Retry after delays that grow geometrically and are randomized, so failed clients spread out instead of hammering a recovering service in synchronized waves.›Largest remainder roundingfinance: Splitting an amount into parts that sum exactly (largest remainder / Hamilton) for allocations, splits and prorationsEvent sourcingfinance: Persist state changes as an append-only stream of immutable events per entity; current state is a fold over them, guarded by expected-version appends.›Hierarchical state machinesystems: Nest states so a substate inherits its parent's reactions and only overrides what differs, with ordered entry and exit actions along the path.›+ 19 moreCPU caches and the memoryhierarchyhardware-os: Sizes, line widths and latencies of L1/L2/L3/DRAM on modern x86 and Apple M-series, and the data-layout rules that follow from them.›Dead-letter queuedistributed: A side queue for messages that keep failing: after N delivery attempts or a permanent error, park them with the reason so the main queue keeps moving.›Write-ahead logstorage: Append each change to a checksummed sequential log and fsync it before touching data pages; the basis of crash durability in nearly every database.›Event deduplication windowdistributed: Keep the ids of recently accepted events for a time window and drop repeats; memory stays bounded by rotating time buckets instead of per-entry timers.›Change data capturestorage: Decode the database's own commit log into row-change events in commit order, with a resume position, so any consumer can mirror the data exactly.›Slab allocatorsystems: Pre-carve pages into equal-size slots per size class with an in-place free list: O(1) alloc and free, no external fragmentation, cache-warm reuse.›PRNGs (PCG, xoshiro)numerics: Small, fast, statistically strong non-cryptographic generators: PCG32 and xoshiro256**, with seeding, parallel streams and unbiased ranges.›+ 4 moreCRC32C checksumshardware-os: The Castagnoli CRC-32 with dedicated instructions on x86 (SSE4.2) and ARMv8. It is the standard fast integrity check for storage blocks, logs and network frames.›fsync, O_DSYNC and O_DIRECThardware-os: What fsync, fdatasync, O_DSYNC, O_DIRECT and F_FULLFSYNC really guarantee on Linux and macOS, and the exact call sequences for crash-safe files.›Consumer offsetsdistributed: Each consumer group stores one integer per partition, the next position to read, so a restarted consumer resumes where it left off with at-least-once delivery.›Incremental sync cursorsdistributed: The client keeps one opaque token; each pull returns only the changes after it plus the next token, so sync cost scales with what changed, not with the dataset.›Message sequence numbersdistributed: A per-stream counter on every message lets the receiver detect loss, duplicates and reordering, ack cheaply and resume after reconnect with "send from N".›+ 3 moreSIMD programminghardware-os: Process 4-16 lanes per instruction with AVX2, AVX-512 or NEON via Rust std::arch, with runtime dispatch and a portable fallback.›Segmented logdistributed: Split an append-only log into files named by their first offset, write only the newest, and find any record by binary search plus a sparse index.›Cursor paginationnetworking: Keyset/opaque-cursor pagination for stable incremental reads from APIs and tables

How it is built

Architecture overview

A car rental platform sells a perishable resource: a vehicle for a time interval at a branch, in a class (compact, SUV, van) rather than as a specific car, until pickup. It has to answer "what is available and at what price" in a fraction of a second for many searches, commit a reservation without selling the same capacity twice, move cars between branches to where demand will be, and handle the physical event of pickup, use and return, with money held, charged and refunded along the way. The idea that makes it work is a split between state that must be exact (inventory per class and interval, payment state, the rental agreement), which sits behind a small transactional core with idempotent operations and explicit state machines, and estimates that may be wrong (demand forecasts, prices, rebalancing plans), which are computed offline or in the background and handed to the core as numbers. Everything else (partner feeds, notifications, accounting, telematics) is driven by events published from the core, never by direct writes into it.

Parts:

 customer / broker / partner feed
        │ search, quote, book                     nightly                  telematics (CAN, BLE, MQTT)
        ▼                                            │                              │
  ┌─────────────┐ reads  ┌───────────────┐   ┌──────▼───────┐               ┌──────▼───────┐
  │ search /    │───────►│ availability  │◄──│ pricing +    │◄── forecasts ─│ fleet state  │
  │ quote API   │        │ + holds (OCC) │   │ revenue mgmt │               │ + positions  │
  └──────┬──────┘        └───────┬───────┘   └──────────────┘               └──────┬───────┘
         │ book (idempotent)     │ commit                                          │
         ▼                       ▼                                                 ▼
  ┌──────────────────────────────────────────┐   outbox / CDC   ┌───────────────────────────┐
  │ rental agreement + payment state machine │─────────────────►│ queue → billing, notify,  │
  │ (events, sagas, deposits, refunds)       │                  │ partner sync, accounting  │
  └──────────────────────────────────────────┘                  └───────────────────────────┘
         ▲ pickup / return at branch (agent app, photos, damage check)

The platform has two modes. The commerce path (search, quote, book) is latency-bound and read-heavy with a short, strict write. The operations path (pickup, return, cleaning, transfers) is slower, driven by people and vehicles, and creates the events that close the loop with the financial path.

How the parts fit together

Availability and reservation

The unit of capacity is "cars of class C at branch B", and a reservation occupies it for an interval. The count for any instant is the number of cars (fleet minus out of service) minus the reservations overlapping that instant. An Interval tree answers "which reservations overlap this window" for a branch; the usual production alternative is a per-day or per-hour counter row per class, which is cheaper to update and read. Which one wins depends on how long rentals are and how fine the time grid is: counters fit rentals counted in days, intervals fit hour-level pricing and one-way trips.

The commit step must not oversell. Two designs work. Optimistic concurrency control reads the counters, computes the new value and writes with a version check; a conflicting booking retries. conditional-writes (no page yet) is the same idea on a key-value store, and the hold lives with an expiry so an abandoned checkout returns its capacity (a Hierarchical timing wheel is the in-memory structure for many expirations; a TTL column and a sweeper is the simple version). Hot rows are the problem: a small branch with few cars of one class at peak is contended by definition, and Sharded counters only help when overselling by a small bound is allowed, which for cars is a business rule (overbooking is deliberate, see Pricing) and not an accident.

Every booking call carries an Idempotency key: a client that times out retries the same request and gets the same reservation back. Dates and times need one rule set (calendar-date-arithmetic (no page yet)): the rental period is in the pickup branch's local time zone, a "day" is 24 hours from pickup on most price lists, and a DST change inside the interval must not change the price by accident.

Demand forecasting

Fleet and price decisions look ahead by days to weeks, per branch, class and length. Time-series forecasting is the recipe: set the horizon, beat a seasonal-naive baseline, and validate by rolling-origin cross-validation. Exponential smoothing and STL decomposition handle weekly and yearly seasonality per series cheaply; arima (no page yet) is the classical alternative. Many small branches have intermittent demand for rare classes, where croston-method (no page yet) fits better than smoothing. Gradient-boosted trees take features (day of week, holiday, local events, flight arrivals, lead time, current bookings on hand) and produce a forecast and quantiles of it; Linear and logistic regression is the baseline for "probability a booking is cancelled or does not show". Observations of the pace of bookings can be filtered with a Kalman filter. Since reservations are censored (the sold-out class hides demand), use Robust statistics and unconstrain demand before fitting. The contract with pricing: each forecast is a distribution (mean and a spread), not a point.

Pricing and revenue management

The price list is a function of class, length of rental, lead time and utilisation. revenue-management-emsr (no page yet) is the classic way to set booking limits when capacity is shared by fare classes: the EMSR heuristic by Belobaba is cited below, and the National Car Rental case study in the sources is the car-rental application. The output is a bid price or a booking limit per class and length: a booking below the bid price is declined or priced up because that capacity is expected to sell for more. When demand for one-day and multi-day rentals competes for the same cars, the allocation is an LP; Linear programming (simplex) solves it, and its dual values are the bid prices. Dynamic pricing covers the price-response side, how a price moves with remaining capacity and demand. Evaluate by Evaluation metrics of forecast error and by holdout revenue, not by model loss alone.

Fleet allocation

Cars drift: one-way rentals and airport demand leave some branches empty and others full. Repositioning is a minimum-cost flow problem (min-cost-flow (no page yet)): branches are nodes with supply (surplus cars) and demand (shortfall from the forecast), edges are transfer lanes with cost (driver, transporter, opportunity), and the solution is the cheapest set of moves that meets demand. The shortest-path building blocks are Dijkstra with a d-ary heap when costs are non-negative and bellman-ford-negative-cycles (no page yet) when the residual graph has negative edges. Geodesic distance gives the first estimate of lane length, and point-in-polygon (no page yet) decides which branch or geofence a telematics ping belongs to. Alternatives: the same problem as a general LP with Linear programming (simplex), or interior-point solvers for very large networks.

Rental agreement and payments

A rental is a long-lived state machine: quoted, held, confirmed, picked up, returned, closed, and the side branches cancelled, no-show, extended, disputed. Hierarchical state machine keeps that tractable (a "picked up" state with substates for "inspected" and "fuel surcharge applied"), and Event sourcing records each transition, which gives an audit trail for disputes and the events the other services consume. Payment has its own life: Payment state machine authorises a deposit at pickup, captures the rental charge at return, and voids or refunds the rest; a gateway timeout is "unknown", not "failed", and is resolved by querying, never by charging again.

Booking across services (inventory, payment, partner confirmation, notification) is a Saga pattern: each step has a compensation (release the hold, void the authorisation), and a failure runs them in reverse. Retries to external gateways use Exponential backoff with jitter. Money is integer minor units (Money representation), tax and fees are split so the parts sum exactly (largest-remainder-rounding (no page yet)), and Floating-point error is why no price touches a binary float except in a forecast.

Events and integration

Other systems (accounting, e-mail, brokers, loyalty) need to learn about every state change, exactly once in effect. The Transactional outbox writes the event in the same local transaction as the state change; a relay publishes it. When the system cannot be changed to write the outbox, Change data capture reads the database log instead. A Durable job queue runs the slow work with leases and retries and parks poison messages in a Dead-letter queue. Consumers are idempotent (Event deduplication) because delivery is at least once, they keep their position in Consumer offsets, and per-stream Message sequence numbers show gaps. Partners that sync inventory and reservations pull deltas with Incremental sync cursors over cursor-pagination (no page yet). When only one worker may run a job (nightly repricing), a Lease with Fencing tokens stops a paused ex-owner from writing late. Underneath, the stores rely on a Write-ahead log and a Segmented log for the event stream; CRC32C checksums protect log frames.

Vehicle connectivity

A connected car reports location, odometer, fuel or charge level, and door and ignition state. In-vehicle signals live on a can-bus-framing (no page yet) bus; a telematics unit gateways them to the cloud over mqtt-protocol (no page yet), which gives QoS levels and a session that survives reconnects. Keyless access uses ble-gatt (no page yet) between phone and car, with keys signed and checked with hashes and MACs (Cryptographic hashes). GPS fixes are noisy, so a Kalman filter smooths position; Geodesic distance turns positions into trip distance and geofence distance. The contract with the core: device messages arrive out of order and duplicated, so they carry a device timestamp and a sequence number, and only the newest per car is applied.

Condition inspection

Damage disputes are costly. A pickup and return photo set per car can be compared by an Object detection model (dents, scratches, glass), whose body is a Convolutional network, whose duplicate boxes are removed by Non-maximum suppression, and which is trained with Data augmentation because lighting and angle vary. A review step by a person is the default for any charge above a threshold, so precision on "new damage" matters more than recall; see Evaluation metrics.

Data and control flow

One booking.

  1. Search reads the price list, the forecast-derived bid prices and approximate availability from a cache; no write.
  2. The customer picks a quote; the API creates a hold on the class and interval (conditional write with a version check) and returns a hold id with an expiry.
  3. The payment step authorises the card with an idempotency key; the payment state machine moves to authorised.
  4. The reservation commits: the agreement changes from held to confirmed, the counter decrement and the outbox row are written in one local transaction.
  5. The relay publishes "reservation confirmed"; consumers send the e-mail, update the partner feed and post the accounting entry. A consumer failure retries and finally lands in the dead-letter queue.
  6. If any step fails, the saga runs the compensations: void the authorisation, release the hold.

One rental.

  1. At pickup the agent (or the keyless flow) assigns a specific car of the booked class; its odometer, fuel and photos are stored, and a deposit is authorised.
  2. During the rental, telematics events update position and mileage; an extension request reprices from the current time and re-checks availability.
  3. At return, the system reads mileage, fuel and photos, runs the damage comparison, and computes the final charge from the price list and the extras.
  4. Capture the rental charge, release the rest of the deposit, close the agreement; the car goes to cleaning and then back into availability.

One nightly planning cycle.

  1. Load bookings and returns, refit or update forecasts per branch and class.
  2. Compute bid prices and booking limits from the forecast distribution and the LP.
  3. Solve the repositioning flow and create transfer tasks for the branches.
  4. Publish new prices and limits; the commerce path picks them up from a cache or table, not from the job.

Scaling

  1. Contention on small capacity is the first limit, not request volume: popular classes at one airport branch on one weekend. Partition the inventory by branch and class (so a conflict stays local), shorten the hold window, and keep the commit as a single conditional write.
  2. Search fan-out comes next: many searches per booking. Cache prices and approximate availability, recompute on events, never on the request.
  3. Forecasting and optimisation compute grows with branches times classes times lengths; run it as a nightly job, per branch in parallel, and keep the model simple (smoothing before trees).
  4. Event volume from telematics is the largest stream; sample or aggregate at the edge, keep only the newest position per car online, and log the rest to a segmented log for analytics.
  5. Partner and broker sync scales by pull cursors and rate limits, not push to every partner.

Failure modes

FailureSymptomCauseFix
Double bookingtwo customers at the counter for one carread-then-write without version check, or retried request without idempotency keyconditional write on the counter, idempotency key per booking
Leaked holdsclass shows sold out with cars in the lothold never expired because the sweeper or the wheel stoppedTTL on the hold row, monitor holds older than the expiry
Duplicate chargecustomer charged twicegateway timeout treated as failure and retried with a new keytimeouts as unknown, query before retry, one key per operation
Price mismatchquote differs from the invoiceprice list or tax rules changed between quote and charge, or floatsstore the quote with its rule version, integer money
Time-zone or DST errorrental day count off by onelocal time and UTC mixed, DST shift inside the rentalrental period in branch-local time, tests across DST changes
Branch imbalanceempty branch, full neighbourno repositioning, forecast bias on one-way demandmin-cost flow plan, include one-way flows in the forecast
Forecast on censored datademand underestimated for sold-out dayssold-out classes hide unmet demandunconstrain demand, track turn-aways
Lost or duplicated eventpartner and system disagree on stateoutbox missing or consumer not idempotentoutbox in the same transaction, dedupe, sequence numbers
Stale telematicscar shows at the wrong placeout-of-order messages applied as newestdevice timestamp and sequence number, apply only newer
False damage claimcustomer dispute, refunddetector confused by dirt, reflections or angleperson review above a threshold, photo guidance at capture

Real implementations

Large rental companies (Enterprise, Hertz, Avis Budget) and peer-to-peer platforms (Turo) run mostly internal, closed systems. The open projects below implement individual parts:

namewhat it isrepolicencenotes
Debeziumchange data capture from database logsdebezium/debeziumApache-2.0outbox/CDC relay
Temporaldurable workflow enginetemporalio/temporalMITsagas with compensation
Kill Billopen-source subscription billing and payments platformkillbill/killbillApache-2.0invoicing and payment plug-ins
statsforecaststatistical and econometric forecasting modelsNixtla/statsforecastApache-2.0smoothing, ARIMA, intermittent demand
OR-Toolsoperations research solvers (routing, LP, flow)google/or-toolsApache-2.0min-cost flow and LP for repositioning
TraccarGPS tracking servertraccar/traccarApache-2.0device protocols, positions, geofences
Fleetbasemodular logistics and supply chain platformfleetbase/fleetbaseAGPL-3.0fleet operations, not rental specific

Sources

  • J. Geraghty and M. Johnson, Revenue Management Saves National Car Rental, Interfaces 27(1), 1997. paper.
  • P. Belobaba, Application of a Probabilistic Decision Model to Airline Seat Inventory Control, Operations Research 37(2), 1989: EMSR for nested booking limits. paper.
  • H. Garcia-Molina and K. Salem, Sagas, SIGMOD 1987. paper.
  • Repositories in the table above (licences from the GitHub API).

Similar systems

Share the most building blocks with this one.