How to use rosbag2 to record and replay ROS 2 bags
What rosbag2 is for
Rosbag2 is the ROS 2 tool for recording and playing back communication in a ROS 2 system, and the successor of ROS 1 rosbag. The repository describes a bag plainly: a file full of timestamped messages (rosbag2 README). The first stated goal is efficient recording in real time, the second is efficient playback.
That combination is what makes bags useful for developers. A bag turns a robot session into an artifact you can replay on demand, which is the difference between a bug that appeared once on hardware and a bug you can rerun on a desk. If the framework itself is new to you, start with What is ROS 2?, and for the interfaces a bag carries see ROS 2 topics vs services vs actions.
What ends up on disk
A recording session produces a directory, named in the pattern rosbag2_year_month_day-hour_minute_second, containing a metadata.yaml file and the storage file itself (recording tutorial). The repository ships two storage plugins, mcap and sqlite3, and the default is mcap, returned by rosbag2_storage::get_default_storage_id() (default_storage_id.cpp). The Storage id: field in ros2 bag info tells you which one a given bag uses, and any ros2 bag verb accepts --storage (or -s) to force one.
Recording topics
Recording needs a selection. The CLI rejects a call with none of --all, --all-topics, --all-services, --services, --topics, --topic-types or --regex and prints the list of valid choices. The positional topic form from older tutorials still works but the recorder warns that it is deprecated in favour of --topics.
ros2 bag record --topics /turtle1/cmd_vel /turtle1/pose
ros2 bag record -o subset --topics /turtle1/cmd_vel /turtle1/pose
ros2 bag record --all --exclude-topics /camera/image_raw
ros2 bag record -a
-a records all topics and services, excluding hidden topics, and keeps discovering topics that appear while it runs. --no-discovery restricts it to whatever existed at startup, while -p or --polling-interval sets how often, in milliseconds, the recorder queries the topic graph for new topics. --exclude-topics and --exclude-services only make sense on top of an inclusive selector, and the CLI enforces that: excluding topics requires --all, --all-topics, --topic-types or --regex.
The usual reason to record less than everything is volume. Camera and LiDAR streams account for the largest share of a bag, so narrowing the topic list is the cheapest form of storage saving, well before compression and splitting enter the picture.
Services, cache size and splitting
Recording services requires Service Introspection to be enabled on the participating nodes. The tutorial enables it with the parameters service_configure_introspection:=contents on the service and client_configure_introspection:=contents on the client, after which ros2 bag record --services /add_two_ints or --all-services records the service’s event topic. Playback returns those recorded requests to the service with ros2 bag play --publish-service-requests.
Messages pass through a cache before hitting disk. --max-cache-size defaults to 100 MB, the cache is handled with double buffering so memory use can reach roughly twice that value in a pessimistic case, and setting it to 0 writes every message directly to disk. For long recordings, splitting bounds the damage of a single corrupt or oversized file: -b or --max-bag-size takes a size in bytes, which must be greater than 86016, and -d or --max-bag-duration takes a duration in seconds. If both are set, the bag splits at whichever threshold is reached first.
Compression, MCAP presets and snapshots
Rosbag2 can compress as it writes, using --compression-mode file or message together with --compression-format zstd, which is the only registered compressor. Specifying a format without a mode is an error, and the documentation notes that the storage plugins have their own compression mechanisms. The MCAP plugin is the interesting case: its chunk compression produces files that remain indexable, whereas the generic Rosbag2 compression does not. It exposes this through --storage-config-file for a YAML file of writer options, or through preset profiles selected with --storage-preset-profile (rosbag2_storage_mcap README):
fastwrite, for maximum write throughput, which disables chunking, skips CRCs and writes no message index. The README warns it is not recommended as a long term storage format because reading messages from a subset of topics or seeking will not work without an index, and suggests post-processing such files withros2 bag convertor the MCAP CLI.zstd_fast, which uses zstd chunk compression at the lowest compression level and disables chunk CRCs.zstd_small, which writes 4 MB chunks at the highest compression ratio and keeps chunk CRCs, intended as a post-processing step.
When you cannot predict whether the interesting event will happen, snapshot mode replaces the normal write path. With --snapshot-mode the recorder keeps recent messages in an in-memory circular buffer bounded by --max-cache-size instead of writing them out, and dumps the buffer on request:
ros2 service call /rosbag2_recorder/snapshot rosbag2_interfaces/Snapshot
The recorder also exposes services for remote control, among them ~/pause, ~/resume, ~/is_paused, ~/split_bagfile, ~/record, ~/stop, ~/start_discovery, ~/stop_discovery and ~/is_discovery_running, all typed from the rosbag2_interfaces package.
Simulation time
For bags recorded in simulation, --use-sim-time makes the recorder subscribe to /clock and stamp messages with the latest received simulation time. The documentation is explicit about the hazard: until the first /clock message arrives, time is zero, and mixing zero timestamps with later simulation timestamps makes a bag essentially unplayable. The same flag also conflicts with --no-discovery, because /clock has to be discoverable, and the CLI rejects that combination outright. If you run a simulator, the published clock is exactly what Isaac Sim and other simulators provide on that topic.
Inspecting a bag
ros2 bag info subset
ros2 bag info --topic-name subset
ros2 bag info --verbose subset
ros2 bag list storage
ros2 bag info prints bag size, storage id, duration, start and end times, message count and per topic counts with types and serialization format. -t or --topic-name narrows the output to topic names only, -v or --verbose adds request and response information for services, and --sort changes the ordering, which defaults to topic name. ros2 bag list is not a bag inventory: it lists the installed plugins, and takes one of storage, converter, compressor or decompressor.
The message counts are worth reading carefully. In the tutorial example, /turtle1/cmd_vel holds nine messages from nine key presses while /turtle1/pose holds over three thousand, because the simulator published pose continuously. That ratio is what you need when estimating storage, and ros2 topic hz tells you the publish rate of a live topic before you decide to record it.
If a recording ends badly and the metadata.yaml file is missing, ros2 bag reindex <bag_directory> reconstructs it. The reindexer reads the enclosed storage files and aggregates the metadata back into a new metadata file, and it requires the path to be a bag directory.
Replaying a bag
ros2 bag play <bag> accepts either the bag directory or a single storage file such as a .mcap or .db3 file, and detects the storage implementation automatically. Several bags play as one sequence when passed with repeated -i flags, ordered by their original recording timestamps.
The options that matter in practice:
-ror--ratescales playback speed, and any positive value is accepted.--start-offset <seconds>begins playback partway into the bag, which is the fastest way to skip a long approach phase and reach the interesting seconds.-dor--delay <seconds>waits before starting,-lor--looprestarts from the beginning indefinitely, and-por--start-pausedarms the player without publishing.--topics,--services,-eor--regex,-xor--exclude-regex,--exclude-topicsand--exclude-servicesselect what to publish.-mor--remaprewrites topic names at playback time in theold_topic:=new_topicform.--clock <Hz>publishes/clockto act as a time source, while--clock-topicstriggers a clock update on chosen topics and--clock-topics-allupdates before every replayed message.--playback-duration,--playback-until-secand--playback-until-nsecbound how long playback continues.--wait-for-all-acked <milliseconds>delays termination until subscribers have acknowledged the messages, which matters when a bag contains large messages that would otherwise be cut off, and the help text notes it applies only where the publisher’s QoS profile is reliable.
The player also exposes control services: ~/play, ~/stop, ~/pause, ~/resume, ~/toggle_paused, ~/seek, ~/set_rate, ~/get_rate, ~/is_paused, ~/burst and ~/play_next. For a test harness that is usually cleaner than scripting around the CLI, because the runner can seek to a timestamp and drive playback from code.
Recording from your own code
Bags do not have to come from the CLI. The Python API exposes rosbag2_py.SequentialWriter, and the tutorial’s recorder node open a bag with StorageOptions(uri='my_bag', storage_id='mcap') plus ConverterOptions('', ''), register a topic with TopicMetadata(id=0, name='chatter', type='std_msgs/msg/String', serialization_format='cdr'), and write messages with the serialized bytes:
self.writer.write(
'chatter',
serialize_message(msg),
self.get_clock().now().nanoseconds)
The writer requires serialized messages, which is why the tutorial calls serialize_message() before writing, and it will not overwrite an existing bag, so the destination directory must not exist. Because anything can be written to a bag, the same API is used to generate synthetic datasets: the tutorial builds a node with a timer and a plain executable with a loop, the second one writing timestamps one second apart as fast as the CPU allows so a hundred seconds of playback material is produced in milliseconds. The reading side is symmetric, with a SequentialReader offering open, read_next, has_next, get_metadata, seek, set_filter and set_read_order, and the documentation’s reading tutorial covers the C++ interface.
Two integration points are worth knowing. A launch file can run the CLI as an executable rather than a node, with <executable cmd="ros2 bag record -a" output="screen"/> in XML or launch.actions.ExecuteProcess(cmd=['ros2', 'bag', 'record', '-a']) in Python. And for high rate recording, rosbag2_transport::Recorder and rosbag2_transport::Player can be loaded as composable nodes so that publishers and subscribers share a process, which lets intra-process communication bypass the middleware serialization and transport. The README lists the cases that motivate it: low network bandwidth, high CPU load, slow mass storage and middleware overhead.
Quality of Service is the usual culprit
Both recording and playback accept --qos-profile-overrides-path, a YAML file mapping topic names to policy values. The documentation’s guide to overriding QoS policies notes that only reliability and durability affect whether endpoints are compatible and can receive data, and gives the canonical example: a publisher offering transient_local durability will not be recorded by a subscriber using the default volatile durability unless the override raises it. If your bag contains a topic entry with zero messages, or a topic that simply never appears, the QoS profile is the first thing to check.
Pitfalls
- Recording into an existing directory fails, because the output folder must not already exist. Delete it or choose another name.
-aon a real robot records far more than intended. Filter, split and compress.- Generic Rosbag2 compression produces files that are not indexable according to the docs, so topic subset reads and seeking suffer. Prefer MCAP chunk compression or a preset profile.
- Simulation time bags break if messages are written before the first
/clockmessage. - Playback timing is not exact. The tutorial warns that the replayed turtle path will not match the original exactly, because the simulation is sensitive to small timing changes.
- Bag size estimates from message counts can be off by orders of magnitude between a sparse command topic and a continuous sensor topic.
When to use bags, and when not to
Record when you need reproducible input: a sensor stream for an offline algorithm change, a navigation failure to replay a hundred times, a dataset for a learning experiment, or evidence for a support ticket. Do not treat a bag as a substitute for logging or metrics, and do not assume it is free at the network layer: the README names low bandwidth, high CPU load and slow storage as the conditions under which recording and playback degrade, which is an argument for writing bags to fast local storage.
Sources
- docs.ros.org: recording and playing back data
- docs.ros.org: recording a bag from your own node (Python)
- docs.ros.org: reading from a bag file (C++)
- docs.ros.org: overriding QoS policies for recording and playback
- github.com/ros2/rosbag2: README (jazzy branch)
- github.com/ros2/rosbag2: rosbag2_storage_mcap README
- github.com/ros2/rosbag2: default_storage_id.cpp
Frequently asked questions
- Where does ros2 bag record save the recorded file?
- In a new directory in the current working directory, named in the pattern rosbag2_year_month_day-hour_minute_second. The directory holds a metadata.yaml file plus the storage file itself, for example a .mcap or .db3 file. Passing -o or --output overrides the generated name, but the directory must not already exist.
- Why does ros2 bag record print an error asking for more arguments?
- Recording requires at least one selection option: --all, --all-topics, --all-services, --services, --topics, --topic-types or --regex. A bare ros2 bag record with no selection is rejected, and the error lists the valid choices.
- Why is a topic missing from a bag even though it existed while recording?
- Most often Quality of Service. Rosbag2 adapts the profile it requests, but a topic published with a durability or reliability policy it cannot match will produce an empty bag entry. Passing a YAML file with --qos-profile-overrides-path, on both record and play, sets an explicit profile for that topic.
- Which storage format should I use, mcap or sqlite3?
- The default storage plugin is mcap. MCAP supports chunk compression that keeps the file indexable and offers preset profiles such as fastwrite, zstd_fast and zstd_small, while Rosbag2's own --compression-mode file or message option produces files that are not indexable according to the rosbag2 documentation.
- Can rosbag2 record a long experiment without filling the disk?
- Yes. Two mechanisms exist: --snapshot-mode keeps recent messages in an in-memory circular buffer bounded by --max-cache-size and writes them to disk only when the recorder's snapshot service is called, and the splitting options -b and -d limit the size or duration of each individual bag file.
Related reading
ROS 2 intra-process communication and composition explained
ROS 2 composition loads several nodes into one process, and intra-process communication passes messages between them without serialisation or a middleware hop.
Map-free drone nav, ARM cross-compiling
An open ROS 2 and PX4 stack flies a drone through urban terrain without a map, colcon gains ARM cross-compiling, and rosbag2 learns to change topics mid-recording.
Fleet APIs, in-orbit assembly and zero-copy rosidl
Serve opens its delivery fleet to any developer, Caltech assembles a truss in orbit, and ROS 2 debates moving zero-copy into rosidl itself.