|
| 1 | +# MongoEngine Async Support Implementation Progress |
| 2 | + |
| 3 | +## 프로젝트 목표 |
| 4 | +PyMongo의 AsyncMongoClient를 활용하여 MongoEngine에 완전한 비동기 지원을 추가하는 것 |
| 5 | + |
| 6 | +## 현재 상황 분석 |
| 7 | + |
| 8 | +### PyMongo Async 지원 현황 |
| 9 | +- PyMongo는 `AsyncMongoClient`를 통해 완전한 비동기 지원 제공 |
| 10 | +- 모든 주요 작업에 async/await 패턴 사용 |
| 11 | +- `async for`를 통한 커서 순회 지원 |
| 12 | +- 기존 동기 API와 병렬로 제공되어 호환성 유지 |
| 13 | + |
| 14 | +### MongoEngine 현재 구조의 문제점 |
| 15 | +1. **동기적 설계**: 모든 데이터베이스 작업이 블로킹 방식으로 구현 |
| 16 | +2. **디스크립터 프로토콜 한계**: ReferenceField의 lazy loading이 동기적으로만 가능 |
| 17 | +3. **전역 상태 관리**: 스레드 로컬 스토리지 사용으로 비동기 컨텍스트 관리 어려움 |
| 18 | +4. **QuerySet 체이닝**: 현재의 lazy evaluation이 async/await와 충돌 |
| 19 | + |
| 20 | +## 개선된 구현 전략 |
| 21 | + |
| 22 | +### 핵심 설계 원칙 |
| 23 | +1. **단일 Document 클래스**: 별도의 AsyncDocument 대신 기존 Document에 비동기 메서드 추가 |
| 24 | +2. **연결 타입 기반 동작**: async connection 사용 시 자동으로 비동기 동작 |
| 25 | +3. **명확한 메서드 네이밍**: `async_` 접두사로 비동기 메서드 구분 |
| 26 | +4. **완벽한 하위 호환성**: 기존 코드는 수정 없이 동작 |
| 27 | + |
| 28 | +### 1단계: 기반 구조 설계 (Foundation) |
| 29 | + |
| 30 | +#### 1.1 하이브리드 연결 관리자 |
| 31 | +```python |
| 32 | +# mongoengine/connection.py 수정 |
| 33 | +- 기존 connect() 함수 유지 |
| 34 | +- connect_async() 함수 추가로 AsyncMongoClient 연결 |
| 35 | +- get_connection()이 연결 타입 자동 감지 |
| 36 | +- contextvars로 비동기 컨텍스트 관리 |
| 37 | +``` |
| 38 | + |
| 39 | +#### 1.2 Document 클래스 확장 |
| 40 | +```python |
| 41 | +# mongoengine/document.py 수정 |
| 42 | +class Document(BaseDocument): |
| 43 | + # 기존 동기 메서드 유지 |
| 44 | + def save(self, ...): |
| 45 | + if is_async_connection(): |
| 46 | + raise RuntimeError("Use async_save() with async connection") |
| 47 | + # 기존 로직 |
| 48 | + |
| 49 | + # 새로운 비동기 메서드 추가 |
| 50 | + async def async_save(self, force_insert=False, validate=True, ...): |
| 51 | + if not is_async_connection(): |
| 52 | + raise RuntimeError("Use save() with sync connection") |
| 53 | + # 비동기 저장 로직 |
| 54 | + |
| 55 | + async def async_delete(self, signal_kwargs=None, ...): |
| 56 | + # 비동기 삭제 로직 |
| 57 | + |
| 58 | + async def async_reload(self): |
| 59 | + # 비동기 새로고침 로직 |
| 60 | +``` |
| 61 | + |
| 62 | +### 2단계: 핵심 CRUD 작업 |
| 63 | + |
| 64 | +#### 2.1 통합된 QuerySet |
| 65 | +```python |
| 66 | +# mongoengine/queryset/queryset.py 수정 |
| 67 | +class QuerySet: |
| 68 | + # 기존 동기 메서드 유지 |
| 69 | + def first(self): |
| 70 | + if is_async_connection(): |
| 71 | + raise RuntimeError("Use async_first() with async connection") |
| 72 | + # 기존 로직 |
| 73 | + |
| 74 | + # 비동기 메서드 추가 |
| 75 | + async def async_first(self): |
| 76 | + # 첫 번째 결과 반환 |
| 77 | + |
| 78 | + async def async_get(self, *q_args, **q_kwargs): |
| 79 | + # 단일 객체 조회 |
| 80 | + |
| 81 | + async def async_count(self): |
| 82 | + # 개수 반환 |
| 83 | + |
| 84 | + async def async_create(self, **kwargs): |
| 85 | + # 객체 생성 및 저장 |
| 86 | + |
| 87 | + def __aiter__(self): |
| 88 | + # 비동기 반복자 |
| 89 | +``` |
| 90 | + |
| 91 | +### 3단계: 고급 기능 |
| 92 | + |
| 93 | +#### 3.1 필드의 비동기 지원 |
| 94 | +```python |
| 95 | +# ReferenceField에 비동기 메서드 추가 |
| 96 | +class ReferenceField(BaseField): |
| 97 | + # 기존 동기 lazy loading은 유지 |
| 98 | + def __get__(self, instance, owner): |
| 99 | + if is_async_connection(): |
| 100 | + # 비동기 컨텍스트에서는 Proxy 객체 반환 |
| 101 | + return AsyncReferenceProxy(self, instance) |
| 102 | + # 기존 동기 로직 |
| 103 | + |
| 104 | + # 명시적 비동기 fetch 메서드 |
| 105 | + async def async_fetch(self, instance): |
| 106 | + # 비동기 참조 로드 |
| 107 | +``` |
| 108 | + |
| 109 | +#### 3.2 비동기 집계 작업 |
| 110 | +```python |
| 111 | +class QuerySet: |
| 112 | + async def async_aggregate(self, pipeline): |
| 113 | + # 비동기 집계 파이프라인 실행 |
| 114 | + |
| 115 | + async def async_distinct(self, field): |
| 116 | + # 비동기 distinct 작업 |
| 117 | +``` |
| 118 | + |
| 119 | +### 4단계: 신호 및 트랜잭션 |
| 120 | + |
| 121 | +#### 4.1 하이브리드 신호 시스템 |
| 122 | +```python |
| 123 | +# 동기/비동기 모두 지원하는 신호 |
| 124 | +class HybridSignal: |
| 125 | + def send(self, sender, **kwargs): |
| 126 | + if is_async_connection(): |
| 127 | + return self.async_send(sender, **kwargs) |
| 128 | + # 기존 동기 신호 전송 |
| 129 | + |
| 130 | + async def async_send(self, sender, **kwargs): |
| 131 | + # 비동기 신호 핸들러 실행 |
| 132 | +``` |
| 133 | + |
| 134 | +#### 4.2 비동기 트랜잭션 |
| 135 | +```python |
| 136 | +# 비동기 트랜잭션 컨텍스트 매니저 |
| 137 | +@asynccontextmanager |
| 138 | +async def async_run_in_transaction(): |
| 139 | + # 비동기 트랜잭션 관리 |
| 140 | +``` |
| 141 | + |
| 142 | +## 구현 로드맵 |
| 143 | + |
| 144 | +### Phase 1: 기본 구조 (2-3주) |
| 145 | +- [ ] 하이브리드 연결 관리자 구현 (connect_async, is_async_connection) |
| 146 | +- [ ] Document 클래스에 async_save(), async_delete() 메서드 추가 |
| 147 | +- [ ] EmbeddedDocument 클래스에 비동기 메서드 추가 |
| 148 | +- [ ] 비동기 단위 테스트 프레임워크 설정 |
| 149 | + |
| 150 | +### Phase 2: 쿼리 작업 (3-4주) |
| 151 | +- [ ] QuerySet에 비동기 메서드 추가 (async_first, async_get, async_count) |
| 152 | +- [ ] 비동기 반복자 (__aiter__) 구현 |
| 153 | +- [ ] async_create(), async_update(), async_delete() 벌크 작업 |
| 154 | +- [ ] 비동기 커서 관리 및 최적화 |
| 155 | + |
| 156 | +### Phase 3: 필드 및 참조 (2-3주) |
| 157 | +- [ ] ReferenceField에 async_fetch() 메서드 추가 |
| 158 | +- [ ] AsyncReferenceProxy 구현 |
| 159 | +- [ ] LazyReferenceField 비동기 지원 |
| 160 | +- [ ] GridFS 비동기 작업 (async_put, async_get) |
| 161 | +- [ ] 캐스케이드 작업 비동기화 |
| 162 | + |
| 163 | +### Phase 4: 고급 기능 (3-4주) |
| 164 | +- [ ] 하이브리드 신호 시스템 구현 |
| 165 | +- [ ] async_run_in_transaction() 트랜잭션 지원 |
| 166 | +- [ ] 비동기 컨텍스트 매니저 (async_switch_db 등) |
| 167 | +- [ ] async_aggregate() 집계 프레임워크 지원 |
| 168 | + |
| 169 | +### Phase 5: 통합 및 최적화 (2-3주) |
| 170 | +- [ ] 성능 최적화 및 벤치마크 |
| 171 | +- [ ] 문서화 (async 메서드 사용법) |
| 172 | +- [ ] 마이그레이션 가이드 작성 |
| 173 | +- [ ] 동기/비동기 통합 테스트 |
| 174 | + |
| 175 | +## 주요 고려사항 |
| 176 | + |
| 177 | +### 1. API 설계 원칙 |
| 178 | +- **통합된 Document 클래스**: 별도 클래스 없이 기존 Document에 비동기 메서드 추가 |
| 179 | +- **명명 규칙**: 비동기 메서드는 'async_' 접두사 사용 (예: save → async_save) |
| 180 | +- **연결 타입 자동 감지**: 연결 타입에 따라 적절한 메서드 사용 강제 |
| 181 | + |
| 182 | +### 2. 호환성 전략 |
| 183 | +- 기존 코드는 100% 호환 |
| 184 | +- 동기 연결에서 async 메서드 호출 시 명확한 에러 |
| 185 | +- 비동기 연결에서 sync 메서드 호출 시 명확한 에러 |
| 186 | + |
| 187 | +### 3. 성능 고려사항 |
| 188 | +- 연결 풀링 최적화 |
| 189 | +- 배치 작업 지원 |
| 190 | +- 불필요한 비동기 오버헤드 최소화 |
| 191 | + |
| 192 | +### 4. 테스트 전략 |
| 193 | +- 모든 비동기 기능에 대한 단위 테스트 |
| 194 | +- 동기/비동기 동작 일관성 검증 |
| 195 | +- 연결 타입 전환 시나리오 테스트 |
| 196 | + |
| 197 | +## 예상 사용 예시 |
| 198 | + |
| 199 | +```python |
| 200 | +from mongoengine import Document, StringField, connect_async |
| 201 | + |
| 202 | +# 비동기 연결 |
| 203 | +await connect_async('mydatabase') |
| 204 | + |
| 205 | +# 모델 정의 (기존과 완전히 동일) |
| 206 | +class User(Document): |
| 207 | + name = StringField(required=True) |
| 208 | + email = StringField(required=True) |
| 209 | + |
| 210 | +# 비동기 사용 |
| 211 | +user = User( name="John", email="[email protected]") |
| 212 | +await user.async_save() |
| 213 | + |
| 214 | +# 비동기 조회 |
| 215 | +user = await User.objects.async_get(name="John") |
| 216 | +users = await User.objects.filter(name__startswith="J").async_count() |
| 217 | + |
| 218 | +# 비동기 반복 |
| 219 | +async for user in User.objects.filter(active=True): |
| 220 | + print(user.name) |
| 221 | + |
| 222 | +# 비동기 업데이트 |
| 223 | +await User.objects.filter( name="John").async_update( email="[email protected]") |
| 224 | + |
| 225 | +# ReferenceField 비동기 로드 |
| 226 | +class Post(Document): |
| 227 | + author = ReferenceField(User) |
| 228 | + title = StringField() |
| 229 | + |
| 230 | +post = await Post.objects.async_first() |
| 231 | +# 비동기 컨텍스트에서는 명시적 fetch 필요 |
| 232 | +author = await post.author.async_fetch() |
| 233 | +``` |
| 234 | + |
| 235 | +## 다음 단계 |
| 236 | + |
| 237 | +1. **Phase 1 시작**: 하이브리드 연결 관리자 구현 |
| 238 | +2. **커뮤니티 피드백**: 통합 설계 방식에 대한 의견 수렴 |
| 239 | +3. **벤치마크 설정**: 동기/비동기 성능 비교 기준 수립 |
| 240 | +4. **CI/CD 파이프라인**: 비동기 테스트 환경 구축 |
| 241 | + |
| 242 | +## 기대 효과 |
| 243 | + |
| 244 | +1. **완벽한 하위 호환성**: 기존 프로젝트는 수정 없이 동작 |
| 245 | +2. **점진적 마이그레이션**: 필요한 부분만 비동기로 전환 가능 |
| 246 | +3. **직관적 API**: async_ 접두사로 명확한 구분 |
| 247 | +4. **성능 향상**: I/O 바운드 작업에서 크게 개선 |
| 248 | + |
| 249 | +--- |
| 250 | + |
| 251 | +이 문서는 구현 진행에 따라 지속적으로 업데이트됩니다. |
0 commit comments