解决 PostgreSQL INSERT 性能不佳问题的系统配置

Whe*_*hee 5 postgresql performance configuration r postgresql-10 postgresql-performance

问题症状

postmaster与尝试插入低容量行的客户端连接相关的子进程的 CPU 使用率较高(导致插入的行比使用相同行慢 25 倍)。COPY ... FROM STDIN

背景

尝试识别系统/数据库配置以缓解上述较差的插入性能。我正在使用多线程 R 脚本来处理数据并将结果插入到 PostgreSQL 数据库中。我对 R 脚本进行了分析,以隔离调用的性能瓶颈DBI::dbBind(),同时用于top监视postmaster与子 R 线程打开的连接关联的子进程(请参阅下面的代码)。在 INSERT 期间,R 子进程大部分时间处于空闲状态(大概是在等待调用返回DBI::dbBind()),而postmaster子进程在其运行大约 2-3 分钟的时间内消耗了 95-100% 的 CPU。

系统/环境:

  • postgresql 版本 10.3(Fedora 包 10.3-5.fc27)
  • uname -a:Linux localhost 4.16.6-202.fc27.x86-64 #1 SMP Wed May 2 00:09:32 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
  • /proc/cpuinfo: 16 个处理器 ( Intel(R) Xeon(R) CPU D-1541 @ 2.10GHz)
  • ulimit -a:

    core file size          (blocks, -c) 0
    data seg size           (kbytes, -d) unlimited
    scheduling priority             (-e) 0
    file size               (blocks, -f) unlimited
    pending signals                 (-i) 515220
    max locked memory       (kbytes, -l) 64
    max memory size         (kbytes, -m) unlimited
    open files                      (-n) 1024
    pipe size            (512 bytes, -p) 8
    POSIX message queues     (bytes, -q) 819200
    real-time priority              (-r) 0
    stack size              (kbytes, -s) 8192
    cpu time               (seconds, -t) unlimited
    max user processes              (-u) 515220
    virtual memory          (kbytes, -v) unlimited
    file locks                      (-x) unlimited
    
    Run Code Online (Sandbox Code Playgroud)
  • /proc/meminfo(在postmaster处理 INSERT 时的任意时间):

    MemTotal:         131923484 kB
    MemFree:          112894260 kB
    MemAvailable:     123181440 kB
    Buffers:             201220 kB
    Cached:            14932288 kB
    SwapCached:               0 kB
    ...
    Mlocked:                  0 kB
    SwapTotal:        201326588 kB
    SwapFree:         201326588 kB
    Dirty:                 3260 kB
    ...
    Shmem:              4251184 kB
    Slab:               1024344 kB
    SReclaimable:        658476 kB
    SUnreclaim:          365868 kB
    ...
    PageTables:           38436 kB
    ...
    CommitLimit:      267288328 kB
    Committed_AS:      35678744 kB
    VmallocTotal:   34359738367 kB
    VmallocUsed:              0 kB
    VmallocChunk:             0 kB
    HardwareCorrupted:        0 kB
    ...
    ShmemHugePages:           0 kB
    ShmemPmdMapped:           0 kB
    ...
    HugePages_Total:          0
    HugePages_Free:           0
    HugePages_Rsvd:           0
    HugePages_Surp:           0
    Hugepagesize:          2048 kB
    Hugetlb:                  0 kB
    ...
    
    Run Code Online (Sandbox Code Playgroud)
  • 测试中的数据库表驻留在 RAID 10 配置中的一对 SSD 驱动器上,日志记录和 WAL 位于单独的驱动器上(因为基线COPY ... FROM STDIN没有写入性能问题——见下文——(计时还包括 bashsync返回时间)并且 CPU 通常不会出现等待 IO,不相信磁盘性能存在瓶颈)

Postgres 配置:

  • 以下是未注释掉的设置(例如,通常非默认设置)
  • 其中一些在尝试识别配置更改时可能显得有点高(请随意注意给定我的系统的任何“疯狂”设置 - 假设服务器主要作为数据库服务器运行)

    max_connections = 20                    # (change requires restart)
    shared_buffers = 32GB                   # min 128kB
    temp_buffers = 128MB                    # min 800kB
    max_prepared_transactions = 20          # Allow 'max_connections'
    work_mem = 16MB                         # min 64kB
    max_stack_depth = 6MB                   # min 100kB
    dynamic_shared_memory_type = posix      # the default is the first option
    max_wal_size = 5GB
    checkpoint_flush_after = 1MB            # measured in pages, 0 disables
    deadlock_timeout = 15s
    max_locks_per_transaction = 2096        # min 10
    
    Run Code Online (Sandbox Code Playgroud)
  • 还(暂时)尝试了以下设置,/lib/systemd/system/postgresql.service.d/10-postgresql-unlimited.conf但没有效果(实际上性能略有下降):[警告对于临时读者,建议使用以下设置]

    # DO NOT USE THIS IN PRODUCTION (or elsewise)
    [Service]
    LimitDATA=infinity
    LimitFSIZE=infinity
    LimitLOCKS=infinity
    LimitMEMLOCK=infinity
    LimitMSGQUEUE=infinity
    LimitNPROC=infinity
    LimitNOFILE=infinity
    LimitSIGPENDING=infinity
    
    Run Code Online (Sandbox Code Playgroud)

