For this installation i’m using a Hardkernel Odroid N2/N2+ which are ARM S922X, means ARM Big Little Architecture with Quad-Core-ARM-Cortex-A73-CPU-Cluster and a Dual-Core-Cortex-A53-Cluster and a Mali-G52-GPU.
These device is normaly a good device as Mediaplayer using Coreelec based on Kodi, it has everything needed for including IR receiver for IR remote device.

The installation of ARMBIAN is a easy step, Odroid N2 devices using a EMMC Module and with a Adapter for SD Cards it is a very easy step putting Armbian on the EMMC using for example Etcher on Windows.
For this Blog I’m using a 16GB EMMC module with Armbian Bullsey which is till now a unstable Release, but for a R&D Task it is fine.

On the first boot user will be asked to replace the Armbian default password 1234 which makes definitly sense.

First boot after changing the default passord.

$   ___      _           _     _   _   _ ____
$  / _   __| |_ __ ___ (_) __| | |  | |___ 
$ | | | |/ _` | '__/ _ | |/ _` | |  | | __) |
$ | |_| | (_| | | | (_) | | (_| | | |  |/ __/
$  ___/ __,_|_|  ___/|_|__,_| |_| _|_____|
$ 
$ Welcome to Armbian 21.08.1 Bullseye with Linux 5.10.60-meson64
$ 
$ System load:   2%               Up time:       2 min
$ Memory usage:  4% of 3.61G      IP:            192.168.128.32
$ CPU temp:      27°C             Usage of /:    12% of 15G
$ 
$ [ General system configuration (beta): armbian-config ]
$ 
$ Last login: Fri Jan 14 08:24:27 2022 from 192.168.128.20
$ root@odroidn2:~#

Reboot after updating:

$   ___      _           _     _   _   _ ____
$  / _   __| |_ __ ___ (_) __| | |  | |___ 
$ | | | |/ _` | '__/ _ | |/ _` | |  | | __) |
$ | |_| | (_| | | | (_) | | (_| | | |  |/ __/
$  ___/ __,_|_|  ___/|_|__,_| |_| _|_____|
$ 
$ Welcome to Armbian 21.08.6 Bullseye with Linux 5.10.81-meson64
$ 
$ System load:   2%               Up time:       0 min
$ Memory usage:  4% of 3.61G      IP:            192.168.128.32
$ CPU temp:      32°C             Usage of /:    11% of 15G
$ 
$ [ General system configuration (beta): armbian-config ]
$ 
$ Last login: Fri Jan 14 08:24:38 2022 from 192.168.128.20
$ root@odroidn2:~#

For /var/lib/postgresqli’m using a USB Stick, in this case a Corsair Slider X2 USB 3.0 which is fine, the Odroid N2/N2+ is using USB 3.0 to.
Here the fdisk -l output for the USB device-

$ root@odroidn2:~# fdisk -l
$ $ Disk /dev/sda: 461.63 GiB, 495666069504 bytes, 968097792 sectors
$ Disk model: Voyager SliderX2
$ Units: sectors of 1 * 512 = 512 bytes
$ Sector size (logical/physical): 512 bytes / 512 bytes
$ I/O size (minimum/optimal): 512 bytes / 512 bytes
$ Disklabel type: dos
$ Disk identifier: 0xd900dfd8

I’m using XFS for PostgreSQL, for this we need to install xfsprogs and create the filesystem.

$ root@odroidn2:~# apt install xfsprogs
$ Reading package lists... Done
$ Building dependency tree... Done
$ Reading state information... Done
$ The following additional packages will be installed:
$   libinih1
$ Suggested packages:
$   xfsdump acl attr quota
$ The following NEW packages will be installed:
$   libinih1 xfsprogs
$ 0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded.
$ Need to get 906 kB of archives.
$ After this operation, 3,250 kB of additional disk space will be used.
$ Do you want to continue? [Y/n] Y

Creating the filesystem on the USB Stick.

$ root@odroidn2:~# mkfs.xfs /dev/sda -f
$ meta-data=/dev/sda               isize=512    agcount=4, agsize=30253056 blks
$          =                       sectsz=512   attr=2, projid32bit=1
$          =                       crc=1        finobt=1, sparse=1, rmapbt=0
$          =                       reflink=1    bigtime=0
$ data     =                       bsize=4096   blocks=121012224, imaxpct=25
$          =                       sunit=0      swidth=0 blks
$ naming   =version 2              bsize=4096   ascii-ci=0, ftype=1
$ log      =internal log           bsize=4096   blocks=59088, version=2
$          =                       sectsz=512   sunit=0 blks, lazy-count=1
$ realtime =none                   extsz=4096   blocks=0, rtextents=0

Mount the filesystem

$ root@odroidn2:~# mkdir /var/lib/postgresql
$ root@odroidn2:~# mount /dev/sda /var/lib/postgresql
$ root@odroidn2:~# df -h
$ Filesystem      Size  Used Avail Use% Mounted on
$ udev            1.4G     0  1.4G   0% /dev
$ tmpfs           370M  832K  369M   1% /run
$ /dev/mmcblk1p1   15G  1.5G   13G  11% /
$ tmpfs           1.9G     0  1.9G   0% /dev/shm
$ tmpfs           5.0M  4.0K  5.0M   1% /run/lock
$ tmpfs           1.9G     0  1.9G   0% /tmp
$ /dev/zram1       49M   12M   34M  25% /var/log
$ tmpfs           370M     0  370M   0% /run/user/0
$ /dev/sda        462G  3.3G  459G   1% /var/lib/postgresql

Mouting permanently, at first we need the UUID and then edit /etc/fstab:

$ root@odroidn2:/# ls -al /dev/disk/by-uuid/
$ total 0
$ drwxr-xr-x 2 root root  80 Jan 14 10:25 .
$ drwxr-xr-x 6 root root 120 Jan  1  1970 ..
$ lrwxrwxrwx 1 root root   9 Jan 14 10:25 755a56a5-79ca-48d5-911a-f5c6d9e81563 -> ../../sda
$ lrwxrwxrwx 1 root root  15 Jan 14 10:25 99afb779-b78b-4d62-9926-d09ecb786cc2 -> ../../mmcblk1p1
$ root@odroidn2:/#nano /etc/fstab
$ UUID=99afb779-b78b-4d62-9926-d09ecb786cc2 / ext4 defaults,noatime,commit=600,errors=remount-ro 0 1
$ tmpfs /tmp tmpfs defaults,nosuid 0 0
$ UUID=755a56a5-79ca-48d5-911a-f5c6d9e81563 /var/lib/postgresql   xfs     0       0

Now the preparation are done, starting with PostgreSQL, Armbian Bullsey is providing PostgreSQL up to PostgreSQL 13.

$ root@odroidn2:~# apt install postgresql-13 postgresql-contrib
$ Preparing to unpack .../3-libxslt1.1_1.1.34-4_arm64.deb ...
$ Unpacking libxslt1.1:arm64 (1.1.34-4) ...
$ Selecting previously unselected package postgresql-client-common.
$ Preparing to unpack .../4-postgresql-client-common_225_all.deb ...
$ Unpacking postgresql-client-common (225) ...
$ Selecting previously unselected package postgresql-client-13.
$ Preparing to unpack .../5-postgresql-client-13_13.5-0+deb11u1_arm64.deb ...
$ Unpacking postgresql-client-13 (13.5-0+deb11u1) ...
$ Selecting previously unselected package ssl-cert.
$ Preparing to unpack .../6-ssl-cert_1.1.0+nmu1_all.deb ...
$ Unpacking ssl-cert (1.1.0+nmu1) ...
$ Selecting previously unselected package postgresql-common.
$ Preparing to unpack .../7-postgresql-common_225_all.deb ...
$ Adding 'diversion of /usr/bin/pg_config to /usr/bin/pg_config.libpq-dev by postgresql-common'
$ Unpacking postgresql-common (225) ...
$ Selecting previously unselected package postgresql-13.
$ Preparing to unpack .../8-postgresql-13_13.5-0+deb11u1_arm64.deb ...
$ Unpacking postgresql-13 (13.5-0+deb11u1) ...
$ Selecting previously unselected package postgresql-contrib.
$ Preparing to unpack .../9-postgresql-contrib_13+225_all.deb ...
$ Unpacking postgresql-contrib (13+225) ...
$ Setting up postgresql-client-common (225) ...
$ Setting up libpq5:arm64 (13.5-0+deb11u1) ...
$ Setting up postgresql-client-13 (13.5-0+deb11u1) ...
$ update-alternatives: using /usr/share/postgresql/13/man/man1/psql.1.gz to provide /usr/share/man/man1/psql.1.gz (psql.1.gz) in auto mode
$ Setting up libz3-4:arm64 (4.8.10-1) ...
$ Setting up libllvm11:arm64 (1:11.0.1-2) ...
$ Setting up ssl-cert (1.1.0+nmu1) ...
$ Setting up postgresql-common (225) ...
$ Adding user postgres to group ssl-cert
$ 
$ Creating config file /etc/postgresql-common/createcluster.conf with new version
$ Building PostgreSQL dictionaries from installed myspell/hunspell packages...
$ Removing obsolete dictionary files:
$ Created symlink /etc/systemd/system/multi-user.target.wants/postgresql.service → /lib/systemd/system/postgresql.service.
$ Setting up libxslt1.1:arm64 (1.1.34-4) ...
$ Setting up postgresql-13 (13.5-0+deb11u1) ...
$ Creating new PostgreSQL cluster 13/main ...
$ /usr/lib/postgresql/13/bin/initdb -D /var/lib/postgresql/13/main --auth-local peer --auth-host md5
$ The files belonging to this database system will be owned by user "postgres".
$ This user must also own the server process.
$ 
$ The database cluster will be initialized with locale "en_US.UTF-8".
$ The default database encoding has accordingly been set to "UTF8".
$ The default text search configuration will be set to "english".
$ 
$ Data page checksums are disabled.
$ 
$ fixing permissions on existing directory /var/lib/postgresql/13/main ... ok
$ creating subdirectories ... ok
$ selecting dynamic shared memory implementation ... posix
$ selecting default max_connections ... 100
$ selecting default shared_buffers ... 128MB
$ selecting default time zone ... Etc/UTC
$ creating configuration files ... ok
$ running bootstrap script ... ok
$ performing post-bootstrap initialization ... ok
$ syncing data to disk ... ok
$ 
$ Success. You can now start the database server using:
$ 
$     pg_ctlcluster 13 main start
$ 
$ Ver Cluster Port Status Owner    Data directory              Log file
$ 13  main    5432 down   postgres /var/lib/postgresql/13/main /var/log/postgresql/postgresql-13-main.log
$ update-alternatives: using /usr/share/postgresql/13/man/man1/postmaster.1.gz to provide /usr/share/man/man1/postmaster.1.gz (postmaster.1.gz) in auto mode
$ Setting up postgresql-contrib (13+225) ...
$ Processing triggers for man-db (2.9.4-2) ...
$ Processing triggers for libc-bin (2.31-13) ...

Time for some configuration of PostgreSQL:

$ postgres@odroidn2:/$ nano /var/lib/postgresql/13/main/postgresql.auto.conf
$ max_connections = 100
$ superuser_reserved_connections = 3
$ shared_buffers = '1024 MB'
$ work_mem = '32 MB'
$ maintenance_work_mem = '320 MB'
$ huge_pages = off
$ effective_cache_size = '3 GB'
$ max_worker_processes = 6
$ max_parallel_workers_per_gather = 3
$ max_parallel_maintenance_workers = 3
$ max_parallel_workers = 6
$ parallel_leader_participation = on
$ wal_level = replica
$ max_wal_senders = 0
$ synchronous_commit = on
$ checkpoint_timeout  = '15 min'
$ checkpoint_completion_target = 0.9
$ max_wal_size = '1024 MB'
$ min_wal_size = '512 MB'
$ wal_compression = on
$ wal_buffers = -1
$ wal_writer_delay = 200ms
$ wal_writer_flush_after = 1MB
$ bgwriter_delay = 200ms
$ bgwriter_lru_maxpages = 100
$ bgwriter_lru_multiplier = 2.0
$ bgwriter_flush_after = 0
$ enable_partitionwise_join = on
$ enable_partitionwise_aggregate = on
$ jit = on

Restarting PostgreSQL:

$ root@odroidn2:~# service postgresql restart

Time for pgbench…

$ postgres@odroidn2:/$ createdb testdb
$ postgres@odroidn2:/$ pgbench -i -s 60 testdb
$ dropping old tables...
$ NOTICE:  table "pgbench_accounts" does not exist, skipping
$ NOTICE:  table "pgbench_branches" does not exist, skipping
$ NOTICE:  table "pgbench_history" does not exist, skipping
$ NOTICE:  table "pgbench_tellers" does not exist, skipping
$ creating tables...
$ generating data (client-side)...
$ 6000000 of 6000000 tuples (100%) done (elapsed 122.47 s, remaining 0.00 s)
$ vacuuming...
$ creating primary keys...
$ done in 370.27 s (drop tables 0.00 s, create tables 3.28 s, client-side generate 122.55 s, vacuum 72.07 s, primary keys 172.38 s).

The performance is good, espacialy comparing with cloud service..

$ latency average = 6.837 ms
$ tps = 1462.673137 (including connections establishing)
$ tps = 1462.914005 (excluding connections establishing)

Comparing with Azure Flexible Server V3 and V4:

V3 without replication:

latency average = 8.343 ms
tps = 479.469320 (including connections establishing)
tps = 479.735705 (excluding connections establishing)

V4 without replication:

latency average = 11.734 ms
tps = 340.893181 (including connections establishing)
tps = 341.094725 (excluding connections establishing)

Have fun playing with ARM.