<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ReductStore</title>
    <description>The latest articles on DEV Community by ReductStore (reductstore).</description>
    <link>https://dev.to/reductstore</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F5829%2Fa0981feb-b10f-44b2-93aa-286aeaf3866d.jpg</url>
      <title>DEV Community: ReductStore</title>
      <link>https://dev.to/reductstore</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/reductstore"/>
    <language>en</language>
    <item>
      <title>Persistent Edge Storage for a Legacy IIoT System</title>
      <dc:creator>Alexey Timin</dc:creator>
      <pubDate>Fri, 29 May 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/reductstore/persistent-edge-storage-for-a-legacy-iiot-system-3228</link>
      <guid>https://dev.to/reductstore/persistent-edge-storage-for-a-legacy-iiot-system-3228</guid>
      <description>&lt;p&gt;In real-life projects, nothing is perfect. But usually we don't touch systems that already work. Especially when a customer has already invested almost a million euros into that system.&lt;/p&gt;

&lt;p&gt;When you come with your solution, you don't have full freedom to change everything as you wish. You cannot just say: "Let's replace this part", "Let's&lt;br&gt;&lt;br&gt;
 change the architecture", or "Let's use a better protocol". Maybe technically it would be better, but in reality the system is already there, people use&lt;br&gt;&lt;br&gt;
 it, and the customer paid a lot of money for it.&lt;/p&gt;

&lt;p&gt;So you have to adjust your solution to the existing system. You have to be flexible, understand the constraints, and adapt to the customer's needs.&lt;/p&gt;

&lt;p&gt;This is a real story about how we enabled long-term persistent storage with &lt;a href="https://www.reduct.store" rel="noopener noreferrer"&gt;ReductStore&lt;/a&gt; for a customer with a legacy IIoT system.&lt;/p&gt;

&lt;h2&gt;
  
  
  No persistent storage, no access to raw data
&lt;/h2&gt;

&lt;p&gt;The customer had a pretty typical IoT setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fleet of IoT devices which collect data from vibration sensors, do analysis on the edge, and send the results to the cloud via MQTT&lt;/li&gt;
&lt;li&gt;Cloud backend based on GCP: Hono for authentication and data ingestion from MQTT to Pub/Sub, Cloud Run for processing, BigQuery for storage and analysis&lt;/li&gt;
&lt;li&gt;Grafana for visualization and alerting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system worked pretty well. But the customer wanted to store raw vibration data for training and validation of AI models. They didn't need all of it—only data collected when production was running at stable speed. That's what was useful for their models.&lt;/p&gt;

&lt;p&gt;The current setup was designed for structured data and didn't have a data flow for raw data. On top of that, many devices had connectivity issues and couldn't store data persistently—so the data was lost when the connection went down. The IoT team had to copy data manually from devices after outages, which was a huge pain and still caused data loss.&lt;/p&gt;

&lt;p&gt;This would be a perfect use case for ReductStore, if we had a chance to replace the existing MQTT → Hono → Pub/Sub flow with ReductStore directly. But the customer didn't want to change the architecture or have an additional service available publicly. They wanted us to integrate ReductStore into the existing system, so that raw data is stored in ReductStore on the edge, replicated to a cloud ReductStore instance, and also sent to Pub/Sub for processing and BigQuery storage.&lt;/p&gt;

&lt;p&gt;So what we had to do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Store all raw data persistently on edge with ReductStore&lt;/li&gt;
&lt;li&gt;Replicate data from edge to a cloud ReductStore instance&lt;/li&gt;
&lt;li&gt;Re-send all data to the existing cloud backend after connectivity outages&lt;/li&gt;
&lt;li&gt;Store vibration data in the cloud only for certain conditions—when production is running at stable speed&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Reliable delivery to the cloud
&lt;/h2&gt;

&lt;p&gt;Storing all MQTT data locally in ReductStore wasn't a problem. We wrote a simple Python service that subscribes to MQTT and stores all messages in ReductStore with necessary labels. The system was based on MQTT v5 and used user properties to pass metadata, so we just extracted them and used them as labels in ReductStore.&lt;/p&gt;

&lt;p&gt;The real challenge was how to re-send data to the cloud after connectivity issues.&lt;/p&gt;

&lt;p&gt;The data lands in BigQuery, and BigQuery doesn't protect against duplicates, so we had to make sure we don't send the same data twice. If we could use ReductStore's replication directly, it wouldn't be an issue at all—we could create a replication task with a filter for the relevant labels, and it would take care of everything. But we had to send data via MQTT, so we had to be more creative. We explored two options:&lt;/p&gt;

&lt;h3&gt;
  
  
  Continuous Queries
&lt;/h3&gt;

&lt;p&gt;ReductStore has an option to query data continuously with a filter. You specify a beginning timestamp, and it returns all data matching the filter that arrives after that timestamp. We could use this to re-send data to the cloud after connectivity issues.&lt;/p&gt;

&lt;p&gt;However, if a device is restarted, it loses the timestamp of the last sent message, and we would have to re-send all data from the beginning. That's not acceptable.&lt;/p&gt;

&lt;p&gt;We could store the timestamp in a file, but that's a bit hacky and not very reliable.&lt;/p&gt;

&lt;p&gt;Another option would be to update labels of sent messages with a "sent" flag and use it in the filter. But that solution is also not ideal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Updating labels on every message is not very efficient and could cause performance issues.&lt;/li&gt;
&lt;li&gt;You still need to query all messages from the beginning to find the last sent one, which is also not efficient.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Looks like standard ReductStore features are not enough to solve this problem, so we had to come up with a custom solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replication to a fake target
&lt;/h3&gt;

&lt;p&gt;ReductStore has replication tasks to replicate data to another instance, selecting by labels. It has a transaction log and guarantees that all data is replicated in order and without duplicates.&lt;/p&gt;

&lt;p&gt;But we have to send data via MQTT!? Actually, it's not a problem if you know how things work. We can create a middleman service which implements the necessary HTTP endpoints for replication and sends data to the cloud via MQTT.&lt;/p&gt;

&lt;p&gt;This is the approach we chose, and it was the right one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the HTTP-to-MQTT bridge
&lt;/h2&gt;

&lt;p&gt;We already had an MQTT bridge service that sent MQTT messages from internal services to the cloud, so we just had to rework it into an HTTP service. It needed to receive HTTP requests from ReductStore's replication task, extract the data and labels, and send them to the cloud via MQTT.&lt;/p&gt;

&lt;p&gt;In other words, the MQTT bridge becomes the replication target—ReductStore thinks it's talking to another ReductStore instance, but behind those endpoints sits our bridge that forwards everything over MQTT.&lt;/p&gt;

&lt;p&gt;The diagram shows the data flow: vibration data enters the Connector, which writes it into a ReductStore bucket. The replication task inside ReductStore pushes batches of records to the MQTT bridge's HTTP API, and the bridge forwards them to the cloud over MQTT.&lt;/p&gt;

&lt;p&gt;The replication task is configured on the edge ReductStore instance with a target URL pointing to the MQTT bridge's HTTP endpoint and a label filter that selects only analytics data collected during stable production speed. This way, only relevant records get replicated to the cloud—raw vibration data and irrelevant measurements stay on disk locally.&lt;/p&gt;

&lt;p&gt;To imitate a target ReductStore instance, the service must implement the following HTTP endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.reduct.store/docs/1.18.x/http-api/bucket-api#get-information-about-a-bucket" rel="noopener noreferrer"&gt;GET /api/v1/b/:bucket_name&lt;/a&gt; - to get information about the bucket to initialize replication&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.reduct.store/docs/1.18.x/http-api/entry-api/write_data#write-a-batch-of-records" rel="noopener noreferrer"&gt;POST /api/v1/b/:bucket_name/:entry_name/batch&lt;/a&gt; - to receive data from the replication task and send it to the cloud via MQTT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not much, but data comes in batches, so we had to parse the HTTP headers and extract data from the HTTP body before sending it to the cloud via MQTT. The batch protocol is documented here: &lt;a href="https://www.reduct.store/docs/next/http-api/entry-api#batch-protocol" rel="noopener noreferrer"&gt;https://www.reduct.store/docs/next/http-api/entry-api#batch-protocol&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If the MQTT bridge is able to send data to the cloud, it returns 200 OK, and the replication task removes the records from the transaction log. So we have a guarantee that data is sent to the cloud without duplicates and in order, and we don't have to worry about connectivity issues at all.&lt;/p&gt;

&lt;p&gt;In case of connectivity issues, the MQTT bridge returns errors per record according to the batch protocol. If the connection drops mid-batch, successfully sent records are acknowledged and only the failed ones are retried by the replication task. This means no data is sent twice and no records are lost—even on partial failures.&lt;/p&gt;

&lt;p&gt;Data is stored persistently on disk and keeps accumulating during outages. ReductStore uses a FIFO quota: once the disk quota is reached, the oldest records are evicted to make room for new ones. So data loss only happens if an outage lasts long enough for the quota to roll over unsent records—in practice, with a reasonable disk size and typical outage durations, this never happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we achieved
&lt;/h2&gt;

&lt;p&gt;Before this project, the customer had two painful gaps: raw vibration data was never stored—it was processed on the edge and discarded immediately, so there was no way to go back and use it for model training later. And processed MQTT data was regularly lost during the frequent connectivity outages their edge devices experienced. The IoT team spent hours after every outage manually pulling data from devices—and still lost some of it.&lt;/p&gt;

&lt;p&gt;With ReductStore on the edge and a thin HTTP-to-MQTT bridge, both problems are gone. Raw vibration data is now persisted in ReductStore on each device and can be queried directly over HTTP whenever the data science team needs it for model training or validation. There is no need to push gigabytes of raw sensor readings to the cloud—it stays where it's most useful and cheapest to store.&lt;/p&gt;

&lt;p&gt;For processed data that must reach the cloud, the replication task handles everything automatically. Data is persisted on disk the moment it arrives, and once connectivity returns the bridge drains the backlog in order, without duplicates, without manual intervention. The FIFO quota ensures the disk never fills uncontrollably, and label-based filtering means only data collected under relevant production conditions is replicated—everything else stays local.&lt;/p&gt;

&lt;p&gt;The most important part: none of this required changes to the customer's existing cloud infrastructure. The MQTT → Hono → Pub/Sub → BigQuery pipeline remained untouched. The bridge simply slots in as a transparent layer between ReductStore and the existing MQTT ingestion point. For the cloud backend, nothing changed—it still receives MQTT messages in the same format as before.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>iot</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>How to Store and Manage Robotics Data</title>
      <dc:creator>AnthonyCvn</dc:creator>
      <pubDate>Tue, 14 Apr 2026 07:53:15 +0000</pubDate>
      <link>https://dev.to/reductstore/how-to-store-and-manage-robotics-data-2bi7</link>
      <guid>https://dev.to/reductstore/how-to-store-and-manage-robotics-data-2bi7</guid>
      <description>&lt;p&gt;Robots generate &lt;em&gt;massive&lt;/em&gt; amounts of data, and managing it well is harder than it looks. Storage fills up fast, cloud transfer gets expensive, and real time ingestion is unforgiving when you're running cameras and sensors at high frequency.&lt;/p&gt;

&lt;p&gt;This article covers practical strategies for handling robotic data, introduces &lt;a href="https://www.reduct.store/" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore&lt;/strong&gt;&lt;/a&gt;, and walks through a hands on example. Along the way, we cover native ROS integration, Grafana dashboards, MCAP export for Foxglove, a Zenoh API, and native S3 and Azure backends. We also compare ReductStore against Rosbag and MongoDB so you can pick the right tool for each part of your stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges in Robotic Data Management
&lt;/h2&gt;

&lt;p&gt;Robots operate in dynamic environments and continuously produce large volumes of data. The core challenges engineers run into are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High frequency, real time requirements&lt;/strong&gt;: A drone navigating a city must process camera and sensor data in milliseconds. Storage solutions need to keep up with these streams and make data accessible fast enough for real time decision making.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited on device storage&lt;/strong&gt;: Most robots can't store everything they generate. Size, weight, and power constraints limit local capacity, so good data management strategies are essential.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High data volume&lt;/strong&gt;: An autonomous vehicle can produce up to &lt;a href="https://www.datacenterfrontier.com/connected-cars/article/11429212/rolling-zettabytes-quantifying-the-data-impact-of-connected-cars" rel="noopener noreferrer"&gt;&lt;strong&gt;5 terabytes per hour&lt;/strong&gt;&lt;/a&gt; across camera feeds, LiDAR, radar, GPS, and sensors. Most databases weren't designed around that kind of throughput.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud storage costs&lt;/strong&gt;: Pushing everything to the cloud isn't practical. Providers charge per gigabyte stored and transferred, and with robots producing terabytes, the bill adds up fast. What you send to the cloud is a decision worth making deliberately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data reduction complexity&lt;/strong&gt;: Filtering out irrelevant data without losing important information is tricky. Without a clear strategy, you either waste storage or throw away data you later need.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  General Strategies for Managing Robotic Data
&lt;/h2&gt;

&lt;p&gt;A few approaches that work well in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use a time series object store&lt;/strong&gt;: Systems like ReductStore are designed specifically for high frequency, timestamped binary data. They offer fast writes, efficient querying, and manageable costs at scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Balance edge and cloud storage&lt;/strong&gt;: Keep recent, critical data on the robot for fast access. Move older or lower priority data to the cloud for long term storage and analysis. Splitting storage this way cuts costs without adding much latency to what matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use volume based retention policies&lt;/strong&gt;: Rather than deleting data based on age, use a FIFO (first in, first out) quota. Data is only removed when the storage limit is reached, which avoids unnecessary deletion during downtime or low activity periods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compress where it makes sense&lt;/strong&gt;: H.265 for video, JPEG for images. The right format can cut storage by an order of magnitude without losing anything you actually need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prioritize what to keep&lt;/strong&gt;: Not all sensor data has equal value. Define what's critical upfront and discard the rest early in the pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ReductStore: Built for Robotic Data
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.reduct.store/" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore&lt;/strong&gt;&lt;/a&gt; is a time series database built specifically for unstructured, binary data. It's designed to handle high frequency sensor streams from autonomous vehicles, drones, industrial robots, and IoT. It stores data with timestamps and labels, supports fast real time ingestion, and comes with batching, filtering, and edge to cloud replication built in.&lt;/p&gt;

&lt;p&gt;Here's what's available today.&lt;/p&gt;

&lt;h3&gt;
  
  
  ROS Integration with reduct-bridge
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/reductstore/reduct-bridge" rel="noopener noreferrer"&gt;&lt;strong&gt;reduct-bridge&lt;/strong&gt;&lt;/a&gt; connects live ROS 1 and ROS 2 systems directly to ReductStore. You configure it with a simple TOML file that defines your ROS topic inputs, pipelines, and the ReductStore destination. It subscribes to topics and stores each message as an individual record.&lt;/p&gt;

