liveBook · Manning

Routing

As explained in Latency

Request routing is the strategy of forwarding incoming requests to the component responsible for processing them.

Request routing forwards each incoming request to the component responsible for processing it. In partitioned systems, effective routing sends requests to the server with the fastest access to the required data; poor steering preserves partitioning but adds latency. section 5.4 section 5.0

Use routing as part of the partitioning design, while accounting for its operational trade-offs:

  • Route requests to the partition hosting the needed data to minimize communication and access latency. section 5.4
  • Balance accesses across partitions because uneven demand can undermine the benefits of independent data subsets. section 5.0
  • Choose among direct, proxy, and forward routing strategies; the routing decision adds application complexity even as partitioning reduces coordination. section 5.7 section 5.0

Node IDs and round-robin requests

A partitioned REST API can assign each server a numeric node ID and listen on port 8080 plus that ID. A partition-aware HTTP benchmark accepts the node count, builds the node URLs, and sends requests round-robin. Comparing one node with five shows lower tail latency for five nodes because requests can execute in parallel. This demonstrates routing infrastructure, but not yet a data-based partitioning strategy. section 5.6

Listing 5.1 REST API server with embedded SQLite and node ID configuration
use actix_web::{
    error::ErrorInternalServerError, web, App, Error,
    HttpServer,
};
use clap::Parser;
use rusqlite::Connection;
use std::env;
use std::sync::{Arc, Mutex};

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
    /// ID of the node.
    #[arg(short, long)]
    id: u16,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let args = Args::parse();
    let port = 8080 + args.id;
    let listen_addr = format!("127.0.0.1:{}", port);
    let database_url = env::var("DATABASE_URL")?;
    let conn = Arc::new(Mutex::new(Connection::open(
        database_url,
    )?));
    let app = move || {
        App::new()
            .app_data(web::Data::new(conn.clone()))
            .service(
                web::resource("/hello")
                    .route(web::get().to(say_hello)),
            )
    };
    Ok(HttpServer::new(app)
        .bind(listen_addr)?
        .run()
        .await?)
}

async fn say_hello(
    conn: web::Data<Arc<Mutex<Connection>>>,
) -> anyhow::Result<String, Error> {
    let conn = conn.lock().unwrap();
    let result = conn
        .query_row("SELECT 'hello world'", [], |row| {
            row.get(0)
        })
        .map_err(ErrorInternalServerError)?;
    Ok(result)
}

Inspect the Rust REST API implementation that converts a node ID into a listening port and exposes the routed service. section 5.6

How Latency covers it

How deep it goes
Defines the strategy, explains locality and balancing, and demonstrates implementation, but defers the actual data-partitioning strategy.
Examples
Demonstrates node-ID ports, a Rust REST API, round-robin HTTP benchmarking, and one-node versus five-node latency measurements.
What you can do after
Apply it via a partition-aware HTTP client that targets configured nodes and measures latency across different node counts.
How it connects
Connects request routing with partitioning, colocation, replication, horizontal partitioning, coordination, and tail latency.

Also explained in

Compare how 22 books explain routing →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Latency. The section links point to the parts of the book it draws on.