UNIX 系統上的 SQLite WAL 並發寫入效能

UNIX 系統上的 SQLite WAL 並發寫入效能

我有兩種設定:一種在 Windows 10(ntfs 分割區)上運行,另一種在 debian(ext4 分割區)上運行。 R原始碼是相同的。主程序在 8 個 vcore 上啟動 8 個子程序 (P-SOCKS),所有子程序都查詢並寫入同一啟用 WAL 的 sqlite 資料庫。

在 Windows 10 上,我將 100% CPU 負載分佈在所有進程上。在 Debian 上,我幾乎沒有獲得 25% 的 CPU 負載。監控 debian 上的進程我認為寫入是瓶頸,因為我看到一次只有一個進程在其 vcore 上達到 100%。 (其他人可能正在等待寫。)

每個連接都使用PRAGMA busy_timeout = 60000;PRAGMA journal_mode = WAL;

我正在嘗試調試這個。我曾嘗試PRAGMA synchronous = OFF;認為這可能與 相關fsync(),但我沒有看到任何改進。對於可能導致 Debian 性能低下的原因,還有其他建議嗎?

編輯: 寫入快取似乎已在 SCSI 磁碟上啟用(使用 進行檢查sdparm),並且調整 ext4 掛載選項(例如barrier=0和 )data=writeback似乎沒有任何效果。

標竿管理

以下是一些用於對並發寫入進行基準測試的簡單程式碼:

