Back to Comparisons
/ COMPARISON · Data formats

MCAP vs ROS 2 bag

MCAP and the sqlite3 ROS 2 bag format solve the same problem differently. Here is how they compare on indexing, seek speed, portability, and long-term storage.

Updated Aug 20264 min read
SHORT ANSWER

Record to MCAP unless you have a specific reason not to. It is the ROS 2 default storage plugin as of Iron, it is self-describing and indexed for fast seeking, and it reads outside ROS. The legacy sqlite3 bag is fine for short local recordings but is slower to seek, larger on disk, and awkward to consume from non-ROS tooling.

Both formats answer the same question — where do the messages coming off a robot go? — and in a ROS 2 stack you choose between them with a single --storage flag. The choice matters most later, when someone has to read a two-year-old recording on a machine that does not have your workspace built.

This page compares the two on the axes that actually decide the question for a data-collection program: what survives outside your workspace, how fast you can seek inside a large file, and how each behaves when a recording is interrupted. If you are choosing a training format rather than a recording format, that is a different decision — see choosing a robot data format.

The short version

 MCAPROS 2 bag (sqlite3)
ContainerPurpose-built append-only logSQLite database file
SchemasEmbedded in the file, self-describingType names only; definitions must exist locally
Random accessChunk + message index, seek by topic and timeSQL index, whole-file scan for many patterns
CompressionPer-chunk zstd or lz4, seeking preservedNone built in
Non-ROS readersPython, C++, Go, Rust, TypeScript, SwiftPractical only via ROS tooling or rosbags
Write robustnessAppend-only; truncated files still readableInterrupted writes can leave the DB inconsistent
ROS 2 statusDefault storage plugin since IronLegacy default, still supported
Behavior as of ROS 2 Jazzy; verify against your distro's rosbag2 version.

Where the difference actually bites

Reading a recording you did not make. A sqlite3 bag stores type names, not type definitions. Open one on a machine without the matching message packages and you get topic names and opaque byte blobs. MCAP embeds the schema text in the file, so a reader can decode messages from a package that no longer exists.

Pulling one topic out of a big file. Robot recordings are dominated by camera streams. When you want the 200 Hz joint states out of a 40 GB session, MCAP's per-topic chunk index means the reader touches only the chunks containing that topic. The sqlite3 layout makes the same query considerably more expensive.

Recording that survives a hard stop. Robots lose power mid-session. MCAP's append-only layout means a truncated file still reads up to the last complete chunk. A SQLite file interrupted mid-transaction can require recovery before it opens at all.

Getting data into non-ROS tooling. Training pipelines are Python and PyTorch, not ROS. Reading MCAP from a plain Python process is a pip install mcap mcap-ros2-support away — no sourced workspace, no rclpy. The same property is what lets a recording feed a visualizer like Foxglove and a training job from the same file, rather than maintaining two export paths.

Inspecting a recording without a robot. Because MCAP carries its own schemas, a reviewer can open a session and plot joint states or scrub camera topics without reproducing the robot's software environment. On a data-collection program that runs QA on a different machine than the rig — which is most of them — this is the difference between a five-minute review and an afternoon of dependency archaeology.

Where the sqlite3 bag is still fine

sqlite3 bag still makes sense when
  • You are on a pre-Iron distro where MCAP is not the default and you cannot add the plugin.
  • Recordings are short, local, and thrown away the same day.
  • You genuinely want to query a recording with SQL from an existing tool.
  • Existing internal tooling reads .db3 directly and rewriting it is not worth it.
switch to MCAP when
  • Recordings are archived, shared, or shipped to a customer.
  • Datasets are read by training code outside ROS.
  • Files exceed a few GB and you need to seek inside them.
  • Message definitions change over the life of the project.

Recording either one

# MCAP (default on Iron and newer)
ros2 bag record -a --storage mcap -o session_014
 
# Explicit sqlite3, if you need it
ros2 bag record -a --storage sqlite3 -o session_014
 
# Convert an existing recording
ros2 bag convert -i session_014 -o convert_to_mcap.yaml

Reading MCAP without ROS on the path:

from mcap_ros2.reader import read_ros2_messages
 
for msg in read_ros2_messages("session_014.mcap", topics=["/joint_states"]):
    print(msg.log_time_ns, msg.ros_msg.position)

What neither format gives you

Both are containers, and a container is not a dataset. Neither format decides where an episode starts and ends, whether the operator marked the attempt a success, which protocol version was in force, or whether the cameras were actually in sync when the messages were stamped. That metadata has to be produced by the recording layer and carried alongside the file — see time synchronization for the failure mode that a container cannot detect for you.

Choosing MCAP therefore settles the storage question and none of the pipeline questions. It is still worth settling: the container is the one decision that is expensive to reverse across a corpus of recordings, because reversing it means rewriting every file you have.

The recommendation

Record MCAP. It is the ROS 2 default for a reason, and every property that makes it better — embedded schemas, indexed seeking, per-chunk compression, readable from any language — is a property you only appreciate months later, when the recording has outlived the workspace that produced it. Keep sqlite3 bags for the narrow legacy cases above, and convert anything you intend to keep.

KEY FACTS

MCAP EXTENSION
.mcap
SQLITE3 BAG EXTENSION
.db3
ROS 2 DEFAULT
MCAP since Iron Irwini (2023)
BOTH READ BY
ros2 bag, Foxglove, rosbags (Python)

/ QUESTIONS

Frequently asked

Put this into practice.

Tell us what your robots need to learn. We will scope the rig, the operators, the protocol, and the first datasets — usually in one call.