&lt;p&gt;It also writes a &lt;code&gt;$ros&lt;/code&gt; attachment to each entry with the message schema, topic name, and encoding — that metadata is what powers MCAP export and Grafana visualization, both covered below. If your stack is already on ROS, setup takes minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud Storage: Native S3 and Azure
&lt;/h3&gt;

&lt;p&gt;ReductStore supports native cloud storage backends for both &lt;a href="https://www.reduct.store/docs/integrations/cloud-storage" rel="noopener noreferrer"&gt;&lt;strong&gt;Amazon S3 and Azure Blob Storage&lt;/strong&gt;&lt;/a&gt;, no FUSE drivers needed. It uses a local cache for hot data and the cloud bucket for long term retention.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S3 compatible&lt;/td&gt;
&lt;td&gt;Works with AWS S3, MinIO, Ceph, Cloudflare R2, and any S3 compatible service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure Blob Storage&lt;/td&gt;
&lt;td&gt;Switch backends by changing a few environment variables. Same deployment patterns.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tiered access&lt;/td&gt;
&lt;td&gt;Recent data stays in the local cache for fast reads. Older data lives in the cloud bucket.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read replicas&lt;/td&gt;
&lt;td&gt;Add read only replicas pointing at the same S3 bucket, close to your consumers.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For records around 100KB (e.g. JPEG images), ReductStore can be &amp;gt;10x faster than traditional time series object stores at a fraction of the cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visualize ROS Data in Grafana
&lt;/h3&gt;

&lt;p&gt;ReductStore has a &lt;a href="https://www.reduct.store/blog/grafana-visualization-ros-data" rel="noopener noreferrer"&gt;&lt;strong&gt;Grafana integration&lt;/strong&gt;&lt;/a&gt; through the ReductStore data source plugin. You can query ROS 2 messages directly in Grafana thanks to the &lt;a href="https://www.reduct.store/docs/extensions/official/ros-ext/raw" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductROS extension&lt;/strong&gt;&lt;/a&gt;. The extension decodes binary CDR messages into JSON on the fly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl0ecdoqregfwv4be5692.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl0ecdoqregfwv4be5692.png" alt="Grafana ROS Dashboard" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can monitor sensor streams live, compare data across multiple robots, and set up alerts when metrics drift.&lt;/p&gt;

&lt;h3&gt;
  
  
  Export to MCAP for Foxglove
&lt;/h3&gt;

&lt;p&gt;You can export raw ROS messages stored in ReductStore directly to MCAP and open them in Foxglove. This is powered by the &lt;a href="https://dev.to/docs/extensions/official/ros-ext/raw"&gt;&lt;strong&gt;ReductROS extension&lt;/strong&gt;&lt;/a&gt; as well, which reconstructs valid MCAP files on demand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpa7ejp6r7bibqtuwvf3b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpa7ejp6r7bibqtuwvf3b.png" alt="MCAP Export" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When records are ingested via reduct-bridge, the &lt;code&gt;$ros&lt;/code&gt; attachment carries the schema and topic information. The extension uses this to reconstruct valid MCAP files on demand. You can cover a full time range across multiple topics in a single query, and split by duration or file size for long recordings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zenoh Native API
&lt;/h3&gt;

&lt;p&gt;ReductStore now includes a &lt;a href="https://www.reduct.store/docs/integrations/zenoh" rel="noopener noreferrer"&gt;&lt;strong&gt;Zenoh native API&lt;/strong&gt;&lt;/a&gt; alongside the existing HTTP API. Zenoh is a pub/sub protocol designed for robotics and distributed systems, and it's widely used in next generation ROS 2 deployments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjpii2mcihdk5w02q2w38.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjpii2mcihdk5w02q2w38.png" alt="Zenoh Native API" width="800" height="138"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When enabled, ReductStore opens a Zenoh session with a subscriber for writes and a queryable for reads, both running in parallel with HTTP and sharing the same data. Zenoh keys become entry names, encodings map to content types, and attachments map to labels. Time range queries, conditional filters, and label lookups all work the same way they do over HTTP.&lt;/p&gt;

&lt;p&gt;It's a natural fit if your robot stack already speaks Zenoh, or if you want to skip HTTP overhead on high frequency ingestion paths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query Language and Batching
&lt;/h3&gt;

&lt;p&gt;ReductStore uses a &lt;a href="https://www.reduct.store/docs/next/conditional-query#query-syntax" rel="noopener noreferrer"&gt;&lt;strong&gt;JSON based query language&lt;/strong&gt;&lt;/a&gt; that supports filtering, aggregation, and time range operations. You can query data from a specific robot within a time window, compare sensor streams across robots in parallel, or filter by any label.&lt;/p&gt;

&lt;p&gt;Retrieval is optimized through &lt;strong&gt;batching&lt;/strong&gt;: multiple records are grouped into a single response based on a time range, which cuts down on request overhead and improves throughput. SDKs are available for Python, C++, JavaScript, Go, and Rust. More in the &lt;a href="https://www.reduct.store/docs/guides/data-querying" rel="noopener noreferrer"&gt;&lt;strong&gt;querying guide&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replication and Edge to Cloud Replication
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjc7a3ob9t2da4hch22l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjc7a3ob9t2da4hch22l.png" alt="Replication Diagram" width="800" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ReductStore replicates at the bucket level based on conditions. That means you can choose to send only high priority sensor data to the cloud based on rules, labels, or events. Replication is incremental, so only new data is transferred. Labels stored alongside records let you define fine grained rules based on content, for example, only replicate records flagged as anomalies or sampled at 1 in 10 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retention Strategies
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.reduct.store/docs/guides/buckets#quota-type" rel="noopener noreferrer"&gt;&lt;strong&gt;Volume based retention&lt;/strong&gt;&lt;/a&gt; follows the FIFO principle: data is only deleted when storage is full, making room for new records. This is different from time based retention, which deletes data after a fixed age regardless of whether storage is full. After an outage, a time based policy might delete data the system never had the chance to process. Volume based retention avoids that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Applications
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Application&lt;/th&gt;
&lt;th&gt;How ReductStore helps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Autonomous Vehicles&lt;/td&gt;
&lt;td&gt;Handles high throughput sensor streams with efficient querying and selective edge to cloud replication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Industrial Robots&lt;/td&gt;
&lt;td&gt;Stores diagnostic data for predictive maintenance and continuous performance monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drones and UAVs&lt;/td&gt;
&lt;td&gt;Supports offline operation in remote areas and syncs data when connectivity is restored&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Comparing ReductStore with Rosbag and MongoDB
&lt;/h2&gt;

&lt;p&gt;Three tools come up most often when robotics teams think about data storage: Rosbag and MCAP, MongoDB, and ReductStore. They're built for different things.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Rosbag / MCAP&lt;/th&gt;
&lt;th&gt;MongoDB&lt;/th&gt;
&lt;th&gt;ReductStore&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data type&lt;/td&gt;
&lt;td&gt;Binary ROS messages&lt;/td&gt;
&lt;td&gt;Semi structured documents&lt;/td&gt;
&lt;td&gt;Binary timestamped records&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query across recordings&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large binary payloads&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Slow via GridFS&lt;/td&gt;
&lt;td&gt;Yes, natively&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retention&lt;/td&gt;
&lt;td&gt;Manual file management&lt;/td&gt;
&lt;td&gt;Time based&lt;/td&gt;
&lt;td&gt;Volume based FIFO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ROS integration&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Via reduct-bridge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud storage&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Atlas&lt;/td&gt;
&lt;td&gt;Native S3 and Azure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write (100 KB blobs)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;694 blob/s&lt;/td&gt;
&lt;td&gt;3,612 blob/s (+420%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read (100 KB blobs)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;1,730 blob/s&lt;/td&gt;
&lt;td&gt;6,250 blob/s (+260%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Rosbag and MCAP&lt;/strong&gt; are the standard for recording ROS sessions. They're great for capturing a snapshot during a test run. But they're file formats, not databases. Querying across many recordings requires custom scripts, there's no built in content indexing, and managing thousands of bag files quickly becomes its own problem. Use them for short recordings and local playback, not long term storage or fleet wide analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MongoDB&lt;/strong&gt; is flexible and works well for structured metadata, labels, and event logs. But it wasn't built for large binary payloads. For blob data, it relies on GridFS, which adds complexity and hurts performance. It also uses time based retention, so data can be deleted during idle periods even when storage isn't full.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ReductStore&lt;/strong&gt; is designed specifically for the data robots actually produce: large, binary, timestamped records at high frequency. It connects to ROS via reduct-bridge, exports to MCAP for Foxglove, plugs into Grafana, and replicates selectively to S3 or Azure.&lt;/p&gt;

&lt;p&gt;In practice: use Rosbag or MCAP for short test recordings, MongoDB for structured metadata and event logs, and ReductStore for raw sensor data that needs to be stored at scale, queried efficiently, and managed over time.&lt;/p&gt;

&lt;p&gt;For a deeper look: &lt;a href="https://www.reduct.store/blog/robotics-mongodb-vs-reductstore" rel="noopener noreferrer"&gt;&lt;strong&gt;MongoDB vs ReductStore: Choosing the Right Database for Robotics Applications&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hands-On Example: Storing Robotic Data in ReductStore
&lt;/h2&gt;

&lt;p&gt;Let's walk through a &lt;a href="https://github.com/reductstore/reduct-robotics-example/blob/main/StoreQueryData.py" rel="noopener noreferrer"&gt;&lt;strong&gt;practical example&lt;/strong&gt;&lt;/a&gt; of storing and querying robotic data with ReductStore. We'll use trajectory data (coordinates, speed, orientation) to keep things simple, but the same approach works for any sensor stream.&lt;/p&gt;

&lt;p&gt;You'll need &lt;em&gt;Python 3.8+&lt;/em&gt; installed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up ReductStore
&lt;/h3&gt;

&lt;p&gt;Create a folder and add a &lt;em&gt;docker-compose.yaml&lt;/em&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.8"&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;reductstore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reduct/store:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8383:8383"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;data:/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RS_API_TOKEN=my-token&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ReductStore will be available at &lt;a href="http://127.0.0.1:8383" rel="noopener noreferrer"&gt;http://127.0.0.1:8383&lt;/a&gt;. Check the container is running with &lt;code&gt;docker ps&lt;/code&gt;, then install the Python libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;reduct-py numpy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Store and Query Data
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Create a Bucket
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_trajectory_bucket&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
 &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8383&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BucketSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="n"&gt;quota_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;QuotaType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FIFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;quota_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trajectory_data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bucket uses a FIFO quota of 1 GB. Old data is only deleted when the limit is reached.&lt;/p&gt;

&lt;h4&gt;
  
  
  Generate Trajectory Data
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_trajectory_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
 &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;frequency&lt;/span&gt;
 &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequency&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;time_step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time_step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time_step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;yaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;degrees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arctan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time_step&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;time_step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;position&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
      &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orientation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yaw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yaw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
      &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;speed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simulates a robot moving in 2D at 10 Hz for 1 second. &lt;em&gt;X&lt;/em&gt; and &lt;em&gt;y&lt;/em&gt; are position coordinates, &lt;em&gt;yaw&lt;/em&gt; is orientation, and &lt;em&gt;speed&lt;/em&gt; is derived from position changes.&lt;/p&gt;

&lt;h4&gt;
  
  
  Calculate Metrics
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_trajectory_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trajectory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;positions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;position&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;position&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt;   &lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trajectory&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="n"&gt;speeds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;speed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trajectory&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="n"&gt;deltas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;distances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deltas&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;total_distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;distances&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;average_speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;speeds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;total_distance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;average_speed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Write to ReductStore
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;store_trajectory_data&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="n"&gt;trajectory_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;data_point&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;generate_trajectory_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequency&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;trajectory_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;total_distance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;average_speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculate_trajectory_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trajectory_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total_distance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;total_distance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;average_speed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;average_speed&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;packed_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pack_trajectory_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trajectory_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8383&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trajectory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;packed_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pack_trajectory_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trajectory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Pack trajectory data json format&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trajectory&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The labels (total distance and average speed) are stored alongside each record. You can later filter or replicate records based on these values.&lt;/p&gt;

&lt;p&gt;Once data is written, you'll see the bucket populate in ReductStore:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpc97lo9ixgwt9qw47fvs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpc97lo9ixgwt9qw47fvs.png" alt="ReductStore Bucket" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Query by Label
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query_by_label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label_value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8383&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;entry_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;label_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$gt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;label_value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="c1"&gt;# Do something with the record
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove the &lt;em&gt;'when'&lt;/em&gt; condition to return all records with no filtering.&lt;/p&gt;

&lt;h4&gt;
  
  
  Main Function
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;create_trajectory_bucket&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;store_trajectory_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;label_query_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;query_by_label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trajectory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;total_distance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HIGH_DISTANCE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;label_query_result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Data queried by label: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label_query_result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Robotics data pipelines don't have to be a mess of bag files, overpriced cloud storage, and custom scripts duct taped together. ReductStore covers ingestion, retention, replication, and querying in one place, with direct integrations into ROS, Foxglove, Grafana, S3, and Azure.&lt;/p&gt;

&lt;p&gt;Start with what you need, and add the rest as your system grows. Check out &lt;a href="https://www.reduct.store/" rel="noopener noreferrer"&gt;&lt;strong&gt;reduct.store&lt;/strong&gt;&lt;/a&gt; or read through the &lt;a href="https://www.reduct.store/docs/how-does-it-work" rel="noopener noreferrer"&gt;&lt;strong&gt;documentation&lt;/strong&gt;&lt;/a&gt; to get going.&lt;/p&gt;




&lt;p&gt;If you have any questions or comments, feel free to use the &lt;a href="https://community.reduct.store/signup" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore Community Forum&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>robotics</category>
    </item>
    <item>
      <title>ReductStore v1.19: Open Data Backbone for Robotics and ROS</title>
      <dc:creator>Alexey Timin</dc:creator>
      <pubDate>Wed, 08 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/reductstore/reductstore-v119-open-data-backbone-for-robotics-and-ros-1efk</link>
      <guid>https://dev.to/reductstore/reductstore-v119-open-data-backbone-for-robotics-and-ros-1efk</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frcfkuziyyn90yrcvntry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frcfkuziyyn90yrcvntry.png" alt="ReductStore v1.19.0 Released" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ReductStore &lt;a href="https://github.com/reductstore/reductstore/releases/tag/v1.19.0" rel="noopener noreferrer"&gt;&lt;strong&gt;1.19.0&lt;/strong&gt;&lt;/a&gt; is now available. This release extends the storage model for robotics and telemetry workloads and introduces new integration points for ROS and Zenoh.&lt;/p&gt;

&lt;p&gt;To download the latest release, visit the &lt;a href="https://www.reduct.store/download" rel="noopener noreferrer"&gt;&lt;strong&gt;Download Page&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's new in 1.19.0?&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_19_0-released#whats-new-in-1190" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The first major change in v1.19 is licensing. &lt;a href="https://dev.to/anthonycvn/reductstore-core-adopts-apache-20-license-4j0k-temp-slug-3253510"&gt;&lt;strong&gt;ReductStore Core is now open source under Apache 2.0&lt;/strong&gt;&lt;/a&gt;, which makes the core database easier to evaluate, integrate, and extend in production systems.&lt;/p&gt;

&lt;p&gt;The second major change is the data model. ReductStore now supports hierarchical entry names, similar to ROS topics, and adds entry attachments for schemas and metadata. This makes it possible to represent structured robotics data without flattening topic hierarchies or moving context into external systems.&lt;/p&gt;

&lt;p&gt;The release also introduces a &lt;a href="https://www.reduct.store/docs/integrations/zenoh" rel="noopener noreferrer"&gt;&lt;strong&gt;native Zenoh API&lt;/strong&gt;&lt;/a&gt; for direct ingestion and querying over Zenoh, and &lt;a href="https://www.reduct.store/docs/reduct-bridge" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductBridge&lt;/strong&gt;&lt;/a&gt; for ROS1 and ROS2 integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nested Data Model with Attachments&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_19_0-released#nested-data-model-with-attachments" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The new hierarchical data model lets you organize data in a path-based structure, similar to ROS topics, Zenoh key expressions, or MQTT topics. Instead of relying on a flat namespace, ReductStore can now store data in a form that matches the structure used by upstream systems.&lt;/p&gt;

&lt;p&gt;Each entry can also include attachments for schemas and metadata. These attachments preserve the context required by downstream tooling without changing the record payload itself. For example, the &lt;a href="https://www.reduct.store/docs/extensions/official/ros-ext" rel="noopener noreferrer"&gt;&lt;strong&gt;ROS Extension&lt;/strong&gt;&lt;/a&gt; can use them to decode serialized ROS messages and export them to MCAP files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk616iksoj3aiertofuq5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk616iksoj3aiertofuq5.png" alt="Web Console with Nested Data Model" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Native Zenoh API&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_19_0-released#native-zenoh-api" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Zenoh is increasingly used in robotics and edge environments as a low-overhead protocol for distributed data exchange. With the &lt;a href="https://www.reduct.store/docs/integrations/zenoh" rel="noopener noreferrer"&gt;&lt;strong&gt;native Zenoh API&lt;/strong&gt;&lt;/a&gt;, ReductStore can participate directly in Zenoh-based systems without requiring an additional bridge or adapter.&lt;/p&gt;

&lt;p&gt;You can start ReductStore with the Zenoh API enabled using a minimal Docker configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull reduct/store:v1.19.0docker run 
 &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="s2"&gt;"RS_ZENOH_ENABLED=ON"&lt;/span&gt; 
 &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="s2"&gt;"RS_ZENOH_CONFIG={}"&lt;/span&gt; 
 &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="s2"&gt;"RS_ZENOH_SUB_KEYEXPRS=**"&lt;/span&gt; 
 &lt;span class="nt"&gt;-p&lt;/span&gt; 8383:8383 &lt;span class="nt"&gt;-p&lt;/span&gt; 36597:36597 &lt;span class="nt"&gt;-p&lt;/span&gt; 7446:7446 reduct/store:v1.19.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once enabled, the API allows you to write data to ReductStore directly over Zenoh. If a sample includes a JSON attachment, ReductStore stores it as record labels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;zenoh&lt;/span&gt;

&lt;span class="n"&gt;KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;factory/line1/camera&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PAYLOAD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;binary payload&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;LABELS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;robot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alpha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;zenoh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zenoh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;PAYLOAD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LABELS&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also query data through Zenoh. For conditional queries, pass a &lt;code&gt;when&lt;/code&gt; expression in the query attachment. If you want all matching records returned individually, use &lt;code&gt;zenoh.ConsolidationMode.NONE&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;zenoh&lt;/span&gt;

&lt;span class="n"&gt;KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;factory/line1/when-query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;CONSOLIDATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zenoh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConsolidationMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NONE&lt;/span&gt;
&lt;span class="n"&gt;attachment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;when&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$eq&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}}}).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;zenoh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zenoh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;replies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;reply&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;attachment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;consolidation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CONSOLIDATION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_bytes&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Data ingested through Zenoh is stored in ReductStore like data written through the HTTP API, so it remains available for querying, replication, and downstream tools and extensions.&lt;/p&gt;