make.con <- function() {
  con <<- DBI::dbConnect(RSQLite::SQLite(), dbname = 'db.sqlite')
  DBI::dbExecute(con, 'PRAGMA journal_mode = WAL;')
  DBI::dbExecute(con, 'PRAGMA busy_timeout = 60000;')
  DBI::dbExecute(con, '
    CREATE TABLE IF NOT EXISTS tmp (
      id INTEGER NOT NULL,
      blob BLOB NOT NULL,
      PRIMARY KEY (id)
  )')
}
make.con()


fn <- function(x) {
  set.seed(x)
  # read
  random.blob.read <- RSQLite::dbGetQuery(con, 'SELECT blob FROM tmp WHERE id = (SELECT abs(random() % max(tm.id)) FROM tmp tm);')
  # write
  blob <- serialize(list(rand = runif(1000)), connection = NULL, xdr = FALSE)
  RSQLite::dbExecute(con, 'INSERT INTO tmp (blob) VALUES (:blob);', params = list('blob' = list(blob)))
}

n <- 30000L

parallel::setDefaultCluster(parallel::makeCluster(spec = 2L))
parallel::clusterExport(varlist = 'make.con')
invisible(parallel::clusterEvalQ(expr = {make.con()}))

microbenchmark::microbenchmark(
  lapply(1:n, fn),
  parallel::parLapplyLB(X = 1:n, fun = fn, chunk.size = 50L), 
  times = 2L
)

parallel::stopCluster(cl = parallel::getDefaultCluster())

程式碼只是讀取 blob 並將其寫入資料庫。首先,進行一些虛擬運行並允許資料庫增加到幾 GB。

在我的 Windows 10 筆記型電腦上,我得到以下結果(6GB 資料庫):

Unit: seconds
                                                       expr      min       lq     mean   median       uq      max neval
                                            lapply(1:n, fn) 26.02392 26.02392 26.54853 26.54853 27.07314 27.07314     2
 parallel::parLapplyLB(X = 1:n, fun = fn, chunk.size = 50L) 15.73851 15.73851 16.44554 16.44554 17.15257 17.15257     2

我清楚地看到 1 個 vcore 處於 100%,然後 2 個 vcore 處於 100%。效能幾乎快了一倍,這表明 2 個並發進程不會互相阻塞。

在 Debian 上我得到這個:

Unit: seconds
                                                       expr      min       lq     mean   median       uq      max neval  
                                            lapply(1:n, fn) 39.96850 39.96850 40.14782 40.14782 40.32714 40.32714     2
 parallel::parLapplyLB(X = 1:n, fun = fn, chunk.size = 50L) 43.34628 43.34628 44.85910 44.85910 46.37191 46.37191     2

兩個 vcore 永遠不會達到最大。此外,當使用 2 個進程時,效能並沒有提高——甚至更糟,因為它們似乎互相阻塞。最後,debian 擁有更好的(儘管是虛擬化的)硬體。

答案1

在 Ubuntu 18.04 上確認,尚未在 Windows 上測試。

我簡化了您的範例並添加了檢測程式碼。第一個圖顯示了為每個子程序寫入的 blob 數量。在第一個圖中,平穩狀態顯示所有核心上的不活動狀態持續約 0.2 秒,而急劇上升是所有核心上的突發寫入。第二個圖顯示了原始數據,對於plotly 最有用,但在StackOverflow 答案中不起作用。

啟用後,gc()運行時間會更長,但負載分佈會更均勻,如下圖所示。

我不知道發生了什麼事。您可以使用此設定進行複製和進一步實驗嗎?如果您能在這裡或在 RSQLite 問題追蹤器中獲得回饋,我將不勝感激。

基本運行,無gc()

make.con <- function() {
  options(digits.secs = 6)

  con <<- DBI::dbConnect(RSQLite::SQLite(), dbname = "db.sqlite")
  DBI::dbExecute(con, "PRAGMA journal_mode = WAL;")
  DBI::dbExecute(con, "PRAGMA busy_timeout = 60000;")
  DBI::dbExecute(con, "PRAGMA synchronous = OFF;")
  DBI::dbExecute(con, "
    CREATE TABLE IF NOT EXISTS tmp (
      id INTEGER NOT NULL,
      blob BLOB NOT NULL,
      PRIMARY KEY (id)
  )")
}
make.con()
#> [1] 0

blob <- serialize(list(rand = runif(1000)), connection = NULL, xdr = FALSE)

fn <- function(x) {
  time0 <- Sys.time()
  rs <- DBI::dbSendQuery(con, "INSERT INTO tmp (blob) VALUES (:blob);")
  time1 <- Sys.time()
  DBI::dbBind(rs, params = list("blob" = list(blob)))
  time2 <- Sys.time()
  DBI::dbClearResult(rs)
  time3 <- Sys.time()
  # gc()
  time4 <- Sys.time()
  list(pid = unix::getpid(), time0 = time0, time1 = time1, time2 = time2, time3 = time3, time4 = time4)
}

n <- 1000L

parallel::setDefaultCluster(parallel::makeCluster(8L))
parallel::clusterExport(varlist = c("make.con", "blob"))
invisible(parallel::clusterEvalQ(expr = {
  make.con()
}))

data <- parallel::parLapply(X = 1:n, fun = fn, chunk.size = 50L)

parallel::stopCluster(cl = parallel::getDefaultCluster())

library(tidyverse)

tbl <-
  data %>%
  transpose() %>%
  map(unlist, recursive = FALSE) %>%
  as_tibble() %>%
  rowid_to_column() %>%
  pivot_longer(-c(rowid, pid), names_to = "step", values_to = "time") %>%
  mutate(time = as.POSIXct(time, origin = "1970-01-01")) %>%
  mutate(pid = factor(pid)) %>%
  arrange(time)

tbl %>%
  group_by(pid) %>%
  mutate(cum = row_number()) %>%
  ungroup() %>%
  ggplot(aes(x = time, y = cum, color = pid)) +
  geom_line()

p <-
  tbl %>%
  ggplot(aes(x = time, y = factor(pid), group = 1)) +
  geom_path() +
  geom_point(aes(color = step))

p

plotly::ggplotly(p)

(plotly 不適用於 StackOverflow)

創建於 2020-01-30 由代表包(v0.3.0)

結果與gc()

相關內容