Django

Django apscheduler SQLAlchemy Connection timed out 오류

UserDonghu 2025. 2. 12. 21:03

Django 서버에서 스케쥴링을 할 일이 있어서 SQLAlchemy 라이브러리를 통해 사용중인 MySQL DB에 job을 저장해서 scheduler를 사용하고있었다.

 

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from django.conf import settings

class SchedulerManager:
    _instance = None

    @classmethod
    def get_scheduler(cls):
        if cls._instance is None:
            db_settings = settings.DATABASES['default']
            db_url = f"mysql+pymysql://{db_settings['USER']}:{db_settings['PASSWORD']}@{db_settings['HOST']}:{db_settings['PORT']}/{db_settings['NAME']}"
            jobstores = {
                'default': SQLAlchemyJobStore(url=db_url)
            }
            cls._instance = BackgroundScheduler(jobstores=jobstores, timezone="Asia/Seoul")
            cls._instance.start()
        return cls._instance

\

scheduler.py 파일을 만들어서 이런식으로 Django setting파일에서 설정해둔 DB 정보를 가져와서 jobstores설정을 해줬는데, jobstores설정을 안해줬을때 서버를 끄거나 재시동하면 기존에 스케쥴링 해둔 task & job들이 모두 없어지는거에 비해서 설정을 해두면 DB에 저장을 해둬서 재시동해도 재시간이되면 동작을 하고, DB에 접속해서 쿼리문으로 앞으로 실행될 예정인 작업들도 볼수있어서 좋았다.

 

 

그런데 분명 테스트를 할때는 잘 실행되던 작업들이 밤만 되면 오류가 나서 실행이 안되는 문제가 발생했다.

처음에는 작업을 스케쥴링 해둔 함수들에 변수로 들어가는 인스턴스가 문제를 일으키는줄 알았는데, 로그 파일에 오류를 확인해보니,

sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2006, "MySQL server has gone away (TimeoutError(110, 'Connection timed out'))")

이런 에러가 떠있었다.

 

분명 MySQL에 잘 연결돼서 쿼리문으로 예정된 작업까지 확인했는데 이게 무슨 오류지..? 하고 생각했었는데 알고보니 MySQL 서버가 긴 시간동안 사용되지 않아서 연결이 끊어진것이었다.

 

이 경우에는 두가지를 의심해볼수있는데,

첫번째는 MySQL 설정의 my.cnf 파일에서 wait_timeout과 interactive_timeout 이 몇초로 설정되어있는지를 확인해야한다.

[mysqld]
wait_timeout = 28800
interactive_timeout = 28800

 

 

두번째는 Scheduler의 SQLAlchemy 설정에서 pool_recycle, pool_pre_ping을 수정하는것이다.

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from django.conf import settings

class SchedulerManager:
    _instance = None

    @classmethod 
    def get_scheduler(cls):
        if cls._instance is None:
            db_settings = settings.DATABASES['default']
            db_url = (
                f"mysql+pymysql://{db_settings['USER']}:{db_settings['PASSWORD']}"
                f"@{db_settings['HOST']}:{db_settings['PORT']}/{db_settings['NAME']}"
                "?charset=utf8mb4"
            )

            jobstores = {
                'default': SQLAlchemyJobStore(
                    url=db_url,
                    engine_options={
                        'pool_size': 20, # 기본적으로 유지할 DB 갯수
                        'max_overflow': 10, # pool_size를 넘어서는 요청이 왔을때 추가로 허용하는 최대 연결 갯수
                        'pool_timeout': 60, # 사용 가능한 연결이 없을때 최대 60초 대기. 이 시간 지나면 Timeout에러
                        'pool_recycle': 3600, # 오래된 연결을 재설정하는 시간(초). 1시간마다 재연결
                        'pool_pre_ping': True, # 쿼리 실행전 연결이 유효한지 검사
                    }
                )
            }

            cls._instance = BackgroundScheduler(
                jobstores=jobstores, 
                timezone="Asia/Seoul",
                job_defaults={
                    'coalesce': True, # Job이 여러 번 스킵된 경우, 마지막 한 번만 실행할지 여부
                    'max_instances': 3, # 동시에 실행할수있는 Job 갯수
                    'misfire_grace_time': 60 # Job이 실행시간보다 늦게 실행될때, 허용할 최대 지연시간. 60초 지나면 실행 안하고 건너뜀.
                }
            )
            cls._instance.start()

 

이렇게 여러 설정들을 추가해주면 오류없이 더 쾌적하게 Django에서 스케쥴러를 사용할수있다.