&lt;h3&gt;
  
  
  ReductBridge for ROS Integration&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_19_0-released#reductbridge-for-ros-integration" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This release also introduces &lt;a href="https://github.com/reductstore/reduct-bridge" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductBridge&lt;/strong&gt;&lt;/a&gt;, a new project for integrating ReductStore with ROS1 and ROS2. ReductBridge automatically labels ROS messages and stores related schemas and metadata as attachments.&lt;/p&gt;

&lt;p&gt;This makes raw ROS payloads usable after ingestion: they can be decoded later with the ROS Extension or exported to MCAP files. ROS support is the first target, but the same pattern can be extended to operating system metrics, logs, and other telemetry to build a unified storage layer with consistent labeling and metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_19_0-released#whats-next" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The next area of work is data compression and storage efficiency, especially for robotics workloads where payload sizes and formats vary significantly. We plan to introduce backend compression optimized for ReductStore's storage model, where many records are packed into a single block.&lt;/p&gt;

&lt;p&gt;We also plan to store metadata in Parquet format. This should improve query efficiency, make metadata accessible without scanning the underlying data blocks, and simplify integration with analytics and data lake tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compatibility and Migration&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_19_0-released#compatibility-and-migration" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Starting with v1.19, ReductStore Docker images no longer run as &lt;code&gt;root&lt;/code&gt; for security reasons. If you deploy with Docker, make sure the mounted data directory is writable by UID/GID &lt;code&gt;10001:10001&lt;/code&gt; before upgrading.&lt;/p&gt;




&lt;p&gt;If you have questions or feedback, join the &lt;a href="https://community.reduct.store/signup" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore Community&lt;/strong&gt;&lt;/a&gt; forum.&lt;/p&gt;

&lt;p&gt;Thanks for using &lt;a href="https://www.reduct.store/" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore&lt;/strong&gt;&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>news</category>
      <category>reductstore</category>
      <category>robotics</category>
      <category>ros</category>
    </item>
    <item>
      <title>ReductStore Core Adopts Apache 2.0 License</title>
      <dc:creator>Alexey Timin</dc:creator>
      <pubDate>Tue, 17 Mar 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/reductstore/reductstore-core-adopts-apache-20-license-2lec</link>
      <guid>https://dev.to/reductstore/reductstore-core-adopts-apache-20-license-2lec</guid>
      <description>&lt;p&gt;Hello, everyone!&lt;/p&gt;

&lt;p&gt;Starting with &lt;strong&gt;ReductStore v1.19&lt;/strong&gt; , we are changing how we license and package the project. From this version onward, ReductStore is split into two editions: &lt;strong&gt;&lt;a href="https://github.com/reductstore/reductstore" rel="noopener noreferrer"&gt;ReductStore Core&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;ReductStore Pro&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Previously, ReductStore shipped as a single edition under &lt;strong&gt;BUSL-1.1 (Business Source License)&lt;/strong&gt;. With v1.19, the core database is open source under Apache 2.0, while Pro continues under commercial terms.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's changing&lt;a href="https://www.reduct.store/blog/news/reductstore-core-apache-2-0#whats-changing" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;With this release, &lt;strong&gt;&lt;a href="https://github.com/reductstore/reductstore" rel="noopener noreferrer"&gt;ReductStore Core&lt;/a&gt;&lt;/strong&gt; becomes the open-source foundation of the project and is now available under the &lt;strong&gt;Apache License 2.0&lt;/strong&gt;. It includes the database server and the core functionality that most users rely on for edge deployments and everyday data workflows.&lt;/p&gt;

&lt;p&gt;Alongside Core, we will continue offering &lt;strong&gt;ReductStore Pro&lt;/strong&gt; , which is distributed under a &lt;strong&gt;commercial license&lt;/strong&gt;. The Pro edition provides additional capabilities and support options for teams with more advanced requirements. See &lt;strong&gt;&lt;a href="https://www.reduct.store/pricing" rel="noopener noreferrer"&gt;Pricing&lt;/a&gt;&lt;/strong&gt; for a clear comparison of what is included in each edition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we're doing this&lt;a href="https://www.reduct.store/blog/news/reductstore-core-apache-2-0#why-were-doing-this" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We want to better support open source communities and make it easier to contribute to, integrate with, and build on top of &lt;strong&gt;&lt;a href="https://github.com/reductstore/reductstore" rel="noopener noreferrer"&gt;ReductStore Core&lt;/a&gt;&lt;/strong&gt;. Just as importantly, we want users to be able to run ReductStore on the edge without licensing restrictions for the core database use cases.&lt;/p&gt;

&lt;p&gt;At the same time, we want to keep a sustainable commercial model for companies that build more complex cloud setups or need advanced support and functionality around robotics and IIoT data formats. That is the role of ReductStore Pro.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for you&lt;a href="https://www.reduct.store/blog/news/reductstore-core-apache-2-0#what-this-means-for-you" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If you use the core database functionality, you can freely adopt, integrate, and distribute &lt;strong&gt;&lt;a href="https://github.com/reductstore/reductstore" rel="noopener noreferrer"&gt;ReductStore Core&lt;/a&gt;&lt;/strong&gt; under the Apache 2.0 license. Existing workflows and upgrades remain straightforward.&lt;/p&gt;

&lt;p&gt;For teams that rely on advanced functionality or require commercial support, &lt;strong&gt;ReductStore Pro&lt;/strong&gt; remains available as the supported commercial edition. We will clearly document which features belong to Core and which are part of Pro in the documentation and release notes.&lt;/p&gt;

&lt;p&gt;If you have questions or want to discuss which edition fits your use case, please reach out on the &lt;strong&gt;&lt;a href="https://community.reduct.store/" rel="noopener noreferrer"&gt;ReductStore Community&lt;/a&gt;&lt;/strong&gt; forum or via our &lt;strong&gt;&lt;a href="https://www.reduct.store/contact" rel="noopener noreferrer"&gt;contact page&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>news</category>
      <category>reductstore</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Air-Gapped Drone Data Operations with Delayed Sync and Auditability</title>
      <dc:creator>AnthonyCvn</dc:creator>
      <pubDate>Tue, 24 Feb 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/reductstore/air-gapped-drone-data-operations-with-delayed-sync-and-auditability-55ne</link>
      <guid>https://dev.to/reductstore/air-gapped-drone-data-operations-with-delayed-sync-and-auditability-55ne</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy9aq3u4rhjg760xmpz7u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy9aq3u4rhjg760xmpz7u.png" alt="Architecture for Air-Gapped Drone Data" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Drones in air-gapped environments produce a &lt;strong&gt;lot&lt;/strong&gt; of data (camera images, telemetry, logs, model outputs). Storing this data reliably on each drone and syncing it to a ground station later can be hard. &lt;strong&gt;ReductStore&lt;/strong&gt; makes this easier: it's a lightweight, time-series object store that works offline and replicate data when a connection is available.&lt;/p&gt;

&lt;p&gt;This guide explains a simple setup where each drone stores data locally with labels, replicates records to a ground station based on what it detects, and keeps a clear audit trail of what was captured and replicated.&lt;/p&gt;

&lt;p&gt;What we'll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.reduct.store/blog/air-gapped-drone-data#drone-to-ground-architecture" rel="noopener noreferrer"&gt;&lt;strong&gt;Drone-to-Ground Architecture&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reduct.store/blog/air-gapped-drone-data#setting-up-the-drone-node" rel="noopener noreferrer"&gt;&lt;strong&gt;Setting Up the Drone Node&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reduct.store/blog/air-gapped-drone-data#storing-drone-data-with-labels" rel="noopener noreferrer"&gt;&lt;strong&gt;Storing Drone Data with Labels&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reduct.store/blog/air-gapped-drone-data#setting-up-selective-replication" rel="noopener noreferrer"&gt;&lt;strong&gt;Setting Up Selective Replication&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reduct.store/blog/air-gapped-drone-data#querying-for-audit-reports" rel="noopener noreferrer"&gt;&lt;strong&gt;Querying for Audit Reports&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reduct.store/blog/air-gapped-drone-data#why-this-setup-works-well-for-drones" rel="noopener noreferrer"&gt;&lt;strong&gt;Why This Setup Works Well for Drones&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Drone-to-Ground Architecture&lt;a href="https://www.reduct.store/blog/air-gapped-drone-data#drone-to-ground-architecture" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The architecture has three main components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Each drone runs a small ReductStore server&lt;/strong&gt; to save images and telemetry locally on disk (this lets the drone operate fully offline).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A ground station runs a ReductStore instance&lt;/strong&gt; that receives replicated data for analysis and archiving.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ReductStore replication tasks&lt;/strong&gt; copy data from drone to ground based on labels and conditions (e.g., only records flagged as anomalies, plus context around them).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh7h80aubzv31qrce5xw5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh7h80aubzv31qrce5xw5.png" alt="Drone Workflow" width="800" height="785"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each drone pushes its data to the ground whenever it is connected. If the network disconnects, replication continues when the drone reconnects. This approach provides offline capability, lets you decide which data to replicate, and keeps a clear record of what happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Drone Node&lt;a href="https://www.reduct.store/blog/air-gapped-drone-data#setting-up-the-drone-node" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Start by running ReductStore on the drone's companion computer. Here is a minimal &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;services:
  reductstore:
    image: reduct/store:latest
    ports:
      - &lt;span class="s2"&gt;"8383:8383"&lt;/span&gt;
    environment:
      RS_API_TOKEN: &amp;lt;DRONE_TOKEN&amp;gt;
      RS_BUCKET_1_NAME: mission-data
      RS_BUCKET_1_QUOTA_TYPE: FIFO
      RS_BUCKET_1_QUOTA_SIZE: 10000000000 &lt;span class="c"&gt;# 10 GB&lt;/span&gt;
    volumes:
      - ./data:/data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This starts a ReductStore server with a &lt;code&gt;mission-data&lt;/code&gt; bucket that uses FIFO retention. Old data is deleted only when the 10 GB limit is reached, so the drone always keeps as much history as possible.&lt;/p&gt;

&lt;p&gt;FIFO quota is volume-based, not time-based. This means data is only deleted when disk space runs out, not after a fixed time period. This is important for drones that may sit idle between missions.&lt;/p&gt;

&lt;p&gt;If you prefer Snap instead of Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;install &lt;/span&gt;reductstore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That starts a ReductStore server on port &lt;code&gt;8383&lt;/code&gt; by default. You can then create the bucket using the &lt;strong&gt;&lt;a href="https://github.com/reductstore/reduct-cli" rel="noopener noreferrer"&gt;Reduct CLI&lt;/a&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;reduct-cli &lt;span class="nb"&gt;alias &lt;/span&gt;add drone &lt;span class="nt"&gt;-L&lt;/span&gt; http://localhost:8383 &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;DRONE_TOKEN&amp;gt;"&lt;/span&gt;
reduct-cli bucket create drone/mission-data &lt;span class="nt"&gt;--quota-type&lt;/span&gt; FIFO &lt;span class="nt"&gt;--quota-size&lt;/span&gt; 10GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Storing Drone Data with Labels&lt;a href="https://www.reduct.store/blog/air-gapped-drone-data#storing-drone-data-with-labels" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Use labels to tag every record with mission context. This is what makes selective replication and auditing possible later. Here is an example using the Python SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;reduct&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8383&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;DRONE_TOKEN&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mission-data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Read a camera frame
&lt;/span&gt;        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;frame.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;checksum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# microseconds
&lt;/span&gt;
        &lt;span class="c1"&gt;# Write with mission labels
