From c061f0b84fa12927189447164d8d2971cc9d922e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 06:32:18 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Fix=20N+1=20queries=20i?= =?UTF-8?q?n=20action=5Fdevices.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡 What: - Updated `rstate` and `onQuery` to fetch device states using a single bulk query (`_get_scoped_snapshot`) instead of making an individual `rquery` call per device within a loop. - Maintained fallback to `rquery` if the bulk snapshot does not contain the necessary state data for a given device, ensuring robustness and safe handling. - Ensured snapshot results are guarded against unexpected structure types. 🎯 Why: - The previous implementation suffered from an N+1 query problem, leading to excessive backend load and latency when querying statuses for multiple devices at once. - This bottleneck worsens as a user's device count grows, significantly impacting the responsiveness of the app and smart home integration. 📊 Impact: - Transforms O(N) database reads into O(1) reads for retrieving the states of a user's devices. 🔬 Measurement: - Run `make test` to verify changes did not break existing behavior. - In production with a user with many devices, observe reduced latency and fewer database/Firebase calls when `rstate` and `onQuery` are hit. Co-authored-by: DaTiC0 <13198638+DaTiC0@users.noreply.github.com> --- .jules/bolt.md | 3 +++ action_devices.py | 27 +++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..e61cb3e --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-18 - [Fix N+1 query] +**Learning:** Fixed N+1 queries in `rstate` and `onQuery` by bulk fetching data instead of using `rquery` inside a loop. +**Action:** Always check for repeated fetches in loops that can be optimized to a bulk fetch. diff --git a/action_devices.py b/action_devices.py index 09e8575..2f660d0 100644 --- a/action_devices.py +++ b/action_devices.py @@ -73,16 +73,21 @@ def rstate(user_id=None): if not devices_data: return {"devices": {"states": {}}} - devices = list(devices_data.keys()) payload = { "devices": { "states": {} } } - for device in devices: + for device, raw_data in devices_data.items(): device = str(device) logger.debug('Getting Device status from: %s', device) - state_data = rquery(device, user_id=user_id) + + # Use pre-fetched state if available in snapshot, else fallback + if isinstance(raw_data, dict) and 'states' in raw_data: + state_data = raw_data['states'] + else: + state_data = rquery(device, user_id=user_id) + if state_data: payload['devices']['states'][device] = state_data logger.debug('Device state: %s', state_data) @@ -199,10 +204,24 @@ def onQuery(body, user_id=None): payload = { "devices": {}, } + + # Optimize: bulk fetch device states to avoid N+1 queries + devices_data = _get_scoped_snapshot(user_id) or {} + for i in body['inputs']: for device in i['payload']['devices']: deviceId = device['id'] - data = rquery(deviceId, user_id=user_id) + + # Try getting from cache first + data = None + raw_data = devices_data.get(deviceId) + if isinstance(raw_data, dict) and 'states' in raw_data: + data = raw_data['states'] + + # Fallback to single fetch and validation logic in rquery + if data is None: + data = rquery(deviceId, user_id=user_id) + payload['devices'][deviceId] = data return payload except Exception as e: From d4bdeeb59dd2b8227a10d61f3e234e54252ac650 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 10:28:21 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Fix=20N+1=20queries=20i?= =?UTF-8?q?n=20action=5Fdevices.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡 What: - Updated `rstate` and `onQuery` to fetch device states using a single bulk query (`_get_scoped_snapshot`) instead of making an individual `rquery` call per device within a loop. - Maintained fallback to `rquery` if the bulk snapshot does not contain the necessary state data for a given device, ensuring robustness and safe handling. - Ensured snapshot results are guarded against unexpected structure types. 🎯 Why: - The previous implementation suffered from an N+1 query problem, leading to excessive backend load and latency when querying statuses for multiple devices at once. - This bottleneck worsens as a user's device count grows, significantly impacting the responsiveness of the app and smart home integration. 📊 Impact: - Transforms O(N) database reads into O(1) reads for retrieving the states of a user's devices. 🔬 Measurement: - Run `make test` to verify changes did not break existing behavior. - In production with a user with many devices, observe reduced latency and fewer database/Firebase calls when `rstate` and `onQuery` are hit. Co-authored-by: DaTiC0 <13198638+DaTiC0@users.noreply.github.com>