Companion code for:
Tanadechopon T., Kasemsontitum B. "Performance Evaluation of Programming Languages as API Services for Cloud Environments: A Comparative Study of PHP, Python, Node.js and Golang." 2023 7th International Conference on Information Technology (InCIT). ieeexplore.ieee.org/document/10413079
Five docker-compose stacks each exposing the same four endpoints (/login, /data, /upload, /calculate) against a shared MariaDB — so the same JMeter test plan can be pointed at any of them and the response-time / CPU-usage numbers compared head-to-head. The paper found Go the strongest scaler under 100 concurrent users.
api-lang-bench/ # github.com/ultramcu/api-lang-bench
├── apache-php/ # PHP 8 via Apache httpd
│ ├── Dockerfile, docker-compose.yml, README.md
│ ├── config/{httpd.conf, php.ini}
│ └── src/{api,cal,data,login,upload,...}.php + db.example.php
├── nginx-php/ # PHP 8 via Nginx + php-fpm (reuses apache-php/src/)
│ ├── Dockerfile, docker-compose.yml, README.md
│ └── nginx/conf/server.conf
├── node/ # Node.js 18 + Express + mysql
│ ├── Dockerfile, docker-compose.yml, README.md
│ ├── app.js, package.json
│ └── const.example.js # template — copy to const.js
├── py/ # Python 3.9 + Flask + PyMySQL
│ ├── Dockerfile, docker-compose.yml, README.md
│ ├── app.py, requirements.txt
│ └── constants.example.py # template — copy to constants.py
├── go/ # Go 1.18 + Echo v4 + go-sql-driver
│ ├── Dockerfile, docker-compose.yml, README.md
│ ├── main.go, main_test.go, go.{mod,sum}
│ ├── api/{login,query,upload}.go
│ ├── config/{Config,Version}.go
│ └── config.example.yml # template — copy to config.yml
├── README.md
├── LICENSE
└── CITATION.cff
Real credential files (py/constants.py, node/const.js, go/config.yml, apache-php/src/db.php) are git-ignored — copy each .example.* sibling and fill in your own DB host / user / password locally; the templates never leave the repo.
Requires Docker + docker-compose. The original paper ran each stack on a DigitalOcean droplet (2 vCPU, 4 GB RAM, Ubuntu 22.04) with MariaDB v10.5 on a second droplet in the same Singapore datacenter; the JMeter client ran on a MacBook Pro M1 / macOS 13.
# 1. Clone: git clone https://github.com/ultramcu/api-lang-bench.git cd api-lang-bench # 2. Provide your own DB credentials (one-time setup; ignored by git): cp py/constants.example.py py/constants.py cp node/const.example.js node/const.js cp go/config.example.yml go/config.yml cp apache-php/src/db.example.php apache-php/src/db.php # Edit each to point at your MariaDB host + user + password. # 3. Boot any stack (each is independent): cd apache-php && docker compose up --build # http://localhost:81 cd nginx-php && docker compose up --build # http://localhost:82 cd node && docker compose up --build # http://localhost:83 cd py && docker compose up --build # http://localhost:84 cd go && docker compose up --build # http://localhost:85
Every stack exposes the same four endpoints; the paper measures them under both 1- and 100-concurrent-user JMeter workloads (100 iterations each).
| Endpoint | Method | Purpose | Notes |
|---|---|---|---|
/login |
POST | MD5(password) + SELECT ... FROM tb_user WHERE username=? AND password=? |
login latency |
/data?n=<int> |
GET | SELECT * FROM tb_data LIMIT n and return the rows as JSON |
query + transmission; paper benchmarks n=100 (≈ 105 KB) and n=10000 (≈ 10.3 MB) |
/upload |
POST | Multipart file upload, write to local disk | file handling; paper benchmarks 1 KB and 500 KB |
/calculate |
GET | Leibniz series for π with terms = 1_000_000 |
pure-CPU work, no DB |
Per-stack details (port mappings, host paths, framework versions) live in each language's README.md.
A shared MariaDB with two tables (see paper Tables I and II):
CREATE TABLE tb_user ( id INT PRIMARY KEY, username VARCHAR(200), password MEDIUMTEXT -- MD5 hex (the /login endpoint hashes the request) ); CREATE TABLE tb_data ( id INT PRIMARY KEY, data_int INT, data_str MEDIUMTEXT, -- 1000-byte filler per row, populates the response payload data_date DATETIME );
The paper seeds tb_data with 12,000 rows so the n=10000 query has a substantial payload to materialise.
JMeter v5.6.2 with two thread groups per scenario:
| Concurrency | Threads | Ramp-up (s) | Loops |
|---|---|---|---|
| 1 | 1 | 1 | 100 |
| 100 | 100 | 1 | 100 |
Point the JMeter plan at one stack at a time (change the host/port between runs). CPU usage on the API host is captured separately with sar from the sysstat package.
Findings from the paper (single concurrent → 100 concurrent):
| Language | Single-concurrent response | 100-concurrent response | 100-concurrent CPU |
|---|---|---|---|
| Go | similar to Node.js (best) | fastest at every endpoint | high (~100% on /calculate) |
| Node.js | similar to Go | second fastest | ≈ ×ばつ Go's CPU on /calculate |
| NGINX + PHP | mid | second on /data?n=10000 |
mid |
| Apache + PHP | mid | mid | mid |
| Python (Flask) | slow on /calculate |
slowest under load | low |
The /upload endpoint at 100 concurrent users with a 50 KB file saturates the client's 100 mbit/s network switch (≈ 23 req/s ×ばつ 50 KB ×ばつ 8 ≈ 92 mbps) before any language difference shows up — that result is throughput-bound, not CPU-bound.
MIT © 2023 Tanadechopon & Kasemsontitum; cleanup © 2026 ultramcu.
See CITATION.cff for a machine-readable citation; in BibTeX:
@inproceedings{tanadechopon2023langperf, author = {Tanadechopon, Teerapong and Kasemsontitum, Boontariga}, title = {Performance Evaluation of Programming Languages as API Services for Cloud Environments: A Comparative Study of PHP, Python, Node.js and Golang}, booktitle = {2023 7th International Conference on Information Technology (InCIT)}, year = {2023}, doi = {10.1109/InCIT60207.2023.10413079}, url = {https://ieeexplore.ieee.org/document/10413079} }