코드로 우주평화

SQLAlchemy _ NotSupportedError(InvalidCachedStatementError) 에러 해결 본문

나는 이렇게 학습한다/Debug

SQLAlchemy _ NotSupportedError(InvalidCachedStatementError) 에러 해결

daco2020 2022. 4. 29. 18:22

AsyncSessionNotSupportedError, InvalidCachedStatementError

 

테스트 코드를 작성하는 중에 에러가 발생했다!!

 

 

에러 메시지는 다음과 같다.

"""
    sqlalchemy.exc.NotSupportedError:
    (sqlalchemy.dialects.postgresql.asyncpg.InvalidCachedStatementError)
    <class 'asyncpg.exceptions.invalidcachedstatementerror'="">:
    cached statement plan is invalid due to a database schema or configuration change
    (SQLAlchemy asyncpg dialect will now invalidate all \\
        prepared caches in response to this exception)

    E                   [SQL: SELECT stock.id, stock.code, stock.name, stock.market
    E                   FROM stock
    E                   WHERE stock.id = %s]
    E                   [parameters: (2,)]
    E                   (Background on this error at: <https://sqlalche.me/e/14/tw8g>)

    ../../.pyenv/versions/3.10.0/envs/ap-toy/lib/python3.10/site-packages/sqlalchemy/
    dialects/postgresql/asyncpg.py:682: NotSupportedError
"""

 

이 에러를 구글링해보았지만 한글로 된 설명은 하나도 없었고 정확한 설명도 찾기 어려웠다.

 

안되겠다 싶어서 깃헙에 직접 에러를 검색했다.

다행히 이 에러에 대한 이슈들이 몇가지 있었고 그 중에 하나를 보다가 문제를 해결할 수 있었다.

 

사실 이 에러는 DB engine을 두 개 이상 사용해서 발생한 에러였다.

 

 

먼저 해결방법부터 보자.

 

 

 

 

해결방법 1.

 

DB와 연결하는데 사용하는 uri가 있을 것이다.

uri 뒷 부분에 ?prepared_statement_cache_size=0 을 붙인다.

 

캐시 사이즈를 0으로 만들어 해결하는 방법이다.

 

예를들어 다음과 같은 uri 형태가 될 것이다.

"postgresql+asyncpg://user:password@localhost:5432/db-name?prepared_statement_cache_size=0"

 

나의 경우 postgresql를 사용하고 비동기를 위해 ‘+asyncpg’ 가 붙어있다.

만약 다른 환경이라면 본인 환경에 맞도록 수정하기 바란다.

 

 

하지만 이 방법의 경우 원인을 해결하는 방법이 아닌 임시방편에 불과하다.

 

게다가 캐시를 사용하지 못하게 되므로 크진 않지만 일부 성능을 저하시킬 우려가 있다.

 

 

 

 

해결방법 2.

 

먼저 공식문서에서 설명하는 에러 원인을 살펴보자.

 

실행 중인 프로그램이 이전에 작동한 풀링된 데이터베이스 연결을 참조하는 경우,

validCachedStatementError, InvalidCachedStatementError가 발생할 수 있다고 한다.

 

 

 

그럼 나의 경우 어떻게 해결했는지 코드로 보자.

 

나는 테스트 코드 중에 문제가 발생했고,

그 이유는 테스트 코드에서 사용하는 db와 테스트 client에서 사용하는 db가 달랐기 때문이었다.

 

# conftest.py

# 기존
@pytest.fixture(scope="session")
async def client() -> AsyncGenerator[AsyncClient, None]:
    async with AsyncClient(app=app, base_url="<http://0.0.0.0:8000>") as client:
        yield client

# 수정
@pytest.fixture(scope="function")
async def client(db) -> AsyncGenerator[AsyncClient, None]:
    async def _get_db() -> AsyncGenerator[AsyncSession, None]:
        yield db

    app.dependency_overrides[get_db] = _get_db
    async with AsyncClient(app=app, base_url="<http://0.0.0.0:8000>") as client:
        yield client

“app.dependency_overrides[get_db] = _get_db” 코드를 보면,

client에 Depends 되는 get_db를 테스트 환경에서 사용하는 db와 동일한 _get_db 로 재정의 했다.

(여기서 db는 AsyncSession을 뜻하고 이는 engine으로 만들어진다)

 

이렇게 db를 하나로 통일하니 앞서 '?prepared_statement_cache_size=0' 문구를 작성하지 않아도 에러를 해결할 수 있었다~~

 

 

 

이 에러를 만나는 상황은 아마 다양할텐데

현재 데이터베이스와의 연결이 어떻게 되어있는지, 연결 engine이 여러개가 아닌지 확인해보길 바란다.

 

 

느낀점

새로운 문제를 만났을 때, 기존에는 구글링에만 의존했다면

이제는 깃헙 이슈를 검색하고 관련 공식문서를 찾아 보곤 한다.

 

특히 문제해결을 위해 함께 노력해주고 힌트를 주시는 사수님께 매우 감사하다.

 

 

 

레퍼런스

 

Using statement_cache_size asyncpg setting / prepared statement name for asyncpg w pgbouncer · Issue #6467 · sqlalchemy/sqlalc

Hi! I use sqlalchemy 1.4 with asyncpg driver with pgbouncer. from sqlalchemy.ext.asyncio import create_async_engine from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.asyncio import AsyncS...

github.com

 

PostgreSQL — SQLAlchemy 1.4 Documentation

Support for the PostgreSQL database via the psycopg2 driver. DBAPI Documentation and download information (if applicable) for psycopg2 is available at: https://pypi.org/project/psycopg2/ Unix Domain Connections psycopg2 supports connecting via Unix domain

docs.sqlalchemy.org