Software Workshop

소개

Python 환경에서 SQLAlchemy를 이용한 DB 애플리케이션을 작성하고 동기적 프로그래밍 또는 비동기적 프로그래밍을 통해 성능을 비교하는 작업을 수행했다. 문자열 필드 하나만을 가지는 테이블에 10만 건의 레코드를 insert하는 애플리케이션을 작성하여 성능 비교에 이용하였다.

대상 DB는 처음에 SQLite를 사용했다가 MySQL로 변경해서 동일한 테스트를 수행했다.

asynchronous의 경우, 한 번의 insert마다 await하는 serial 방식이 있고, 한 번의 insert에 대응되는 coroutine들을 다 만들어 두고 한 번에 모으는 concurrent 방식이 있어서 나눠서 평가하였다.

SQLite

비동기적(asynchronous) 순차 실행

#!/usr/bin/env python

import asyncio
from sqlalchemy import Column, MetaData, String, Table
from sqlalchemy.ext.asyncio import create_async_engine

meta = MetaData()
t1 = Table("t1", meta, Column("name", String(50), primary_key=True))


async def async_main() -> None:
    engine = create_async_engine("sqlite+aiosqlite:///../sqlalchemy_test")
    size = 100000

    async with engine.begin() as conn:
        await conn.run_sync(meta.create_all)

        for i in range(size):
            await conn.execute(t1.insert(), {"name": f"some name {i}"})

    await engine.dispose()


asyncio.run(async_main())

비동기적(asynchronous) 동시 실행

#!/usr/bin/env python

import asyncio
from sqlalchemy import Column, MetaData, select, String, Table
from sqlalchemy.ext.asyncio import create_async_engine

meta = MetaData()
t1 = Table("t1", meta, Column("name", String(50), primary_key=True))


async def async_main() -> None:
    engine = create_async_engine("sqlite+aiosqlite:///../sqlalchemy_test")
    size = 100000
    queries = [None] * size
    async with engine.begin() as conn:
        await conn.run_sync(meta.create_all)

        for i in range(size):
            queries[i] = conn.execute(t1.insert(), {"name": f"some name {i}"})
        result = await asyncio.gather(*queries)

    await engine.dispose()


asyncio.run(async_main())

동기적(synchronous) 실행

#!/usr/bin/env python

from sqlalchemy import Column, MetaData, select, String, Table, create_engine

meta = MetaData()
t1 = Table("t1", meta, Column("name", String(50), primary_key=True))


def main() -> None:
    engine = create_engine("sqlite:///../sqlalchemy_test")
    size = 100000
    queries = [None] * size
    with engine.begin() as conn:
        meta.create_all(engine)

        for i in range(size):
            queries[i] = conn.execute(t1.insert(), {"name": f"some name {i}"})


    engine.dispose()


if __name__ == "__main__":
    main()

MySQL

비동기적(asynchronous) 순차 실행

    engine = create_async_engine("mysql+aiomysql://localhost:3306/sqlalchemy", pool_size=10, max_overflow=20)

비동기적(asynchronous) 동시 실행

    engine = create_async_engine("mysql+aiomysql://localhost:3306/sqlalchemy", pool_size=10, max_overflow=20)

동기적(synchronous) 실행

    engine = create_engine("mysql://localhost:3306/sqlalchemy", pool_size=10, max_overflow=20)

실험 결과

SQLite

프로그래밍 방식 수행 시간
async/serial 13.318
async/concurrent 11.783
sync 4.045

MySQL 8.x

프로그래밍 방식 수행 시간
async/serial 10.227
async/concurrent 14.016
sync 6.434

참고사항