Julia 클라이언트

Julia 클라이언트 (Julia Client)

DuckDB Julia 패키지는 DuckDB를 위한 고성능 프론트엔드를 제공해요. SQLite와 비슷하게 DuckDB는 Julia 클라이언트 안에서 프로세스 내(in-process)로 실행되며, DBInterface 프론트엔드를 제공해요.

출처: 문서

본문

이 패키지는 멀티스레드 실행도 지원해요. 이를 위해 Julia 스레드/태스크를 사용해요. 쿼리를 병렬로 실행하려면 (예: JULIA_NUM_THREADS 환경 변수를 설정해서) 멀티스레딩 지원으로 Julia를 실행해야 해요.

설치 (Installation)

DuckDB는 다음과 같이 설치해요.

using Pkg
Pkg.add("DuckDB")

또는 ] 키로 패키지 매니저에 들어가 다음 명령을 실행해요.

pkg> add DuckDB

기본 (Basics)

using DuckDB

# create a new in-memory database
con = DBInterface.connect(DuckDB.DB, ":memory:")

# create a table
DBInterface.execute(con, "CREATE TABLE integers (i INTEGER)")

# insert data by executing a prepared statement
stmt = DBInterface.prepare(con, "INSERT INTO integers VALUES (?)")
DBInterface.execute(stmt, [42])

# query the database
results = DBInterface.execute(con, "SELECT 42 a")
print(results)

PIVOT나 IMPORT DATABASE 같은 일부 SQL 문은 여러 준비문(prepared statement)으로 실행되며, DuckDB.execute()를 사용하면 오류가 나요. 대신 DuckDB.execute() 대신 DuckDB.query()로 실행할 수 있고, 항상 구체화된(materialized) 결과를 반환해요.

데이터프레임 스캔 (Scanning DataFrames)

DuckDB Julia 패키지는 Julia DataFrames를 쿼리하는 것도 지원해요. DataFrames는 DuckDB가 직접 읽는다는 점을 기억하세요. 데이터베이스 자체에 삽입되거나 복사되지 않아요.

DataFrame에서 DuckDB 테이블로 데이터를 로드하려면 CREATE TABLE ... AS 또는 INSERT INTO 쿼리를 실행하면 돼요.

using DuckDB
using DataFrames

# create a new in-memory database
con = DBInterface.connect(DuckDB.DB)

# create a DataFrame
df = DataFrame(a = [1, 2, 3], b = [42, 84, 42])

# register it as a view in the database
DuckDB.register_data_frame(con, df, "my_df")

# run a SQL query over the DataFrame
results = DBInterface.execute(con, "SELECT * FROM my_df")
print(results)

Appender API

DuckDB Julia 패키지는 Appender API도 지원해요. 이는 준비문이나 개별 INSERT INTO 문보다 훨씬 빠르답니다. Append는 행 단위 형식으로 이루어져요. 각 컬럼마다 append() 호출을 하고, 그 뒤에 행은 flush()를 호출해 마무리해요. 모든 행이 append되면 close()를 사용해 Appender를 마무리하고 결과 메모리를 정리해요.

using DuckDB, DataFrames, Dates
db = DuckDB.DB()
# create a table
DBInterface.execute(db,
    "CREATE OR REPLACE TABLE data (id INTEGER PRIMARY KEY, value FLOAT, timestamp TIMESTAMP, date DATE)")
# create data to insert
len = 100
df = DataFrames.DataFrame(
        id = collect(1:len),
        value = rand(len),
        timestamp = Dates.now() + Dates.Second.(1:len),
        date = Dates.today() + Dates.Day.(1:len)
    )
# append data by row
appender = DuckDB.Appender(db, "data")
for i in eachrow(df)
    for j in i
        DuckDB.append(appender, j)
    end
    DuckDB.end_row(appender)
end
# close the appender after all rows
DuckDB.close(appender)

동시성 (Concurrency)

Julia 프로세스 안에서, 각 태스크가 데이터베이스에 대한 자신만의 커넥션을 유지한다면 태스크들은 데이터베이스에 동시에 읽고 쓸 수 있어요. 아래 예시에서는 단일 태스크가 주기적으로 데이터베이스를 읽도록 생성되고, 많은 태스크가 INSERTAppender API를 모두 사용해 데이터베이스에 쓰도록 생성돼요.

using Dates, DataFrames, DuckDB
db = DuckDB.DB()
DBInterface.connect(db)
DBInterface.execute(db, "CREATE OR REPLACE TABLE data (date TIMESTAMP, id INTEGER)")

function run_reader(db)
    # create a DuckDB connection specifically for this task
    conn = DBInterface.connect(db)
    while true
        println(DBInterface.execute(conn,
                "SELECT id, count(date) AS count, max(date) AS max_date
                FROM data GROUP BY id ORDER BY id") |> DataFrames.DataFrame)
        Threads.sleep(1)
    end
    DBInterface.close(conn)
end
# spawn one reader task
Threads.@spawn run_reader(db)

function run_inserter(db, id)
    # create a DuckDB connection specifically for this task
    conn = DBInterface.connect(db)
    for i in 1:1000
        Threads.sleep(0.01)
        DuckDB.execute(conn, "INSERT INTO data VALUES (current_timestamp, ?)"; id);
    end
    DBInterface.close(conn)
end
# spawn many insert tasks
for i in 1:100
    Threads.@spawn run_inserter(db, 1)
end

function run_appender(db, id)
    # create a DuckDB connection specifically for this task
    appender = DuckDB.Appender(db, "data")
    for i in 1:1000
        Threads.sleep(0.01)
        row = (Dates.now(Dates.UTC), id)
        for j in row
            DuckDB.append(appender, j);
        end
        DuckDB.end_row(appender);
    end
    DuckDB.close(appender);
end
# spawn many appender tasks
for i in 1:100
    Threads.@spawn run_appender(db, 2)
end

원래 Julia 커넥터 (Original Julia Connector)

원래 DuckDB Julia 커넥터에 대한 크레딧은 kimmolinna에게 있어요.

더 알아보기 (Learn more)