Files
TimeTracker/docs/implementation-notes/COMMENT_ATTACHMENTS_OPTIMIZATION.md
T
Dries Peeters d1c3471ba4 docs: Add comprehensive implementation documentation
- Add ENHANCEMENT_PLAN_IMPLEMENTATION_STATUS.md
- Add ENHANCEMENT_PLAN_PROGRESS_SUMMARY.md
- Add COMMENT_ATTACHMENTS_IMPLEMENTATION.md
- Add COMMENT_ATTACHMENTS_OPTIMIZATION.md
- Add FINAL_IMPLEMENTATION_SUMMARY.md

Documentation covers all enhancements, implementation status,
remaining work, and optimization recommendations.
2026-01-04 06:23:55 +01:00

3.1 KiB

Comment Attachments Performance Optimization

Date: 2025-01-27
Status: Recommended Enhancement


Overview

Comment attachments are loaded using a lazy="dynamic" relationship, which means attachments are loaded on-demand when accessed. This can lead to N+1 query problems when displaying multiple comments with attachments.


Current Implementation

Relationship Definition

# app/models/comment_attachment.py
comment = db.relationship("Comment", backref=db.backref("attachments", lazy="dynamic", cascade="all, delete-orphan"))

The lazy="dynamic" means:

  • comment.attachments returns a query object, not a list
  • Accessing comment.attachments triggers a database query
  • Iterating over attachments in templates will work (SQLAlchemy auto-executes), but each comment triggers a separate query

Performance Issue

When displaying a list of comments with attachments:

  1. Load all comments (1 query)
  2. For each comment, access comment.attachments (N queries, one per comment)
  3. Total: 1 + N queries (N+1 problem)

Use selectinload() to eager load attachments when querying comments:

Task View Route (Already Updated)

# app/routes/tasks.py - UPDATED
from sqlalchemy.orm import selectinload

all_comments = (
    Comment.query.filter_by(task_id=task_id)
    .options(
        joinedload(Comment.author),
        selectinload(Comment.replies).joinedload(Comment.author),
        selectinload(Comment.attachments)  # Added
    )
    .order_by(Comment.created_at.asc())
    .all()
)

Project Service (Needs Update)

If ProjectService.get_project_view_data() loads comments, it should also eager load attachments:

# In ProjectService.get_project_view_data()
from sqlalchemy.orm import selectinload

comments = (
    Comment.query.filter_by(project_id=project_id)
    .options(
        joinedload(Comment.author),
        selectinload(Comment.replies).joinedload(Comment.author),
        selectinload(Comment.attachments)  # Add this
    )
    .order_by(Comment.created_at.asc())
    .all()
)

Benefits

  1. Performance: Reduces N+1 queries to 2 queries (comments + attachments)
  2. Scalability: Works efficiently with many comments
  3. Consistency: Matches pattern used for replies and authors

Implementation Status

  • Task View Route: Updated to eager load attachments
  • Project Service: Needs review and update
  • Quote Comments: Needs review if quotes have comments
  • API Endpoints: May benefit from eager loading

Testing

After implementing, verify:

  1. No N+1 queries in database logs
  2. Comments with attachments load correctly
  3. Performance improvement with many comments
  4. No breaking changes to existing functionality

Notes

  • selectinload() is preferred over joinedload() for one-to-many relationships (like attachments)
  • selectinload() uses a separate SELECT IN query, which is more efficient than joins for collections
  • The dynamic relationship still works for programmatic access, but templates benefit from eager loading