diff --git a/src/mavlink.rs b/src/mavlink.rs
index f1517ccf525029cb9782ba848669ab65611fb369..9a05aa884935d8e1f278f356abc7ecc16faf320f 100644
--- a/src/mavlink.rs
+++ b/src/mavlink.rs
@@ -34,4 +34,8 @@ impl TimedMessage {
             time: Instant::now(),
         }
     }
+
+    pub fn id(&self) -> u32 {
+        self.message.message_id()
+    }
 }
diff --git a/src/message_broker.rs b/src/message_broker.rs
index 7cf10e703a48e9144e2d6577723b453930ceddd8..ddf1c1717dbeb0849581b28dc43f2c33d5cdad9c 100644
--- a/src/message_broker.rs
+++ b/src/message_broker.rs
@@ -11,7 +11,6 @@ pub use message_bundle::MessageBundle;
 use reception_queue::ReceptionQueue;
 
 use std::{
-    collections::HashMap,
     sync::{Arc, Mutex},
     time::Duration,
 };
@@ -21,7 +20,7 @@ use tracing::error;
 use crate::{
     communication::{Connection, ConnectionError, TransceiverConfigExt},
     error::ErrInstrument,
-    mavlink::{MavFrame, MavHeader, MavMessage, MavlinkVersion, Message, TimedMessage},
+    mavlink::{MavFrame, MavHeader, MavMessage, MavlinkVersion, TimedMessage},
 };
 
 const RECEPTION_QUEUE_INTERVAL: Duration = Duration::from_secs(1);
