summaryrefslogtreecommitdiff
path: root/_content/journal/2026
diff options
context:
space:
mode:
Diffstat (limited to '_content/journal/2026')
-rw-r--r--_content/journal/2026/index.adoc11
-rw-r--r--_content/journal/2026/reducing_idle_services/host_processes.pngbin0 -> 201651 bytes
-rw-r--r--_content/journal/2026/reducing_idle_services/index.adoc277
3 files changed, 288 insertions, 0 deletions
diff --git a/_content/journal/2026/index.adoc b/_content/journal/2026/index.adoc
index ff5d0fd..531ac52 100644
--- a/_content/journal/2026/index.adoc
+++ b/_content/journal/2026/index.adoc
@@ -3,6 +3,17 @@
=== 2026
+link:/journal/2026/reducing_idle_services/[Reducing Idle Services with
+SystemD^].
+In this journal, we will take a look on how to minimize idle services
+running in the background using
+https://man.archlinux.org/man/systemd-socket-proxyd.8[systemd-socket-proxyd(8)^]
+for services that we cannot control their code
+and using
+https://man.archlinux.org/man/systemd.socket.5[systemd.socket(5)^]
+for services that we can changes their code.
+
+
link:/journal/2026/systemd_socket_with_go/[systemd Socket Activation with Go^].
In this journal, we will take a look on how to implement socket-based
activation on Go program that listen and serve HTTP.
diff --git a/_content/journal/2026/reducing_idle_services/host_processes.png b/_content/journal/2026/reducing_idle_services/host_processes.png
new file mode 100644
index 0000000..6fcde60
--- /dev/null
+++ b/_content/journal/2026/reducing_idle_services/host_processes.png
Binary files differ
diff --git a/_content/journal/2026/reducing_idle_services/index.adoc b/_content/journal/2026/reducing_idle_services/index.adoc
new file mode 100644
index 0000000..bbf4775
--- /dev/null
+++ b/_content/journal/2026/reducing_idle_services/index.adoc
@@ -0,0 +1,277 @@
+= Reducing Idle Services with systemd
+1 February 2026
+:description: Minimize running services in Linux with systemd-socket-proxyd
+:sectanchors:
+:toc:
+
+In this journal, we will take a look on how to minimize idle services
+running in the background using
+https://man.archlinux.org/man/systemd-socket-proxyd.8[systemd-socket-proxyd(8)^]
+for services that we cannot control their code
+and using
+https://man.archlinux.org/man/systemd.socket.5[systemd.socket(5)^]
+for services that we can changes their code.
+
+== Background
+
+Lets take a look running processes on my host machine
+(click on the image to enlarge):
+
+.Host processes with top sorted by VIRT column
+image:host_processes.png[Host process,width=100%]
+
+At the top, we have a process with 261.3 gigabytes virtual memory address.
+This service rarely used, only once a day or less.
+
+Below that, we have HTTP services that I manage for local development using
+systemd user services.
+For example, this website has their own local domain `kilabit.local` that
+run on specific port.
+
+My goal is to reduce the running processes and activate it only when I
+opened the port or domain in the browser or when some other services connect
+to the port directly.
+
+As for measurement, we count number of tasks using `ps`, memory using
+`free`, and boot time using `systemd-analyze` after booting and login.
+Here is the initial value,
+
+----
+$ ps -e | wc -l
+198
+
+$ free -m
+ total used free shared buff/cache available
+Mem: 15620 3562 10370 142 2132 12058
+Swap: 4095 0 4095
+
+$ systemd-analyze
+Startup finished in 6.868s (firmware) + 2.737s (loader) + 934ms (kernel) + \
+ 1.693s (initrd) + 6.198s (userspace) = 18.432s
+graphical.target reached after 6.193s in userspace.
+
+$
+----
+
+Lets start with external services first.
+
+== Using systemd-socket-proxyd
+
+For external services where I cannot changes their code, I use
+systemd-socket-proxyd.
+
+[quote, systemd-socket-proxyd, https://man.archlinux.org/man/systemd-socket-proxyd.8]
+systemd-socket-proxyd is a generic socket-activated network socket
+forwarder proxy daemon for IPv4, IPv6 and UNIX stream sockets.
+It may be used to bi-directionally forward traffic from a local listening
+socket to a local or remote destination socket.
+
+For example, let say I have service `X` that listen on port 8080.
+We will let the systemd.socket listen to that port and run the actual
+service on port 18080.
+Any other services still connect to port 8080 to communicate to service `X`
+through systemd-socket-proxyd.
+
+First, we create the `/etc/systemd/system/proxy-X.socket` that will listen
+on port 8080,
+
+----
+[Socket]
+ListenStream=127.0.0.1:8080
+
+[Install]
+WantedBy=sockets.target
+----
+
+Next, we create the `/etc/systemd/system/proxy-X.service` that will run
+`systemd-socket-proxyd` and channel the traffic to port 18080,
+
+----
+[Unit]
+Requires=X.service
+Requires=proxy-X.socket
+After=X.service
+After=proxy-X.socket
+
+[Service]
+Type=notify
+ExecStartPre=/bin/sleep 3
+ExecStart=/usr/lib/systemd/systemd-socket-proxyd --exit-idle-time=5m \
+ 127.0.0.1:18080
+ExecStopPost=systemctl stop X.service
+----
+
+Note that, in this case, we add the `ExecStartPre=` to wait for `X.service`
+run successfully, and the `ExecStopPost=` to stop the service.
+Both of these may or may not needed, depends on the service.
+
+In the `X.service`, we override the service unit file to run on port 18080
+instead of default port,
+
+----
+# /etc/systemd/system/X.service.d/override.conf
+[Unit]
+StopWhenUnneeded=true
+
+[Service]
+ExecStart=
+ExecStart=/usr/bin/X --Port 18080
+----
+
+With `--exit-idle-time=5m` and `StopWhenUnneeded=true`, we tell the systemd
+to stop the service when no traffic received after 5 minutes.
+
+Now stop the actual service and start the `proxy-X.socket` service,
+
+----
+$ sudo systemctl disable --now X.service
+$ sudo systemctl enable --now proxy-X.socket
+$
+----
+
+We test it by opening the service port `:8080` in the browser.
+Here is sample log in the journal,
+
+----
+Feb 01 18:20:11 systemd[1]: Started X Daemon.
+Feb 01 18:20:11 systemd[1]: Starting proxy-X.service...
+<TRUNCATED>
+Feb 01 18:20:14 X[8144]: Now listening on: http://127.0.0.1:8080
+Feb 01 18:20:14 systemd[1]: Started proxy-X.service.
+----
+
+After 5 minutes we will the service stopped automatically,
+
+----
+Feb 01 18:25:34 systemd[1]: proxy-X.service: Deactivated successfully.
+Feb 01 18:25:34 systemd[1]: Stopping X Daemon...
+Feb 01 18:25:34 X[8144]: Application is shutting down...
+Feb 01 18:25:34 X[8144]: 02-01 18:25:34 Info X stopped
+Feb 01 18:25:34 systemd[1]: X.service: Deactivated successfully.
+Feb 01 18:25:34 systemd[1]: Stopped X Daemon.
+Feb 01 18:25:34 systemd[1]: X.service: Consumed 4.010s CPU time over 5min 23.145s wall clock time, 163.1M memory peak.
+----
+
+Great.
+We can repeat the same configurations to other services.
+
+
+== Using systemd.socket
+
+Now, the hard part is to rewrite all of the programs so it can be activated
+using
+https://man.archlinux.org/man/systemd.socket.5[systemd.socket(5)^].
+
+All of the program that we want to convert are built with Go.
+In the
+link:/journal/2026/systemd_socket_with_go/[previous journal],
+I have describe how to modify the Go program so it can be activate using
+systemd.socket(5).
+If you have a program that need to be converted too, but not written using
+Go, you can read the following blog first to understand the basic,
+
+- https://0pointer.de/blog/projects/socket-activation.html[systemd for Developers I^]
+- https://0pointer.de/blog/projects/socket-activation2.html[systemd for Developers II^]
+
+We will modify each of the main function in the program.
+There are at least 11 of them.
+Fortunately, most of them are using the same library/package.
+
+- local.awwan.service => link:/project/ciigo/[ciigo] based.
+ This serve the local version of https://awwan.org .
+
+- local.golang-id.service => link:/project/ciigo/[ciigo] based. This serve
+ the local version of https://golang-id.org .
+
+- local.golang-id-tour.service => net/http based.
+ This serve the local version of https://tour.golang-id.org .
+
+- local.home.bahasa-go.service => link:/project/ciigo/[ciigo] based.
+
+- local.home.gitdoc.service =>
+ https://git.sr.ht/~shulhan/pakakeh.go/tree/main/item/cmd/httpdfs[httpdfs]
+ => lib/http based. This serve the
+ https://git.kernel.org/pub/scm/git/git.git/tree/Documentation[Git documentation]
+ directory
+
+- local.home.go.service => net/http based.
+ This run golang.org/x/website for offline documentation (the
+ same content as https://go.dev ).
+ You can view the patch
+ https://git.sr.ht/~shulhan/go-x-website/commit/1490b977eefea3c9cb1b35aaf211cd6934299383[here^]
+ (subject to changes in the future).
+
+- local.home.godoc.service => net/http based.
+ This run golang.org/x/pkgsite with local modules.
+ You can see the patch
+ https://git.sr.ht/~shulhan/go-x-pkgsite/commit/a2088f34de993bb4a4bd24dd6d722605373e5d08[here^]
+ (subject to changes in the future).
+
+- local.home.gorankusu.service => lib/http based.
+
+- local.home.ops.service => lib/http based
+
+- local.kilabit.service => link:/project/ciigo/[ciigo] based.
+
+- local.kilabit.umum.service =>
+ https://git.sr.ht/~shulhan/pakakeh.go/tree/main/item/cmd/httpdfs[httpdfs]
+ => lib/http based.
+
+Most of the modification are adding the following identical code,
+
+----
+...
+ listeners, err := systemd.Listeners(true)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if len(listeners) > 1 {
+ log.Fatal(`too many listeners received for activation`)
+ }
+ var listener net.Listener
+ if len(listeners) == 1 {
+ listener = listeners[0]
+ gotAddr := listener.Addr().String()
+ if gotAddr != serveOpts.Address {
+ log.Fatalf(`invalid Listener address, got %s, want %s`,
+ gotAddr, serveOpts.Address)
+ }
+ }
+ // Pass the listener to HTTP Server.
+...
+----
+
+
+== Results
+
+Let's reboot the machine and see the results.
+
+----
+$ diff -u before after
+--- before 2026-02-02 23:52:11.677244743 +0700
++++ after 2026-02-02 23:52:25.315331792 +0700
+@@ -1,12 +1,12 @@
+ $ ps -e | wc -l
+-198
++182
+
+ $ free -m
+ total used free shared buff/cache available
+-Mem: 15620 3562 10370 142 2132 12058
++Mem: 15620 2898 12491 102 601 12722
+ Swap: 4095 0 4095
+
+ $ systemd-analyze
+-Startup finished in 6.868s (firmware) + 2.737s (loader) + 934ms (kernel) + \
+- 1.693s (initrd) + 6.198s (userspace) = 18.432s
+-graphical.target reached after 6.193s in userspace.
++Startup finished in 6.804s (firmware) + 1.438s (loader) + 985ms (kernel) + \
++ 1.508s (initrd) + 5.985s (userspace) = 16.722s
++graphical.target reached after 5.983s in userspace.
+----
+
+The number of processes started on boot reduced by 16,
+the total memory used reduced by 664 MB (-18%), and
+the boot time reduced by 1.71 second (-9%).
+Well, it is not so bad. The big win is in the reduced memory usage and
+learning how to use `systemd.socket` and `systemd-socket-proxyd`.