R 클라이언트

R 클라이언트 (R Client)

설치: DuckDB R 클라이언트를 사용하려면 [R 설치 페이지]({% link install/index.html %}?environment=r)를 방문해요.

DuckDB R 클라이언트의 최신 안정 버전: {% if site.current_duckdb_r_version != "" %}{{ site.current_duckdb_r_version }}{% else %}{{ site.lts_duckdb_r_version }}{% endif %}

출처: 문서

본문

설치 (Installation)

duckdb: R 클라이언트

DuckDB R 클라이언트는 다음 명령으로 설치할 수 있어요:

install.packages("duckdb")

자세한 내용은 [설치 페이지]({% link install/index.html %}?environment=r)와 CRAN을 참고해요.

duckplyr: dplyr 클라이언트

DuckDB는 duckplyr 패키지를 통해 dplyr 호환 API를 제공해요. install.packages("duckplyr")로 설치할 수 있어요. 자세한 내용은 duckplyr 문서를 참고해요.

참조 매뉴얼 (Reference Manual)

DuckDB R 클라이언트의 참조 매뉴얼은 r.duckdb.org에서 볼 수 있어요.

기본 클라이언트 사용법

표준 DuckDB R 클라이언트는 R용 DBI 인터페이스를 구현해요. DBI에 아직 익숙하지 않다면 Using DBI 페이지에서 소개를 볼 수 있어요.

시작 & 종료

DuckDB를 사용하려면 먼저 데이터베이스를 나타내는 연결 객체를 만들어야 해요. 연결 객체는 읽고 쓸 데이터베이스 파일을 파라미터로 받아요. 데이터베이스 파일이 없으면 생성돼요 (파일 확장자는 .db, .duckdb, 또는 다른 것이어도 됨). 특수 값 :memory: (기본값)는 인메모리 데이터베이스를 만드는 데 사용돼요. 인메모리 데이터베이스의 경우 데이터가 디스크에 유지되지 않아요 (즉, R 프로세스에서 나가면 모든 데이터가 손실됨). 기존 데이터베이스에 읽기 전용 모드로 연결하려면 read_only 플래그를 TRUE로 설정해요. 여러 R 프로세스가 같은 데이터베이스 파일을 동시에 접근하려면 읽기 전용 모드가 필요해요.

library("duckdb")
# 인메모리 데이터베이스 시작
con <- dbConnect(duckdb())
# 또는
con <- dbConnect(duckdb(), dbdir = ":memory:")
# 데이터베이스 파일 사용 (프로세스 간 공유 안 함)
con <- dbConnect(duckdb(), dbdir = "my-db.duckdb", read_only = FALSE)
# 데이터베이스 파일 사용 (프로세스 간 공유)
con <- dbConnect(duckdb(), dbdir = "my-db.duckdb", read_only = TRUE)

연결은 범위를 벗어나면 암시적으로 닫히거나 dbDisconnect()로 명시적으로 닫혀요. 연결과 연관된 데이터베이스 인스턴스를 종료하려면 duckdb()가 반환한 드라이버 객체에서 duckdb_shutdown()을 사용해요.

쿼리 (Querying)

DuckDB는 쿼리를 보내고 결과 집합을 검색하는 표준 DBI 메서드를 지원해요. dbExecute()CREATE TABLE이나 UPDATE 등 결과가 예상되지 않는 쿼리용이고, dbGetQuery()는 결과를 생성하는 쿼리(예: SELECT)용이에요. 아래에 예시가 있어요.

# 테이블 생성
dbExecute(con, "CREATE TABLE items (item VARCHAR, value DECIMAL(10, 2), count INTEGER)")
# 테이블에 두 항목 삽입
dbExecute(con, "INSERT INTO items VALUES ('jeans', 20.0, 1), ('hammer', 42.2, 2)")

# 항목 다시 검색
res <- dbGetQuery(con, "SELECT * FROM items")
print(res)
#     item value count
# 1  jeans  20.0     1
# 2 hammer  42.2     2

DuckDB는 R 클라이언트의 dbExecutedbGetQuery 메서드로 prepared statements도 지원해요. 예시를 볼게요:

# prepared statement 파라미터는 리스트로 주어진다
dbExecute(con, "INSERT INTO items VALUES (?, ?, ?)", list('laptop', 2000, 1))

# prepared statement를 여러 번 재사용하려면 dbSendStatement()와 dbBind()를 사용
stmt <- dbSendStatement(con, "INSERT INTO items VALUES (?, ?, ?)")
dbBind(stmt, list('iphone', 300, 2))
dbBind(stmt, list('android', 3.5, 1))
dbClearResult(stmt)

# prepared statement로 데이터베이스 쿼리
res <- dbGetQuery(con, "SELECT item FROM items WHERE value > ?", list(400))
print(res)
#       item
# 1 laptop

