web-dev-qa-db-ja.com

UNIXシステムでのSQLiteWAL同時書き込みパフォーマンス

2つのセットアップがあります。1つはWindows10(ntfsパーティション)で実行され、もう1つはdebian(ext4パーティション)で実行されます。 Rのソースコードは同じです。メインプロセスは、8つの子プロセス(P-SOCKS)を開始します(8つのvcoreで)。これらはすべて、同じWAL対応のsqliteデータベースに対してクエリと書き込みを行います。

Windows 10では、100%のCPU負荷がすべてのプロセスに分散されます。 Debianでは、25%のCPU負荷はほとんど得られません。 Debianでプロセスを監視することは、一度に1つのプロセスだけがvcoreで100%に達するのを見るので、書き込みがボトルネックだと思います。 (他の人はおそらく書くのを待っています。)

各接続は_PRAGMA busy_timeout = 60000;_と_PRAGMA journal_mode = WAL;_を使用しています。

私はこれをデバッグしようとしています。 fsync()と関係があるのではないかと考えて_PRAGMA synchronous = OFF;_を試しましたが、改善は見られません。 Debianでパフォーマンスが低下する原因について他に何か提案はありますか?

編集:書き込みキャッシュはSCSIディスクで有効になっているようです(sdparmでチェック)。_barrier=0_や_data=writeback_などのext4マウントオプションを調整しません。効果があるようです。

ベンチマーク

同時書き込みのベンチマークを行うための簡単なコードを次に示します。

_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に増やします。

私のWindows10ラップトップでは、次の結果が得られます(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
_

100%で1つのvcoreがはっきりと表示され、次に100%で2つのvcoreが表示されます。パフォーマンスはほぼ2倍高速であり、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
_

2つのvcoreが最大になることはありません。また、2つのプロセスを使用してもパフォーマンスは向上しません。互いにブロックしているように見えるため、さらに悪化します。そして最後に、debianは(仮想化されているとはいえ)より優れたハードウェア上にあります。

1
Davor Josipovic

Ubuntu 18.04で確認済みで、Windowsではテストされていません。

例を簡略化し、インストルメンテーションコードを追加しました。最初のプロットは、サブプロセスごとに書き込まれたBLOBの数を示しています。最初のプロットでは、プラトーはすべてのコアで約0.2秒間非アクティブであることを示しており、急激な上昇はすべてのコアでのバースト書き込みです。 2番目のプロットは生データを示しており、StackOverflowの回答では機能しないplotlyで最も役立ちます。

gc()を有効にすると、実行時間が長くなりますが、負荷がより均等に分散されます(以下の2番目のプロット)。

何が起こっているのかわかりません。この設定を複製してさらに実験できますか?ここまたはおそらく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)
_

(StackOverflowではプロットが機能しません)

reprexパッケージ (v0.3.0)によって2020-01-30に作成されました

gc()の結果

1
krlmlr