Skip to main content

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.

ItemValue
Moduleplantpulse-datalake-cli
Location/opt/kopens/plantpulse-platform/plantpulse-datalake-cli/bin/pd inside the container (it's in PATH)
On the hostNot present — always use docker exec plantpulse-datalake pd …
docker exec plantpulse-datalake pd status # 하나만 칠 때
/opt/kopens/plantpulse-platform-docker/bin/shell.sh # 여러 개 칠 때 — 셸을 열고 pd … 만 친다
The operator's first entry point is not here

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.

VerbOne-linerRating
pd status [서비스] [--wait]Port table. Exits 0 only when all rows are RUNNINGRead
pd doctorInspect environment, secrets, templates, runtime tree (PASS / FAIL / N/A)Read
pd envThis node's directory, image identity — declarative only, no measurementRead
pd versionVersion number read from what's installed — from the jar MANIFEST, not this file's constantRead
pd logs [--list] [--lines N] [서비스…]Tail service logsRead
pd storageVolumes, WAL, replication slots, storage size, Kafka retentionRead
pd retentionTable TTL, topic retention, cold tier, archive jobsRead
pd flowKafka consumer group lagRead
pd downtimeSections that were dead outside the plan — reaps them onlyRead
pd config list · diff · diff --templatesTemplate → path table, runtime vs render, host templates vs image defaultsRead
pd config show <대상>Rendered files that a service actually reads. Credentials are maskedRead
pd workflow status · list · historyTwo workflow engines (Temporal, Kestra) — the engines and their pollers, held schedules, recent runs and failure reasons. Read-onlyRead
pd node status · info · cql · psql …Aster TSDB / PostgreSQL queries and shellRead
pd backup list · schedule · statusPhysical backup sets, schedule table, what's currently runningRead
pd backup restore … --dry-runRestore plan onlyRead
pd start [서비스] · stop · restart [--clean]Start in order, stop in reverse order (with proof of stop), or bothModify
pd config renderActually writes configuration artifactsModify
pd secret rotate VAR=값Credential rotation — called from the host's passwd.sh. Don't type it directlyModify
pd secret probe-mqttWith given credentials, actually connects to the MQTT broker (validation used by the rotation procedure)Read
pd backup · pd backup run --engine E --type full|diffLogical dump, one-time physical backupModify
pd backup schedule set|resetBackup job's Quartz cron and on/off switchModify
pd node add · rf · repair · compact · flush · drain …Aster TSDB maintenance — when adding or removing nodes, follow the pd node rf orderModify
pd cleanDelete logs and temp files of a stopped moduleDestructive (logs)
pd kill [--dry-run]SIGKILL all processes under PP_HOMEDestructive
pd recover [파일]DROP the pp DB and restore from logical dumpDestructive
pd backup restore … --yesRestore data directory from a physical backupDestructive
pd node cleanup · remove <host-id>Delete snapshot, remove node from ringDestructive

The option set has the same meaning for any verb.

OptionMeaning
--jsonOne 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-secretsconfig diff only. Does not mask passwords with ***. Cannot be used with --json
PD_DEBUG=1DEBUG 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.

OrderServiceMASTERWORKERComponents Insidepd status row (ports)
1storage○○valkey → postgres → cassandra → rustfs (MASTER only)6379 · 5432 · 9042 · 9000
2analytics○○spark-master → hive → gravitino → kyuubi (WORKER has spark-worker, kyuubi)7077 · 4440 · 9083 · 19001 · 10000
3messaging○—kafka · mqtt (HiveMQ)9092 · 1883
4timeseries○—engine (TSE) · dashboard (PlantPulse Dashboard)7800 · 3000
5cep○—Tomcat7400
6workflow○—temporal → kestra7233 · 8233 · 8380
7data-gateway○—Tomcat5500
8admin-api○○Management console backend4949
  • After each service starts, wait until the health check returns UP before moving on (caps: storage 1800s, analytics 600s, rest 300s).
  • storage and analytics are 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).
  • timeseries is 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.

StateMeaning
RUNNINGPort is open
STOPPEDPort is closed. The first few tens of seconds after startup are a window, so check again after 1 minute
UNKNOWNCouldn't measure — when the tool to check ports is absent. Not the same as dead
DISABLEDStopped with PD_OPTIONS. Exit code is not changed
If pd stop exits 7, don't immediately run pd start

STILL 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 changesRatingOne-liner
pd node addModifyIncrease 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 · drainModifyFlush 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 cleanupDestructiveDelete all snapshots plus data that this node no longer owns
pd node remove <host-id>DestructiveRemove a dead node from the ring. Do not use on a live node. Lower RF first — see below
pd node rf [--check] [<노드수>]ModifyReassess the ring and set the replication factor (RF) for all keyspaces to match that count
pd node upgrade · init-cms · train-zstd · cache-clearModifyInstallation 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.

WhenOrder
Addingpd node add → pd node rf → nodetool repair
Removingpd node rf <빠진 뒤의 노드수> → node leaves → nodetool cleanup
Dead nodepd node rf → pd node remove <host-id>
Lower the RF first, then remove the node — if the order is reversed, you won't even be able to log in

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.

RF counts "members," not "live nodes"

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 reaps

pd 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​

CodeMeaning
0Success. status all RUNNING, doctor FAIL 0, config diff no differences
1General failure, "different," "something's wrong"
2Usage error — unknown verb, service, or option, --json rejected, PP_HOME not specified
3Missing required secret — the message includes all names. In backup run and restore, it means "another backup is running"
4Render failure — unsubstituted ${PP_*}, all names listed
5Gatekeeper service (storage · analytics) didn't start, so startup aborted
6Startup finished but some services aren't ready
7Failed to prove stop (STILL RUNNING)
8Advertised 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

Don't look for the app restart script inside the container

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 <서비스>