pd — DataLake CLI
pd(PlantPulse DataLake) is a single entry-point command that starts, stops, and diagnoses eight services inside the DataLake container, renders configuration, and creates backups. Nearly everything shown in the management console is the output of this command's --json.
| Item | Value |
|---|---|
| Module | plantpulse-datalake-cli |
| Location | /opt/kopens/plantpulse-platform/plantpulse-datalake-cli/bin/pd inside the container (it's in PATH) |
| On the host | Not present — always use docker exec plantpulse-datalake pd … |
docker exec plantpulse-datalake pd status # 하나만 칠 때
/opt/kopens/plantpulse-platform-docker/bin/shell.sh # 여러 개 칠 때 — 셸을 열고 pd … 만 친다
Starting, stopping, and restarting the entire platform is done from the host's /opt/kopens/plantpulse-platform-docker/bin/ — which are up.sh, down.sh, restart.sh, restart-datalake.sh, and status.sh. pd works one level below that, dealing with services inside the container. Since the container itself runs pd start when it starts and pd stop when it stops, the operator rarely needs to run pd start manually. Most of the time, it's about "viewing status" and "restarting one service."
30-Second Summary
Safety rating: read = changes nothing, modify = changes state, destructive = deletes data or processes.
| Verb | One-liner | Rating |
|---|---|---|
pd status [서비스] [--wait] | Port table. Exits 0 only when all rows are RUNNING | Read |
pd doctor | Inspect environment, secrets, templates, runtime tree (PASS / FAIL / N/A) | Read |
pd env | This node's directory, image identity — declarative only, no measurement | Read |
pd version | Version number read from what's installed — from the jar MANIFEST, not this file's constant | Read |
pd logs [--list] [--lines N] [서비스…] | Tail service logs | Read |
pd storage | Volumes, WAL, replication slots, storage size, Kafka retention | Read |
pd retention | Table TTL, topic retention, cold tier, archive jobs | Read |
pd flow | Kafka consumer group lag | Read |
pd downtime | Sections that were dead outside the plan — reaps them only | Read |
pd config list · diff · diff --templates | Template → path table, runtime vs render, host templates vs image defaults | Read |
pd config show <대상> | Rendered files that a service actually reads. Credentials are masked | Read |
pd workflow status · list · history | Two workflow engines (Temporal, Kestra) — the engines and their pollers, held schedules, recent runs and failure reasons. Read-only | Read |
pd node status · info · cql · psql … | Aster TSDB / PostgreSQL queries and shell | Read |
pd backup list · schedule · status | Physical backup sets, schedule table, what's currently running | Read |
pd backup restore … --dry-run | Restore plan only | Read |
pd start [서비스] · stop · restart [--clean] | Start in order, stop in reverse order (with proof of stop), or both | Modify |
pd config render | Actually writes configuration artifacts | Modify |
pd secret rotate VAR=값 | Credential rotation — called from the host's passwd.sh. Don't type it directly | Modify |
pd secret probe-mqtt | With given credentials, actually connects to the MQTT broker (validation used by the rotation procedure) | Read |
pd backup · pd backup run --engine E --type full|diff | Logical dump, one-time physical backup | Modify |
pd backup schedule set|reset | Backup job's Quartz cron and on/off switch | Modify |
pd node add · rf · repair · compact · flush · drain … | Aster TSDB maintenance — when adding or removing nodes, follow the pd node rf order | Modify |
pd clean | Delete logs and temp files of a stopped module | Destructive (logs) |
pd kill [--dry-run] | SIGKILL all processes under PP_HOME | Destructive |
pd recover [파일] | DROP the pp DB and restore from logical dump | Destructive |
pd backup restore … --yes | Restore data directory from a physical backup | Destructive |
pd node cleanup · remove <host-id> | Delete snapshot, remove node from ring | Destructive |
The option set has the same meaning for any verb.
| Option | Meaning |
|---|---|
--json | One JSON document instead of a human-readable table. This is the format the console reads. It's accepted by specific verbs; attaching it to others is rejected with exit 2 |
--show-secrets | config diff only. Does not mask passwords with ***. Cannot be used with --json |
PD_DEBUG=1 | DEBUG logs. Prepend like PD_DEBUG=1 pd start |
Services and Startup Order
The eight services start in a fixed order. The authoritative version is a single services/order.txt. pd start runs top to bottom, pd stop runs in reverse.
| Order | Service | MASTER | WORKER | Components Inside | pd status row (ports) |
|---|---|---|---|---|---|
| 1 | storage | ○ | ○ | valkey → postgres → cassandra → rustfs (MASTER only) | 6379 · 5432 · 9042 · 9000 |
| 2 | analytics | ○ | ○ | spark-master → hive → gravitino → kyuubi (WORKER has spark-worker, kyuubi) | 7077 · 4440 · 9083 · 19001 · 10000 |
| 3 | messaging | ○ | — | kafka · mqtt (HiveMQ) | 9092 · 1883 |
| 4 | timeseries | ○ | — | engine (TSE) · dashboard (PlantPulse Dashboard) | 7800 · 3000 |
| 5 | cep | ○ | — | Tomcat | 7400 |
| 6 | workflow | ○ | — | temporal → kestra | 7233 · 8233 · 8380 |
| 7 | data-gateway | ○ | — | Tomcat | 5500 |
| 8 | admin-api | ○ | ○ | Management console backend | 4949 |
- After each service starts, wait until the health check returns UP before moving on (caps: storage 1800s, analytics 600s, rest 300s).
storageandanalyticsare gatekeepers. If they don't start, it doesn't attempt the rest and exits 5. The others warn and continue (exiting 6 at the end).timeseriesis MASTER-only starting from 2026-09-05.
Lifecycle
pd status # 전체 표 — 마지막 줄 "0 STOPPED" 면 정상
pd status storage # 서비스 하나의 헬스체크
pd status storage --wait # UP 이 될 때까지 대기
pd start cep # 죽은 서비스 하나 다시 띄우기
pd stop cep # 서비스 하나 내리기 — «멈췄다» 를 증명한다 (30초 + SIGTERM 15초 + SIGKILL 10초)
pd restart cep # stop → (정지가 증명되면) 5초 → start
pd restart cep --clean # 사이에 pd clean
pd restart # 전부 — 몇 분 걸린다. storage 가 먼저 돌아온다
There are four states for pd status.
| State | Meaning |
|---|---|
RUNNING | Port is open |
STOPPED | Port is closed. The first few tens of seconds after startup are a window, so check again after 1 minute |
UNKNOWN | Couldn't measure — when the tool to check ports is absent. Not the same as dead |
DISABLED | Stopped with PD_OPTIONS. Exit code is not changed |
pd stop exits 7, don't immediately run pd startSTILL RUNNING means it failed to prove the stop. If you start on top of a surviving process, you'll get port conflicts and data corruption. Use pd kill --dry-run to see what's left, then pd kill, check with pd status that it's empty, and then pd start. Don't use FORCE=1 pd start.
The container's own Docker HEALTHCHECK doesn't call pd status (it's slow and occasionally flaky). Instead, it checks whether postgres and cassandra answer real queries and whether all ports are open. So "docker ps is healthy but pd status is STOPPED" means a startup window or a transient miss; "unhealthy but pd status is all RUNNING" means ports are open but queries go unanswered (pd logs storage).
Diagnosis
pd doctor # 여섯 절 검사. 마지막 줄 FAIL 0 이면 된다
pd env # 디렉터리 · 이미지 정체 (0.25초)
pd logs --list # 따라갈 파일 목록만
pd logs --lines 50 cep # cep 만, 마지막 50줄부터
pd storage # 볼륨 90% 이상이면 FAIL
pd retention # 왜 안 줄어드나 — TTL · 토픽 보존 · 콜드 티어
pd flow # 데이터가 안 들어온다 — 컨슈머 lag
pd downtime # 자꾸 죽는 것 같다 — 계획 밖 정지 기록
Six sections of pd doctor: [1] inputs (secrets, node files, images), [2] tools, [3] config templates (do all render), [4] runtime tree, [5] TLS material, [6] core ports. N/A is "can't measure here," with the reason in parentheses. It's normal for there to be no sidecar file inside the container — values come in as environment variables.
pd flow's NA is not "lagging," it's "has never been read." If the MEMB column is 0, the consumer isn't running; if it's greater than 0 and the topic is empty, that's normal (nothing to read); if it's greater than 0 and the topic has data, that's a real warning signal.
Configuration
pd config list # 이 모드의 템플릿 → 경로 표
pd config diff # 런타임 파일 vs 지금 렌더하면 나올 것 (0 같음 / 1 다름 / 3 시크릿 없음 / 4 렌더 실패)
pd config diff --templates # 호스트 템플릿 vs 이미지 기본값 (same / differs / local / missing)
pd config render # 실제로 쓴다 — 그 뒤 pd restart <서비스> 까지가 한 세트
Procedures for changing values and "where to fix it" are in Changing Configuration. Don't directly modify artifacts inside the container — they'll disappear at the next pd start.
Aster TSDB · PostgreSQL Node Operations
pd node status # nodetool status — UN 이 정상, DN 이면 죽은 노드
pd node status --json # + PostgreSQL 복제 · Valkey 복제 · 워커 명부
pd node info # 노드 상세
pd node cql # cqlsh (cassandra 계정)
pd node psql # psql (postgres OS 사용자)
pd node errors # cassandra debug.log 의 최근 WARN/ERROR
pd node topic # kafka 토픽 "event" describe
pd node tpstats | compactionstats | proxyhistograms | table-stats [ks] | table-histograms <ks> <tbl> | sstable-size <ks> <tbl> | disk
| Verb that changes | Rating | One-liner |
|---|---|---|
pd node add | Modify | Increase keyspace RF to match the number of alive nodes (max 3) and repair. Do this on the master after attaching a worker |
pd node repair · repair-table <ks> <tbl> | Modify (heavy) | Fix data inconsistencies between nodes |
pd node flush · drain | Modify | Flush in-memory writes to disk. drain rejects further writes afterward, so only use it right before stopping |
pd node compact [ks] [tbl] | Modify (heavy) | Compact SSTables |
pd node cleanup | Destructive | Delete all snapshots plus data that this node no longer owns |
pd node remove <host-id> | Destructive | Remove a dead node from the ring. Do not use on a live node. Lower RF first — see below |
pd node rf [--check] [<노드수>] | Modify | Reassess the ring and set the replication factor (RF) for all keyspaces to match that count |
pd node upgrade · init-cms · train-zstd · cache-clear | Modify | Installation procedure, version upgrade, clear OS page cache |
pd node rf — Align Replication Factor to the Ring
When adding or removing nodes, don't manually fix the RF. This single verb reassesses the ring, calculates the target RF, and applies it to all keyspaces. The call site is fixed for each direction.
| When | Order |
|---|---|
| Adding | pd node add → pd node rf → nodetool repair |
| Removing | pd node rf <빠진 뒤의 노드수> → node leaves → nodetool cleanup |
| Dead node | pd node rf → pd node remove <host-id> |
The broken state is RF > node count. Lowering it first guarantees you never enter that state.
If you do it in the opposite order, you create a window where RF 3 sits on a 2-node ring, and if one more node is lost during that window, QUORUM reads and writes all fail. This includes system_auth — so no one can even log in to see what happened.
nodetool decommission·removenode stream out the leaving node's ranges based on the current RF. If you lower it first, there's less data to stream, and the remaining nodes will own only what the new RF dictates.
That's why <노드수> has that argument — when reducing RF, the departing node is still in the ring, so the target is "the count after that node leaves." If the node is already dead and outside the ring, you can call remove without the argument.
A temporarily off node won't decrease the RF. If there are 3 members but only 2 are alive, it prints a warning but keeps RF at 3 — because when the node returns, it should be back to normal.
If there are fewer than 3 nodes, the RF matches (1 node → RF 1). When a third node attaches and you run pd node rf again, it goes back to 3. An RF larger than the node count causes all QUORUM queries to fail, so we don't leave it that way.
--check only reapspd node rf --check changes nothing — it just checks whether the current RF matches the ring.
If it can't measure, it ends with "can't judge" instead of "matches" (exit code 2) — if it can't read the datacenter name or count nodes, it will not guess. Because if it picks the wrong name, replication will silently disappear.
If a keyspace uses a replication strategy that pd doesn't know, it prints the name and leaves it alone — it doesn't silently skip it.
There's also a reason it's repair when adding and cleanup after reducing. New replicas start empty and need to be filled (repair), and after reducing, you need to discard ranges that are no longer owned (cleanup).
Backup and Restore
pd backup # PostgreSQL 논리 덤프 → /data1/pp-data/postgres/dump/
pd backup list | schedule | status
pd backup run --engine postgres --type diff
pd backup restore --engine postgres --set <세트> --dry-run # 계획 먼저
pd recover [덤프파일] # pp DB 하나를 논리 덤프로 되돌림 (파괴적)
Procedures are in Backup and Restore.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success. status all RUNNING, doctor FAIL 0, config diff no differences |
| 1 | General failure, "different," "something's wrong" |
| 2 | Usage error — unknown verb, service, or option, --json rejected, PP_HOME not specified |
| 3 | Missing required secret — the message includes all names. In backup run and restore, it means "another backup is running" |
| 4 | Render failure — unsubstituted ${PP_*}, all names listed |
| 5 | Gatekeeper service (storage · analytics) didn't start, so startup aborted |
| 6 | Startup finished but some services aren't ready |
| 7 | Failed to prove stop (STILL RUNNING) |
| 8 | Advertised address is loopback (127.0.0.1) — render rejected |
Log Format and Journal
Every line written by pd has a fixed shape — [시각] [DATALAKE-CLI] [레벨] [동사] 메시지. INFO goes to stdout, the rest to stderr.
pd start · stop · restart · backup leave one JSON line each in the event journal plantpulse-datalake-admin-api/logs/pd-events.jsonl, and the console shows them as an event timeline. Commands executed from the console are recorded as actor by operator:<name>.
PD_OPTIONS — Selectively Turn Off Components
The variable exists, but the current compose stack doesn't pass this value to the container. So, configuring components to be selectively turned off isn't supported right now → FAQ
Server, batch, warehouse, OPC-UA, and AASX modules run in their own containers. The DataLake container doesn't include those modules. Restart the app from the host.
cd /opt/kopens/plantpulse-platform-docker/bin
./restart-server.sh # 또는 restart-batch.sh · restart-warehouse.sh · restart-one.sh <서비스>
Related Documents
- Start · Stop · Restart — the relationship between host verbs and
pd - Logs and Health
- Changing Configuration