100만 행에서 DuckDB, SQLite 및 Pandas를 벤치마킹했습니다. 발생한 상황은 다음과 같습니다.


작성자별 이미지
# 소개
오늘날 데이터 세트를 처리하기 위한 수많은 도구가 있습니다. 그들은 모두 자신들이 귀하에게 가장 좋고 올바른 선택이라고 주장합니다. 물론 그렇습니다. 하지만 그렇습니까? 이러한 도구가 충족해야 하는 두 가지 주요 요구 사항은 일상적인 데이터 분석 작업을 쉽게 수행하고 대규모 데이터 세트의 압박 속에서도 신속하게 수행해야 한다는 것입니다.
가장 좋은 도구를 결정하려면 덕DB, SQLite그리고 팬더우리는 이러한 조건에서 테스트했습니다.
첫째, 우리는 그들에게 값 합산, 범주별 그룹화, 조건 필터링, 다중 필드 집계 등 일상적인 분석 작업만 제공했습니다. 이는 도구의 최상의 특성을 보여주기 위해 설계된 시나리오와 비교하여 분석가가 실제 데이터 세트를 사용하여 실제로 작업하는 방식을 반영했습니다.
둘째, 우리는 100만 개 이상의 행이 있는 Kaggle 데이터 세트에서 해당 작업을 수행했습니다. 이는 현실적인 전환점입니다. 단일 시스템에서 실행할 수 있을 만큼 작지만 메모리 압력과 쿼리 속도가 도구 간의 명확한 차이를 드러내기 시작할 만큼 충분히 큽니다.
테스트가 어떻게 진행되었는지 살펴보겠습니다.
# 우리가 사용한 데이터 세트
// 데이터 세트 개요
우리는 Bank 데이터세트를 사용했습니다. 캐글. 이 데이터 세트에는 5개의 열로 구성된 백만 개가 넘는 행이 포함되어 있습니다.
열 이름 | 설명 |
---|---|
날짜 | 거래가 발생한 날짜 |
도메인 | 비즈니스 카테고리 또는 유형(소매, 레스토랑) |
위치 | 지리적 지역(고아, 마투라) |
값 | 거래가치 |
거래_건수 | 해당 날짜의 총 거래 수 |
이 데이터 세트는 Python을 사용하여 생성됩니다. 실제 데이터와 완전히 유사하지는 않지만 그 크기와 구조는 도구 간의 성능 차이를 테스트하고 비교하기에 충분합니다.
// Pandas로 데이터 엿보기
우리는 팬더 데이터 세트를 Jupyter 노트북에 로드하고 일반 구조, 차원 및 null 값을 검사합니다. 코드는 다음과 같습니다.
import pandas as pd
df = pd.read_excel('bankdataset.xlsx')
print("Dataset shape:", df.shape)
df.head()
출력은 다음과 같습니다.
데이터 세트를 탐색할 때 일반적인 작업에 대한 빠른 참조를 원한다면 이 편리한 Pandas 치트 시트를 확인하세요.
벤치마킹에 앞서 환경설정 방법을 알아보겠습니다.
# 공정한 시험환경 조성
DuckDB, SQLite, Pandas 등 세 가지 도구 모두 동일한 Jupyter Notebook 환경에서 설정 및 실행되어 테스트가 공정했는지 확인했습니다. 이를 통해 런타임 중 조건과 메모리 사용이 전체적으로 일정하게 유지되었습니다.
먼저 필요한 패키지를 설치하고 로드했습니다.
우리에게 필요한 도구는 다음과 같습니다.
- 팬더: 표준용
DataFrame
운영 - duckdb: SQL 실행용
DataFrame
- sqlite3: 내장 SQL 데이터베이스 관리용
- time: 실행 시간을 캡처하기 위해
- memory_profiler: 메모리 할당을 측정합니다.
# Install if any of them are not in your environment
!pip install duckdb --quiet
import pandas as pd
import duckdb
import sqlite3
import time
from memory_profiler import memory_usage
이제 세 가지 도구 모두에서 공유할 수 있는 형식으로 데이터를 준비해 보겠습니다.
// Pandas에 데이터 로드
Pandas를 사용하여 데이터세트를 한 번 로드한 다음 이를 DuckDB 및 SQLite에 공유하거나 등록하겠습니다.
df = pd.read_excel('bankdataset.xlsx')
df.head()
유효성을 검사할 출력은 다음과 같습니다.
// DuckDB에 데이터 등록
DuckDB를 사용하면 Pandas에 직접 액세스할 수 있습니다. DataFrame
에스. 아무것도 변환할 필요가 없습니다. 등록하고 쿼리만 하면 됩니다. 코드는 다음과 같습니다.
# Register DataFrame as a DuckDB table
duckdb.register("bank_data", df)
# Query via DuckDB
duckdb.query("SELECT * FROM bank_data LIMIT 5").to_df()
출력은 다음과 같습니다.
// SQLite용 데이터 준비
SQLite는 Excel 파일을 직접 읽지 않기 때문에 Pandas를 추가하는 것부터 시작했습니다. DataFrame
메모리 내 데이터베이스에. 그런 다음 간단한 쿼리를 사용하여 데이터 형식을 검사했습니다.
conn_sqlite = sqlite3.connect(":memory:")
df.to_sql("bank_data", conn_sqlite, index=False, if_exists="replace")
pd.read_sql_query("SELECT * FROM bank_data LIMIT 5", conn_sqlite)
출력은 다음과 같습니다.
# 도구를 벤치마킹한 방법
성능을 비교하기 위해 DuckDB, SQLite 및 Pandas에 대해 동일한 4개의 쿼리를 사용했습니다. 각 쿼리는 데이터 분석이 실제 세계에 적용되는 방식을 반영하는 일반적인 분석 작업을 처리하도록 설계되었습니다.
// 일관된 설정 보장
인메모리 데이터 세트는 세 가지 도구 모두에서 사용되었습니다.
- 팬더는 다음과 같이 질문했습니다.
DataFrame
곧장 - DuckDB는 SQL 쿼리를 직접 실행했습니다.
DataFrame
- SQLite는
DataFrame
인메모리 데이터베이스에서 SQL 쿼리를 실행했습니다.
이 방법을 사용하면 세 가지 도구 모두 동일한 데이터를 사용하고 동일한 시스템 설정으로 작동할 수 있습니다.
// 실행 시간 측정
쿼리 기간을 추적하기 위해 Python의 time
모듈은 각 쿼리를 간단한 시작/종료 타이머로 래핑했습니다. 쿼리 실행 시간만 기록되었습니다. 데이터 로딩 및 준비 단계는 제외되었습니다.
// 메모리 사용량 추적
처리 시간과 함께 메모리 사용량은 각 엔진이 대규모 데이터 세트에서 얼마나 잘 작동하는지를 나타냅니다.
원하는 경우 각 쿼리 직전과 직후에 메모리 사용량을 샘플링하여 증분 RAM 소비를 추정할 수 있습니다.
// 벤치마크 쿼리
우리는 동일한 네 가지 일상적인 분석 작업에서 각 엔진을 테스트했습니다.
- 총 거래 금액: 숫자 열 합산
- 도메인별 그룹화: 카테고리별 거래 수 집계
- 위치별 필터링: 집계 전 조건을 기준으로 행 필터링
- 도메인 및 위치별 그룹화: 평균을 사용한 다중 필드 집계
# 벤치마크 결과
// 질의 1: 총 거래 금액
여기서는 Pandas, DuckDB 및 SQLite가 다음을 합산할 때 어떻게 수행되는지 측정합니다. Value
데이터세트 전체의 열입니다.
// 팬더 성능
우리는 다음을 사용하여 총 거래 가치를 계산합니다. .sum()
에 Value
열. 코드는 다음과 같습니다.
pandas_results = []
def pandas_q1():
return df['Value'].sum()
mem_before = memory_usage(-1)[0]
start = time.time()
pandas_q1()
end = time.time()
mem_after = memory_usage(-1)[0]
pandas_results.append({
"engine": "Pandas",
"query": "Total transaction value",
"time": round(end - start, 4),
"memory": round(mem_after - mem_before, 4)
})
pandas_results
출력은 다음과 같습니다.
// DuckDB 성능
전체 열 집계를 사용하여 총 거래 가치를 계산합니다. 코드는 다음과 같습니다.
duckdb_results = []
def duckdb_q1():
return duckdb.query("SELECT SUM(value) FROM bank_data").to_df()
mem_before = memory_usage(-1)[0]
start = time.time()
duckdb_q1()
end = time.time()
mem_after = memory_usage(-1)[0]
duckdb_results.append({
"engine": "DuckDB",
"query": "Total transaction value",
"time": round(end - start, 4),
"memory": round(mem_after - mem_before, 4)
})
duckdb_results
출력은 다음과 같습니다.
// SQLite 성능
우리는 다음을 합산하여 총 거래 가치를 계산합니다. value
열. 코드는 다음과 같습니다.
sqlite_results = []
def sqlite_q1():
return pd.read_sql_query("SELECT SUM(value) FROM bank_data", conn_sqlite)
mem_before = memory_usage(-1)[0]
start = time.time()
sqlite_q1()
end = time.time()
mem_after = memory_usage(-1)[0]
sqlite_results.append({
"engine": "SQLite",
"query": "Total transaction value",
"time": round(end - start, 4),
"memory": round(mem_after - mem_before, 4)
})
sqlite_results
출력은 다음과 같습니다.
// 전반적인 성능 분석
이제 실행 시간과 메모리 사용량을 비교해 보겠습니다. 코드는 다음과 같습니다.
import matplotlib.pyplot as plt
all_q1 = pd.DataFrame(pandas_results + duckdb_results + sqlite_results)
fig, axes = plt.subplots(1, 2, figsize=(10,4))
all_q1.plot(x="engine", y="time", kind="barh", ax=axes[0], legend=False, title="Execution Time (s)")
all_q1.plot(x="engine", y="memory", kind="barh", color="salmon", ax=axes[1], legend=False, title="Memory Usage (MB)")
plt.tight_layout()
plt.show()
출력은 다음과 같습니다.
Pandas는 여기에서 가장 빠르고 메모리 효율성이 뛰어나 최소한의 RAM 사용으로 거의 즉시 완료됩니다. DuckDB는 약간 느리고 더 많은 메모리를 사용하지만 효율성을 유지하는 반면, SQLite는 메모리 소비 측면에서 가장 느리고 가장 무겁습니다.
// 쿼리 2: 도메인별로 그룹화
여기서는 트랜잭션을 그룹화할 때 Pandas, DuckDB 및 SQLite의 성능을 측정합니다. Domain
그리고 그들의 수를 합산합니다.
// 팬더 성능
다음을 사용하여 도메인당 총 거래 수를 계산합니다. .groupby()
에 Domain
열.
def pandas_q2():
return df.groupby('Domain')['Transaction_count'].sum()
mem_before = memory_usage(-1)[0]
start = time.time()
pandas_q2()
end = time.time()
mem_after = memory_usage(-1)[0]
pandas_results.append({
"engine": "Pandas",
"query": "Group by domain",
"time": round(end - start, 4),
"memory": round(mem_after - mem_before, 4)
})
[p for p in pandas_results if p["query"] == "Group by domain"]
출력은 다음과 같습니다.
// DuckDB 성능
SQL을 사용하여 도메인당 총 거래 수를 계산합니다. GROUP BY
에 domain
열.
def duckdb_q2():
return duckdb.query("""
SELECT domain, SUM(transaction_count)
FROM bank_data
GROUP BY domain
""").to_df()
mem_before = memory_usage(-1)[0]
start = time.time()
duckdb_q2()
end = time.time()
mem_after = memory_usage(-1)[0]
duckdb_results.append({
"engine": "DuckDB",
"query": "Group by domain",
"time": round(end - start, 4),
"memory": round(mem_after - mem_before, 4)
})
[p for p in duckdb_results if p["query"] == "Group by domain"]
출력은 다음과 같습니다.
// SQLite 성능
SQL을 사용하여 도메인당 총 거래 수를 계산합니다. GROUP BY
인 메모리 테이블에.
def sqlite_q2():
return pd.read_sql_query("""
SELECT domain, SUM(transaction_count) AS total_txn
FROM bank_data
GROUP BY domain
""", conn_sqlite)
mem_before = memory_usage(-1)[0]
start = time.time()
sqlite_q2()
end = time.time()
mem_after = memory_usage(-1)[0]
sqlite_results.append({
"engine": "SQLite",
"query": "Group by domain",
"time": round(end - start, 4),
"memory": round(mem_after - mem_before, 4)
})
[p for p in sqlite_results if p["query"] == "Group by domain"]
출력은 다음과 같습니다.
// 전반적인 성능 분석
이제 실행 시간과 메모리 사용량을 비교해 보겠습니다. 코드는 다음과 같습니다.
import pandas as pd
import matplotlib.pyplot as plt
groupby_results = [r for r in (pandas_results + duckdb_results + sqlite_results)
if "Group by" in r["query"]]
df_groupby = pd.DataFrame(groupby_results)
fig, axes = plt.subplots(1, 2, figsize=(10,4))
df_groupby.plot(x="engine", y="time", kind="barh", ax=axes[0], legend=False, title="Execution Time (s)")
df_groupby.plot(x="engine", y="memory", kind="barh", color="salmon", ax=axes[1], legend=False, title="Memory Usage (MB)")
plt.tight_layout()
plt.show()
출력은 다음과 같습니다.
DuckDB는 가장 빠르고, Pandas는 더 낮은 메모리를 위해 약간 더 많은 시간을 투자하는 반면, SQLite는 가장 느리고 메모리를 가장 많이 소모합니다.
// 쿼리 3: 위치별 필터링(고아)
여기서는 데이터 세트를 필터링할 때 Pandas, DuckDB 및 SQLite의 성능을 측정합니다. Location = 'Goa'
그리고 거래 가치를 합산합니다.
// 팬더 성능
행을 필터링합니다. Location == 'Goa'
그리고 그 값을 합산합니다. 코드는 다음과 같습니다.
def pandas_q3():
return df[df['Location'] == 'Goa']['Value'].sum()
mem_before = memory_usage(-1)[0]
start = time.time()
pandas_q3()
end = time.time()
mem_after = memory_usage(-1)[0]
pandas_results.append({
"engine": "Pandas",
"query": "Filter by location",
"time": round(end - start, 4),
"memory": round(mem_after - mem_before, 4)
})
[p for p in pandas_results if p["query"] == "Filter by location"]
출력은 다음과 같습니다.
// DuckDB 성능
거래를 필터링합니다. Location = 'Goa'
그리고 그 총 가치를 계산해 보세요. 코드는 다음과 같습니다.
def duckdb_q3():
return duckdb.query("""
SELECT SUM(value)
FROM bank_data
WHERE location = 'Goa'
""").to_df()
mem_before = memory_usage(-1)[0]
start = time.time()
duckdb_q3()
end = time.time()
mem_after = memory_usage(-1)[0]
duckdb_results.append({
"engine": "DuckDB",
"query": "Filter by location",
"time": round(end - start, 4),
"memory": round(mem_after - mem_before, 4)
})
[p for p in duckdb_results if p["query"] == "Filter by location"]
출력은 다음과 같습니다.
// SQLite 성능
거래를 필터링합니다. Location = 'Goa'
그리고 그 값을 합산합니다. 코드는 다음과 같습니다.
def sqlite_q3():
return pd.read_sql_query("""
SELECT SUM(value) AS total_value
FROM bank_data
WHERE location = 'Goa'
""", conn_sqlite)
mem_before = memory_usage(-1)[0]
start = time.time()
sqlite_q3()
end = time.time()
mem_after = memory_usage(-1)[0]
sqlite_results.append({
"engine": "SQLite",
"query": "Filter by location",
"time": round(end - start, 4),
"memory": round(mem_after - mem_before, 4)
})
[p for p in sqlite_results if p["query"] == "Filter by location"]
출력은 다음과 같습니다.
// 전반적인 성능 분석
이제 실행 시간과 메모리 사용량을 비교해 보겠습니다. 코드는 다음과 같습니다.
import pandas as pd
import matplotlib.pyplot as plt
filter_results = [r for r in (pandas_results + duckdb_results + sqlite_results)
if r["query"] == "Filter by location"]
df_filter = pd.DataFrame(filter_results)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
df_filter.plot(x="engine", y="time", kind="barh", ax=axes[0], legend=False, title="Execution Time (s)")
df_filter.plot(x="engine", y="memory", kind="barh", color="salmon", ax=axes[1], legend=False, title="Memory Usage (MB)")
plt.tight_layout()
plt.show()
출력은 다음과 같습니다.
DuckDB는 가장 빠르고 효율적입니다. Pandas는 메모리 사용량이 높을수록 속도가 느려집니다. SQLite는 가장 느리지만 메모리는 더 가볍습니다.
// 쿼리 4: 도메인 및 위치별로 그룹화
// 팬더 성능
우리는 두 가지로 그룹화된 평균 거래 가치를 계산합니다. Domain
그리고 Location
. 코드는 다음과 같습니다.
def pandas_q4():
return df.groupby(['Domain', 'Location'])['Value'].mean()
mem_before = memory_usage(-1)[0]
start = time.time()
pandas_q4()
end = time.time()
mem_after = memory_usage(-1)[0]
pandas_results.append({
"engine": "Pandas",
"query": "Group by domain & location",
"time": round(end - start, 4),
"memory": round(mem_after - mem_before, 4)
})
[p for p in pandas_results if p["query"] == "Group by domain & location"]
출력은 다음과 같습니다.
// DuckDB 성능
우리는 두 가지로 그룹화된 평균 거래 가치를 계산합니다. domain
그리고 location
. 코드는 다음과 같습니다.
def duckdb_q4():
return duckdb.query("""
SELECT domain, location, AVG(value) AS avg_value
FROM bank_data
GROUP BY domain, location
""").to_df()
mem_before = memory_usage(-1)[0]
start = time.time()
duckdb_q4()
end = time.time()
mem_after = memory_usage(-1)[0]
duckdb_results.append({
"engine": "DuckDB",
"query": "Group by domain & location",
"time": round(end - start, 4),
"memory": round(mem_after - mem_before, 4)
})
[p for p in duckdb_results if p["query"] == "Group by domain & location"]
출력은 다음과 같습니다.
// SQLite 성능
우리는 두 가지로 그룹화된 평균 거래 가치를 계산합니다. domain
그리고 location
. 코드는 다음과 같습니다.
def sqlite_q4():
return pd.read_sql_query("""
SELECT domain, location, AVG(value) AS avg_value
FROM bank_data
GROUP BY domain, location
""", conn_sqlite)
mem_before = memory_usage(-1)[0]
start = time.time()
sqlite_q4()
end = time.time()
mem_after = memory_usage(-1)[0]
sqlite_results.append({
"engine": "SQLite",
"query": "Group by domain & location",
"time": round(end - start, 4),
"memory": round(mem_after - mem_before, 4)
})
[p for p in sqlite_results if p["query"] == "Group by domain & location"]
출력은 다음과 같습니다.
// 전반적인 성능 분석
이제 실행 시간과 메모리 사용량을 비교해 보겠습니다. 코드는 다음과 같습니다.
import pandas as pd
import matplotlib.pyplot as plt
gdl_results = [r for r in (pandas_results + duckdb_results + sqlite_results)
if r["query"] == "Group by domain & location"]
df_gdl = pd.DataFrame(gdl_results)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
df_gdl.plot(x="engine", y="time", kind="barh", ax=axes[0], legend=False,
title="Execution Time (s)")
df_gdl.plot(x="engine", y="memory", kind="barh", ax=axes[1], legend=False,
title="Memory Usage (MB)", color="salmon")
plt.tight_layout()
plt.show()
출력은 다음과 같습니다.
DuckDB는 적당한 메모리 사용으로 다중 필드 그룹화를 가장 빠르게 처리하고, Pandas는 메모리 사용이 매우 높을 때 속도가 느리며, SQLite는 상당한 메모리 소비로 가장 느립니다.
# 모든 쿼리에 대한 최종 비교
우리는 메모리와 속도 측면에서 이 세 가지 엔진을 서로 비교했습니다. 실행 시간을 다시 한 번 확인해 보겠습니다. 코드는 다음과 같습니다.
import pandas as pd
import matplotlib.pyplot as plt
all_results = pd.DataFrame(pandas_results + duckdb_results + sqlite_results)
measure_order = [
"Total transaction value",
"Group by domain",
"Filter by location",
"Group by domain & location",
]
engine_colors = {"Pandas": "#1f77b4", "DuckDB": "#ff7f0e", "SQLite": "#2ca02c"}
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.ravel()
for i, q in enumerate(measure_order):
d = all_results[all_results["query"] == q]
axes[i].barh(d["engine"], d["time"],
color=[engine_colors[e] for e in d["engine"]])
for y, v in enumerate(d["time"]):
axes[i].text(v, y, f" {v:.3f}", va="center")
axes[i].set_title(q, fontsize=10)
axes[i].set_xlabel("Seconds")
fig.suptitle("Per-Measure Comparison — Execution Time", fontsize=14)
plt.tight_layout()
plt.show()
출력은 다음과 같습니다.
이 차트는 DuckDB가 Pandas가 가장자리를 차지하는 총 트랜잭션 값을 제외하고 거의 모든 쿼리에 대해 일관되게 가장 낮은 실행 시간을 유지한다는 것을 보여줍니다. SQLite는 전반적으로 가장 느립니다. 다음은 메모리를 확인해 보겠습니다. 코드는 다음과 같습니다.
import pandas as pd
import matplotlib.pyplot as plt
all_results = pd.DataFrame(pandas_results + duckdb_results + sqlite_results)
measure_order = [
"Total transaction value",
"Group by domain",
"Filter by location",
"Group by domain & location",
]
engine_colors = {"Pandas": "#1f77b4", "DuckDB": "#ff7f0e", "SQLite": "#2ca02c"}
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.ravel()
for i, q in enumerate(measure_order):
d = all_results[all_results["query"] == q]
axes[i].barh(d["engine"], d["memory"],
color=[engine_colors[e] for e in d["engine"]])
for y, v in enumerate(d["memory"]):
axes[i].text(v, y, f" {v:.1f}", va="center")
axes[i].set_title(q, fontsize=10)
axes[i].set_xlabel("MB")
fig.suptitle("Per-Measure Comparison — Memory Usage", fontsize=14)
plt.tight_layout()
plt.show()
출력은 다음과 같습니다.
이 차트는 SQLite가 메모리 사용량에 있어 최고와 최악 사이를 오가고, Pandas는 두 가지 최고 사례와 두 가지 최악 사례로 극단적인 반면, DuckDB는 모든 쿼리에서 일관되게 중간을 유지한다는 것을 보여줍니다. 결과적으로 DuckDB는 전체적으로 가장 균형 잡힌 선택임이 입증되었으며 적당한 메모리 사용량으로 일관되게 빠른 성능을 제공합니다. Pandas는 때로는 가장 빠르고 때로는 가장 무거운 극단적인 성능을 보이는 반면, SQLite는 속도 문제로 어려움을 겪고 종종 메모리 측면에서 비효율적인 측면에 도달합니다.
네이트 로시디 데이터 과학자이자 제품 전략 분야의 전문가입니다. 그는 또한 분석을 가르치는 부교수이기도 하며 데이터 과학자가 상위 기업의 실제 인터뷰 질문을 사용하여 인터뷰를 준비하는 데 도움이 되는 플랫폼인 StrataScratch의 창립자이기도 합니다. Nate는 취업 시장의 최신 동향에 대해 글을 쓰고, 인터뷰 조언을 제공하고, 데이터 과학 프로젝트를 공유하고, SQL의 모든 것을 다룹니다.
Post Comment