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.

## 2024-05-14 - Batch operations on JSON file-backed adapters
**Learning:** In JSON file-backed adapters (like JsonFilePracticeRepository), `save_items` performs O(N) deserialization and serialization of all stored items on every call. This creates a severe performance bottleneck when dealing with large repositories.
**Action:** When performing in-place dictionary updates for batch operations, update the raw dictionary entries in-place rather than fully deserializing and reserializing all stored records to domain models. This prevents O(N) CPU/memory penalties.
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,24 @@ 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 = []

storage["items"] = raw_items

# Update items in-place to avoid O(N) deserialization of the entire repository
items_to_update = {str(item.id): item for item in items}
for i, entry in enumerate(raw_items):
if not isinstance(entry, dict):
continue

entry_id = str(entry.get("id"))
if entry_id in items_to_update:
raw_items[i] = _item_to_dict(items_to_update.pop(entry_id))

for item in items_to_update.values():
raw_items.append(_item_to_dict(item))

self._save_storage(storage)

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