plantpulse-server (Web Console + IIoT Engine)
Role
The core module that serves as the face and brain of the Platform. The web console that operators view, the REST API that external systems call, and the IIoT engine that actually processes industrial data all run within this single module.
| Item | Value |
|---|---|
| Module name | plantpulse-server |
| Container | plantpulse-server-web (own image, own container) |
| Path in container | /opt/kopens/plantpulse-platform/plantpulse-server/ |
| User access | 80 / 443 — plantpulse-proxy receives and forwards to this container |
| Admin console | 7443 (HTTPS, published by plantpulse-datalake) |
| Host port | None — runs only behind a proxy |
| Memory limit | DOCKER_SERVER_MEMORY default 16g / heap DOCKER_SERVER_HEAP default 12g |
| Runtime | Apache Tomcat 9 + Java 21 |
| Throughput | 40,000 messages per second (36-thread pipeline) |
User traffic always goes through plantpulse-proxy (80/443). This means even if the backend moves or the name changes, you don't need to touch user or equipment settings. Container state is viewed via bin/status.sh, and logs via bin/logs.sh plantpulse-server-web.
Responsibility area
Directory structure
plantpulse-server/
├── config/ # 서버 관리 설정 (외부화 — 배포해도 유지)
│ ├── plantpulse-engine.properties # IIoT 엔진 튜닝
│ ├── plantpulse-storage.properties # DB 연결
│ ├── plantpulse-mq.properties # 메시징 연결
│ ├── plantpulse-mail.properties # SMTP
│ ├── plantpulse-ai.properties # AI 게이트웨이
│ └── log4j2.xml # 로깅 설정 (자가 시딩 — 최초 기동 시 생성)
├── bin/
│ ├── start.sh
│ ├── stop.sh
│ └── log-viewer.sh
├── logs/
│ └── system.log
├── path/ # 경로 매핑
└── server/ # 내장 Tomcat
├── conf/
│ ├── server.xml # 커넥터 / 포트
│ ├── context.xml # 컨텍스트 / 데이터소스
│ ├── catalina.properties
│ ├── logging.properties
│ ├── tomcat-users.xml
│ ├── web.xml
│ └── Catalina/localhost/
│ └── ROOT.xml # 컨텍스트 설정 (sessionCookieName 등)
└── webapps/
├── ROOT.war # 웹 애플리케이션 (WAR 통배포)
└── ROOT/ # Tomcat 이 unpack 한 디렉토리 (수정 금지)
Deployment method: The web application is deployed via
server/webapps/ROOT.warreplacement method (full WAR deployment, transition in progress). The legacyapp/plantpulse-server-webexploded directory method is deprecated, and ContextdocBaseoverride inserver.xmlhas been removed. Direct modification of files insideROOT/will be lost on the next deployment, so configuration changes must always be made inconfig/.
Key configuration files
Configuration files are located in the module's config/ directory, and the server searches in the order: -Dpp.conf.dir → ${catalina.base}/../config → classpath (fallback). When external configuration is loaded, Properties loaded from external conf: is printed in the startup log.
application.properties has been removed (2026.06). Console operation settings such as theme and homepage are now managed in the console's System > Configuration Management (
mm_configtable), andalarm.duplicate.check.minuteshas been migrated toplantpulse-engine.properties.
plantpulse-engine.properties
IIoT engine pipeline tuning.
| Item | Default | Description |
|---|---|---|
engine.pipeline.threads | 36 | Number of parallel processing threads |
engine.pipeline.ratelimit | 40000 | Maximum messages per second |
engine.pipeline.batch.size | 1000 | Batch size |
engine.pipeline.queue.size | 1200000 | Queue capacity |
engine.dedup.enabled | true | Deduplication enabled |
plantpulse-storage.properties
Data storage connection.
# Cassandra
cassandra.host=${PP_CASSANDRA_HOST}
cassandra.port=${PP_CASSANDRA_PORT}
cassandra.keyspace=${PP_KEYSPACE}
cassandra.user=${PP_CASSANDRA_USER}
cassandra.password=${PP_CASSANDRA_PASSWORD}
# PostgreSQL (Spring DataSource)
postgres.url=jdbc:postgresql://${PP_POSTGRES_HOST}:${PP_POSTGRES_PORT}/${PP_DB_NAME}
postgres.user=${PP_PG_USER}
postgres.password=${PP_PG_PASSWORD}
# Valkey
redis.host=${PP_REDIS_HOST}
redis.port=${PP_REDIS_PORT}
redis.password=${PP_REDIS_PASSWORD}
This application does not render configuration. After splitting into containers, the web server starts without the data lake's configuration renderer and reads values from three places — ① external file placed by operators → ② defaults built into the WAR → ③ environment variables passed by compose. Environment variables override built-in defaults.
Therefore,
compose/docker-compose.ymlis the sole source of truth for what the app sees. It only exists on the data lake side, and names that are only in the data lake but not in compose never reach this app — this was the kind of accident where a site changed identifiers only on the data lake but five app instances kept using the old names (2026-08-29).Do not modify files inside the web application (
server/webapps/ROOT/) — they will be removed during deployment.
server/conf/server.xml
Tomcat connector configuration. Port changes / TLS / compression / thread pool.
<Connector port="80" protocol="HTTP/1.1"
connectionTimeout="20000"
maxThreads="500"
acceptCount="200"
URIEncoding="UTF-8"
compression="on"
compressibleMimeType="text/html,text/css,application/json,application/javascript" />
<Connector port="443" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
SSLEnabled="true"
maxThreads="500"
sslEnabledProtocols="TLSv1.3,TLSv1.2">
<SSLHostConfig>
<Certificate certificateKeystoreFile="/var/security/plantpulse/server.jks"
certificateKeystorePassword="${PP_TLS_KEYSTORE_PASSWORD}" />
</SSLHostConfig>
</Connector>
Operational commands
From host — container level
Bringing a container up is equivalent to restarting this module. Infrastructure and other applications are not affected.
cd /opt/kopens/plantpulse-platform-docker
# 서버만 재시작
docker compose -f compose/docker-compose.yml restart plantpulse-server-web
# 상태 · 로그
bin/status.sh
bin/logs.sh plantpulse-server-web -n 200
Entering the container
cd /opt/kopens/plantpulse-platform-docker/bin
./shell.sh plantpulse-server-web
restart-server.sh from the data lakeThat script is a thin delegation that calls the module's launcher within each app's own container. The data lake container has no web server executable, so it ends with an "module not found" error. Use the container-level restart above.
Hot reload (reflect settings without restart)
Some settings can be hot-reloaded from the console's System > Configuration Management menu. Property files in config/ and boot-time settings such as server.xml require a restart.
Logs
| Log | Path | Contents |
|---|---|---|
| Main log | logs/system.log | Application logic, errors |
| Tomcat catalina | server/logs/catalina.out | Tomcat standard output |
| Access log | server/logs/localhost_access_log.YYYY-MM-DD.txt | HTTP request log |
| Engine log | logs/engine.log | IIoT engine processing details |
| Slow query | logs/slow.log | Queries taking 200ms or longer |
Log level change — logging configuration (log4j2.xml) is externalized to module config/ (self-seeding: on first startup, WAR defaults are copied to config/log4j2.xml, and Logging reconfigured from external conf: is printed in the startup log when applied):
# 웹 서버 컨테이너 안에서
cd /opt/kopens/plantpulse-platform-docker/bin
./shell.sh plantpulse-server-web
vi /opt/kopens/plantpulse-platform/plantpulse-server/config/log4j2.xml
# <Logger name="plantpulse" level="DEBUG"/> 등 수정 후 exit
# 호스트에서 그 컨테이너만 재시작
cd /opt/kopens/plantpulse-platform-docker
docker compose -f compose/docker-compose.yml restart plantpulse-server-web
Since it is an external file, it persists even across WAR redeployments. Do not modify the web app internal (
server/webapps/ROOT/WEB-INF/classes/log4j2.xml) — it will be removed during deployment.
Performance tuning
JVM heap
# server/bin/setenv.sh (없으면 생성)
export CATALINA_OPTS="-Xms16g -Xmx32g \
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/plantpulse/heap/ \
-Xlog:gc*:file=/var/log/plantpulse/gc.log:time,uptime:filecount=10,filesize=100M"
Tomcat thread pool
Adjust maxThreads / acceptCount in the Connector of server.xml. For high-concurrency environments, the following is recommended:
| Concurrent connections | maxThreads | acceptCount |
|---|---|---|
| ~ 100 | 200 | 100 |
| ~ 500 | 500 | 200 |
| 500+ | 1000 | 500 |
IIoT engine throughput
Set engine.pipeline.threads in plantpulse-engine.properties to 70–80% of host core count. If messages are backing up, also increase engine.pipeline.queue.capacity.
For detailed tuning, see the Performance tuning page.
Health check
# 표준 헬스체크 (익명, readiness — 엔진이 전 단계 기동을 완료(RUNNING)했을 때만 UP. 배포 자동검증 / 모니터링용)
curl -fsS http://127.0.0.1/api/health
# 준비 완료: 200 {"status":"UP","service":"plantpulse-server-web","ts":1765500000000,"checks":{"engine":"RUNNING"}}
# 기동 중: 503 {"status":"STARTING",...} / 기동 실패·중지: 503 {"status":"DEGRADED",...}
# 콘솔 ping
curl -fsS http://127.0.0.1/api/v5/ping
# 인증 필요 헬스체크
curl -fsS -u admin:admin123! http://127.0.0.1/api/v5/health
# WebSocket 핸드셰이크 확인
curl -i -N \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==" \
-H "Sec-WebSocket-Version: 13" \
http://127.0.0.1:8000/ws
Common issues
| Symptom | Cause | Action |
|---|---|---|
OutOfMemoryError | Heap shortage | Increase -Xmx in setenv.sh + analyze GC logs |
Too many open files | File descriptor limit | Set nofile in /etc/security/limits.conf to 65535+ |
| Console slow response | Slow DB queries | Check PostgreSQL slow query log, Valkey cache hit rate |
| Login loop | Session / cookie issue | Clear browser cache, check sessionCookieName in server/conf/Catalina/localhost/ROOT.xml |
| 502 / 504 (reverse proxy) | Backend response timeout | Increase nginx proxy_read_timeout |
| WebSocket disconnection | Firewall idle timeout | Firewall timeout > 60s, adjust tomcat.websocket.session.timeout |
For detailed diagnostics, see the Troubleshooting page.
Security checklist
- Change default admin password (
admin / admin123!) - Remove or use strong password for manager account in
tomcat-users.xml - Block external access to
shutdown port (7000)inserver.xml - Apply HTTPS certificate (
prepare-ssl.shresult or external CA) - Verify X-Frame-Options / CSP / HSTS headers are set
- Store API Bearer Token in operations vault
- Disable debug options and restrict permissions (
chmod 600) forconfig/property file
For detailed security configuration, see the Security configuration page.