Pylance 말 맞음. datetime.utcnow()는 timezone 없는(naive) UTC라 요즘은 권장 안 함. SQLAlchemy에서도 UTC timezone 붙여서 가는 이유가? SQLAlchemy에서 나중에 개고생하는 포인트가 몇 개 보인다. 특히 네가 말한 lazy loading / N+1 / 중복 시청로그 / 공통 timestamp 상속

 두 개 합치면 네가 처음 만든 버전(건너뛰기 감지 max_time, 완료 여부 is_completed) 기능이 더 강력하고, 두 번째 버전(FK + relationship)이 DB 설계는 더 제대로 되어 있음.

last_watched_time은 사실 current_time이랑 역할이 겹쳐서 제거하는 게 맞고, FK/relationship 유지하면서 어뷰징 방지까지 넣는 게 최종형임.






Pylance 말 맞음. datetime.utcnow()는 timezone 없는(naive) UTC라 요즘은 권장 안 함. SQLAlchemy에서도 UTC timezone 붙여서 가는 게 깔끔함.


SQLAlchemy 2.x/Pylance 기준


더 개선가능한거 있나? 특히나 상속이랑 저번에 뭐 레이지 로딩때문에 개고생한거 지금 미리 방지하려는데 여기서 설정 잘하면 될거같은데


있음. 지금 구조도 괜찮은데 SQLAlchemy에서 나중에 개고생하는 포인트가 몇 개 보인다. 특히 네가 말한 lazy loading / N+1 / 중복 시청로그 / 공통 timestamp 상속은 지금 미리 잡는 게 좋음.

바로 적용 가능한 개선 포인트:

1) 공통 created_at, updated_at 상속

지금 모든 테이블에 반복됨. 나중에 수정하다 한 군데 빼먹기 쉽다.

from sqlalchemy.orm import declarative_base
from sqlalchemy import Column, DateTime
from datetime import datetime, timezone

Base = declarative_base()

class TimestampMixin:
    created_at = Column(
        DateTime(timezone=True),
        default=lambda: datetime.now(timezone.utc)
    )

    updated_at = Column(
        DateTime(timezone=True),
        default=lambda: datetime.now(timezone.utc),
        onupdate=lambda: datetime.now(timezone.utc)
    )

사용:

class User(TimestampMixin, Base):
    __tablename__ = "users"

이걸 Course, Video, WatchLog, UserCoursePermission 전부 적용.


2) lazy 로딩 미리 설정 (중요)

네가 전에 터진 게 거의 이거일 가능성 큼.

기본:

watch_logs = relationship("WatchLog")

이러면:

users = db.query(User).all()

for user in users:
    print(user.watch_logs)

사용자 수만큼 SQL 또 날림 (N+1 지옥)

미리:

watch_logs = relationship(
    "WatchLog",
    back_populates="user",
    lazy="selectin"
)

추천:

  • lazy="selectin" ← 거의 국룰

  • lazy="joined" ← 항상 JOIN

  • lazy="raise" ← 실수하면 즉시 에러

내 추천:

course_permissions = relationship(
    "UserCoursePermission",
    back_populates="user",
    lazy="selectin"
)

watch_logs = relationship(
    "WatchLog",
    back_populates="user",
    lazy="selectin"
)

전부 동일하게.


3) WatchLog 중복 방지 (매우 중요)

지금은 같은 유저가 같은 영상 기록 무한 생성 가능

예:

user=1
video=3

10개 생성 가능

대신:

from sqlalchemy import UniqueConstraint

class WatchLog(...):

    __table_args__ = (
        UniqueConstraint(
            "user_id",
            "video_id",
            name="uq_user_video"
        ),
    )

그러면:

유저당 영상 하나의 로그만 유지

업데이트만 하면 됨.

이거 안 넣으면 나중에 이어보기 꼬임.


4) 인덱스 추가

조회 패턴 생각하면:

__table_args__ = (
    UniqueConstraint(
        "user_id",
        "video_id",
        name="uq_user_video"
    ),
    Index(
        "idx_watch",
        "user_id",
        "video_id"
    )
)

시청기록 조회 빨라짐.


5) cascade 정리

지금 일부만 있음.

추천:

watch_logs = relationship(
    "WatchLog",
    back_populates="user",
    cascade="all, delete-orphan",
    lazy="selectin"
)

의미:

유저 삭제 → 시청로그 자동삭제

안 하면 고아 데이터 쌓임.


6) max_watched_time 보호

실수 방지:

현재:

max_watched_time=20
last_watched_time=100

가능

말 안 됨

나중에 서비스 레이어:

watch.max_watched_time = max(
    watch.max_watched_time,
    incoming_time
)

스킵감지 안정됨.


최종 추천 우선순위

필수:

  1. TimestampMixin

  2. lazy="selectin"

  3. UniqueConstraint(user_id, video_id)

있으면 좋음:
4. cascade
5. Index

이 5개 넣으면 SQLAlchemy에서 겪는 70~80% 삽질 미리 막는다.

댓글

가장 많이 본 글