Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@
## 2024-03-16 - Batch JSON File I/O Operations
**Learning:** When using JSON file-backed repositories, iterating over items sequentially and calling `save_item` or `record_attempt` inside a loop leads to N+1 file read/write operations. This creates a significant performance bottleneck, especially when importing progress snapshots with numerous items and attempts.
**Action:** Prefer batch processing methods (e.g., `save_items`, `record_attempts`) so file-backed adapters can load storage once, update it in memory, and write it back in a single pass.

## 2025-03-22 - Optimize JSON file-backed adapters with in-place dict updates

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The date in this new entry's heading, 2025-03-22, is in the future. This is likely a typo and should probably be for the current year, 2024, to maintain chronological order and avoid confusion.

**Learning:** Fully deserializing and reserializing stored records to domain models for batch write operations in JSON file-backed adapters causes a massive $O(N)$ CPU/memory penalty. Iterating through the large stored JSON payload to construct heavy domain model lists before mutating values is very inefficient compared to directly mutating the dictionary objects in place.
**Action:** Always update raw dictionary entries directly in-place rather than instantiating entire domain models, mapping them back, and overwriting, significantly boosting serialization/write speeds.
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,22 @@ def save_items(self, items: list[LearningItem]) -> None:

storage = self._load_storage()
raw_items = storage.get("items", [])
existing_items = []
if isinstance(raw_items, list):
existing_items = [
_item_from_dict(entry) for entry in raw_items if isinstance(entry, dict)
]
by_id = {existing.id: existing for existing in existing_items}
for item in items:
by_id[item.id] = item
storage["items"] = [_item_to_dict(entry) for entry in by_id.values()]
if not isinstance(raw_items, list):
raw_items = []

items_to_save = {str(item.id): _item_to_dict(item) for item in items}

updated_raw_items = []
for entry in raw_items:
if isinstance(entry, dict):
entry_id = str(entry.get("id"))
if entry_id in items_to_save:
updated_raw_items.append(items_to_save.pop(entry_id))
else:
updated_raw_items.append(entry)
Comment on lines +55 to +61

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of this loop has a bug that can lead to data loss. If raw_items contains any elements that are not dictionaries, they will be dropped from updated_raw_items because the if isinstance(entry, dict): check on line 56 does not have a corresponding else block to handle other types. This could corrupt the JSON file over time. I suggest a more concise and robust implementation that correctly preserves all elements from raw_items.

        for entry in raw_items:
            if isinstance(entry, dict) and str(entry.get("id")) in items_to_save:
                updated_raw_items.append(items_to_save.pop(str(entry.get("id"))))
            else:
                updated_raw_items.append(entry)


updated_raw_items.extend(items_to_save.values())
storage["items"] = updated_raw_items
self._save_storage(storage)

def list_attempts(self) -> list[Attempt]:
Expand Down
Loading