In the last post we’ve looked into how you can build PostgreSQL with Meson instead of the traditional way using configure/make. The reason for this was a thread on hackers which described the downsides of the traditional approach and explained the advantages of using Meson. Some of the advantages mentioned were the time required for the build, readability of the output and decreased time to run the regression tests. In this post we’ll run a few tests and check if this really improves the situation.

All the test we’ll run are executed on an AWS EC2 instance, the instance type is c5.xlarge. The operating system used is openSUSE Leap 15.4. You might ask yourself: Why openSUSE? Because there are not only Red Hat based distributions like Red Hat itself, Rocky Linux, Alma Linux, or Oracle Linux. Because there are not only Debian based distribution such as Debian itself, Ubuntu and all the other flavors. There is much more to choose from, and openSUSE is another great distribution, which usually is underrated.

All the required packages have already been installed, we’ll not go into the details of this. The only preparations that have been done are this:

postgres@opensuse-leap:~> git clone http://git.postgresql.org/git/postgresql.git
postgres@opensuse-leap:~> mv postgresql/ postgresql-meson
postgres@opensuse-leap:~> cp -pr postgresql-meson postgresql-traditional
postgres@opensuse-leap:~> ls -l
total 8
drwxr-xr-x 2 postgres postgres    6 Mar 15  2022 bin
drwxr-xr-x 7 postgres postgres 4096 Oct 18 19:30 postgresql-meson
drwxr-xr-x 7 postgres postgres 4096 Oct 18 19:30 postgresql-traditional

No magic here: The PostgreSQL source code was cloned and duplicated. There is no real reason to have the source code twice, this is just for readability of the scripts that follow. We can do the very same with just one copy of the source code. Note that the source code of PostgreSQL used here is the development version of PostgreSQL, currently 16devel.

We’ll start with the traditional way of building PostgreSQL, this is the script:

postgres@opensuse-leap:~> cat build-traditional.sh 
#!/bin/bash

cd

BUILD_SOURCE="/home/postgres/postgresql-traditional"
BUILD_DIR="/home/postgres/build-traditional"
PGHOME="/home/postgres//pgsql-traditional"
SEGSIZE=2
BLOCKSIZE=8

rm -rf ${PGHOME}
rm -rf ${BUILD_DIR}
mkdir ${BUILD_DIR}
cd ${BUILD_DIR}
${BUILD_SOURCE}/configure --prefix=${PGHOME} \
                          --exec-prefix=${PGHOME} \
                          --bindir=${PGHOME}/bin \
                          --libdir=${PGHOME}/lib \
                          --sysconfdir=${PGHOME}/etc \
                          --includedir=${PGHOME}/include \
                          --datarootdir=${PGHOME}/share \
                          --datadir=${PGHOME}/share \
                          --with-pgport=5432 \
                          --with-perl \
                          --with-python \
                          --with-openssl \
                          --with-pam \
                          --with-ldap \
                          --with-libxml \
                          --with-libxslt \
                          --with-segsize=${SEGSIZE} \
                          --with-blocksize=${BLOCKSIZE} \
                          --with-llvm LLVM_CONFIG='/usr/bin/llvm-config' \
                          --with-uuid=ossp \
                          --with-lz4 \
                          --with-zstd \
                          --with-gssapi \
                          --with-systemd \
                          --with-icu \
                          --with-system-tzdata=/usr/share/zoneinfo
make -j 6 all
make -j 6 install
cd contrib
make -j 6 install

The reason we go for 6 jobs is, that Ninja, which is used later by Meson, defaults to 6 jobs on this system as well:

postgres@opensuse-leap:~> ninja --help
usage: ninja [options] [targets...]

if targets are unspecified, builds the 'default' target (see manual).

