Skip to main content

Overview

Mueve’s exchange rate system is built on a multi-layered architecture that ensures high availability, performance, and data persistence. The system uses Redis for real-time caching and MySQL for historical persistence, with intelligent fallback mechanisms to guarantee service continuity.

Architecture Components

The rate management system consists of three main components:
1

Redis Cache Layer

Primary source for real-time exchange rates with sub-millisecond access times
2

MySQL Persistence Layer

Long-term storage for historical rates and fallback data
3

Automatic Persistence Service

Background service that intelligently syncs rates from Redis to MySQL

Rate Retrieval

When the API retrieves the current USD to Bolivares exchange rate, it follows a two-tier fallback strategy:
export async function getBTC() {
  try {
    const value = await redis.get("btc:value");
    if (value) {
      const n = parseFloat(value);
      if (!Number.isNaN(n)) return n;
    }
  } catch (err) {
    console.error("Redis get error:", err);
  }

  // Fallback to latest value from DB
  try {
    const latest = await rateModel.getLatestRate();
    if (latest && latest.price != null) return latest.price;
  } catch (err) {
    console.error("DB fallback error:", err);
  }

  return "Dato no disponible";
}
The fallback mechanism ensures that even if Redis is temporarily unavailable, the API can continue serving exchange rates using the most recent persisted value from MySQL.

Rate Persistence Strategy

The persistence service runs in the background and intelligently decides when to save rates to MySQL based on two conditions:

Persistence Triggers

Rates are persisted when the price changes by at least 0.2% (0.002) relative to the last saved rate. This prevents excessive database writes for minor fluctuations.
const relDelta = absDelta / Math.max(0.0000001, lastPrice);
if (relDelta >= minDelta) shouldInsert = true;
Even if the rate hasn’t changed significantly, rates are persisted at least once per hour (3600 seconds) to maintain a continuous historical record.
const ageSeconds = (now - new Date(latest.created_at)) / 1000;
if (ageSeconds >= maxAgeSeconds) shouldInsert = true;

Configuration Parameters

The persistence service accepts configurable parameters:
intervalSeconds
number
default:"300"
How often the service checks if rates should be persisted (in seconds). Default is 5 minutes.
minDelta
number
default:"0.002"
Minimum relative change (0.2%) required to trigger persistence.
maxAgeSeconds
number
default:"3600"
Maximum time (1 hour) before forcing a persistence operation regardless of price change.
export async function startRatePersistence(options = {}) {
  const toNumber = (v, def) => {
    const n = Number(v);
    return Number.isFinite(n) ? n : def;
  };

  let intervalSeconds = toNumber(
    options.intervalSeconds ?? process.env.RATE_PERSIST_INTERVAL_SECONDS, 
    300
  );
  
  let minDelta = toNumber(
    options.minDelta ?? process.env.RATE_PERSIST_MIN_DELTA, 
    0.002
  );
  
  let maxAgeSeconds = toNumber(
    options.maxAgeSeconds ?? process.env.RATE_PERSIST_MAX_AGE_SECONDS, 
    3600
  );
}

Rate Validation and Tolerance

When users initiate transactions, the API validates that the rate they’re using is still current. This prevents issues where users might complete a transaction with an outdated rate.

Tolerance Mechanism

Mueve implements a ±0.35 tolerance window for rate validation:
const rateInRedis = await getBTC();
const rateSentByUser = parseFloat(rate_bs);

const tolerance = 0.35;
const diff = Math.abs(rateInRedis - rateSentByUser);

if (diff > tolerance) {
  return res.status(400).json({
    message:
      "La tasa ha cambiado. Por favor actualiza la página para continuar.",
  });
}
If the difference between the user’s rate and the current rate exceeds 0.35, the transaction is rejected and the user must refresh to get the updated rate.

Database Schema

Rates are stored in MySQL with the following structure:
CREATE TABLE IF NOT EXISTS rates (
  id INT AUTO_INCREMENT PRIMARY KEY,
  price DECIMAL(18,8) NOT NULL,
  source VARCHAR(64) DEFAULT 'unknown',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
id
integer
Auto-incrementing primary key
price
decimal(18,8)
Exchange rate with 8 decimal precision
source
string
Origin of the rate data (typically “redis”)
created_at
timestamp
When the rate was persisted to the database

High Availability Design

The multi-layered architecture provides several availability benefits:
Redis Primary: Ultra-fast access for 99.9% of requests with sub-millisecond latency.
MySQL Fallback: Automatic failover to database if Redis is unavailable, ensuring continuous service.
Historical Data: Complete audit trail of rate changes for compliance and analysis.
The system can tolerate temporary Redis outages without impacting transaction processing, as long as MySQL contains recent rate data.

Best Practices

When integrating with the rate system:
  1. Always validate rates before finalizing transactions using the tolerance mechanism
  2. Cache rates client-side for short periods (30-60 seconds) to reduce API calls
  3. Handle rate change errors gracefully by prompting users to refresh
  4. Monitor rate freshness by checking the created_at timestamp from the database
  5. Never hardcode rates - always fetch from the API in real-time
  • Transactions - Learn how rates are applied to buy and sell transactions
  • Commissions - Understand how commission fees are calculated
  • API Reference - Get current exchange rate endpoint

Build docs developers (and LLMs) love