Project/트렌드 매거진

[트렌드매거진] 25.03.24 개발일지/ 아티클 작성 프로세스 최적화 및 트랜잭션 적용

한33 2025. 3. 26. 14:04

📚 Article 작성 로직 고민

아티클 생성 (3.62s)

 

태그 5개 및 아티클과의 연관성 분석 및 테이블 생성  (9.16s)
기존 태그들과의 유사도 분석 및 테이블 생성 (1m 30s)

 

💡 현재 로직:

아티클 생성 (3.62s) → 태그 5개 및 아티클과의 연관성 분석 및 테이블 생성 (9.16s)   → 기존 태그들과의 유사도 분석 및 테이블 생성 (1m 30s)

 

문제: 시간이 많이 걸린다.

 

1. 우선 태그를 5개에서 3개로 줄여야겠다. ai 가 5개를 뽑으면서 태그가 너무 다양해지고 의미는 같고 단어만 조금 다른 태그들이 생겼다.

 

2. 연관성 분석을 꼭 해야할까? 라는 생각을 했다. 어차피 연관이 있는 태그를 ai 가 뽑아주는건데 그걸 수치화로 더 자세히 할 필요가 있을까.

 

3. 기존 태그들을 전체 조회하면서 생성된 태그들과의 유사도를 분석하고 저장하는 로직을 게시글 등록과는 분리시켜야겠다. 시간이 많이 걸리고 이는 점점 태그가 많아질수록 증가될 것이다.

추가로 태그 전체 조회가 아닌 메인카테고리별로 태그 테이블을 분리하면 조금 더 나아지지 않을까 싶다. 

또는 태그 테이블에 main category 칼럼을 추가해서 해당 main category 의 tag 들 안에서 조회를 하도록 하는 방법을 생각해보았다.

 

💡 수정

1. 태그 3개로 축소, 연관성 분석 삭제 ( 9.16s → 7.64s )


📚 Article 작성 로직에 트랜잭션 적용

Article 생성 시에 article 이외에 tag, tag_similarities 등 다른 table 들도 함께 저장되는 로직이다.

이 부분에서 open ai 인 외부 서비스가 묶여서 실행되기 때문에 안정성을 위해 하나라도 실패하면 에러를 내보내는 작업이 필요했다.

그렇기 때문에 데이터 원자성과 무결성을 보장하기 위해 트랜잭션을 적용시키고자 했다.

트랜잭션 데코레이터 추가

def transaction(func: Callable) -> Callable:
    @wraps(func)
    async def wrapper(self, *args, **kwargs):
        # 세션이 이미 있으면 사용, 없으면 새로 열기
        db_session = getattr(self, 'db_session', None)
        if db_session is None:
            async with AsyncSessionFactory() as db_session:
                self.db_session = db_session  # 임시로 할당
                async with db_session.begin():
                    result = await func(self, *args, **kwargs)
                return result
        else:
            async with self.db_session.begin():
                result = await func(self, *args, **kwargs)
            return result
    return wrapper

 

문제: 

 

트랜잭션이 끝난 뒤에 자동으로 세션이 닫히는 구조여서 이후에 refresh() 를 실행하는데에 실패했다는 오류이다.

기존 코드:

async def get_db() -> AsyncGenerator[AsyncSession, None]:
    async with AsyncSessionFactory() as db:
        yield db

 

  • AsyncSessionFactory() 로 세션을 만들고 async with 안에서 세션을 열었다.
  • yield db 로 세션을 FastAPI 에 넘겨준다.
  • async with 블록이 끝나면 세션이 자동으로 닫힌다. ( FastAPI 요청이 끝날 때 )

 

수정 코드:

connection.py

async def get_db() -> AsyncGenerator[AsyncSession, None]:
    session = AsyncSessionFactory()
    yield session
    await session.close()
  • AsyncSessionFactory()로 세션을 만든다.
  • yield session으로 FastAPI에 세션을 넘겨준다.
  • FastAPI가 요청을 처리하는 동안 세션은 열려있다.
  • 요청이 완전히 끝난 뒤(yield 이후), await session.close()로 세션을 닫는다.

트랜잭션 데코레이터가 commit 을 담당하기 때문에 

 

article > repository > article.py

# 아티클 db 저장
async def save_article(self, article: Article) -> Article:
    self.session.add(instance=article)
    await self.session.flush()
    await self.session.refresh(instance=article)
    return article

 

해결:

기존에는 `commit`을 써서 각 작업마다 확정했는데, 이제 `flush`로 바꿔서 트랜잭션을 유지하게 했다.
→ 예전에는 `article`, `tag`, `tag_similarities`가 각각 `commit`으로 DB에 저장을 확정했다면, 지금은 `flush`로 데이터를 준비만 하고, `@transaction` 데코레이터에서 한 번에 `commit`으로 마무리하는 구조로 수정했다.