From bd30ece28485f100e38bae1aff364f49558415e5 Mon Sep 17 00:00:00 2001 From: thug0bin Date: Fri, 27 Feb 2026 16:17:20 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20SQLite=20=EC=97=B0=EA=B2=B0=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=ED=8A=B8=EB=9F=AC=EB=B8=94=EC=8A=88=ED=8C=85=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/TROUBLESHOOTING-SQLITE-CONNECTION.md | 113 ++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 docs/TROUBLESHOOTING-SQLITE-CONNECTION.md diff --git a/docs/TROUBLESHOOTING-SQLITE-CONNECTION.md b/docs/TROUBLESHOOTING-SQLITE-CONNECTION.md new file mode 100644 index 0000000..debfef6 --- /dev/null +++ b/docs/TROUBLESHOOTING-SQLITE-CONNECTION.md @@ -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/` | app.py | 87a56d0 | +| `/admin/search/user` | app.py | 1414bb1 | +| `/admin/search/product` | app.py | 1414bb1 | +| `/admin/user/` | 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/ SQLite 연결 에러 해결 +866d10f fix: lottie CDN을 로컬 파일로 변경 +1414bb1 fix: /admin 사이드바 검색 SQLite 연결 에러 해결 +87a56d0 fix: /api/members/* SQLite 연결 에러 해결 +```