options:
  --version      print ninja version ("1.10.0")
  -v, --verbose  show all command lines while building

  -C DIR   change to DIR before doing anything else
  -f FILE  specify input build file [default=build.ninja]

  -j N     run N jobs in parallel (0 means infinity) [default=6 on this system]
  -k N     keep going until N jobs fail (0 means infinity) [default=1]
  -l N     do not start new jobs if the load average is greater than N
  -n       dry run (don't run commands but act like they succeeded)

  -d MODE  enable debugging (use '-d list' to list modes)
  -t TOOL  run a subtool (use '-t list' to list subtools)
    terminates toplevel options; further flags are passed to the tool
  -w FLAG  adjust warnings (use '-w list' to list warnings)

Lets execute this three times and record the time it took for each build:

postgres@opensuse-leap:~> time ./build-traditional.sh
real    3m13.077s
user    9m54.203s
sys     1m0.454s
postgres@opensuse-leap:~> time ./build-traditional.sh
real    3m12.056s
user    9m55.699s
sys     1m0.765s
postgres@opensuse-leap:~> time ./build-traditional.sh
real    3m12.199s
user    9m57.002s
sys     1m0.192s

Quite consistent at 3 minutes and 12/13 seconds. This already is not so bad.

Lets see how Meson is performing. This is the script:

postgres@opensuse-leap:~> cat build-meson.sh 
#!/bin/bash

cd

BUILD_SOURCE="/home/postgres/postgresql-meson"
BUILD_DIR="/home/postgres/build-meson"
PGHOME="/home/postgres//pgsql-meson"
SEGSIZE=2
BLOCKSIZE=8

rm -rf ${PGHOME}
rm -rf ${BUILD_DIR}
mkdir ${BUILD_DIR}
cd ${BUILD_DIR}

meson setup . ${BUILD_SOURCE}
meson configure -Dprefix=${PGHOME} \
                -Dbindir=${PGHOME}/bin \
                -Ddatadir=${PGHOME}/share \
                -Dincludedir=${PGHOME}/include \
                -Dlibdir=${PGHOME}/lib \
                -Dsysconfdir=${PGHOME}/etc \
                -Dpgport=5432 \
                -Dplperl=enabled \
                -Dplpython=enabled \
                -Dssl=openssl \
                -Dpam=enabled \
                -Dldap=enabled \
                -Dlibxml=enabled \
                -Dlibxslt=enabled \
                -Dsegsize=${SEGSIZE} \
                -Dblocksize=${BLOCKSIZE} \
                -Dllvm=enabled \
                -Duuid=ossp \
                -Dzstd=enabled \
                -Dlz4=enabled \
                -Dgssapi=enabled \
                -Dsystemd=enabled \
                -Dicu=enabled \
                -Dsystem_tzdata=/usr/share/zoneinfo
ninja
ninja install

Same procedure, three times in a row:

postgres@opensuse-leap:~> time ./build-meson.sh 
real    2m15.689s
user    7m14.784s
sys     0m38.827s
postgres@opensuse-leap:~> time ./build-meson.sh 
real    2m14.565s
user    7m13.626s
sys     0m39.631s
postgres@opensuse-leap:~> time ./build-meson.sh 
real    2m15.287s
user    7m14.789s
sys     0m39.323s

Almost a minute less, which is a nice improvement. This does not seem to be much if you only build once in a while, but if you build often I am sure you’ll appreciate the time savings. What makes it even more impressive is that you can change build options without re-configuring the whole tree. Lets say you want to rebuild with PAM disabled. All you need to do is this:

postgres@opensuse-leap:~/build-meson> time meson configure \
>      -Dprefix=${PGHOME} \
>      -Dbindir=${PGHOME}/bin \
>      -Ddatadir=${PGHOME}/share \
>      -Dincludedir=${PGHOME}/include \
>      -Dlibdir=${PGHOME}/lib \
>      -Dsysconfdir=${PGHOME}/etc \
>      -Dpgport=5432 \
>      -Dplperl=enabled \
>      -Dplpython=enabled \
>      -Dssl=openssl \
>      -Dpam=disabled \
>      -Dldap=enabled \
>      -Dlibxml=enabled \
>      -Dlibxslt=enabled \
>      -Dsegsize=${SEGSIZE} \
>      -Dblocksize=${BLOCKSIZE} \
>      -Dllvm=enabled \
>      -Duuid=ossp \
>      -Dzstd=enabled \
>      -Dlz4=enabled \
>      -Dgssapi=enabled \
>      -Dsystemd=enabled \
>      -Dicu=enabled \
>      -Dsystem_tzdata=/usr/share/zoneinfo

real    0m0.331s
user    0m0.307s
sys     0m0.021s

This is an almost immediate change and after rebuilding you have what you want:

postgres@opensuse-leap:~/build-meson> ninja
...
  External libraries
    bonjour                  : NO
    bsd_auth                 : NO
    gss                      : YES 1.19.2
    icu                      : YES 65.1
    ldap                     : YES
    libxml                   : YES 2.9.14
    libxslt                  : YES 1.1.34
    llvm                     : YES 13.0.1
    lz4                      : YES 1.9.3
    nls                      : YES
    pam                      : NO
...
postgres@opensuse-leap:~/build-meson> ninja install
postgres@opensuse-leap:~> cd
postgres@opensuse-leap:~> pgsql-meson/bin/pg_config | grep -i pam
postgres@opensuse-leap:~> 

This save the whole “configure” phase of the traditional build process.

Finally, lets look at the regression tests. With the traditional build system it goes like this (again, three times in a row):

postgres@opensuse-leap:~/build-traditional> pwd
/home/postgres/build-traditional
postgres@opensuse-leap:~/build-traditional> time make -j 6 check-world >/dev/null
...
real    1m7.388s
user    1m8.953s
sys     0m23.537s

postgres@opensuse-leap:~/build-traditional> time make -j 6 check-world >/dev/null
...
real    0m59.688s
user    0m59.238s
sys     0m21.616s

postgres@opensuse-leap:~/build-traditional> time make -j 6 check-world>/dev/null
...
real    1m2.093s
user    0m59.820s
sys     0m21.880s

For the Meson build it looks like this:

postgres@opensuse-leap:~/build-meson> time meson test
...
real    1m5.175s
user    0m48.452s
sys     0m18.157s

postgres@opensuse-leap:~/build-meson> time meson test
...
real    1m4.979s
user    0m49.441s
sys     0m18.468s

postgres@opensuse-leap:~/build-meson> time meson test
...
real    1m4.494s
user    0m49.510s
sys     0m17.946s

There is not much difference here, but the important point is the readability of the output. For make it looks like this all over the output:

...
running on port 61696 with PID 8991
============== creating database "contrib_regression" ==============
CREATE DATABASE
ALTER DATABASE
ALTER DATABASE
ALTER DATABASE
ALTER DATABASE
ALTER DATABASE
ALTER DATABASE
============== installing ltree                       ==============
CREATE EXTENSION
============== running regression test queries        ==============
test ltree_plpython               ... ok           31 ms
============== shutting down postmaster               ==============
============== removing temporary instance            ==============

=====================
 All 1 tests passed. 
=====================

make[2]: Leaving directory '/home/postgres/build-traditional/contrib/ltree_plpython'
ok          407 ms
test concurrent_ddl_dml           ... ok          392 ms
test oldest_xmin                  ... ok          220 ms
test snapshot_transfer            ... ok           23 ms
test subxact_without_top          ... ok           21 ms
test concurrent_stream            ... ok          172 ms
test twophase_snapshot            ... ok          406 ms
test slot_creation_error          ... ok          204 ms
test catalog_change_snapshot      ... ok           35 ms
============== shutting down postmaster               ==============
============== removing temporary instance            ==============

======================
 All 11 tests passed. 
======================
...

Compare that to the output of Meson:

 1/80 postgresql:setup / tmp_install                                       OK                0.60s
 2/80 postgresql:plpgsql / plpgsql/regress                                 OK                3.53s
 3/80 postgresql:plperl / plperl/regress                                   OK                2.90s
 4/80 postgresql:plpython / plpython/regress                               OK                4.61s
 5/80 postgresql:adminpack / adminpack/regress                             OK                2.03s
 6/80 postgresql:amcheck / amcheck/regress                                 OK                3.63s
 7/80 postgresql:basic_archive / basic_archive/regress                     OK                1.93s
 8/80 postgresql:snapshot_too_old / snapshot_too_old/isolation             OK               20.24s
 9/80 postgresql:bloom / bloom/regress                                     OK                3.03s
10/80 postgresql:main / main/regress                                       OK               22.59s
11/80 postgresql:bool_plperl / bool_plperl/regress                         OK                2.49s
12/80 postgresql:btree_gin / btree_gin/regress                             OK                2.43s
13/80 postgresql:citext / citext/regress                                   OK                1.99s
14/80 postgresql:btree_gist / btree_gist/regress                           OK                2.94s
15/80 postgresql:cube / cube/regress                                       OK                2.01s
16/80 postgresql:dblink / dblink/regress                                   OK                1.95s
17/80 postgresql:dict_int / dict_int/regress                               OK                1.81s
18/80 postgresql:dict_xsyn / dict_xsyn/regress                             OK                1.84s
19/80 postgresql:earthdistance / earthdistance/regress                     OK                1.93s
20/80 postgresql:file_fdw / file_fdw/regress                               OK                1.86s
21/80 postgresql:fuzzystrmatch / fuzzystrmatch/regress                     OK                1.80s
22/80 postgresql:hstore_plperl / hstore_plperl/regress                     OK                1.92s
23/80 postgresql:hstore / hstore/regress                                   OK                2.56s
24/80 postgresql:hstore_plpython / hstore_plpython/regress                 OK                1.91s
25/80 postgresql:isn / isn/regress                                         OK                2.07s
26/80 postgresql:jsonb_plperl / jsonb_plperl/regress                       OK                1.91s
27/80 postgresql:main / main/isolation                                     OK               34.31s
28/80 postgresql:intarray / intarray/regress                               OK                3.43s
29/80 postgresql:jsonb_plpython / jsonb_plpython/regress                   OK                1.95s
30/80 postgresql:lo / lo/regress                                           OK                2.04s
31/80 postgresql:ltree_plpython / ltree_plpython/regress                   OK                1.82s
32/80 postgresql:ltree / ltree/regress                                     OK                2.17s
33/80 postgresql:pageinspect / pageinspect/regress                         OK                2.09s
34/80 postgresql:passwordcheck / passwordcheck/regress                     OK                1.82s
35/80 postgresql:pg_buffercache / pg_buffercache/regress                   OK                1.79s
36/80 postgresql:pg_freespacemap / pg_freespacemap/regress                 OK                1.77s
37/80 postgresql:pgcrypto / pgcrypto/regress                               OK                2.70s
38/80 postgresql:pgrowlocks / pgrowlocks/isolation                         OK                1.83s
39/80 postgresql:pg_stat_statements / pg_stat_statements/regress           OK                1.84s
40/80 postgresql:pgstattuple / pgstattuple/regress                         OK                1.90s
41/80 postgresql:pg_surgery / pg_surgery/regress                           OK                1.90s
42/80 postgresql:pg_visibility / pg_visibility/regress                     OK                1.83s
43/80 postgresql:pg_trgm / pg_trgm/regress                                 OK                2.60s
44/80 postgresql:pg_walinspect / pg_walinspect/regress                     OK                1.87s
45/80 postgresql:seg / seg/regress                                         OK                1.86s
46/80 postgresql:tablefunc / tablefunc/regress                             OK                1.85s
47/80 postgresql:tcn / tcn/isolation                                       OK                1.86s
48/80 postgresql:postgres_fdw / postgres_fdw/regress                       OK                4.41s
49/80 postgresql:tsm_system_rows / tsm_system_rows/regress                 OK                1.80s
50/80 postgresql:tsm_system_time / tsm_system_time/regress                 OK                1.84s
51/80 postgresql:test_decoding / test_decoding/isolation                   OK                4.39s
52/80 postgresql:unaccent / unaccent/regress                               OK                1.92s
53/80 postgresql:uuid-ossp / uuid-ossp/regress                             OK                1.79s
54/80 postgresql:test_decoding / test_decoding/regress                     OK                5.70s
55/80 postgresql:xml2 / xml2/regress                                       OK                1.84s
56/80 postgresql:brin / brin/isolation                                     OK                1.89s
57/80 postgresql:commit_ts / commit_ts/regress                             OK                1.79s
58/80 postgresql:dummy_index_am / dummy_index_am/regress                   OK                1.75s
59/80 postgresql:delay_execution / delay_execution/isolation               OK                2.33s
60/80 postgresql:dummy_seclabel / dummy_seclabel/regress                   OK                1.80s
61/80 postgresql:plsample / plsample/regress                               OK                1.75s
62/80 postgresql:spgist_name_ops / spgist_name_ops/regress                 OK                1.84s
63/80 postgresql:test_bloomfilter / test_bloomfilter/regress               OK                1.96s
64/80 postgresql:test_copy_callbacks / test_copy_callbacks/regress         OK                1.83s
65/80 postgresql:test_ddl_deparse / test_ddl_deparse/regress               OK                2.11s
66/80 postgresql:test_extensions / test_extensions/regress                 OK                1.91s
67/80 postgresql:test_ginpostinglist / test_ginpostinglist/regress         OK                1.91s
68/80 postgresql:test_lfind / test_lfind/regress                           OK                1.74s
69/80 postgresql:test_oat_hooks / test_oat_hooks/regress                   OK                1.83s
70/80 postgresql:test_parser / test_parser/regress                         OK                1.89s
71/80 postgresql:test_pg_dump / test_pg_dump/regress                       OK                1.79s
72/80 postgresql:test_integerset / test_integerset/regress                 OK                4.88s
73/80 postgresql:test_predtest / test_predtest/regress                     OK                1.88s
74/80 postgresql:test_rbtree / test_rbtree/regress                         OK                1.94s
75/80 postgresql:test_regex / test_regex/regress                           OK                2.16s
76/80 postgresql:test_rls_hooks / test_rls_hooks/regress                   OK                1.81s
77/80 postgresql:unsafe_tests / unsafe_tests/regress                       OK                1.97s
78/80 postgresql:worker_spi / worker_spi/regress                           OK                2.11s
79/80 postgresql:test_shm_mq / test_shm_mq/regress                         OK                4.38s
80/80 postgresql:ecpg / ecpg/ecpg                                          OK                3.49s


Ok:                 80  
Expected Fail:      0   
Fail:               0   
Unexpected Pass:    0   
Skipped:            0   
Timeout:            0   

This gives a much better overview. Instead of grepping through the individual test results, failed tests can easily be identified.

There for sure is much more to explore with Meson, but that’s it for now.