In today’s world, the quest for ultra-fast, low-latency applications is relentless. As the demand for real-time user experiences grows, optimizing serverless applications for runtime performance is not just desirable, but essential. Enter Wizer - the WebAssembly pre-initializer that is redefining the way we build and deploy lightning-fast workloads. In this post, we’ll explore how Wizer can be leveraged to compile data directly into Spin applications, with a hands-on use-case: delivering geo location lookup from a client’s IP address.
Our stack? We use Rust for implementing the Spin application, and we’ll deploy it to Fermyon Wasm Functions — the world’s fastest serverless compute platform—running on Akamai Cloud, the planet’s largest and speediest network.
Why Wizer?
Traditional serverless and microservice architectures often grapple with the classic cold-start problem and data loading overheads. Thanks to WebAssembly’s inherent speed and the ultra-efficient, high-density runtime powering Fermyon Wasm Functions, the typical cold start dilemma simply doesn’t exist in Fermyon Wasm Functions. Applications launch instantly, making real-time responsiveness the norm.
However, when your workloads rely on external data stores, you inevitably introduce latency—every remote fetch or database query adds precious milliseconds to your compute duration and undermines that hard-won performance edge.
Traditional serverless and microservice architectures have long struggled with these data access delays. Even as Wasm shaves execution time to the bare minimum, each round trip to a database can erode the benefits of instant startup. This emphasizes the need for new approaches to data handling when striving for truly lightning-fast applications.
That’s where Wizer shines:
- Pre-initialized State: Wizer allows you to snapshot your application state—including loaded data structures—at build time. When your Wasm module gets instantiated, you’re already primed for action.
- No External Dependency
- No Latency: By baking data directly into the Wasm binary, your workload can respond to requests in record time.
Taking into context that Spin apps running on Fermyon Wasm Functions are distributed around the globe and incoming requests are routed to the closest data center, integrating data into the application binary is even more important. In a globally distributed setup like this, relying on a non-globally distributed external service (e.g., a hosted database or blob storage) will have a dramatic impact for a huge fraction of users, as their requests may travel across the globe for querying necessary data from the external service.
Example Use-Case: Lightning-Fast GeoIP Lookup
Imagine needing to determine a user’s country, city, or region based solely on their IP address. This is a common requirement for content localization, regulatory compliance, analytics, and more. The challenge? Most IP geolocation databases, like MaxMind’s GeoLite2, are massive and expensive to parse at runtime.
What if you could bake the entire geolocation database directly into your application binary, and serve geo lookups in just a few milliseconds—no database connections, no cold data fetches? With Spin and Wizer, you can!
Integrating Data Into a Spin Application
You can find the entire source code of the GeoIP-Lookup sample over on GitHub at fermyon/fwf-examples/tree/main/samples/geo-ip. We followed four steps for integrating the database into the application binary with Wizer:
- We downloaded a database compatible with the maxminddb crate and place it in the
geoip-static-db
directory of the Spin application - We defined an
init
function that loads the database and stores it in a static variable - We ran
wizer
to pre-initialize the Wasm module. Wizer executes theinit
function at build-time, snapshots the in-memory state (including the loaded geo database), and emits a pre-initialized Wasm module. - We deployed the resulting - pre-initialized - Spin application to Fermyon Wasm Functions
Under the Hood: Browsing the source code
Let’s go one layer deeper and look at the actual source code, invoked by Wizer and required to store the actual data in the Wasm binary. We’re using the maxminddb
crate for Rust allowing us to load the database and execute queries against it.
The database itself is held in memory using a static variable:
use maxminddbReader;
use std::sync::OnceLock;
static DB: OnceLock<Reader<Vec<u8>>> = OnceLock::new();
The exported init
function (which will be invoked by wizer
at build-time, is responsible for loading the MaxMind database from the disk and stores it in our static DB
variable:
#[export_name = "wizer.initialize"]
pub extern "C" fn init() {
let mut args = String::new();
std::io::stdin()
.read_line(&mut args)
.expect("failed to read stdin");
let args = args.trim().split_whitespace().collect::<Vec<_>>();
match args[..] {
[mmdb_path] => {
println!("Loading maxminddb file from {mmdb_path}");
let db = maxminddb::Reader::open_readfile(mmdb_path)
.expect("Failed to open {mmdb_path}");
DB.set(db).expect("Failed to set DB");
}
_ => {
panic!("Expected one argument: <mmdb_path>");
}
}
}
The geoip-static-db
Wasm component encapsulates data and lookup logic from the HTTP API, and exposes the lookup
interface - as defined in WIT:
package fermyon:geoip;
interface lookup {
lookup: func(ip: string) -> result<location, error>;
record location {
country: string,
city: string,
latitude: f64,
longitude: f64,
}
enum error {
invalid-ip,
not-found,
internal,
}
}
world geoip {
export lookup;
}
The implementation of the lookup
function is straightforward thanks to the maxminddb
crate. The provided IP address is parsed into the IpAddr
structure (std::net::IpAddr
), which we can use to query the city, country, longitude and latitude and create a response according to the location
record defined in WIT:
fn lookup(ip: String) -> Result<Location, Error> {
let db = DB.get().ok_or(Error::Internal)?;
let ip_addr: IpAddr = ip.parse().map_err(|_| Error::InvalidIp)?;
let record: geoip2::City = db
.lookup(ip_addr)
.map_err(|e| {
eprintln!("Error looking up IP: {e}");
Error::Internal
})?.ok_or(Error::NotFound)?;
let city = record
.city
.and_then(|city| city.names.and_then(|names| names.get("en").cloned()))
.filter(|name| !name.is_empty())
.unwrap_or_else(|| "Unknown");
let country = record
.country
.and_then(|country| country.names.and_then(|names| names.get("en").cloned()))
.filter(|name| !name.is_empty())
.unwrap_or_else(|| "Unknown");
let longitude = record
.location
.as_ref()
.and_then(|loc| loc.longitude)
.unwrap_or(0.0);
let latitude = record.location.and_then(|loc| loc.latitude).unwrap_or(0.0);
Ok(Location {
city: city.to_string(),
country: country.to_string(),
longitude,
latitude,
})
}
With the lookup
interface implemented, the HTTP API component can simply grab the client’s IP address from the true-client-ip
request header and call the lookup
function:
#[http_component]
fn handle_hello_geoip(req: Request) -> anyhow::Result<impl IntoResponse> {
let ip = req.header("true-client-ip")
.expect("true-client-ip")
.as_str()
.unwrap();
let res = fermyon::geoip::lookup::lookup(ip)?;
Ok(Response::builder()
.status(200)
.header("content-type", "text/plain")
.body(format!(
"IP: {}\\nCountry: {}\\nCity: {}\\nLatitude: {}\\nLongitude: {}",
ip, res.country, res.city, res.latitude, res.longitude
))
.build())
}
Building and Testing the Spin Application
For compiling the source code to Wasm and to perform the pre-initialization, the following tools must be installed on your machine
- Rust - Including the
wasm32-wasip1
target wizer
- You can install Wizer usingcargo install wizer --all-features
wget
- For downloading a MaxMindDB compliant databasewasm-opt
(optional) - You can installwasm-opt
usingcargo install wasm-opt
# Download a MaxMindDB compliant sample database from <https://github.com/P3TERX/GeoLite.mmdb>
wget <https://git.io/GeoLite2-City.mmdb>
# Move the database to the desired location
mv GeoLite2-City.mmdb geoip-static-db/geoip.mmdb
# Run the build script to
# - Compile the Rust source code
# - Execute Wizer
# - Optimize Wasm binary file size if wasm-opt is installed
make build
Once application compilation has finished, you can run the app on your local machine using spin up
. When sending requests to app, ensure to provide the desired IP address using the true-client-ip
HTTP header:
curl -H 'true-client-ip:46.165.131.203' localhost:3000
IP: 46.165.131.203
Country: Germany
City: Siebeldingen
Latitude: 49.2059
Longitude: 8.0524
Deploying to Fermyon Wasm Functions
For deploying the application to Fermyon Wasm Functions, you must be authenticated. (spin aka login
). Once authenticated, deployment is as easy as executing the spin aka deploy
command and waiting for the app to be deployed across all service regions:
spin aka deploy --create-name geoip-lookup --no-confirm
Workspace linked to app geoip-lookup
Waiting for app to be ready... ready
App Routes:
- geoip-example: <https://86d3582b-a318-431f-bc7a-e26a2350137b.aka.fermyon.tech> (wildcard)
Once app deployment has been finished you can start sending requests to the generated endpoint and see how fast our Spin app is able to locate you, based on your IP address:
curl <https://86d3582b-a318-431f-bc7a-e26a2350137b.aka.fermyon.tech>
IP: 46.165.131.203
Country: Germany
City: Siebeldingen
Latitude: 49.2059
Longitude: 8.0524
time curl <https://86d3582b-a318-431f-bc7a-e26a2350137b.aka.fermyon.tech>
IP: 46.165.131.203
Country: Germany
City: Siebeldingen
Latitude: 49.2059
Longitude: 8.0524
curl <https://86d3582b-a318-431f-bc7a-e26a2350137b.aka.fermyon.tech> 0.01s user 0.01s system 4% cpu 0.083 total
See the output from time curl ...
indicating that the end-to-end execution time is as little as 83ms! But we can get more insights. Just sent another curl
request to the app and provide the -i
flag to print all response headers to stdout
:
curl -i <https://86d3582b-a318-431f-bc7a-e26a2350137b.aka.fermyon.tech>
HTTP/1.1 200 OK
Content-Type: text/plain
x-envoy-upstream-service-time: 1
Server: envoy
Date: Fri, 04 Jul 2025 11:32:33 GMT
Content-Length: 90
Connection: keep-alive
IP: 46.165.131.203
Country: Germany
City: Siebeldingen
Latitude: 49.2059
Longitude: 8.0524
Find the x-envoy-upstream-service-time
HTTP header, indicating the pure compute time within Fermyon Wasm Functions is below 1ms.
Conclusion
By combining the Wasm pre-initialization with Wizer, Spin and the incredible speed provided by Fermyon Wasm Functions, you can deliver serverless applications with performance once thought impossible. Whether you’re building geo-aware content, compliance solutions, or real-time request processing, this stack lets you optimize for pure speed.