@@ -34,7 +33,7 @@ const SEGS_COMPONENT_ID: u8 = 1;
 /// dispatching them to the views that are interested in them.
 pub struct MessageBroker {
     /// A map of all messages received so far, indexed by message ID
-    messages: HashMap<u32, Vec<TimedMessage>>,
+    messages: Vec<TimedMessage>,
     /// instant queue used for frequency calculation and reception time
     last_receptions: Arc<Mutex<ReceptionQueue>>,
     /// Connection to the Mavlink listener
@@ -47,7 +46,7 @@ impl MessageBroker {
     /// Creates a new `MessageBroker` with the given channel size and Egui context.
     pub fn new(ctx: egui::Context) -> Self {
         Self {
-            messages: HashMap::new(),
+            messages: Vec::new(),
             // TODO: make this configurable
             last_receptions: Arc::new(Mutex::new(ReceptionQueue::new(RECEPTION_QUEUE_INTERVAL))),
             connection: None,
@@ -88,8 +87,11 @@ impl MessageBroker {
         self.last_receptions.lock().log_unwrap().frequency()
     }
 
-    pub fn get(&self, id: u32) -> &[TimedMessage] {
-        self.messages.get(&id).map_or(&[], |v| v.as_slice())
+    pub fn get(&self, ids: &[u32]) -> Vec<&TimedMessage> {
+        self.messages
+            .iter()
+            .filter(|msg| ids.contains(&msg.id()))
+            .collect()
     }
 
     /// Processes incoming network messages. New messages are added to the
@@ -108,10 +110,7 @@ impl MessageBroker {
                         self.last_receptions.lock().log_unwrap().push(message.time);
 
                         // Store the message in the broker
-                        self.messages
-                            .entry(message.message.message_id())
-                            .or_default()
-                            .push(message);
+                        self.messages.push(message);
                     }
                     self.ctx.request_repaint();
                 }
diff --git a/src/message_broker/message_bundle.rs b/src/message_broker/message_bundle.rs
index 52568b935f267e0af070423a746299ddba2151c7..eaa06df7254819bf6e9c348c094343a589cb7f97 100644
--- a/src/message_broker/message_bundle.rs
+++ b/src/message_broker/message_bundle.rs
@@ -1,4 +1,4 @@
-use crate::mavlink::{Message, TimedMessage};
+use crate::mavlink::TimedMessage;
 
 /// A bundle of messages, indexed by their ID.
 /// Allows for efficient storage and retrieval of messages by ID.
@@ -10,36 +10,22 @@ use crate::mavlink::{Message, TimedMessage};
 /// method to clear the content of the bundle and prepare it for reuse.
 #[derive(Default)]
 pub struct MessageBundle {
-    storage: Vec<(u32, Vec<TimedMessage>)>,
+    storage: Vec<TimedMessage>,
     count: u32,
 }
 
 impl MessageBundle {
     /// Returns all messages of the given ID contained in the bundle.
-    pub fn get(&self, id: u32) -> &[TimedMessage] {
+    pub fn get(&self, ids: &[u32]) -> Vec<&TimedMessage> {
         self.storage
             .iter()
-            .find(|&&(queue_id, _)| queue_id == id)
-            .map_or(&[], |(_, messages)| messages.as_slice())
+            .filter(|msg| ids.contains(&msg.id()))
+            .collect()
     }
 
     /// Inserts a new message into the bundle.
     pub fn insert(&mut self, message: TimedMessage) {
-        let message_id = message.message.message_id();
-
-        // Retrieve the queue for the ID, if it exists
-        let maybe_queue = self
-            .storage
-            .iter_mut()
-            .find(|&&mut (queue_id, _)| queue_id == message_id)
-            .map(|(_, queue)| queue);
-
-        if let Some(queue) = maybe_queue {
-            queue.push(message);
-        } else {
-            self.storage.push((message_id, vec![message]));
-        }
-
+        self.storage.push(message);
         self.count += 1;
     }
 
@@ -49,15 +35,9 @@ impl MessageBundle {
     }
 
     /// Resets the content of the bundle, preparing it to be efficiently reused.
-    /// Effectively, it clears the content of the bundle, but with lower
-    /// allocation cost the next time the bundle is reused.
+    /// Effectively, it clears the content of the bundle.
     pub fn reset(&mut self) {
-        // Clear the individual queues instead of the full storage, to avoid
-        // the allocation cost of the already used per-id queues.
-        for (_, queue) in &mut self.storage {
-            queue.clear();
-        }
-
+        self.storage.clear();
         self.count = 0;
     }
 }
diff --git a/src/ui/app.rs b/src/ui/app.rs
index ad8fa9fe0387921d5596236afa2f415efecbe8e5..902e5860bc472feb991261d1eb49c62519a111fd 100644
--- a/src/ui/app.rs
+++ b/src/ui/app.rs
@@ -318,14 +318,12 @@ impl App {
             // Skip non-pane tiles
             let Tile::Pane(pane) = tile else { continue };
             // Skip panes that do not have a subscription
-            let Some(sub_id) = pane.get_message_subscription() else {
-                continue;
-            };
+            let sub_ids: Vec<u32> = pane.get_message_subscriptions().collect();
 
             if pane.should_send_message_history() {
-                pane.update(self.message_broker.get(sub_id));
+                pane.update(self.message_broker.get(&sub_ids[..]).as_slice());
             } else {
-                pane.update(self.message_bundle.get(sub_id));
+                pane.update(self.message_bundle.get(&sub_ids[..]).as_slice());
             }
         }
 
diff --git a/src/ui/panes.rs b/src/ui/panes.rs
index 9b27514344054069934b1ff81472c9e7052b5cf6..5d031f3abf6888b69894e3d6899ea776eb9a8696 100644
--- a/src/ui/panes.rs
+++ b/src/ui/panes.rs
@@ -34,11 +34,11 @@ pub trait PaneBehavior {
 
     /// Updates the pane state. This method is called before `ui` to allow the
     /// pane to update its state based on the messages received.
-    fn update(&mut self, _messages: &[TimedMessage]) {}
+    fn update(&mut self, _messages: &[&TimedMessage]) {}
 
     /// Returns the ID of the messages this pane is interested in, if any.
-    fn get_message_subscription(&self) -> Option<u32> {
-        None
+    fn get_message_subscriptions(&self) -> Box<dyn Iterator<Item = u32>> {
+        Box::new(None.into_iter())
     }
 
     /// Checks whether the full message history should be sent to the pane.
@@ -61,12 +61,12 @@ impl PaneBehavior for Pane {
         self.pane.contains_pointer()
     }
 
-    fn update(&mut self, messages: &[TimedMessage]) {
+    fn update(&mut self, messages: &[&TimedMessage]) {
         self.pane.update(messages)
     }
 
-    fn get_message_subscription(&self) -> Option<u32> {
-        self.pane.get_message_subscription()
+    fn get_message_subscriptions(&self) -> Box<dyn Iterator<Item = u32>> {
+        self.pane.get_message_subscriptions()
     }
 
     fn should_send_message_history(&self) -> bool {
diff --git a/src/ui/panes/default.rs b/src/ui/panes/default.rs
index c3f7979e5ec83ea72df13087940f2fec997af02b..e4d1befeda37b7cca5ed8d10cd23c3a18d81527e 100644
--- a/src/ui/panes/default.rs
+++ b/src/ui/panes/default.rs
@@ -2,9 +2,12 @@ use super::PaneBehavior;
 use serde::{Deserialize, Serialize};
 use tracing::debug;
 
-use crate::ui::{
-    app::{PaneAction, PaneResponse},
-    utils::{SizingMemo, vertically_centered},
+use crate::{
+    mavlink::TimedMessage,
+    ui::{
+        app::{PaneAction, PaneResponse},
+        utils::{SizingMemo, vertically_centered},
+    },
 };
 
 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
@@ -60,10 +63,10 @@ impl PaneBehavior for DefaultPane {
         self.contains_pointer
     }
 
-    fn update(&mut self, _messages: &[crate::mavlink::TimedMessage]) {}
+    fn update(&mut self, _messages: &[&TimedMessage]) {}
 
-    fn get_message_subscription(&self) -> Option<u32> {
-        None
+    fn get_message_subscriptions(&self) -> Box<dyn Iterator<Item = u32>> {
+        Box::new(None.into_iter())
     }
 
     fn should_send_message_history(&self) -> bool {
diff --git a/src/ui/panes/pid_drawing_tool.rs b/src/ui/panes/pid_drawing_tool.rs
index 57c49cab71c6ad5e3a89c9b3e1fea908a55ea3b3..6f7695ebc98510a0f467e500dcae13020590faa3 100644
--- a/src/ui/panes/pid_drawing_tool.rs
+++ b/src/ui/panes/pid_drawing_tool.rs
@@ -151,7 +151,7 @@ impl PaneBehavior for PidPane {
         self.contains_pointer
     }
 
-    fn update(&mut self, messages: &[TimedMessage]) {
+    fn update(&mut self, messages: &[&TimedMessage]) {
         if let Some(msg) = messages.last() {
             for element in &mut self.elements {
                 element.update(&msg.message, self.message_subscription_id);
@@ -159,8 +159,8 @@ impl PaneBehavior for PidPane {
         }
     }
 
-    fn get_message_subscription(&self) -> Option<u32> {
-        Some(self.message_subscription_id)
+    fn get_message_subscriptions(&self) -> Box<dyn Iterator<Item = u32>> {
+        Box::new(Some(self.message_subscription_id).into_iter())
     }
 }
 
diff --git a/src/ui/panes/plot.rs b/src/ui/panes/plot.rs
index 684f2211ad620a4edc404c170e05ee2916ef75ab..42444abb31fb64a6bf431b8b79d806ad7d489ad2 100644
--- a/src/ui/panes/plot.rs
+++ b/src/ui/panes/plot.rs
@@ -186,7 +186,7 @@ impl PaneBehavior for Plot2DPane {
     }
 
     #[profiling::function]
-    fn update(&mut self, messages: &[TimedMessage]) {
+    fn update(&mut self, messages: &[&TimedMessage]) {
         if !self.state_valid {
             self.line_data.clear();
         }
@@ -226,8 +226,8 @@ impl PaneBehavior for Plot2DPane {
         self.state_valid = true;
     }
 
-    fn get_message_subscription(&self) -> Option<u32> {
-        Some(self.settings.plot_message_id)
+    fn get_message_subscriptions(&self) -> Box<dyn Iterator<Item = u32>> {
+        Box::new(Some(self.settings.plot_message_id).into_iter())
     }
 
     fn should_send_message_history(&self) -> bool {