I Replaced SQLite with a Rust Database in My AI Robot — Here's What Happened

rust dev.to

I used SQLite for everything.

For years, it was my default answer to "where do I store stuff." Config files, sensor logs, user data, even model outputs — SQLite handled it. When I started building an AI-powered robot that needed on-device memory, the choice felt obvious: embed SQLite, done.

Three weeks later, the robot's memory was a mess of incompatible schemas, I was writing custom serialization code for every data type, and I'd accumulated roughly 400 lines of glue code just to store and retrieve things that weren't text or numbers. Image embeddings. Audio fingerprints. Sensor state vectors.

That's when I started questioning whether SQLite was actually the right tool for this problem, or just the familiar one.

What AI Agents Actually Need to Remember

Here's the thing about robot memory that isn't obvious until you're building it: AI agents don't remember rows of data. They remember moments — multimodal snapshots of state.

A moment might be:

  • A 512-dimensional face embedding from the camera
  • A timestamp
  • An associated audio clip (4KB PCM)
  • A confidence score
  • A label ("this is the owner")

In SQLite, storing this requires: a TEXT field for the embedding (serialized JSON or base64), a BLOB for audio, two REAL fields, and a TEXT field. Then on retrieval, you deserialize everything back. For one record, fine. For 10,000 records when you need to find the 5 most similar faces to the one in the current frame? Now you're loading blobs you don't need, deserializing vectors you'll immediately re-encode for comparison, and doing similarity math in Python one row at a time.

I was making SQLite solve a problem it wasn't designed for.

The Real Performance Problem

Let me share the actual numbers from my setup (Raspberry Pi 5, 8GB RAM):

SQLite approach:

  • Store a face embedding (512-dim float32 array): ~2.1ms avg
  • Find top-5 similar faces in corpus of 1,000 records: ~340ms (full scan + Python cosine similarity)
  • RAM overhead for 1,000 embeddings loaded for comparison: ~180MB

The problem: 340ms for recognition is perceptible. A robot that pauses for a third of a second to recognize someone it's seen before feels broken. And 180MB just for embeddings in a device with 8GB — fine now, but what happens at 10,000 records?

The bottleneck wasn't SQLite being slow at SQL. It was SQLite being the wrong abstraction for vector similarity search.

Enter moteDB

I started working on moteDB after hitting this wall. The design goal was narrow: an embedded database written in Rust that handles multimodal data natively and makes vector search a first-class operation, specifically on edge/IoT hardware.

The core difference from SQLite is the data model. Instead of tables and rows, moteDB stores fragments — typed data units that can be embeddings, blobs, scalars, or structured records. A "memory" is a collection of fragments with a timestamp and context metadata. Vector search operates on embedding fragments directly without deserialization.

# Cargo.toml
[dependencies]
motedb = "0.1.6"
Enter fullscreen mode Exit fullscreen mode
use motedb::{Mote, Fragment, VecQuery};

// Store a face recognition moment
let mote = Mote::new()
    .fragment(Fragment::embedding("face", &embedding_512d))
    .fragment(Fragment::blob("audio_clip", audio_bytes))
    .fragment(Fragment::scalar("confidence", 0.94))
    .label("owner");

db.insert(mote)?;

// Find top-5 similar faces
let results = db.search(VecQuery::new("face", &current_embedding).top_k(5))?;
Enter fullscreen mode Exit fullscreen mode

Same logical operation. No serialization. No schema definition. No glue code.

The Numbers After Switching

Running the same Raspberry Pi 5 benchmarks:

moteDB approach:

  • Store a face embedding: ~0.3ms avg
  • Find top-5 similar in 1,000 records: ~8ms
  • RAM for 1,000 embeddings active index: ~22MB

The 340ms → 8ms improvement on search is the one that changed the robot's behavior. Recognition went from perceptible pause to imperceptible. The 180MB → 22MB improvement means I can scale to 10x the memory corpus before hitting constraints.

Are these dramatic numbers? Yes, but the comparison is a bit unfair — SQLite is doing general-purpose relational work, moteDB is doing one specific thing. The point isn't "moteDB is faster than SQLite," it's that you're comparing apples to purpose-built apples.

When SQLite Is Still the Right Answer

This isn't an "SQLite is bad" post. SQLite remains the right choice for:

  • Config and settings storage — nothing beats SQLite's simplicity for "remember this preference"
  • Relational queries across structured data — if your data is naturally tabular with joins, use SQL
  • Maximum ecosystem compatibility — SQLite has bindings in every language, tooling everywhere
  • Audit logs and event history — append-only structured records are exactly what SQLite is great at

The switch to moteDB made sense for my use case because my data was fundamentally multimodal and similarity search was a core operation. If I'd been building a robot that needed to remember facts and query them relationally ("what's the last time I saw Alice?"), SQLite would have been fine.

The Real Lesson

I wasted three weeks because I reached for the familiar tool instead of asking what the problem actually required. SQLite is excellent — but "excellent general-purpose database" and "right tool for AI agent memory" are different things.

If you're building AI systems that work with embeddings, sensor data, and multimodal inputs — especially on edge hardware — the database choice deserves more deliberate thought than it usually gets. The ecosystem defaults (SQLite, PostgreSQL with pgvector) work, but they're not optimized for this access pattern.

I'm curious: what are others using for on-device AI memory storage? Have you hit similar schema/performance walls, or found ways to make SQLite work cleanly with embedding data?


moteDB is open-source and written in Rust. If you're working on edge AI and want to try it: cargo add motedb. GitHub: motedb/motedb

Source: dev.to

arrow_back Back to Tutorials