数据库简介:

  • 数据库集群只有一个用户,通常由多线程进程访问(1 到 3 个线程用于测试,但自动化进程预计最多 16 个,管理员需要额外 3-4 个连接)。
  • 测试使用表定义初始化的数据库,但没有行。表定义包括 28 个分区表(使用声明式分区),这些表缺乏触发器函数并且仅具有有限的外键或约束/索引。
  • 每个分区表有 180 个分区和 42 到 81 个列(类型double precision)。

    注意:虽然这些表比理想情况更宽,并且分区比推荐的要多,但我尝试将列数减少到 12,将分区数减少到 30,但 INSERT 性能相当差。

代码(R):

  • 目前基于 PostgreSQL ODBC 驱动程序(只有一个我可以使用多行准备好的语句)

    library(odbc); 
    cdb <- DBI::dbConnect(drv=odbc::odbc(),driver="PostgreSQL",...);
    on.exit({DBI::dbDisconnect(cdb);}, add=TRUE);
    DBI::dbBegin(cdb);
    sth <- DBI::dbSendStatement(cdb,paste(rep("INSERT INTO <table> (<col_1>,...,<col_n>) VALUES (?,...,?);",100),collapse=""));   # Bind up to 8,000 placeholders at a time (ref in source?) -- use 100 multi-line statements in this example
    DBI::dbBind(sth,bvallist);   # Here, 'bvallist' is a list of values to bind in the multi-line prepared statement -- have tested and checked values in database are correct after INSERT
    num_recs <- DBI::dbGetRowsAffected(sth);
    DBI::dbClearResult(sth);
    DBI::dbCommit(cdb);
    
    Run Code Online (Sandbox Code Playgroud)
    • 注意:还使用了以下具有类似性能的方法:(A) 仅使用一个 INSERT(在驱动程序中)的预准备语句odbc以及RPostgres(B) 在不使用预准备语句的情况下组装多行 INSERT SQL 字符串。

绩效报告:

基线

  • 基于COPY ... FROM STDIN-- 一个简单的 bash 脚本,包含以下命令(28 个表中的每个表一个):cat <tablename>.out | psql -c 'COPY <tablename> FROM STDIN;'
  • 在 14 秒内将 11,117 行插入到 28 个分区表中的每一个中 => 22,234 记录/秒(或 4.5e-5 秒/行)
  • vmstat -wt 1信息COPY ... FROM STDIN(基于 16 个处理器的 CPU 信息):

    procs -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu-------- -----timestamp-----
    r  b         swpd         free         buff        cache   si   so    bi    bo   in   cs  us  sy  id  wa  st                 EST
    2  0            0    113563384       200600     15965816    0    0     0   696 4178  1518  14   1  85   0   0 2018-11-27 14:29:19
    2  0            0    113562280       200600     15967184    0    0     0  1483 4781  1727  14   1  84   0   0 2018-11-27 14:29:20
    3  0            0    113564520       200604     15962952    0    0     0  1123 4847  1790  15   2  83   0   0 2018-11-27 14:29:21
    3  0            0    113565664       200604     15962020    0    0     0   800 4512  1646  15   2  84   0   0 2018-11-27 14:29:22
    >>>>>>>> START: COPY ... FROM STDIN bash script (Tue Nov 27 14:29:23 EST 2018)
    3  0            0    113566992       200604     15960036    0    0     0   760 4730  1655  14   2  84   0   0 2018-11-27 14:29:23
    3  1            0    113551160       200604     15961608    0    0     0  9704 5271  5025  15   3  82   1   0 2018-11-27 14:29:24
    3  0            0    113548176       200604     15966864    0    0     0  8764 6269  6335  19   3  77   1   0 2018-11-27 14:29:25
    3  0            0    113552104       200604     15969684    0    0     0  7744 6331  5648  19   3  77   1   0 2018-11-27 14:29:26
    1  0            0    113521248       200604     15975508    0    0     0  8624 3717  5478   9   2  89   0   0 2018-11-27 14:29:27
    2  0            0    113535536       200604     15976840    0    0     0 12563 4982  8784  11   3  86   1   0 2018-11-27 14:29:28
    3  0            0    113535640       200604     15978772    0    0     0 11223 5473  6182  12   3  84   1   0 2018-11-27 14:29:29
    2  0            0    113533576       200604     15977312    0    0     0 11180 5032  6443  12   3  85   0   0 2018-11-27 14:29:30
    2  0            0    113534384       200604     15978180    0    0     0 11169 4961  6511  12   3  86   0   0 2018-11-27 14:29:31
    2  0            0    113504656       200604     16004428    0    0     0 32691 4551 13584  10   3  84   2   0 2018-11-27 14:29:32
    2  0            0    113486672       200604     16023572    0    0     0 26133 4387  8803  10   3  86   2   0 2018-11-27 14:29:33
    4  0            0    113459744       200604     16033296    0    0     0 12535 5709  8188  17   3  80   1   0 2018-11-27 14:29:34
    1  0            0    113444128       200604     16057612    0    0     0 78953 8980 10186  12   4  82   2   0 2018-11-27 14:29:35
    1  1            0    113415520       200604     16087380    0    0     0 23640 5576  8781  15   3  80   1   0 2018-11-27 14:29:36
    3  0            0    113400864       200604     16108080    0    0     0 14733 3348 10182   6   2  90   1   0 2018-11-27 14:29:37
    <<<<<<<< END  : COPY ... FROM STDIN bash script (Tue Nov 27 14:29:37 EST 2018)
    1  0            0    113393552       200632     16127372    0    0     0 56640 4456  4481   4   3  93   1   0 2018-11-27 14:29:38
    0  1            0    113392368       200632     16127680    0    0     0  1688 2446  1406   2   1  96   0   0 2018-11-27 14:29:39
    1  0            0    113391032       200632     16128472    0    0     0  1616 2372  1396   2   1  97   0   0 2018-11-27 14:29:40
    0  0            0    113389376       200632     16128440    0    0     0  1896 2474  1402   2   1  96   0   0 2018-11-27 14:29:41
    
    Run Code Online (Sandbox Code Playgroud)

