Python, Flask 프레임웍으로 개발했던 서비스에서 월초만 되면 API에서 에러가 발생했다. DB는 MySQL, ORM은 SQLAlchemy를 사용했다. API 에러는 다음과 같았다.
ERROR in app: Exception on /XXXX/XXXX [POST]
...
OperationalError: (_mysql_exceptions.OperationalError) (1213, 'Deadlock found when trying to get lock; try restarting transaction') [SQL: u'INSERT INTO ...'] [parameters: (...)]
OperationalError 에러이고, 락을 가져오려고 할 때, Deadlock이 발견됐으니 트랜잭션을 다시 시작하라는 내용이다.
문제가 되는 부분을 찾았다. insert, commit 하는 부분이었다.
session.commit()
그래서 에러 메시지 내용에 따라 예외 처리를 했다.
try:
session.commit()
except Exception as e:
session.rollback()
raise e
이 상태에서 예외를 처리하지 않으면, 다시 말해서 rollback을 하지 않으면 이 API(세션)는 더 이상 DB Select조차 할 수 없다. 그때 발생하는 에러는 다음과 같다.
InvalidRequestError: This Session's transaction has been rolled back due to a previous exception during flush.
Dead Lock 발생으로 서비스가 되지 않는 문제는 rollback으로 해결했다. 이제 Dead Lock이 걸리는 원인을 찾아야겠다.
일단 2개의 MySQL 이벤트가 있다.
- 이벤트 A : 월마다 한 번 오전 9시에 A 테이블에 파티션 추가하는 프로시저 실행
- 이벤트 B : 매일 한 번 오전 11시에 B 테이블에 파티션 추가하는 프로시저 실행
특이사항:
- 스토리지 엔진은 MyISAM.
- 엔진 자체가 트랜잭션을 지원하지 않으므로, SQLAlchemy가 애플리케이션 단에서 처리하는 것 같음.
- 매일 11시에 INSERT가 많이 발생한다.
- lock 걸렸는지 확인한 결과 문제가 없었다.
- 에러가 발생하고, Wait 상태인 프로세스도 없었다.
- API만 재시작하면 정상 동작한다. 이것은 그 세션만 문제라는 얘기다.
그래서 개발 서버에서 재현하려고 시도했다. jMeter로 API에 Insert를 대량 발생시켰다. 그러나 에러는 발생하지 않았다.
여러 가지 설정도 바꿔봤지만, Dead Lock은 전혀 발생하지 않았다.
- concurrent_insert
- table_lock_wait_timeout
개발 서버와 실 서버의 mysql 버전 차이도 영향이 있을까?
- Development : 5.1.73-log
- Production 5.5.27-log
실 서버에서는 발생하는데 어째서 개발 서버에서는 발생하지 않을까? 정확한 원인은 찾지 못했다. 이벤트에서 테이블 락을 걸어주고, 프로시저 작업이 끝나면 락을 풀고, 에러가 발생하는지 확인해봐야겠다.