docs: SQLite 연결 에러 트러블슈팅 문서 추가
This commit is contained in:
parent
94a8df6653
commit
bd30ece284
113
docs/TROUBLESHOOTING-SQLITE-CONNECTION.md
Normal file
113
docs/TROUBLESHOOTING-SQLITE-CONNECTION.md
Normal file
@ -0,0 +1,113 @@
|
||||
# 트러블슈팅: SQLite "I/O operation on closed file" 에러
|
||||
|
||||
## 발생일
|
||||
2026-02-27
|
||||
|
||||
## 증상
|
||||
- 관리자 페이지에서 회원 검색 시 500 에러 발생
|
||||
- 에러 메시지: `조회 실패: I/O operation on closed file.`
|
||||
- 서버 로그에는 200 OK로 찍히지만 응답 body에 에러 포함
|
||||
|
||||
## 원인
|
||||
|
||||
### 1. SQLite 싱글톤 연결 문제
|
||||
Flask의 멀티스레드 환경에서 `db_manager.get_sqlite_connection()`이 **싱글톤 연결**을 반환.
|
||||
한 요청에서 연결을 닫으면 다른 요청에서 "closed file" 에러 발생.
|
||||
|
||||
**문제 코드:**
|
||||
```python
|
||||
conn = db_manager.get_sqlite_connection() # 싱글톤 연결 반환
|
||||
cursor = conn.cursor()
|
||||
# ... 작업 ...
|
||||
# finally에서 conn.close() 호출 시 다른 요청에 영향
|
||||
```
|
||||
|
||||
### 2. 존재하지 않는 테이블 참조
|
||||
`product_category_mapping` 테이블이 DB에 없는데 쿼리 시도 → SQLite 에러 발생
|
||||
|
||||
## 해결 방법
|
||||
|
||||
### 1. 새 연결 사용 + finally에서 close
|
||||
```python
|
||||
conn = None
|
||||
try:
|
||||
conn = db_manager.get_sqlite_connection(new_connection=True) # 새 연결!
|
||||
cursor = conn.cursor()
|
||||
# ... 작업 ...
|
||||
except Exception as e:
|
||||
logging.error(f"에러: {e}")
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
finally:
|
||||
if conn:
|
||||
try:
|
||||
conn.close()
|
||||
except:
|
||||
pass
|
||||
```
|
||||
|
||||
### 2. 없는 테이블 조회 시 예외 처리
|
||||
```python
|
||||
try:
|
||||
cursor.execute("SELECT * FROM product_category_mapping WHERE ...")
|
||||
# ...
|
||||
except Exception:
|
||||
pass # 테이블 없으면 무시
|
||||
```
|
||||
|
||||
## 수정된 API 목록
|
||||
|
||||
| API | 파일 | 커밋 |
|
||||
|-----|------|------|
|
||||
| `/api/members/search` | app.py | 87a56d0 |
|
||||
| `/api/members/history/<id>` | app.py | 87a56d0 |
|
||||
| `/admin/search/user` | app.py | 1414bb1 |
|
||||
| `/admin/search/product` | app.py | 1414bb1 |
|
||||
| `/admin/user/<id>` | app.py | 4691d65, 94a8df6 |
|
||||
|
||||
## dbsetup.py 수정사항
|
||||
|
||||
`get_sqlite_connection()` 메서드에 `new_connection` 파라미터 추가:
|
||||
|
||||
```python
|
||||
def get_sqlite_connection(self, new_connection=False):
|
||||
"""
|
||||
SQLite 연결 반환
|
||||
- new_connection=True: 새 연결 생성 (API 요청마다 독립적 연결 필요시)
|
||||
- new_connection=False: 기존 싱글톤 연결 반환 (기본값, 하위 호환성)
|
||||
"""
|
||||
if new_connection:
|
||||
conn = sqlite3.connect(self.sqlite_path, check_same_thread=False)
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
# 기존 싱글톤 로직
|
||||
if self._sqlite_conn is None:
|
||||
self._sqlite_conn = sqlite3.connect(self.sqlite_path, check_same_thread=False)
|
||||
self._sqlite_conn.row_factory = sqlite3.Row
|
||||
return self._sqlite_conn
|
||||
```
|
||||
|
||||
## 추가 수정사항
|
||||
|
||||
### CDN 차단 문제
|
||||
Edge 브라우저의 Tracking Prevention이 cdnjs.cloudflare.com 차단
|
||||
→ lottie.min.js를 로컬 파일(`/static/js/lottie.min.js`)로 변경
|
||||
|
||||
**커밋:** 866d10f
|
||||
|
||||
## 교훈
|
||||
|
||||
1. **Flask 멀티스레드 환경에서 SQLite 연결은 요청마다 새로 생성**해야 안전
|
||||
2. **API 응답은 HTTP 상태코드로 판단하지 말고 body의 success 필드 확인**
|
||||
3. **없을 수 있는 테이블/컬럼 조회는 try-except로 감싸기**
|
||||
4. **CDN 의존성은 로컬 fallback 준비**
|
||||
|
||||
## 관련 커밋
|
||||
|
||||
```
|
||||
94a8df6 fix: product_category_mapping 테이블 없을 때 에러 무시
|
||||
4691d65 fix: /admin/user/<id> SQLite 연결 에러 해결
|
||||
866d10f fix: lottie CDN을 로컬 파일로 변경
|
||||
1414bb1 fix: /admin 사이드바 검색 SQLite 연결 에러 해결
|
||||
87a56d0 fix: /api/members/* SQLite 연결 에러 해결
|
||||
```
|
||||
Loading…
Reference in New Issue
Block a user