&lt;/span&gt;        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;camera&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mission_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;m-2026-02-24-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;platform_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uav-07&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anomaly&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.95&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;checksum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;checksum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image/jpeg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Write telemetry as a CSV batch
&lt;/span&gt;        &lt;span class="n"&gt;csv_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ts,lat,lon,alt,speed&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;csv_data&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1708771200000000,47.3769,8.5417,450.2,12.5&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;csv_data&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1708771201000000,47.3770,8.5418,451.0,12.8&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telemetry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;csv_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mission_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;m-2026-02-24-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;platform_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uav-07&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anomaly&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;checksum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csv_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;anomaly&lt;/code&gt; label is important: it lets the replication task decide what to sync based on what the drone actually sees. For example, if the drone detects something unusual (an object, a warning, a low confidence score), it sets &lt;code&gt;anomaly=true&lt;/code&gt;. The replication task can then automatically sync that record — plus the context around it.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;checksum&lt;/code&gt; label gives you a simple way to verify data integrity during audits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Selective Replication&lt;a href="https://www.reduct.store/blog/air-gapped-drone-data#setting-up-selective-replication" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Once the drone connects to a trusted network, replication sends only the relevant records to the ground station. The simplest approach is to replicate based on a label, for example only records where the drone detected an anomaly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;reduct-cli &lt;span class="nb"&gt;alias &lt;/span&gt;add drone &lt;span class="nt"&gt;-L&lt;/span&gt; http://localhost:8383 &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;DRONE_TOKEN&amp;gt;"&lt;/span&gt;

reduct-cli replica create drone/mission-to-ground &lt;span class="se"&gt;\&lt;/span&gt;
    mission-data &lt;span class="se"&gt;\&lt;/span&gt;
    https://&amp;lt;GROUND_TOKEN&amp;gt;@&amp;lt;ground-address&amp;gt;/drone-data &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--when&lt;/span&gt; &lt;span class="s1"&gt;'{"&amp;amp;anomaly": {"$eq": "true"}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a replication task that copies only records where &lt;code&gt;anomaly=true&lt;/code&gt; from the drone's &lt;code&gt;mission-data&lt;/code&gt; bucket to the ground station.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replicating with context (before and after)&lt;a href="https://www.reduct.store/blog/air-gapped-drone-data#replicating-with-context-before-and-after" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;In many cases, you don't just want the anomaly record itself — you also want to see what happened &lt;strong&gt;before&lt;/strong&gt; it. ReductStore supports this with the &lt;code&gt;#ctx_before&lt;/code&gt; and &lt;code&gt;#ctx_after&lt;/code&gt; directives. For example, to replicate each anomaly record plus 30 seconds of data before it and 10 seconds after:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"&amp;amp;anomaly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"$eq"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"#ctx_before"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"30s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"#ctx_after"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10s"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is powerful for drone operations: imagine the drone's onboard model detects an unexpected object. ReductStore will replicate that record &lt;strong&gt;and&lt;/strong&gt; the 30 seconds of camera frames leading up to the detection, so the ground team can review what happened.&lt;/p&gt;

&lt;p&gt;You can provision this directly in Docker using environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;services:
  reductstore:
    image: reduct/store:latest
    ports:
      - &lt;span class="s2"&gt;"8383:8383"&lt;/span&gt;
    environment:
      RS_API_TOKEN: &amp;lt;DRONE_TOKEN&amp;gt;
      RS_BUCKET_1_NAME: mission-data
      RS_BUCKET_1_QUOTA_TYPE: FIFO
      RS_BUCKET_1_QUOTA_SIZE: 10000000000
      RS_REPLICATION_1_NAME: mission-to-ground
      RS_REPLICATION_1_SRC_BUCKET: mission-data
      RS_REPLICATION_1_DST_BUCKET: drone-data
      RS_REPLICATION_1_DST_HOST: https://&amp;lt;ground-address&amp;gt;
      RS_REPLICATION_1_DST_TOKEN: &amp;lt;GROUND_TOKEN&amp;gt;
      RS_REPLICATION_1_WHEN: |
        &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"&amp;amp;anomaly"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;eq"&lt;/span&gt;: &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;,
          &lt;span class="s2"&gt;"#ctx_before"&lt;/span&gt;: &lt;span class="s2"&gt;"30s"&lt;/span&gt;,
          &lt;span class="s2"&gt;"#ctx_after"&lt;/span&gt;: &lt;span class="s2"&gt;"10s"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    volumes:
      - ./data:/data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup, the drone can operate fully offline. Replication runs automatically when a connection is available and waits when it's not. It's also possible to pause replication tasks if needed. And because context is included, the ground team always has enough data to understand what triggered the event.&lt;/p&gt;

&lt;h2&gt;
  
  
  Querying for Audit Reports&lt;a href="https://www.reduct.store/blog/air-gapped-drone-data#querying-for-audit-reports" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;After a mission, you can query the ground station to check what was captured and replicated. Here is a simple example that lists all records from a specific mission:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import asyncio
from reduct import Client


async def main():
    async with Client("https://&amp;lt;ground-address&amp;gt;", api_token="&amp;lt;GROUND_TOKEN&amp;gt;") as client:
        bucket = await client.get_bucket("drone-data")

        # Query all camera records from a specific mission
        async for record in bucket.query(
            "camera",
            when={"&amp;amp;mission_id": {"$eq": "m-2026-02-24-01"}},
        ):
            print(
                f"ts={record.timestamp}, "
                f"anomaly={record.labels.get('anomaly')}, "
                f"checksum={record.labels.get('checksum')}, "
                f"size={record.size}"
            )


asyncio.run(main())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a clear log of every record in that mission: timestamp, anomaly flag, checksum, and size. You can use this to verify that all expected data arrived on the ground side.&lt;/p&gt;

&lt;p&gt;To go further, compare the checksums on the drone with the ground side to confirm nothing was altered during transfer. You can also check the error logs of the replication task to see if any records failed to replicate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Setup Works Well for Drones&lt;a href="https://www.reduct.store/blog/air-gapped-drone-data#why-this-setup-works-well-for-drones" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Drones have specific constraints that general purpose databases don't handle well. Here is what makes this setup practical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full offline operation.&lt;/strong&gt; Drones store everything locally and don't need a network connection during the mission. Data is safe on disk until sync happens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic sync when connected.&lt;/strong&gt; When the drone lands or connects to a trusted network, replication picks up where it left off. No manual file transfers, no rsync scripts, no USB sticks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart replication with context.&lt;/strong&gt; You don't have to sync everything. The replication task filters by labels and can include past records around each event using &lt;code&gt;#ctx_before&lt;/code&gt;. The ground team gets exactly what they need to understand what happened.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disk never fills up unexpectedly.&lt;/strong&gt; FIFO retention removes the oldest data only when the disk is full. The drone always keeps as much history as possible without running out of space mid mission.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy auditing.&lt;/strong&gt; Every record has a timestamp, labels, and a checksum. After a mission, you can query the ground station and verify exactly what was captured and what was synced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store any file type.&lt;/strong&gt; Camera frames, telemetry CSV, logs, MCAP files, model outputs. Everything goes into the same system with the same interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps&lt;a href="https://www.reduct.store/blog/air-gapped-drone-data#next-steps" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If you want to go deeper, check out these articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/reductstore/distributed-storage-in-mobile-robotics-1oe0"&gt;Distributed Storage in Mobile Robotics&lt;/a&gt;&lt;/strong&gt; for a similar setup with mobile robots and S3 cloud backend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/reductstore/how-to-store-and-manage-robotic-data-3ojp"&gt;How to Store and Manage Robotics Data&lt;/a&gt;&lt;/strong&gt; for a broader look at ReductStore features for robotics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.reduct.store/docs/guides/data-replication" rel="noopener noreferrer"&gt;Data Replication Guide&lt;/a&gt;&lt;/strong&gt; for the full documentation on replication tasks, filters, and modes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.reduct.store/docs/conditional-query" rel="noopener noreferrer"&gt;Conditional Query Reference&lt;/a&gt;&lt;/strong&gt; for all available conditional query operators you can use in replication filters and queries&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I hope you found this article helpful! If you have any questions or feedback, don't hesitate to reach out on our &lt;a href="https://community.reduct.store/" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore Community&lt;/strong&gt;&lt;/a&gt; forum.&lt;/p&gt;

</description>
      <category>aerospace</category>
      <category>robotics</category>
      <category>database</category>
    </item>
    <item>
      <title>ReductStore v1.18.0 Released with Resilient Deployments and the Multi-entry API</title>
      <dc:creator>Alexey Timin</dc:creator>
      <pubDate>Thu, 05 Feb 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/reductstore/reductstore-v1180-released-with-resilient-deployments-and-the-multi-entry-api-3coh</link>
      <guid>https://dev.to/reductstore/reductstore-v1180-released-with-resilient-deployments-and-the-multi-entry-api-3coh</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48hovxrs663gjezd39is.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F48hovxrs663gjezd39is.png" alt="ReductStore v1.18.0 Released" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are pleased to announce the release of the latest minor version of &lt;a href="https://www.reduct.store/" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://github.com/reductstore/reductstore/releases/tag/v1.18.0" rel="noopener noreferrer"&gt;&lt;strong&gt;1.18.0&lt;/strong&gt;&lt;/a&gt;. ReductStore is a high-performance storage and streaming solution designed for storing and managing large volumes of historical data.&lt;/p&gt;

&lt;p&gt;To download the latest released version, please visit our &lt;a href="https://www.reduct.store/download" rel="noopener noreferrer"&gt;&lt;strong&gt;Download Page&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's new in 1.18.0?&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_18_0-released#whats-new-in-1180" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In this release, we have added support for resilient deployments to build a more robust, fault-tolerant, and highly available ReductStore cluster. Now, you can implement hot-standby configurations, automatic failover, and seamless recovery to ensure uninterrupted service even in the face of hardware failures or network issues. You can also elastically scale read-only nodes to handle increased read workloads without impacting the performance of the primary nodes.&lt;/p&gt;