当前的

  • 当在单个事务块中绑定多行准备好的语句时(请参阅代码),将相同的行插入到 28 个分区表中的每一个中需要 390 秒,总吞吐量为 9 行/秒(相比之下,22,234 行/秒)在基线中)
  • vmstat -wt 1INSERT 期间产生的最大/平均/最小值DBI::dbBind()如下所示:

    procs -----------------------memory---------------------- ---swap-- -----io---- --system-- --------cpu--------
    r  b         swpd         free         buff        cache   si   so    bi    bo    in    cs  us  sy  id  wa  st     
    6  1            0    112348416       200092     15661572    0    0     0 10090 12259 37796  32   3  81   4   0   # MAX
    3  0            0    111860781       199437     15608955    0    0     0  1708  5658  6371  21   1  78   0   0   # AVG
    3  0            0    111353344       199372     15572860    0    0     0   184  4115  1602  17   0  67   0   0   # MIN
    
    Run Code Online (Sandbox Code Playgroud)

其他注意事项:

  • 运行单个或多个 R 子线程(即一个或多个数据库客户端连接)时,性能不受影响。
  • 尝试使用RPostgresRPostgreSQLodbcR 包作为数据库驱动程序(虽然驱动程序之间存在轻微的性能差异,但没有特定于实现的工件似乎对处理时间有显着贡献)。
  • 正在研究 SQL 的 EXPLAIN ANALYZE,但欢迎提前对潜在的内核设置/数据库配置/SystemD 设置/等提出想法。

先感谢您。

更新(2018.11.28)

  • 使用 R 脚本生成的 INSERT 语句创建 SQL 平面文件(将语句放置在BEGIN ... COMMIT事务块内)并使用加载psql -f <file>.sql- 没有性能改进。
  • 在单个 INSERT 上运行EXPLAIN ANALYZE VERBOSE,输出如下:

    Insert on <tablename>  (cost=0.00..0.26 rows=1 width=1952) (actual time=0.074..0.074 rows=0 loops=1)
       ->  Result  (cost=0.00..0.26 rows=1 width=1952) (actual time=0.006..0.007 rows=1 loops=1)
             Output: <row data>
    Planning time: 0.303 ms
    Trigger RI_ConstraintTrigger_c_274329 for constraint <foreign_key_constraint_name> on <child_tablename>: time=0.071 calls=1
    Execution time: 13.345 ms
    
    Run Code Online (Sandbox Code Playgroud)

Whe*_*hee 2

对准备好的 SQL 语句进行更改后,性能提高了六倍。

SQL 更改为:

    INSERT INTO <table> (<col_1>,...,<col_n>) VALUES (?,...,?);
    ...
    INSERT INTO <table> (<col_1>,...,<col_n>) VALUES (?,...,?);
Run Code Online (Sandbox Code Playgroud)

到:

    INSERT INTO <table> (<col_1>,...,<col_n>) VALUES
    (?,...,?),
    ...
    (?,...,?);
Run Code Online (Sandbox Code Playgroud)

换句话说,我将多个 INSERT 语句折叠为单个语句,这将时间从 390 秒减少到 66 秒,即 56 行/秒。虽然速度仍然较慢,但需要重新分析执行情况,看看剩余的延迟是否与应用程序或数据库相关。