경고: DuckDB에 대량의 데이터를 삽입하는 데 prepared statements는 사용하지 마세요. 더 나은 옵션은 아래를 참고해요.

효율적인 전송 (Efficient Transfer)

R 데이터 프레임을 DuckDB에 쓰려면 표준 DBI 함수 dbWriteTable()을 사용해요. 이는 DuckDB에 테이블을 만들고 데이터 프레임 내용으로 채워요. 예를 들어:

dbWriteTable(con, "iris_table", iris)
res <- dbGetQuery(con, "SELECT * FROM iris_table LIMIT 1")
print(res)
#   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
# 1          5.1         3.5          1.4         0.2  setosa

R 데이터 프레임을 SQL VIEW에 해당하는 가상 테이블로 "등록"할 수도 있어요. 이것은 아직 DuckDB로 데이터를 실제로 전송하지 않아요. 아래에 예시가 있어요:

duckdb_register(con, "iris_view", iris)
res <- dbGetQuery(con, "SELECT * FROM iris_view LIMIT 1")
print(res)
#   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
# 1          5.1         3.5          1.4         0.2  setosa

DuckDB는 등록 후 R 데이터 프레임에 대한 참조를 유지해요. 이것은 데이터 프레임이 가비지 컬렉션되는 것을 방지해요. 참조는 연결이 닫힐 때 해제되지만, duckdb_unregister() 메서드로 수동으로 해제할 수도 있어요.

데이터를 효율적으로 가져오는 더 많은 옵션은 [데이터 가져오기 문서]({% link docs/current/data/overview.md %})를 참고해요.

dbplyr

DuckDB는 R에서 프로그래밍 방식으로 쿼리를 구성하는 dbplyr / dplyr 패키지와도 잘 어울려요. 예시를 볼게요:

library("duckdb")
library("dplyr")
con <- dbConnect(duckdb())
duckdb_register(con, "flights", nycflights13::flights)

tbl(con, "flights") |>
  group_by(dest) |>
  summarise(delay = mean(dep_time, na.rm = TRUE)) |>
  collect()

dbplyr을 사용할 때 CSV와 Parquet 파일은 dplyr::tbl 함수로 읽을 수 있어요.

# 이 예시를 위해 CSV 생성
write.csv(mtcars, "mtcars.csv")

# R의 메모리에 전체 CSV를 읽지 않도록 DuckDB에서 데이터셋 요약
tbl(con, "mtcars.csv") |>
  group_by(cyl) |>
  summarise(across(disp:wt, .fns = mean)) |>
  collect()
# Parquet 파일 집합 생성
dbExecute(con, "COPY flights TO 'dataset' (FORMAT parquet, PARTITION_BY (year, month))")

# 12개의 Parquet 파일을 R의 메모리에 읽지 않도록 DuckDB에서 데이터셋 요약
tbl(con, "read_parquet('dataset/**/*.parquet', hive_partitioning = true)") |>
  filter(month == "3") |>
  summarise(delay = mean(dep_time, na.rm = TRUE)) |>
  collect()

메모리 제한 (Memory Limit)

[memory_limit 구성 옵션]({% link docs/current/configuration/pragmas.md %})으로 DuckDB의 메모리 사용을 제한할 수 있어요. 예를 들어:

SET memory_limit = '2GB';

이 제한은 DuckDB가 사용하는 메모리에만 적용되며 다른 R 라이브러리의 메모리 사용에는 영향을 주지 않아요. 따라서 R 프로세스가 사용하는 총 메모리는 구성된 memory_limit보다 높을 수 있어요.

문제 해결 (Troubleshooting)

macOS에서 설치 시 경고

macOS에서 DuckDB를 설치하면 unable to load shared object '.../R_X11.so' 경고가 발생할 수 있어요:

Warning message:
In doTryCatch(return(expr), name, parentenv, handler) :
  unable to load shared object '/Library/Frameworks/R.framework/Resources/modules//R_X11.so':
  dlopen(/Library/Frameworks/R.framework/Resources/modules//R_X11.so, 0x0006): Library not loaded: /opt/X11/lib/libSM.6.dylib
  Referenced from: <31EADEB5-0A17-3546-9944-9B3747071FE8> /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/modules/R_X11.so
  Reason: tried: '/opt/X11/lib/libSM.6.dylib' (no such file) ...

이것은 단지 경고일 뿐이므로, 가장 간단한 해결책은 무시하는 것이에요. 또는 R-universe에서 DuckDB를 설치할 수 있어요:

install.packages("duckdb", repos = c("https://duckdb.r-universe.dev", "https://cloud.r-project.org"))

선택적 xquartz 의존성을 Homebrew로 설치할 수도 있어요.

더 알아보기 (Learn more)

R 클라이언트의 참조 매뉴얼은 r.duckdb.org을 참고해요.