&lt;p&gt;Additionally, we have introduced a new Multi-entry API that allows you to efficiently manage and query multiple entries in a single request. This API is designed to optimize performance and reduce latency when working with large datasets, making it easier to retrieve and manipulate data in bulk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resilient Deployments&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_18_0-released#resilient-deployments" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In ReductStore v1.18.0, resilient deployments are now a first-class feature. Using the &lt;code&gt;RS_INSTANCE_ROLE&lt;/code&gt; setting, you can build topologies that keep your ingestion endpoint available during node failures and scale reads independently from writes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hot standby (active-passive) for write availability&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_18_0-released#hot-standby-active-passive-for-write-availability" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Run two nodes against the same backend (a shared filesystem or the same remote backend). Only one node is active at a time: the active node holds a lock file and refreshes it, while the standby waits and takes over when the lock becomes stale.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa8f1f34bkyntt2anxr01.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa8f1f34bkyntt2anxr01.webp" alt="ReductStore Hot standby deployment" width="800" height="794"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;RS_INSTANCE_ROLE=PRIMARY&lt;/code&gt; for the active node and &lt;code&gt;RS_INSTANCE_ROLE=SECONDARY&lt;/code&gt; for the standby.&lt;/li&gt;
&lt;li&gt;Put both nodes behind a single virtual endpoint (load balancer / reverse proxy).&lt;/li&gt;
&lt;li&gt;Route traffic only to the node that returns &lt;code&gt;200 OK&lt;/code&gt; on &lt;code&gt;GET /api/v1/ready&lt;/code&gt; (the inactive node returns &lt;code&gt;503&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Tune failover behavior with &lt;code&gt;RS_LOCK_FILE_TTL&lt;/code&gt; (how long the standby waits) and &lt;code&gt;RS_LOCK_FILE_TIMEOUT&lt;/code&gt; (how long a node waits to acquire the lock).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To avoid split-brain writes, don’t run both nodes in &lt;code&gt;STANDALONE&lt;/code&gt; mode against the same dataset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read-only replicas for read scaling&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_18_0-released#read-only-replicas-for-read-scaling" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Add one or more &lt;code&gt;REPLICA&lt;/code&gt; nodes to serve queries from the same dataset. Replicas never write and periodically refresh bucket metadata and indexes from the backend, so newly written data may appear with a small delay.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fytiv5b84kh0cyzy1clox.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fytiv5b84kh0cyzy1clox.webp" alt="ReductStore Read-only replicas" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route writes to a dedicated ingestion node (or the active node in a hot-standby pair).&lt;/li&gt;
&lt;li&gt;Route reads to replicas to scale query workloads horizontally.&lt;/li&gt;
&lt;li&gt;Tune staleness with &lt;code&gt;RS_ENGINE_REPLICA_UPDATE_INTERVAL&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an end-to-end walkthrough (including S3-based standalone, active-passive, and replicas), see the &lt;strong&gt;&lt;a href="https://www.reduct.store/docs/integrations/s3" rel="noopener noreferrer"&gt;S3 Backend&lt;/a&gt;&lt;/strong&gt; tutorial. For architecture options and operational notes, see the &lt;strong&gt;&lt;a href="https://www.reduct.store/docs/guides/disaster-recovery" rel="noopener noreferrer"&gt;Disaster Recovery&lt;/a&gt;&lt;/strong&gt; guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-entry API&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_18_0-released#multi-entry-api" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The new &lt;strong&gt;Multi-entry API&lt;/strong&gt; makes it possible to work with multiple entries in a single request. In practice, this is most useful for &lt;strong&gt;querying&lt;/strong&gt; : instead of running one query per sensor/stream and merging results on the client, you can request all the entries you need at once and process a single result stream (each returned record includes its &lt;code&gt;entry&lt;/code&gt; name).&lt;/p&gt;

&lt;p&gt;Here is a Python example using &lt;code&gt;reduct-py&lt;/code&gt; to query multiple entries in one call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asynciofrom&lt;/span&gt; &lt;span class="n"&gt;reduct&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Clientasync&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8383&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Query multiple entries in a single request (since ReductStore v1.18). # You can mix exact names and wildcards. entries = ["sensor-*", "camera"] async for record in bucket.query( entries, start="2026-02-05T10:00:00Z", stop="2026-02-05T10:05:00Z", when={"&amp;amp;score": {"$gte": 10}}, ): payload = await record.read_all() print(record.entry, record.timestamp, len(payload))if __name__ == " __main__": asyncio.run(main())
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What’s Next&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_18_0-released#whats-next" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We’re already working on the next improvements to make ReductStore easier to integrate into real-world data pipelines:&lt;/p&gt;

&lt;h3&gt;
  
  
  Native Zenoh API&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_18_0-released#native-zenoh-api" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Zenoh is becoming a common choice for data exchange in distributed, edge-first systems (robotics, industrial IoT, and telemetry). In upcoming releases, we plan to add a &lt;strong&gt;native Zenoh API&lt;/strong&gt; so ReductStore can join Zenoh networks seamlessly.&lt;/p&gt;

&lt;p&gt;This will make it easier to ingest and serve data directly through Zenoh—without custom bridges—so your storage layer fits naturally into existing Zenoh-based deployments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Entry attachments (metadata)&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_18_0-released#entry-attachments-metadata" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Today, labels work well for filtering and replication, but many projects also need structured metadata tied to an entry itself: data format, schema version, units, encoding, calibration details, and other context.&lt;/p&gt;

&lt;p&gt;We plan to introduce &lt;strong&gt;attachments for entries&lt;/strong&gt; , allowing you to store and retrieve this kind of metadata alongside your data streams, making datasets more self-describing and easier to consume across teams and tools.&lt;/p&gt;




&lt;p&gt;I hope you find those new features useful. If you have any questions or feedback, don’t hesitate to use the &lt;a href="https://community.reduct.store/signup" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore Community&lt;/strong&gt;&lt;/a&gt; forum.&lt;/p&gt;

&lt;p&gt;Thanks for using &lt;a href="https://www.reduct.store/" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore&lt;/strong&gt;&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>news</category>
    </item>
    <item>
      <title>Comparing Data Management Tools for Robotics</title>
      <dc:creator>AnthonyCvn</dc:creator>
      <pubDate>Thu, 04 Dec 2025 09:26:57 +0000</pubDate>
      <link>https://dev.to/reductstore/comparing-data-management-tools-for-robotics-5a61</link>
      <guid>https://dev.to/reductstore/comparing-data-management-tools-for-robotics-5a61</guid>
      <description>&lt;p&gt;Modern robots collect a lot of data from sensors, cameras, logs, and system outputs. Managing this data well is important for debugging, performance tracking, and training machine learning models.&lt;/p&gt;

&lt;p&gt;Over the past few years, we've been building a storage system from scratch. As part of that work, we spoke with many robotics teams across different industries to understand their challenges with data management.&lt;/p&gt;

&lt;p&gt;Here's what we heard often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only a subset of what robots generate is actually useful&lt;/li&gt;
&lt;li&gt;Network connections are not always stable or fast&lt;/li&gt;
&lt;li&gt;On-device storage is limited (hard drive swaps is not practical)&lt;/li&gt;
&lt;li&gt;Teams rely on manual workflows with scripts and raw files&lt;/li&gt;
&lt;li&gt;It's hard to find and extract the right data later&lt;/li&gt;
&lt;li&gt;ROS bag files get large quickly and are difficult to manage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, we compare four tools built to handle robotics data: &lt;strong&gt;ReductStore&lt;/strong&gt;, &lt;strong&gt;Foxglove&lt;/strong&gt;, &lt;strong&gt;Rerun&lt;/strong&gt;, and &lt;strong&gt;Heex&lt;/strong&gt;. We look at how they work, what they're good at, and which use cases they support.&lt;/p&gt;

&lt;p&gt;If you're working with robots and need to organize, stream, or store data more effectively, this overview should help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Criteria for Comparison
&lt;/h2&gt;

&lt;p&gt;When picking a data tool for robotics, focus on these areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Types&lt;/strong&gt;
Robotics is a large field with many sensor types. The tool should support the data you work with, such as:

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Telemetry:&lt;/em&gt; Lightweight (GPS, IMU, joints), ideal for monitoring.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Downsampled Data:&lt;/em&gt; Lower-rate images or lidar for incident review without high storage cost.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Full-Resolution:&lt;/em&gt; Raw sensor outputs for deep debugging or training. This is storage-intensive but essential for some applications.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Integration&lt;/strong&gt;
The tool should work with what you already use, like ROS, Grafana, MQTT, cloud platforms (S3, Azure, Google Cloud), and your development environment to avoid extra glue code and simplify workflows.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Performance and Scalability&lt;/strong&gt;
Data must move quickly (both locally and to the cloud). Large files or slow queries can block robots or delay analysis.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Ease of Use and APIs&lt;/strong&gt;
A simple UI and solid API support make it easier to automate, scale, and adapt the tool to different use cases.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tool Overviews
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ReductStore
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03tsk44ak9or6allpkk1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03tsk44ak9or6allpkk1.png" alt="ReductStore Dashboard" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ReductStore&lt;/strong&gt; is a storage and streaming system designed for robotics data. It works both on the robot and in central storage (on-premise/self-hosted or in the cloud) with the same interface and SDKs (in Python, C++, Go, Javascript/TypeScript or Rust). That means your code stays the same whether you're reading local, remote data (or creating a browser-based dashboard).&lt;/p&gt;

&lt;p&gt;To move data to the cloud, ReductStore uses &lt;strong&gt;conditional replication&lt;/strong&gt;. You can define rules to upload only certain records: by label, rules, or event. For example, replicate all incident data, or just 1 out of 10 entries for routine monitoring.&lt;/p&gt;

&lt;p&gt;ReductStore handles storage limits on edge devices with &lt;strong&gt;FIFO retention&lt;/strong&gt;. Old data is deleted only when the device is full. Each bucket can have different rules, so you can keep more images and less telemetry, for example.&lt;/p&gt;

&lt;p&gt;With an &lt;strong&gt;S3 backend&lt;/strong&gt;, ReductStore batches small records together before uploading. This cuts down the number of requests and lowers cloud storage costs. For observability, you can connect &lt;strong&gt;Grafana&lt;/strong&gt; to ReductStore to create dashboards with system metrics and sensor data. For MCAP files, ReductStore supports shareable query links that open directly in &lt;strong&gt;Foxglove v1/v2&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It also lets you &lt;strong&gt;filter or merge records server-side&lt;/strong&gt;. For example, you can pull all temperature readings above a threshold over a time range without downloading full datasets.&lt;/p&gt;

&lt;p&gt;Want more technical detail? Check out &lt;a href="https://www.reduct.store/blog/database-for-robotics" rel="noopener noreferrer"&gt;&lt;strong&gt;The Missing Database for Robotics Is Out&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Foxglove and MCAP
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqkvdrb2pukv2h4k9jwpu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqkvdrb2pukv2h4k9jwpu.png" alt="Foxglove Dashboard" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Foxglove&lt;/strong&gt; is a browser-based visualization and observability tool for robotics. It supports &lt;strong&gt;ROS 1, ROS 2&lt;/strong&gt;, and &lt;strong&gt;MCAP logs&lt;/strong&gt;, and handles data types like telemetry, camera feeds, lidar, and depth maps.&lt;/p&gt;

&lt;p&gt;It uses &lt;strong&gt;MCAP&lt;/strong&gt;, an open-source log format built for robotics, to store high-resolution data efficiently. You can explore MCAP files interactively in &lt;strong&gt;Foxglove Studio&lt;/strong&gt; or stream them programmatically.&lt;/p&gt;

&lt;p&gt;Foxglove provides an &lt;strong&gt;agent&lt;/strong&gt; that detects new MCAP files on the robot and uploads them to the cloud automatically. This requires robots to record short rosbag segments (typically a few minutes each) which are closed and rotated continuously.&lt;/p&gt;

&lt;p&gt;It integrates natively with &lt;strong&gt;ROS topics, services, and actions&lt;/strong&gt;, and offers &lt;strong&gt;WebSocket and REST APIs&lt;/strong&gt;. It also connects to major cloud providers like &lt;strong&gt;AWS, Azure,&lt;/strong&gt; and &lt;strong&gt;Google Cloud&lt;/strong&gt; for scalable storage.&lt;/p&gt;

&lt;p&gt;The interface is built for time-series and sensor data, with interactive 2D/3D views, plots, and drag-and-drop panels for quick setup and review.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rerun
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2whlpk1u114zfrutvs3f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2whlpk1u114zfrutvs3f.png" alt="Rerun Dashboard" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rerun&lt;/strong&gt; is an open-source visualization solution for time-series and multimodal data. It supports data types like images, point clouds, lidar, depth maps, tensors, and other sensor streams.&lt;/p&gt;

&lt;p&gt;Its main strength is combining flexible logging with a fast, built-in 3D viewer designed for robotics and extended reality (XR) applications. For large datasets, Rerun provides a &lt;strong&gt;column-oriented API&lt;/strong&gt; to speed up ingestion and reduce memory usage. It also uses efficient internal structures to minimize allocations and optimize performance on edge devices.&lt;/p&gt;

&lt;p&gt;Rerun doesn't offer native ROS integration yet, but it can be used in ROS projects by adding custom logging to nodes.&lt;/p&gt;

&lt;p&gt;You can embed Rerun in &lt;strong&gt;Jupyter notebooks&lt;/strong&gt; or web pages, and use loggers for &lt;strong&gt;Python, Rust, and C++&lt;/strong&gt; to stream data into the viewer.&lt;/p&gt;

&lt;p&gt;The UI is built for &lt;strong&gt;real-time 3D exploration&lt;/strong&gt;, with overlays and live tracking that make it easy to inspect different data types in the same visual space.&lt;/p&gt;

&lt;h3&gt;
  
  
  Heex
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsc01tbs5lqwlhxkl70zz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsc01tbs5lqwlhxkl70zz.png" alt="Heex Dashboard" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Heex&lt;/strong&gt; is a data capture and review platform for autonomous systems that focuses on collecting only key moments—like errors or specific events instead of logging everything. This reduces bandwidth and storage needs while keeping important context.&lt;/p&gt;

&lt;p&gt;Robots using Heex record data continuously in short ROSbag segments. A small agent on the robot watches for triggers and uploads only selected segments to the cloud based on rules.&lt;/p&gt;

&lt;p&gt;A core feature is &lt;strong&gt;RDA (Resource and Data Automation)&lt;/strong&gt; for ROS 2, which automates what to record and when. Rules can be changed remotely without restarting the robot.&lt;/p&gt;

&lt;p&gt;Data is stored in &lt;strong&gt;ROSbag&lt;/strong&gt; and can be reviewed directly in the &lt;strong&gt;Heex dashboard&lt;/strong&gt;, which includes built-in open-source version of &lt;strong&gt;Foxglove&lt;/strong&gt;. This setup makes it easy to manage data across fleets and locations.&lt;/p&gt;

&lt;p&gt;Heex supports both &lt;strong&gt;ROS 1 and ROS 2&lt;/strong&gt;, and integrates with other systems through &lt;strong&gt;SDKs, APIs, and a CLI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The interface includes customizable dashboards to monitor sensor data, errors, and system status. Timelines and streams are easy to navigate for quick analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparative Analysis Table
&lt;/h2&gt;

&lt;p&gt;To help visualize the differences between the tools, here is a comparison table summarizing their main characteristics:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Tool&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Core Focus&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Data Types&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Storage Strategy&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Visualization&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;ROS Integration&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Unique Features&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ReductStore&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Time-series storage and streaming for robotics&lt;/td&gt;
&lt;td&gt;Telemetry, camera images, lidar, logs&lt;/td&gt;
&lt;td&gt;Local + cloud with same API (supports S3, FIFO retention, conditional replication)&lt;/td&gt;
&lt;td&gt;Grafana, Foxglove (via MCAP links)&lt;/td&gt;
&lt;td&gt;Integrated with ROS via extensions&lt;/td&gt;
&lt;td&gt;Filter/merge on server, batch uploads, topic-level control, efficient on edge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Foxglove&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Visualization and observability for robotics logs&lt;/td&gt;
&lt;td&gt;MCAP logs (telemetry, lidar, camera, depth)&lt;/td&gt;
&lt;td&gt;ROSbag short segments, auto-upload with agent&lt;/td&gt;
&lt;td&gt;Foxglove Studio (2D/3D, timeline, plots)&lt;/td&gt;
&lt;td&gt;Native ROS 1 &amp;amp; 2&lt;/td&gt;
&lt;td&gt;Drag-and-drop views, real-time stream inspection, cloud integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rerun&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Real-time 3D visualization of multimodal time-series data&lt;/td&gt;
&lt;td&gt;Images, lidar, point clouds, tensors, metrics&lt;/td&gt;
&lt;td&gt;User-defined logging; logs streamed into viewer or embedded in notebooks&lt;/td&gt;
&lt;td&gt;Built-in viewer (3D overlays, tracking)&lt;/td&gt;
&lt;td&gt;Not native (custom logging)&lt;/td&gt;
&lt;td&gt;Column-oriented API, fast ingestion, selective logging, notebook/web integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Heex&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Event-driven data capture for fleets of robots&lt;/td&gt;
&lt;td&gt;ROSbag (telemetry, images, lidar, metrics)&lt;/td&gt;
&lt;td&gt;Continuous recording, uploads filtered by event-based rules via onboard agent&lt;/td&gt;
&lt;td&gt;Built-in Foxglove in dashboard&lt;/td&gt;
&lt;td&gt;Native ROS 1 &amp;amp; 2&lt;/td&gt;
&lt;td&gt;RDA (automated capture rules), remote config, scalable fleet-wide dashboards&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Each tool addresses a different part of the robotics data workflow. &lt;strong&gt;ReductStore&lt;/strong&gt; is ideal for distributed storage across many robots, with selective replication to the cloud and flexible integration with tools like Grafana and Foxglove. &lt;strong&gt;Foxglove&lt;/strong&gt; excels at visualizing MCAP logs and ROS topics. &lt;strong&gt;Rerun&lt;/strong&gt; offers flexible, real-time 3D inspection for custom applications. &lt;strong&gt;Heex&lt;/strong&gt; focuses on capturing just the important moments for efficient fleet analysis.&lt;/p&gt;

&lt;p&gt;Choosing the right tool depends on what kind of data you collect, how you process it, and where you need it to go. In many cases, combining tools can give you the best of all worlds.&lt;/p&gt;




&lt;p&gt;Thanks for reading. I hope this article helps you decide on the right storage strategy for your vibration data.&lt;br&gt;
If you have questions or comments, feel free to visit the &lt;a href="https://community.reduct.store/signup" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore Community Forum&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>robotics</category>
      <category>ros</category>
    </item>
    <item>
      <title>Distributed Storage in Mobile Robotics</title>
      <dc:creator>AnthonyCvn</dc:creator>
      <pubDate>Mon, 17 Nov 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/reductstore/distributed-storage-in-mobile-robotics-1oe0</link>
      <guid>https://dev.to/reductstore/distributed-storage-in-mobile-robotics-1oe0</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1numadno34nlnfk2m0g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1numadno34nlnfk2m0g.png" alt="Distributed Storage in Mobile Robotics" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mobile robots produce a &lt;strong&gt;lot&lt;/strong&gt; of data (camera images, IMU readings, logs, etc). Storing this data reliably on each robot and syncing it to the cloud can be hard. &lt;strong&gt;ReductStore&lt;/strong&gt; makes this easier: it's a lightweight, time-series object store built for robotics and industrial IoT. It stores binary blobs (images, logs, CSV sensor data, MCAP, JSON) with timestamps and labels so you can quickly find and query them later.&lt;/p&gt;

&lt;p&gt;This introduction guide explains a simple setup where each robot stores data locally and automatically syncs it to a cloud ReductStore instance backed by Amazon S3.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge-to-Cloud Architecture&lt;a href="https://www.reduct.store/blog/distributed-storage-mobile-robotics#edge-to-cloud-architecture" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The architecture has three main components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Each robot runs a small ReductStore server&lt;/strong&gt; in order to save images and IMU data locally on disk (this let the robot operate offline).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A cloud ReductStore instance runs on a server (e.g., EC2)&lt;/strong&gt; and uses S3 for long-term storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ReductStore replication tasks&lt;/strong&gt; copies data from robot to cloud based on labels, events, or rules (e.g., 1 record every minute).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each robot pushes its data to the cloud whenever it is connected to the network. This approach provides the robots with offline capability, allows you to decide which data to replicate, and easily scales to support many robots.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Replication Works&lt;a href="https://www.reduct.store/blog/distributed-storage-mobile-robotics#how-replication-works" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;ReductStore uses an &lt;strong&gt;append-only&lt;/strong&gt; replication model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The robot stores new data locally.&lt;/li&gt;
&lt;li&gt;ReductStore automatically detects new records.&lt;/li&gt;
&lt;li&gt;It sends them to the cloud in batches (or streams large files).&lt;/li&gt;
&lt;li&gt;If the network disconnects, replication continues when the robot reconnects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can replicate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;everything&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;or only specific sensors&lt;/li&gt;
&lt;li&gt;or only records with certain labels&lt;/li&gt;
&lt;li&gt;or based on rules (e.g., 1 record every S seconds or every N records)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This can be configured per robot using environment variables (provisioning), with the web console or via the CLI (as shown in this guide).&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud ReductStore With S3 Backend&lt;a href="https://www.reduct.store/blog/distributed-storage-mobile-robotics#cloud-reductstore-with-s3-backend" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;ReductStore supports storing all records directly in S3. It keeps a local cache for fast access and batches many small blobs into larger blocks to save on S3 costs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By batching data into S3 objects, you can save &lt;strong&gt;significantly&lt;/strong&gt; on storage costs compared to storing many small files individually.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is an example &lt;code&gt;docker-compose.yml&lt;/code&gt; to run a ReductStore server that uses S3 as the remote backend and provisions buckets for robots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;reductstore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reduct/store:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reductstore&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8383:8383"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# AWS credentials and S3 bucket configuration&lt;/span&gt;
      &lt;span class="na"&gt;RS_REMOTE_BACKEND_TYPE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;s3&lt;/span&gt;
      &lt;span class="na"&gt;RS_REMOTE_BUCKET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;YOUR_S3_BUCKET_NAME&amp;gt;&lt;/span&gt;
      &lt;span class="na"&gt;RS_REMOTE_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;YOUR_S3_REGION&amp;gt;&lt;/span&gt;
      &lt;span class="na"&gt;RS_REMOTE_ACCESS_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;YOUR_AWS_ACCESS_KEY_ID&amp;gt;&lt;/span&gt;
      &lt;span class="na"&gt;RS_REMOTE_SECRET_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;YOUR_AWS_SECRET_ACCESS_KEY&amp;gt;&lt;/span&gt;
      &lt;span class="na"&gt;RS_REMOTE_CACHE_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data/cache&lt;/span&gt;
      &lt;span class="c1"&gt;# Bucket provisioning&lt;/span&gt;
      &lt;span class="na"&gt;RS_BUCKET_ROBOT_1_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;robot1-data&lt;/span&gt;
      &lt;span class="na"&gt;RS_BUCKET_ROBOT_2_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;robot2-data&lt;/span&gt;
      &lt;span class="c1"&gt;# .. additional buckets as needed&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./cache:/data/cache&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This starts a ReductStore server that writes to S3 automatically. There are many more configuration options available in the &lt;strong&gt;&lt;a href="https://www.reduct.store/docs/configuration" rel="noopener noreferrer"&gt;configuration documentation&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Replication&lt;a href="https://www.reduct.store/blog/distributed-storage-mobile-robotics#setting-up-replication" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;First spin up a local ReductStore on each robot. Here with Snap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;install &lt;/span&gt;reductstore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That starts a ReductStore server on port &lt;code&gt;8383&lt;/code&gt; by default. Then you can use the &lt;strong&gt;&lt;a href="https://github.com/reductstore/reduct-cli" rel="noopener noreferrer"&gt;Reduct CLI&lt;/a&gt;&lt;/strong&gt; to set up replication from the robot to the cloud instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Point the CLI to the robot's local ReductStore&lt;/span&gt;
reduct-cli &lt;span class="nb"&gt;alias &lt;/span&gt;add &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; http://localhost:8383 &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;ROBOT_API_TOKEN&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Create a bucket for that robot&lt;/span&gt;
reduct-cli bucket create &lt;span class="nb"&gt;local&lt;/span&gt;/robot1-data

&lt;span class="c"&gt;# Create a replication task to the cloud&lt;/span&gt;
reduct-cli replica create &lt;span class="nb"&gt;local&lt;/span&gt;/robot1-to-cloud &lt;span class="se"&gt;\&lt;/span&gt;
    robot1-data &lt;span class="se"&gt;\&lt;/span&gt;
    https://&amp;lt;CLOUD_API_TOKEN&amp;gt;@&amp;lt;cloud-address&amp;gt;/robot1-data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a replication task called &lt;code&gt;robot1-to-cloud&lt;/code&gt; that copies all data from the robot's local &lt;code&gt;robot1-data&lt;/code&gt; bucket to the cloud instance. You can customize replication further by adding filters or rules. See the &lt;strong&gt;&lt;a href="https://www.reduct.store/docs/guides/data-replication" rel="noopener noreferrer"&gt;replication guide&lt;/a&gt;&lt;/strong&gt; for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storing Sensor Data&lt;a href="https://www.reduct.store/blog/distributed-storage-mobile-robotics#storing-sensor-data" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;There are many ways to store data. When it comes to high-frequency sensor data like IMU readings, a common approach is to store them in 1-second files. Images can be stored as binary blobs (e.g., JPEG or PNG files). Here is an example of storing IMU data as CSV files and images as binary blobs using the Python SDK (this stores 10,000 samples and one camera image for a given timestamp as an example):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;reduct&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8383&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;ROBOT_API_TOKEN&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;robot1-data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Current timestamp to index the data by time in ReductStore
&lt;/span&gt;        &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# microseconds
&lt;/span&gt;
        &lt;span class="c1"&gt;# Generate 10'000 IMU samples
&lt;/span&gt;        &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# microseconds
&lt;/span&gt;                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linear_acceleration_x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linear_acceleration_y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linear_acceleration_z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Convert to CSV (store 1 seconds of data per file)
&lt;/span&gt;        &lt;span class="n"&gt;csv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ts,linear_acceleration_x,linear_acceleration_y,linear_acceleration_z&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;linear_acceleration_x&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;linear_acceleration_y&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;linear_acceleration_z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Write the IMU batch
&lt;/span&gt;        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;entry_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;imu_logs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sensor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;imu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# MIME type
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Write one camera image
&lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;camera_image.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;entry_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;images&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sensor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;camera&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image/png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you are considering storing all IMU data as individual records in a time series database (TSDB) like Timescale or InfluxDB, keep in mind that high-frequency sensors (e.g., 1000 Hz) can lead to performance and cost issues. Batching samples into files (e.g., one second of data per CSV file) is a more efficient storage and querying method.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Querying Sensor Data Using ReductSelect&lt;a href="https://www.reduct.store/blog/distributed-storage-mobile-robotics#querying-sensor-data-using-reductselect" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If your IMU data is stored as CSV, the &lt;strong&gt;ReductSelect extension&lt;/strong&gt; lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;extract only certain columns&lt;/li&gt;
&lt;li&gt;filter rows based on conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: filter CSV rows where &lt;code&gt;acc_x &amp;gt; 10&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "#ext": {
        "select": {
            "csv": {"has_headers": True},
            "columns": [
                {"name": "ts", "as_label": "ts_ns"},
                {"name": "linear_acceleration_x", "as_label": "acc_x"},
                {"name": "linear_acceleration_y"},
                {"name": "linear_acceleration_z"},
            ],
        },
        "when": {"@acc_x": {"$gt": 1.9}},
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;reduct&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# the JSON condition from above
&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://&amp;lt;cloud-address&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;TOKEN&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;robot1-data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;imu_logs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns only the rows where &lt;code&gt;linear_acceleration_x &amp;gt; 1.9&lt;/code&gt;, along with the timestamp.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Setup Works Well for Robotics&lt;a href="https://www.reduct.store/blog/distributed-storage-mobile-robotics#why-this-setup-works-well-for-robotics" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;There are several advantages to using a specialized storage solution like ReductStore for mobile robotics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Robots can store data locally&lt;/strong&gt; and operate offline without network connectivity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic replication when connected&lt;/strong&gt; to avoid manual uploads and simplify data management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selective replication&lt;/strong&gt; lets you control what data is sent to the cloud (i.e. decide on your reduction strategy) to save bandwidth and storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Labels and timestamps&lt;/strong&gt; make it easy to organize and query sensor data later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store files of any type&lt;/strong&gt; (images, CSV, logs, MCAP) in a single system without needing separate storage solutions for each data type.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps&lt;a href="https://www.reduct.store/blog/distributed-storage-mobile-robotics#next-steps" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;ReductStore also integrates into robotics observability stacks such as the Canonical Observability Stack (COS) for robotics. You can visualize sensor data, logs, and metrics in Grafana dashboards alongside your other robot telemetry. More details in our blog post &lt;strong&gt;&lt;a href="https://dev.to/reductstore/the-missing-database-for-robotics-is-out-4p4i"&gt;The Missing Database for Robotics Is Out&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;I hope you found this article helpful! If you have any questions or feedback, don't hesitate to reach out on our &lt;a href="https://community.reduct.store/signup" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore Community&lt;/strong&gt;&lt;/a&gt; forum.&lt;/p&gt;

</description>
      <category>database</category>
      <category>ros</category>
      <category>robotics</category>
    </item>
    <item>
      <title>The Missing Database for Robotics Is Out</title>
      <dc:creator>AnthonyCvn</dc:creator>
      <pubDate>Wed, 22 Oct 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/reductstore/the-missing-database-for-robotics-is-out-4p4i</link>
      <guid>https://dev.to/reductstore/the-missing-database-for-robotics-is-out-4p4i</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5p4xxqhkx9pq86jm95d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5p4xxqhkx9pq86jm95d.png" alt="Img example" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Robotics teams today wrestle with data that grows faster than their infrastructure. Every robot generates streams of images, sensor readings, logs, and events in different formats. These data piles are fragmented, expensive to move, and slow to analyze. Teams often rely on generic cloud tools that are not built for robotics. They charge way too much per gigabyte (when it should cost little per terabyte), hide the raw data behind proprietary APIs, and make it hard for robots (and developers) to access or use their own data.&lt;/p&gt;

&lt;p&gt;ReductStore introduces a new category: a database purpose built for robotics data pipelines. It is open, efficient, and developer friendly. It lets teams store, query, and manage any time series of unstructured data directly from robots to the cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes It a New Category&lt;a href="https://www.reduct.store/blog/database-for-robotics#what-makes-it-a-new-category" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;ReductStore treats robotics with the respect it deserves. It captures everything in its raw form and stores it with a time index and labels for flexible querying and management. It ingests and streams any type of data (images, sensor frames, logs, MCAP files, CSVs, JSON, etc) without forcing developers to convert or reformat it.&lt;/p&gt;

&lt;p&gt;It works on robots and in the cloud using the same interface and SDKs (Python, C++, Rust, Javascript, Go). This means developers can build data pipelines that run the same way on robots or in the cloud without needing to change code or learn new tools.&lt;/p&gt;

&lt;p&gt;Developers can run ReductStore on an edge device for local data capture and replicate to a cloud instance (with S3 backend) for cloud analytics or archiving.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is the first and only database designed specifically for unstructured, time series robotics data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Data Handling and Querying&lt;a href="https://www.reduct.store/blog/database-for-robotics#data-handling-and-querying" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Developers can work directly with data using simple queries and SDKs. The focus is speed and flexibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. MCAP topic filtering&lt;a href="https://www.reduct.store/blog/database-for-robotics#1-mcap-topic-filtering" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;You can filter topics directly from multiple MCAP files stored in ReductStore without needing to download and reprocess everything locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;reduct&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="c1"&gt;# Extract only the IMU topic from MCAP&amp;nbsp;files
&lt;/span&gt;&lt;span class="n"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ros&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extract&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;topic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/imu/data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://test.reduct.store&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-robotics-data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcap-entry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;header&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1_000_000_000&lt;/span&gt;
                &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;header&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nanosec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linear_acceleration_x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linear_acceleration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linear_acceleration_y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linear_acceleration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linear_acceleration_z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linear_acceleration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows you to extract only the relevant topics from multiple bags. In this example, we extract only the IMU topic as a stream of JSON records, which would look like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ts&lt;/th&gt;
&lt;th&gt;linear_acceleration_x&lt;/th&gt;
&lt;th&gt;linear_acceleration_y&lt;/th&gt;
&lt;th&gt;linear_acceleration_z&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1633024800000&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;0.3&lt;/td&gt;
&lt;td&gt;-9.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1633024801000&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;-9.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  2. CSV/JSON field extraction and filtering&lt;a href="https://www.reduct.store/blog/database-for-robotics#2-csvjson-field-extraction-and-filtering" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;You can extract specific JSON fields or CSV columns when querying data. This lets you select only the information you need, for example, filtering and visualizing certain fields from streams of JSON or CSV sensor readings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;reduct&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="c1"&gt;# Select specific CSV columns and filter rows
&lt;/span&gt;&lt;span class="n"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;select&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;has_headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="c1"&gt;# Use "json": {}, for JSON data
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;columns&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linear_acceleration_x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;as_label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;acc_x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linear_acceleration_y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linear_acceleration_z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;when&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$gt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$abs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@acc_x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://test.reduct.store&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-robotics-data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Loop over filtered CSV entries
&lt;/span&gt;    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;csv_sensor_readings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;csv_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tabular result will only include the selected columns and rows that match the filter &lt;code&gt;abs(linear_acceleration_x) &amp;gt; 10&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ts&lt;/th&gt;
&lt;th&gt;linear_acceleration_x&lt;/th&gt;
&lt;th&gt;linear_acceleration_y&lt;/th&gt;
&lt;th&gt;linear_acceleration_z&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1633024800000&lt;/td&gt;
&lt;td&gt;12.5&lt;/td&gt;
&lt;td&gt;0.3&lt;/td&gt;
&lt;td&gt;-9.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1633024801000&lt;/td&gt;
&lt;td&gt;-15.2&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;-9.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Query any type of data&lt;a href="https://www.reduct.store/blog/database-for-robotics#3-query-any-type-of-data" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;ReductStore automatically batches small records and streams large ones for efficient storage and access. You can query any type of data, from lightweight telemetry to high-resolution images or point clouds, efficiently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;reduct&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="c1"&gt;# Every 5 seconds, limit to 5 records
&lt;/span&gt;&lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$each_t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://test.reduct.store&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-robotics-data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;camera_frames&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example above retrieves camera frames at 5-second intervals. You can then process or visualize these images as needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frs41awykv9yru7r8ybsw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frs41awykv9yru7r8ybsw.png" alt="Query Images Example" width="800" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Browse petabytes of data&lt;a href="https://www.reduct.store/blog/database-for-robotics#4-browse-petabytes-of-data" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;ReductStore is designed to handle massive volumes of data. Its indexing and storage architecture allows you to efficiently browse data at scale without downloading everything locally.&lt;/p&gt;

&lt;p&gt;For example, you can quickly navigate records and preview your data directly in the ReductStore &lt;a href="https://www.reduct.store/docs/glossary#web-console" rel="noopener noreferrer"&gt;&lt;strong&gt;web console&lt;/strong&gt;&lt;/a&gt;, even when working with petabytes of robotics data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gbh4ko0yqdal2udvoxp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gbh4ko0yqdal2udvoxp.png" alt="Browse Large Datasets" width="800" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;info&lt;/p&gt;

&lt;p&gt;You can build custom applications on top of ReductStore using its SDKs for Python, C++, Rust, Javascript, and Go. This makes it easy to build data pipelines, dashboards that works in the browser, or integrate with existing tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud Integration and Cost Savings&lt;a href="https://www.reduct.store/blog/database-for-robotics#cloud-integration-and-cost-savings" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;ReductStore connects robots and the cloud in a simple and flexible way. It works with S3-compatible storage and includes a robust replication system to transfer data from robots to the cloud (even when the network is unstable or intermittent), making it perfect for field robots that often go offline.&lt;/p&gt;

&lt;p&gt;Replication tasks can be configured to replicate only specific data based on labels or any criteria (for example, only replicate data when the confidence score is below a threshold, or &lt;strong&gt;replicate everything from a 10-minute window around a specific event&lt;/strong&gt; ).&lt;/p&gt;

&lt;p&gt;In the cloud, by batching multiple records into single data blocks, ReductStore minimizes both the number of blobs and the number of API calls to S3. This design reduces storage and retrieval costs by leveraging S3's pricing model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdj8tgvb7zoiln9zoimgo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdj8tgvb7zoiln9zoimgo.png" alt="Diagram Cloud Integration" width="800" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This approach can deliver major savings when working with large volumes of robotics data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Observability Stack Integration&lt;a href="https://www.reduct.store/blog/database-for-robotics#observability-stack-integration" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;ReductStore works with the tools robotics engineers already trust.&lt;/p&gt;

&lt;h3&gt;
  
  
  Foxglove Studio&lt;a href="https://www.reduct.store/blog/database-for-robotics#foxglove-studio" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Foxglove is an amazing tool for visualizing robotics data and debugging robots for the MCAP format.&lt;/p&gt;

&lt;p&gt;To share data from ReductStore to Foxglove, you can use the ReductStore web console (or the SDKs) to generate a &lt;a href="https://www.reduct.store/docs/glossary#query-link" rel="noopener noreferrer"&gt;&lt;strong&gt;query link&lt;/strong&gt;&lt;/a&gt; that Foxglove can open directly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvr17w58ytt4cjd4069l1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvr17w58ytt4cjd4069l1.png" alt="ReductStore Query Link" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can then paste the query link into Foxglove Studio to visualize the data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F058x6bhrz4stf0fut1tj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F058x6bhrz4stf0fut1tj.png" alt="Foxglove Studio" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Grafana&lt;a href="https://www.reduct.store/blog/database-for-robotics#grafana" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Grafana is a popular open-source tool for creating dashboards and visualising time-series data. You can connect Grafana to ReductStore using the ReductStore data source plugin, which allows you to query and visualise data stored in ReductStore.&lt;/p&gt;

&lt;p&gt;You can query data using labels, for example, localization coordinates, object detected, confidence score, etc:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjoqmnbn97pjsocud3zv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjoqmnbn97pjsocud3zv.png" alt="Grafana Query Labels" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or you can query based on content, such as JSON files with sensor readings or other structured data:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmr8ivq30fhabtjrsbtv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmr8ivq30fhabtjrsbtv.png" alt="Grafana Query Content" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Canonical Observability Stack (COS)&lt;a href="https://www.reduct.store/blog/database-for-robotics#canonical-observability-stack-cos" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Canonical's COS (Canonical Observability Stack) for robotics is an end to end observability framework built on open source tools such as Prometheus, Loki, Grafana, and Foxglove.&lt;/p&gt;

&lt;p&gt;The missing piece in this stack has always been a purpose built system for storing and managing robotics data efficiently from robot to cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffg09arjzl4jeks920hmf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffg09arjzl4jeks920hmf.png" alt="Diagram Observability Stack Integration" width="800" height="742"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ReductStore closes that gap. It provides a data storage and streaming solution optimized for both edge and cloud environments, along with an agent that captures data directly from ROS and streams it into the observability pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fda5hez3zprc4mrw8vnov.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fda5hez3zprc4mrw8vnov.png" alt="COS with ReductStore" width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts&lt;a href="https://www.reduct.store/blog/database-for-robotics#closing-thoughts" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Robotics teams no longer need to choose between control and convenience. ReductStore gives full ownership of data from robot to cloud. It removes vendor lock, cuts cost, and keeps everything observable and connected. It is the new foundation for robotics data infrastructure (the missing database for robotics).&lt;/p&gt;

&lt;p&gt;If you are interested to compare ReductStore with other databases (like MongoDB or InfluxDB), you can read our &lt;a href="https://www.reduct.store/whitepaper" rel="noopener noreferrer"&gt;&lt;strong&gt;white paper&lt;/strong&gt;&lt;/a&gt; that goes deeper into the architecture and design choices.&lt;/p&gt;




&lt;p&gt;I hope you found this article helpful! If you have any questions or feedback, don't hesitate to reach out on our &lt;a href="https://community.reduct.store/signup" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore Community&lt;/strong&gt;&lt;/a&gt; forum.&lt;/p&gt;

</description>
      <category>ros</category>
      <category>robotics</category>
    </item>
    <item>
      <title>ReductStore v1.17.0 Released with Query Links and S3 Storage Backend Support</title>
      <dc:creator>Alexey Timin</dc:creator>
      <pubDate>Tue, 21 Oct 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/reductstore/reductstore-v1170-released-with-query-links-and-s3-storage-backend-support-447j</link>
      <guid>https://dev.to/reductstore/reductstore-v1170-released-with-query-links-and-s3-storage-backend-support-447j</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmbm0m00zkjfcvyzs7dt6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmbm0m00zkjfcvyzs7dt6.png" alt="ReductStore v1.17.0 Released" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are pleased to announce the release of the latest minor version of &lt;a href="https://www.reduct.store/" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://github.com/reductstore/reductstore/releases/tag/v1.17.0" rel="noopener noreferrer"&gt;&lt;strong&gt;1.17.0&lt;/strong&gt;&lt;/a&gt;. ReductStore is a high-performance storage and streaming solution designed for storing and managing large volumes of historical data.&lt;/p&gt;

&lt;p&gt;To download the latest released version, please visit our &lt;a href="https://www.reduct.store/download" rel="noopener noreferrer"&gt;&lt;strong&gt;Download Page&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's new in 1.17.0?&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_17_0-released#whats-new-in-1170" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This release includes several new features and enhancements. First, there are query links for simplified data access. Second, there is support for S3-compatible storage backends.&lt;/p&gt;

&lt;p&gt;These new features enhance the usability and flexibility of ReductStore for various use cases in the cloud and on-premises environments and make it easier to share and access data stored in the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔗 Query Links for Data Access&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_17_0-released#-query-links-for-data-access" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;ReductStore now supports &lt;strong&gt;&lt;a href="https://www.reduct.store/docs/glossary#query-link" rel="noopener noreferrer"&gt;query links&lt;/a&gt;&lt;/strong&gt;, enabling users to generate temporary, public URLs for specific data records — without requiring authentication. This makes it easier to share datasets with &lt;strong&gt;external collaborators&lt;/strong&gt; , embed links into dashboards, or integrate with &lt;strong&gt;third-party systems&lt;/strong&gt; that need read-only access to specific data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4l5ism0xyqfqobolt3px.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4l5ism0xyqfqobolt3px.webp" alt="Generate Query Links in ReductStore Web Console" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can create query links directly from the &lt;strong&gt;Web Console&lt;/strong&gt; (or any SDKs):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;strong&gt;Data Browser&lt;/strong&gt; page and select a record you want to share.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;“Share record”&lt;/strong&gt; icon in the action panel.&lt;/li&gt;
&lt;li&gt;Configure an &lt;strong&gt;expiration time&lt;/strong&gt; to automatically revoke access after a defined period.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once generated, anyone with the link can access the selected record via a simple HTTP(S) request — no access token required. The link only has access to the specific query for which it was created, along with the creator's permissions. This provides a secure and convenient way to expose selected data for collaboration and analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  ☁️ S3-Compatible Storage Backend&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_17_0-released#%EF%B8%8F-s3-compatible-storage-backend" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;ReductStore now supports &lt;strong&gt;S3-compatible storage backends&lt;/strong&gt; , allowing you to use &lt;strong&gt;object storage&lt;/strong&gt; instead of a local file system for your underlying data. This update brings greater flexibility and scalability for managing large datasets in the cloud.&lt;/p&gt;

&lt;p&gt;Previously, ReductStore supported only local disk storage, and users had to mount S3 buckets as local disks via FUSE drivers. With this release, ReductStore can now natively integrate with S3-compatible backends — no additional software or mounting is required.&lt;/p&gt;

&lt;p&gt;This feature is designed with performance and &lt;strong&gt;cost optimization&lt;/strong&gt; in mind. ReductStore uses a local disk cache layer to speed up read and write operations, while batching multiple records into a single data block to reduce storage and retrieval costs. This approach works especially well with cost-efficient AWS S3 storage classes such as &lt;strong&gt;S3 Standard-IA&lt;/strong&gt; or &lt;strong&gt;S3 Glacier&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To run ReductStore with an S3-compatible backend, use the following environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -p 8383:8383 \
 -e RS_REMOTE_BACKEND_TYPE=s3 \
 -e RS_REMOTE_BUCKET=&amp;lt;YOUR_S3_BUCKET_NAME&amp;gt; \
 -e RS_REMOTE_REGION=&amp;lt;YOUR_S3_REGION&amp;gt; \
 -e RS_REMOTE_ACCESS_KEY=&amp;lt;YOUR_S3_ACCESS_KEY_ID&amp;gt; \
 -e RS_REMOTE_SECRET_KEY=&amp;lt;YOUR_S3_SECRET_ACCESS_KEY&amp;gt; \ 
 -e RS_REMOTE_CACHE_PATH=/data/cache \
 -e RS_LICENSE_PATH=&amp;lt;PATH_TO_YOUR_LICENSE_FILE&amp;gt; \ 
 -v ${PWD}/data:/data/cache \
 reduct/store:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read more about configuring S3-compatible storage backend in the &lt;a href="https://www.reduct.store/docs/configuration#remote-backend-settings" rel="noopener noreferrer"&gt;&lt;strong&gt;documentation&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;info&lt;/p&gt;

&lt;p&gt;This feature requires a commercial license. Please see the &lt;strong&gt;&lt;a href="https://www.reduct.store/pricing" rel="noopener noreferrer"&gt;Pricing page&lt;/a&gt;&lt;/strong&gt; for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_17_0-released#whats-next" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We’re continuing to develop new features to make ReductStore even more powerful and user-friendly. Here’s a preview of what’s coming in the next releases:&lt;/p&gt;

&lt;h3&gt;
  
  
  📦 Multiple Entries in a Single Request&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_17_0-released#-multiple-entries-in-a-single-request" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Currently, each write or query request must target a &lt;strong&gt;single entry&lt;/strong&gt;. This can be limiting when dealing with &lt;strong&gt;multiple entries&lt;/strong&gt; or dynamic lists of entries in your applications.&lt;/p&gt;

&lt;p&gt;In upcoming versions, ReductStore will support &lt;strong&gt;batch operations&lt;/strong&gt; across multiple entries within a single API request. This improvement will simplify integrations and reduce overhead for large-scale data ingestion and querying workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔒 Read-Only Mode for ReductStore&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_17_0-released#-read-only-mode-for-reductstore" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Like most databases, ReductStore currently requires &lt;strong&gt;exclusive access&lt;/strong&gt; to its data directory while running. As a result, running multiple instances on the same dataset—for load balancing or high availability—is not yet possible.&lt;/p&gt;

&lt;p&gt;To address this, we’re introducing a &lt;strong&gt;read-only mode&lt;/strong&gt; that will allow one writer instance* and multiple reader instances to access the same dataset concurrently. This approach will enable &lt;strong&gt;scalable read operations&lt;/strong&gt; and &lt;strong&gt;improved availability&lt;/strong&gt; without adding the complexity of clustering or replication mechanisms.&lt;/p&gt;




&lt;p&gt;I hope you find those new features useful. If you have any questions or feedback, don’t hesitate to use the &lt;a href="https://community.reduct.store/signup" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore Community&lt;/strong&gt;&lt;/a&gt; forum.&lt;/p&gt;

&lt;p&gt;Thanks for using &lt;a href="https://www.reduct.store/" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore&lt;/strong&gt;&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>news</category>
    </item>
    <item>
      <title>Building a Resilient ReductStore Deployment with NGINX</title>
      <dc:creator>Alexey Timin</dc:creator>
      <pubDate>Sat, 13 Sep 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/reductstore/building-a-resilient-reductstore-deployment-with-nginx-59jb</link>
      <guid>https://dev.to/reductstore/building-a-resilient-reductstore-deployment-with-nginx-59jb</guid>
      <description>&lt;p&gt;If you’re collecting high-rate sensor or video data at the edge and need zero-downtime ingestion and fault-tolerant querying, an &lt;strong&gt;&lt;a href="https://www.reduct.store/docs/guides/disaster-recovery#active-active-setup" rel="noopener noreferrer"&gt;active–active ReductStore setup&lt;/a&gt;&lt;/strong&gt; fronted by NGINX is a clean, practical pattern.&lt;/p&gt;

&lt;p&gt;This tutorial walks you through the &lt;strong&gt;&lt;a href="https://github.com/reductstore/nginx-resilient-setup" rel="noopener noreferrer"&gt;reference implementation&lt;/a&gt;&lt;/strong&gt;, explains the architecture, and shows production-grade NGINX snippets you can adapt.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We’ll Build&lt;a href="https://www.reduct.store/blog/nginx-resilient-deployment#what-well-build" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We’ll set up a &lt;strong&gt;ReductStore cluster&lt;/strong&gt; with NGINX as a reverse proxy, separating the &lt;strong&gt;ingress&lt;/strong&gt; and &lt;strong&gt;egress&lt;/strong&gt; layers. This architecture allows for independent scaling of write and read workloads, ensuring high availability and performance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fno4ylirej4b8tpfrhctg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fno4ylirej4b8tpfrhctg.png" alt="NGINX Resilient Deployment" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Ingress layer&lt;a href="https://www.reduct.store/blog/nginx-resilient-deployment#ingress-layer" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;ingress layer&lt;/strong&gt; handles all writes and replicates data to the egress layer. Its nodes may have limited storage capacity, while they need only to handle writes and replicate data to the &lt;strong&gt;egress&lt;/strong&gt; nodes. It can use high-rate storage like NVMe SSDs or even RAM disks, depending on your data volume.&lt;/p&gt;

&lt;h3&gt;
  
  
  Egress layer&lt;a href="https://www.reduct.store/blog/nginx-resilient-deployment#egress-layer" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;egress layer&lt;/strong&gt; handles all reads and serves data to clients. Its nodes are optimized for read performance and can use larger, slower storage like HDDs or cloud object storage. Each egress node holds a complete copy of the dataset, allowing for high availability and load balancing.&lt;/p&gt;

&lt;h3&gt;
  
  
  NGINX Load Balancer&lt;a href="https://www.reduct.store/blog/nginx-resilient-deployment#nginx-load-balancer" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;NGINX&lt;/strong&gt; load balancer sits in front of both layers, exposing two stable endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;http://&amp;lt;host&amp;gt;/ingress&lt;/code&gt; → load balances writes across ingress nodes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;http://&amp;lt;host&amp;gt;/egress&lt;/code&gt; → load balances reads across egress nodes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation allows you to scale each layer independently and ensures that writes and reads are handled optimally.&lt;/p&gt;

&lt;p&gt;It is also important to note that NGINX must maintain &lt;strong&gt;session affinity&lt;/strong&gt; (stickiness) for both ingress and egress requests to ensure that queries remain consistent and throughput is maximized.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Start&lt;a href="https://www.reduct.store/blog/nginx-resilient-deployment#quick-start" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Clone the example and bring it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/reductstore/nginx-resilient-setup
&lt;span class="nb"&gt;cd &lt;/span&gt;nginx-resilient-setupdocker 
compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will start two ingress nodes and two egress nodes with NGINX in front, all configured to replicate data between them. Check the docker compose file for details on how the nodes are set up.&lt;/p&gt;

&lt;p&gt;Now we need to write some data and verify that we can read it back.&lt;a href="https://www.reduct.store/download" rel="noopener noreferrer"&gt;&lt;strong&gt;Install the &lt;code&gt;reduct-cli&lt;/code&gt; tool&lt;/strong&gt;&lt;/a&gt; if you haven't already, then run the following commands to set up aliases for the ingress and egress endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;reduct-cli &lt;span class="nb"&gt;alias &lt;/span&gt;add ingress &lt;span class="nt"&gt;-L&lt;/span&gt; http://localhost:80/ingress &lt;span class="nt"&gt;--token&lt;/span&gt; secret
reduct-cli &lt;span class="nb"&gt;alias &lt;/span&gt;add egress &lt;span class="nt"&gt;-L&lt;/span&gt; http://localhost:80/egress &lt;span class="nt"&gt;--token&lt;/span&gt; secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then copy some data from our &lt;a href="https://play.reduct.store/" rel="noopener noreferrer"&gt;&lt;strong&gt;Demo Server&lt;/strong&gt;&lt;/a&gt; to the ingress layer and read it back from the egress layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add demo server alias to the CLI&lt;/span&gt;
reduct-cli &lt;span class="nb"&gt;alias &lt;/span&gt;add play &lt;span class="nt"&gt;-L&lt;/span&gt; https://play.reduct.store &lt;span class="nt"&gt;--token&lt;/span&gt; reductstore
&lt;span class="c"&gt;# Copy data from the demo server to ingress&lt;/span&gt;
reduct-cli &lt;span class="nb"&gt;cp &lt;/span&gt;play/datasets ingress/bucket-1 &lt;span class="nt"&gt;--limit&lt;/span&gt; 1000
&lt;span class="c"&gt;# Read/export via egress&lt;/span&gt;
reduct-cli &lt;span class="nb"&gt;cp &lt;/span&gt;egress/bucket-1 ./export_folder &lt;span class="nt"&gt;--limit&lt;/span&gt; 1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  NGINX Configuration&lt;a href="https://www.reduct.store/blog/nginx-resilient-deployment#nginx-configuration" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Below is a distilled config you can adapt for open-source NGINX:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Upstreams
# Separate pools for ingress (writes) and egress (reads)
upstream reduct_ingress {
    ip_hash;   # stickiness for writes
    server ingress-1:8383 max_fails=3 fail_timeout=10s;
    server ingress-2:8383 max_fails=3 fail_timeout=10s;
    keepalive 64;
}

upstream reduct_egress {
    ip_hash;   # stickiness for queries
    server egress-1:8383 max_fails=3 fail_timeout=10s;
    server egress-2:8383 max_fails=3 fail_timeout=10s;
    keepalive 64;
}

server {
    listen 80;
    server_name _;

    client_max_body_size 512m;
    proxy_read_timeout 600s;
    proxy_send_timeout 600s;

    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $remote_addr;

    location /ingress/ {
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_pass http://reduct_ingress/;
    }

    location /egress/ {
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_pass http://reduct_egress/;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the config above, we define two upstream blocks: &lt;code&gt;reduct_ingress&lt;/code&gt; for handling write requests and &lt;code&gt;reduct_egress&lt;/code&gt; for handling read requests. Each block uses &lt;code&gt;ip_hash&lt;/code&gt; to ensure session affinity, which is crucial for maintaining consistent writes and reads.&lt;/p&gt;

&lt;h2&gt;
  
  
  ReductStore Configuration Notes&lt;a href="https://www.reduct.store/blog/nginx-resilient-deployment#reductstore-configuration-notes" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The configuration between nodes of each layer is identical. To reach the desired architecture, you need to provision buckets and replication tasks for ingress nodes and buckets only for egress nodes. See the configuration files in the example repo for details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Drills&lt;a href="https://www.reduct.store/blog/nginx-resilient-deployment#failure-drills" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;When the setup is running, you can simulate failures to see how it behaves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Kill an ingress node&lt;/strong&gt; → writes continue via other ingress nodes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kill an egress node&lt;/strong&gt; → reads continue via other egress nodes; replication resyncs when it’s back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simulate total ingress outage&lt;/strong&gt; → analysis continues on egress; for true ingestion continuity, pair with a pilot-light instance in another location.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Runbook&lt;a href="https://www.reduct.store/blog/nginx-resilient-deployment#runbook" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Here’s a high-level runbook for deploying this architecture in production:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Provision ingress + egress ReductStore nodes&lt;/li&gt;
&lt;li&gt;Create buckets and replication tasks&lt;/li&gt;
&lt;li&gt;Expose &lt;code&gt;/ingress&lt;/code&gt; and &lt;code&gt;/egress&lt;/code&gt; via NGINX with &lt;code&gt;ip_hash&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Test with demo dataset&lt;/li&gt;
&lt;li&gt;Validate reads from egress&lt;/li&gt;
&lt;li&gt;Run failure drills&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  References&lt;a href="https://www.reduct.store/blog/nginx-resilient-deployment#references" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/reductstore/nginx-resilient-setup" rel="noopener noreferrer"&gt;NGINX Resilient Setup Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reduct.store/docs/guides/disaster-recovery" rel="noopener noreferrer"&gt;Disaster Recovery Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I hope you find this article interesting and useful. If you have any questions or feedback, don’t hesitate to use the &lt;a href="https://community.reduct.store/signup" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore Community&lt;/strong&gt;&lt;/a&gt; forum.&lt;/p&gt;

</description>
      <category>tutorials</category>
      <category>nginx</category>
    </item>
    <item>
      <title>ReductStore v1.16.0 Released With New Extensions and Context Replication</title>
      <dc:creator>Alexey Timin</dc:creator>
      <pubDate>Sat, 30 Aug 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/reductstore/reductstore-v1160-released-with-new-extensions-and-context-replication-562c</link>
      <guid>https://dev.to/reductstore/reductstore-v1160-released-with-new-extensions-and-context-replication-562c</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F034g7npet9bq7xya5pln.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F034g7npet9bq7xya5pln.webp" alt="ReductStore v1.16.0 Released" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are pleased to announce the release of the latest minor version of &lt;a href="https://www.reduct.store/" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://github.com/reductstore/reductstore/releases/tag/v1.16.0" rel="noopener noreferrer"&gt;&lt;strong&gt;1.16.0&lt;/strong&gt;&lt;/a&gt;. ReductStore is a high-performance storage and streaming solution designed for storing and managing large volumes of historical data.&lt;/p&gt;

&lt;p&gt;To download the latest released version, please visit our &lt;a href="https://www.reduct.store/download" rel="noopener noreferrer"&gt;&lt;strong&gt;Download Page&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's new in 1.16.0?&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_16_0-released#whats-new-in-1160" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The v1.16.0 release introduces two new extensions designed to enhance data workflows for robotics and columnar data, along with support for replicating context records during queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Querying and Replicating Data with Context&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_16_0-released#querying-and-replicating-data-with-context" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;We’ve extended the conditional query syntax with &lt;strong&gt;&lt;a href="https://www.reduct.store/docs/conditional-query/directives" rel="noopener noreferrer"&gt;directives&lt;/a&gt;&lt;/strong&gt; that allow users to modify global query behavior. The first directives introduced are &lt;code&gt;#ctx_before&lt;/code&gt; and &lt;code&gt;#ctx_after&lt;/code&gt;, which enable the inclusion of context records that occur before or after each matching record in a query.&lt;/p&gt;

&lt;p&gt;This feature is particularly useful when analyzing specific events or conditions in your data, as it helps provide a clearer picture of the surrounding context. For instance, you can use these directives to include records from a few seconds before or after an anomaly or incident, aiding in root cause analysis or pattern recognition.&lt;/p&gt;

&lt;p&gt;Here’s an example of how to use the &lt;code&gt;#ctx_before&lt;/code&gt; directive in a query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"#ctx_before"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"&amp;amp;anomaly_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"$gt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query returns all records with an anomaly score greater than 0.8, along with the context records that occurred within 5 seconds before each matching entry.&lt;/p&gt;

&lt;h3&gt;
  
  
  New ReductSelect Extension&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_16_0-released#new-reductselect-extension" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;ReductStore is fundamentally a blob storage system and does not allow direct manipulation of stored data. However, with its extension mechanism, we can introduce new capabilities while keeping the core system simple.&lt;/p&gt;

&lt;p&gt;The new &lt;a href="https://www.reduct.store/docs/extensions/official/select-ext" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductSelect&lt;/strong&gt;&lt;/a&gt; extension enables users to query and transform data stored in CSV or JSON formats, making it easier to build flexible and efficient data processing workflows.&lt;/p&gt;

&lt;p&gt;For example, the following query uses ReductSelect to extract specific columns from CSV data and filter rows using the same conditional syntax available in ReductStore's native query language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ext"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"select"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"csv"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"has_headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"columns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"temperature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"as_labels"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"temp"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"humidity"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"when"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"&amp;amp;temperature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"$gt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query selects the &lt;code&gt;temperature&lt;/code&gt; and &lt;code&gt;humidity&lt;/code&gt; columns from a CSV file, renames &lt;code&gt;temperature&lt;/code&gt; to &lt;code&gt;temp&lt;/code&gt;, and filters rows where the temperature is greater than 30°C.&lt;/p&gt;

&lt;p&gt;These simple transformations enable you to ingest structured data very quickly and retrieve only subsets of it for further processing and analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  New ReductROS Extension&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_16_0-released#new-reductros-extension" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Another exciting addition is the &lt;a href="https://www.reduct.store/docs/extensions/official/ros-ext" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductROS&lt;/strong&gt;&lt;/a&gt; extension, which provides tools for extracting and transforming data stored in ReductStore into formats compatible with the Robot Operating System (ROS).&lt;/p&gt;

&lt;p&gt;With this extension, you can extract data from MCAP files containing ROS 2 messages and convert it into JSON format, making it easier to analyze and visualize. It also supports transforming raw binary data—such as images—into more accessible formats like JPEG or base64 strings.&lt;/p&gt;

&lt;p&gt;For example, the following query extracts data from a ROS 2 topic and encodes the image payload as a JPEG:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "ext": {
    "ros": {
      "extract": {
        "topic": "/camera/image",
        "encode": { "data": "jpeg" }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ReductROS is still in active development, and we plan to expand its capabilities with support for additional ROS message types and more flexible extraction options in future releases. Stay tuned for updates!&lt;/p&gt;

&lt;h2&gt;
  
  
  What next?&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_16_0-released#what-next" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We are constantly working on improving ReductStore and adding new features to provide the best experience for our users. In the next release we plan to add new features and improvements, including:&lt;/p&gt;

&lt;h3&gt;
  
  
  Shareable Query Links&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_16_0-released#shareable-query-links" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;We are developing a feature that allows users to generate and share links to specific queries in ReductStore.&lt;/p&gt;

&lt;p&gt;This will simplify collaboration by enabling team members to access query results without needing direct access to the ReductStore instance. It will also allow users to download results directly via a link and support integration with external tools and platforms such as &lt;strong&gt;&lt;a href="https://foxglove.dev/" rel="noopener noreferrer"&gt;Foxglove&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration with Grafana&lt;a href="https://www.reduct.store/blog/news/reductstore-v1_16_0-released#integration-with-grafana" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;We are also working on a &lt;strong&gt;&lt;a href="https://github.com/reductstore/reduct-grafana" rel="noopener noreferrer"&gt;Grafana plugin&lt;/a&gt;&lt;/strong&gt; that enables users to visualize and analyze data stored in ReductStore directly within Grafana dashboards.&lt;/p&gt;

&lt;p&gt;This integration will provide a seamless experience with Grafana’s powerful visualization tools, allowing you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build custom dashboards using data from ReductStore.&lt;/li&gt;
&lt;li&gt;Monitor your data streams and historical records in real time.&lt;/li&gt;
&lt;li&gt;Visualize labels and data output in JSON or CSV formats.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay tuned for the first release—coming soon!&lt;/p&gt;




&lt;p&gt;I hope you find those new features useful. If you have any questions or feedback, don’t hesitate to use the &lt;a href="https://community.reduct.store/signup" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore Community&lt;/strong&gt;&lt;/a&gt; forum.&lt;/p&gt;

&lt;p&gt;Thanks for using &lt;a href="https://www.reduct.store/" rel="noopener noreferrer"&gt;&lt;strong&gt;ReductStore&lt;/strong&gt;&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>news</category>
    </item>
  </channel>
</rss>
