업그레이드 노트
판을 올릴 때 연동 쪽이 함께 고쳐야 하는 것과 첫 기동에 일어나는 일을 판별로 적습니다. 올리는 절차(설치 원라이너 재실행)는 업그레이드에 있습니다. 최신 판이 위에 옵니다.
2026.11 — 올리기 전에 할 일과 바뀐 이름
지원 범위는 2026.11 이후 판 중 최근 3판입니다. 2026.10 이하에서는 업그레이드가 되지 않으니 재설치하세요(시도하면 업그레이드가 실패하고 자동으로 원래 판으로 되돌아갑니다). 업그레이드를 보세요. 아래 바뀐 이름과 연동 변경은 재설치할 때도 그대로 적용됩니다.
아래 순서대로 준비하세요. Cassandra 는 올리면 되돌릴 수 없고, 바뀐 이름에는 호환이 없습니다 — 옛 경로 · 옛 칸 · 옛 도구 이름은 업그레이드 순간 사라지므로 연동을 서버와 같이 올립니다. 현장 앱도 같은 판으로 함께 올립니다. 워크오더 수량 · 이름 변경은 다음 절에 있습니다.
1. 백업 — Cassandra r4 는 되돌릴 수 없습니다
이 판의 Cassandra 본체는 Aster TSDB v6.0.0-r4 입니다.
클러스터 메타데이터의 스냅샷 형식이 바뀌어, 옛 본체가 읽지 못합니다. 되돌릴 길을 남기려면 올리기 전에 해 두어야 합니다.
- PostgreSQL 전체 백업 —
./backup.sh --postgres full - Aster TSDB 전체 백업 —
./backup.sh --cassandra full(백업과 복구) - 노드마다 Cassandra 데이터 디렉터리의
system_cluster_metadata를 복사해 둡니다 - 그 뒤에 새 판을 올립니다
2. 마이그레이션 V121~ — 첫 기동이 오래 걸릴 수 있습니다
첫 기동에 Flyway 가 V121~V152 을 한 번에 적용합니다. 알람 표 정리(V121V124) · 다섯 층 설비 트리(V137V139) · 기록 표 키와 정비 이름(V140) · 외래 키(V127~V133) 등 모양을 크게 바꾸는 단계입니다. V123 은 mm_alarm 전체 행을 고치므로, 소요 시간은 알람 행 수에 따라 늘어납니다. 그동안 서버는 응답하지 않으며, 헬스체크가 먼저 unhealthy 를 낼 수 있습니다 — 컨테이너를 재시작하지 말고 로그의 Flyway migration completed. 를 기다리세요.
조건에 맞지 않는 데이터가 있으면 마이그레이션이 멈추고 서버가 뜨지 않습니다. 멈춘 단계의 변경은 되돌려지고 앞선 단계는 남습니다. 로그의 Platform version update failed 뒤에 나오는 문장대로 데이터를 고친 뒤 다시 기동하면 그 단계부터 이어집니다. 아래는 올리기 전 DB 에서 미리 볼 수 있습니다.
-- V121: 0 행이어야 합니다 — 숨겨진 채 켜져 있는 알람
SELECT alarm_seq FROM pp.mm_alarm WHERE shelved AND is_on;
-- V121: 0 이어야 합니다 — 숨겨진 알람에 확인 · 라벨이 붙은 행
SELECT count(*) FROM pp.mm_alarm
WHERE shelved AND (is_read IS TRUE OR read_user_id IS NOT NULL OR disposition IS NOT NULL
OR cause_note IS NOT NULL OR labeled_by IS NOT NULL OR labeled_at IS NOT NULL
OR failure_record_id IS NOT NULL);
-- V128: 0 이어야 합니다 — 고장 기록을 가리키지 못하는 알람 연결
SELECT count(*) FROM pp.mm_alarm a
WHERE a.failure_record_id IS NOT NULL
AND (a.failure_record_id !~ '^[0-9]{1,18}$'
OR NOT EXISTS (SELECT 1 FROM pp.mm_ram_failure f WHERE f.failure_id = a.failure_record_id::bigint));
-- V137: 1 행이어야 합니다 — 설치당 기업은 하나
SELECT enterprise_id FROM pp.mm_enterprise;
3. 바뀐 이름 — 옛 이름 호환은 없습니다
옛 이름은 받지도 돌려주지도 않습니다. 옛 V5 경로는 404 입니다.
정비 «기록» 은 정비 «오더» 입니다.
| 어디 | 옛 | 새 |
|---|---|---|
| V5 경로 | /api/v5/maintenance-records… | /api/v5/maintenance-orders… |
| V5 경로 | /api/v5/asset/{asset_id}/maintenance-records | /api/v5/asset/{asset_id}/maintenance-orders |
| MCP 도구 | list_maintenance_records | list_maintenance_orders |
| 칸 · 질의 | record_id | maintenance_order_id — 숫자 → 문자 |
| 칸 · 질의 | kind · status · cancel_reason | maintenance_kind_code · status_code · cancel_reason_code |
| 칸 | failure_id | failure_record_seq |
| 칸 | plan_id · plan_name | maintenance_plan_id(숫자 → 문자) · maintenance_plan_name |
| 칸 · 질의 | failure_mode · failure_mechanism · failure_cause · maintainable_item | 뒤에 _code. 표시 이름 *_name 과 cause_code 는 그대로 |
| 칸 | checklist_results[].attachment_id | checklist_results[].maintenance_attachment_seq |
| 첨부 응답 | attachment_id · record_id | maintenance_attachment_seq · maintenance_order_id |
목록 sort 값 | status · kind | status_code · maintenance_kind_code |
| 정비 계획 달력 | record_id · record_status · open_record_id · open_status · record_ids | maintenance_order_id · maintenance_order_status_code · open_maintenance_order_id · open_status_code · maintenance_order_ids |
| DB 표 | mm_maintenance_record · mm_ram_failure | mm_maintenance_order · mm_failure_record |
maintenance_order_id · maintenance_plan_id 를 숫자로 읽거나 비교하지 마세요. 옛 행은 번호를 그대로 둔 문자열("12")이고, 새 행은 MAINTENANCE_ORDER_000013 꼴입니다.
CMMS 웹훅의 키(PP_MAINTENANCE_WEBHOOK_URL) — 받는 쪽을 먼저 고치세요.
| 옛 키 | 새 키 | 값 |
|---|---|---|
record_id | maintenance_order_id | 문자 |
kind | maintenance_kind_code | 같음 |
status | status_code | 같음 |
failure_mode | failure_mode_code | 같음 |
cancel_reason | cancel_reason_code | 같음 |
plan_id | maintenance_plan_id | 문자 |
checklist_results[].attachment_id | checklist_results[].maintenance_attachment_seq | 같음 |
text · event · asset_id · asset_name · description · requested_by · accepted_by · assignee_employee_id · external_system · external_id · owner · anomaly_found · plan_due_at · at · version 은 그대로입니다 — CMMS 웹훅.
그 밖의 기록 키와 ID 칸.
| 어디 | 옛 | 새 |
|---|---|---|
/ram/failures… | failure_id · asset_class · equipment_class · code_status | failure_record_seq · asset_class_code · equipment_class_code · raw_code_status_code |
/ram/failures… | failure_mode · mechanism · iso_cause · maintainable_item | failure_mode_code · failure_mechanism_code · failure_cause_code · maintainable_item_code |
| 고장 · 정비 코드 매핑 | map_id(숫자) | fault_code_map_id · maintenance_code_map_id(문자) |
/defects… | defect_id | defect_record_seq |
/order/{id}/notes · /progress | note_id · progress_id | order_note_seq · order_progress_seq |
/alarm/config/{id}/revisions | revision_id | alarm_config_revision_seq |
| 근무 계획 구간 | generation_id | asset_plan_generation_seq |
알람 label | failure_id | failure_record_seq |
설비 · 사이트.
| 옛 | 새 | 비고 |
|---|---|---|
table_type | asset_class_code | 화면 말은 «설비 분류». 표 mm_asset_class 의 코드 칸은 code |
사이트 status | operational_status_code | ACTIVE → IN_OPERATION, INACTIVE → OUT_OF_SERVICE, CLOSED → DECOMMISSIONED. 모든 층의 공통 칸입니다 |
| 트리에 구역 · 라인 · 설비만 | 기업 → 사이트 → 구역 → 라인 → 설비 | asset_type 에 E · S 가 새로 나옵니다. 설비 수를 셀 때 거르세요 |
ID 꼴. 새로 만드는 ID 는 도메인 풀네임 + 6자리 번호입니다. 기존 ID 는 그대로 유효하고 바뀌지 않습니다. ID 접두로 층이나 종류를 판단하지 말고 asset_type 을 읽으세요.
| 도메인 | 옛 꼴(계속 유효) | 새 꼴 |
|---|---|---|
| 구역 · 라인 · 설비 | ASSET_… | AREA_000001 · LINE_000001 · EQUIPMENT_000001 |
| 워크오더 | ORD_… | ORDER_000001 |
| 캘린더 · 교대 예외 | CAL_… · OV_… | CALENDAR_000001 · SHIFT_OVERRIDE_000001 |
| 설비 관계 · 공정 세그먼트 | REL_… · SEG_… | ASSET_RELATION_000001 · PROCESS_SEGMENT_000001 |
| 명령 영역 · 설비 명령 | CMDAREA_… · CMD_… | COMMAND_AREA_000001 · ASSET_COMMAND_000001 |
| 이미지 | IMG_… | IMAGE_000001 |
| 직원 | EMP_… | EMPLOYEE_… |
| 알람 설정 | ALARM_CONFIG_00375 · ALARM_CONFIG_<태그 ID> | ALARM_CONFIG_000376 |
번호는 빌 수 있고, 999999 를 넘으면 자릿수가 늡니다. 자세한 것은 도메인 ID 규칙.
알람 설정.
POST /alarm/config에 직접 지은 ID 를 넣으면 422alarm.invalid.alarm_config_id입니다. 새 ID 는GET /alarm/config/new-id로 받습니다. 옛 꼴의 ID 는 수정만 됩니다.- 대역(밴드) 알람 설정은 태그당 하나입니다. 둘째는 409 입니다. EQL 알람 설정은 수에 제한이 없습니다.
- 대역 알람 설정 ID 를
ALARM_CONFIG_<태그 ID>로 지어서 찾지 마세요. 새 설정은 그 이름으로 없습니다.GET /alarm/config/list/by-tag?tag_id=…에서alarm_config_priority가BAND인 것을 찾습니다.
스테이트먼트 필수 칸
저장할 때 종류별 필수 출력 칸을 검사합니다. 빠지면 422 asset_statement.eql.missing_columns 로 거절하고, 빠진 칸을 알려 줍니다.
| 종류 | 필수 칸 |
|---|---|
EVENT | timestamp · plugin · name · color · from_timestamp · to_timestamp · value |
CONTEXT · AGGREGATION · COMMAND | timestamp · plugin · key · value |
이미 저장된 문장은 업그레이드로 지워지지도 멈추지도 않습니다. 기동 때 그대로 배포됩니다. 다만 필수 칸이 없는 문장은 전에도 결과가 기록되지 않았고 지금도 그렇습니다 — 이벤트마다 실패로 남고 진단 diagnostic.asset.statement_failing 이 켜집니다. 그런 문장은 수정해서 저장할 때 위 검사에 걸리므로, 칸을 채워야 저장됩니다. 올린 뒤 진단 화면에서 이 코드를 찾아 고치세요.
4. 진단 — CEP 데드레터는 24시간 · 256 MiB 만 남습니다
CEP 가 처리하지 못한 이벤트를 남기는 데드레터(PP:CEP-DEAD-LETTER)의 상한이 나이와 바이트로 정해졌습니다. 24시간이 지나거나 합계 256 MiB 를 넘으면 오래된 것부터 사라집니다. 세 번 배달돼도 처리되지 못한 이벤트만 들어갑니다. 실패 이벤트를 조사하려면 그 안에 봐야 합니다 — 데이터 보존.
5. CEP — Esper 백엔드와 문장별 CPU 지표가 없습니다
cep.backend 값은 aster · flink 둘이고, 설정하지 않으면 aster 입니다. 표준 배포 템플릿은 cep.backend=aster 이므로 손으로 고치지 않은 설치는 할 일이 없습니다.
cep.backend=esper 로 두면 CEP 가 뜨지 않습니다오타와 같이 값을 대며 부팅이 멈춥니다. 올리기 전에 plantpulse-cep.properties 에서 그 줄을 aster 로 바꾸거나 지우세요.
- Esper 로 돌리던 설치는 올리기 전에
GET /api/v1/eql/portability로 Aster CEP 가 받지 못할 문장을 확인합니다. 받지 못한 문장은 지워지지 않고REJECTED로 남습니다 — 한계와 거부 토큰. GET /api/v1/eql/{name}/metrics가 없어졌습니다. 문장별 CPU · 벽시계 시간도 게이지 · REST · 콘솔 패널 · 저장 모두 없습니다. 문장 지표는GET /api/v1/eql/metrics/snapshot의 매치율 · 출력율을 씁니다. PlantPulse Dashboard 의EQL_CPU_TIME패널에는 값이 들어오지 않습니다.- 읽는 코드가 없어진 설정:
cep.ha.*·cep.monitor.statement_metrics_enabled·cep.monitor.cpu_estimate_max_error·cep.performance.cpu_warn_threshold_ns·cep.performance.wall_warn_threshold_ns·cep.storage.max_inflight_writes. 룰 상태 보존은cep.aster.state.*입니다 — 프로퍼티 참조.
6. 올린 뒤에
- 설정 화면에
PP_UI_CONSOLE_URL을 넣습니다. 비어 있으면 알람 메일에 링크가 없고 QR 라벨을 만들 수 없습니다. 현장 앱을 쓰면PP_UI_FRONTLINE_URL도 넣습니다. - QR 공개 페이지는 모든 사이트에서 꺼진 채로 시작합니다.
- 설정 화면의
MAIL_SMTP_*값이 발송에 적용되지 않던 결함과 STARTTLS 가 «No appropriate protocol» 로 실패하던 결함이 고쳐졌습니다. 메일이 실제로 나가기 시작할 수 있으니 설정을 확인하세요 — 메일 설정.
2026-11-05 전의 2026.11 판에서 붙인 플러그인 — RAM_FAILURE_CODE_TAG · OEE_PRODUCT_BAD_COUNT_CODED 는 옛 문장이 남아 없는 태그(<태그>_CODE · <태그>_CAUSE)를 듣습니다. 다시 적용만 하면 «이미 있음 (건너뜀)» 이라 문장이 바뀌지 않습니다. 그 문장을 지운 뒤(DELETE /api/v5/asset/{asset_id}/statement/{statement_name}) 플러그인을 다시 적용하면서 코드 태그 · 불량 원인 태그를 고르십시오. 다른 문장은 그대로 둡니다. 다시 적용하기 전에 난 고장은 코드 없음으로 남습니다. 코드 태그가 실제로 <고장 태그 ID>_CODE 로 있어 잘 돌던 설치는 할 일이 없습니다.
2026.11 — 워크오더 수량과 이름을 ISA-95 / B2MML 로
워크오더의 수량이 «숫자 하나» 에서 값 + 단위 객체(ISA-95 / B2MML QuantityValueType)로 바뀌었습니다.
칸 이름도 ISO 22400 용어로 바뀌었고, 불량이 폐기와 재작업으로 나뉘었습니다. 같은 판에서 워크오더·캘린더·고객·제품의 칸 이름도
ISA-95 / B2MML 이름으로 바뀌었습니다(title → order_name, fix_start_timestamp → planned_start_timestamp …, 아래 이름 바꿈).
호환 칸·호환 경로·별칭은 없습니다 — 옛 이름을 읽거나 보내는 연동은 업그레이드와 함께 고쳐야 합니다.
기준: V5 API 5.0 · 수량 계약 2026-10-30 · 이름 웨이브 2026-10-31 · server-web 974891b4(수량 웨이브는 30b7d9af 부터) · Flyway V99·V100·V103 ·
core 마이그레이션 V12V16·V21V23 · 이벤트 스키마 plantpulse.order.*·plantpulse.calendar.* major 2, plantpulse.kpi.oee.finalized major 1.
무엇이 바뀌나
| 영역 | 바뀐 것 | 자세히 |
|---|---|---|
| V5 수량 칸 | target_units+unit → planned_quantity, total_good_count → good_quantity, total_bad_count → scrap_quantity, total_production_count → produced_quantity 등. 값은 {quantity_string, data_type, unit_of_measure} 객체 | Order 서비스 — 수량 |
| 폐기 · 재작업 | scrap_quantity 는 폐기만(ISO 22400 SQ), 재작업은 새 칸 rework_quantity(RQ). 옛 total_bad_count 는 불량 합이었다 — 합이 필요하면 둘을 더한다. 화면도 폐기만인 자리는 «폐기», 합은 «불량» | Order 서비스 — 폐기와 재작업 |
| 단위 | UN/ECE Rec 20 허용 목록 13개(EA·H87·C62·BX·SET·KGM·GRM·TNE·LTR·MLT·MTQ·MTR·MTK). 실적은 워크오더 단위로만 받는다(환산 없음). 거절은 422 E1002 + reason(quantity.unit_mismatch·quantity.not_integer …) | Order 서비스 — 요청 규칙과 거절 |
| 제품 기본 단위 | 제품의 unit_of_measure 가 새 워크오더의 기본 단위가 된다. 옛 값은 코드로 옮긴다(BOX→BX, KG→KGM, PCS·개→EA …), 알 수 없는 값은 «단위 없음»(그 제품의 새 워크오더는 EA) | 생산 관리 — 수량과 단위 |
| 우선순위 | priority 가 B2MML Priority 정수로(1 긴급·2 높음·3 보통·4 낮음, 작을수록 높다). 옛 URGENT·HIGH·NORMAL·LOW 는 1~4 로 옮겨진다 | |
| 기간 KPI | 단위가 하나면 숫자 그대로 + unit_of_measure, 섞이면 합치지 않고 quantities_by_unit | 같은 곳 |
| EMS 단위당 값 | energy_per_unit_kwh·co2_per_unit_kg 옆에 분모 단위 unit_of_measure | 같은 곳 |
| 이름 (워크오더·캘린더·고객·제품) | title → order_name, fix_start_timestamp/fix_end_timestamp → 워크오더는 planned_start_timestamp/planned_end_timestamp, 캘린더는 start_timestamp/end_timestamp, external_order_id → external_id, external_po_id → customer_po_id, 고객·제품의 external_customer_id/external_product_id → external_id(+ external_system). V5·SDK·이벤트·MCP·플로우 노드·DB 칸 모두. 별칭 없음 | 이름 바꿈 |
| 외부 워크오더 식별 | (external_system, external_id) 쌍. V5 POST·PUT·PATCH /order 가 둘 다 받고 응답·실적 문서가 둘 다 싣는다. GET /order/performance/by-external/{external_order_id} 는 지우고 …/by-external/{external_system}/{external_id} 로. 워크오더 하나는 GET /order/by-external/{system}/{external_id} | MES 연동 |
| 이벤트 | plantpulse.order.* 에 planned_quantity_unit_of_measure·external_system, plantpulse.kpi.oee.finalized 에 *_quantity + unit_of_measure(정본)와 재작업 rq·rq_quantity. 타입 변경 없이 끝에 덧붙임. 이름 바꿈으로 plantpulse.order.*·plantpulse.calendar.* 는 스키마 major 2(dataschema 끝이 :2) | Order 서비스 — 수량 |
| 플로우 | OEE_EVENT 의 수량이 Quantity 객체(rework_quantity 추가). flow_create_work_order 는 단위 값이 필수(경로 unit_of_measure_field, 기본 data.unit_of_measure). 이 노드의 경로 설정 이름도 바뀌었다(title_field → order_name_field …) | 플로우 |
| 화면 | 수량마다 단위 기호가 붙는다(언어 무관 — EA, kg, L, m² …). 워크오더 양식에 단위 칸 | 생산 관리 — 수량과 단위 |
숫자가 바뀌지는 않습니다. 업그레이드 전의 워크오더는 모두 EA 로 옮겨지고, 정수 개수는 그대로이므로
OEE·가용성·성능·품질과 수량 합계는 업그레이드 전후에 같습니다. 재작업은 기존 REWORK 불량 기록의 합으로 채워지고
(없으면 0), 그만큼만 scrap_quantity 가 옛 total_bad_count 보다 작아집니다.
이름 바꿈 — 옛 이름과 새 이름
워크오더·캘린더의 칸 이름을 ISA-95 / B2MML 이름 하나로 맞췄습니다. 와이어·SDK·이벤트·DB 칸이 모두 같은 새 이름이고
옛 이름의 별칭·호환 뷰·이중 쓰기는 없습니다(server-web 974891b4, Flyway V103 order_calendar_names_isa95).
| 옛 이름 | 새 이름 | 바뀌는 곳 |
|---|---|---|
title | order_name | 워크오더 — V5 요청·응답·/order/query 행, SDK setOrder_name, 이벤트 plantpulse.order.*(모든 종류에 덧붙음, deleted 포함), MCP list_work_orders·get_work_order_kpi, DB mm_order·mm_order_revision, Cassandra tm_asset_order |
fix_start_timestamp / fix_end_timestamp | planned_start_timestamp / planned_end_timestamp | 워크오더 — 위와 같은 곳. MCP 의 fix_start_at/fix_end_at 은 planned_start_at/planned_end_at |
fix_start_timestamp / fix_end_timestamp | start_timestamp / end_timestamp | 캘린더 — V5 요청·응답·개정 이력, SDK setStart_timestamp/setEnd_timestamp, 이벤트 plantpulse.calendar.created·updated·entry.*, DB mm_calendar·mm_calendar_revision·mm_shift |
target_units | planned_quantity | DB 칸 mm_order·mm_order_revision·mm_order_oee. V5·SDK·이벤트는 이미 planned_quantity 였다 |
external_order_id | external_id | 워크오더 — V5 요청·응답·실적 문서·/order/query 필터, SDK setExternal_id·OrderQuery.externalId(..), 이벤트, MCP get_work_order_kpi, DB |
external_po_id | customer_po_id | 워크오더 — V5 요청·응답·실적 문서, SDK setCustomer_po_id, 전이 이벤트(started~aborted), MCP, DB |
external_customer_id / external_product_id | external_id + 새 칸 external_system | 고객·제품 — V5 요청·응답, SDK, DB. 쌍으로 찾기 GET /customer/by-external/{system}/{external_id}·/product/by-external/… 가 새로 생겼다 |
baseline_fix_start / baseline_fix_end | baseline_planned_start_timestamp / baseline_planned_end_timestamp | 워크오더 deviation 객체(V5·SDK·MCP) |
정렬 fix_start_desc / fix_start_asc | planned_start_desc / planned_start_asc | /order/query 의 sort |
플로우 title_field · target_units_field · external_order_id_field · external_po_id_field | order_name_field · planned_quantity_field · external_id_field · customer_po_id_field | flow_create_work_order 노드 설정. 기본 경로도 data.order_name · data.planned_quantity · data.planned_start_timestamp · data.planned_end_timestamp · data.external_id · data.customer_po_id 로 |
옛 이름은 받지 않습니다 — 조용히 무시되는 곳이 있으므로 연동을 먼저 고치세요.
POST·PUT /order에 옛 이름을 보내면 그 칸은 읽히지 않습니다.order_name이 비므로 검증 오류로 거절되고(order_name은 필수, 공백 불가 —order.name.required), 계획 시각도 빈 값이 됩니다. 새 이름과 옛 이름을 섞어 보내면 저장은 되지만,PUT은 요청 칸 전체를 바꾸므로 옛 이름으로 보낸external_order_id·external_po_id는 지워집니다.PATCH /order에 옛 이름을 보내면 422 «not patchable» 입니다./order/query?external_order_id=…는 무시하지 않고 422 로 거절합니다 —external_id와external_system을 쓰세요.- 이벤트는 major 2 로만 나갑니다. major 1 스키마 파일은 재생용으로 남아 있을 뿐, 새로 나가는 이벤트는 새 이름을 싣습니다.
/kpi/*응답과plantpulse.kpi.oee.finalized의 칸 이름은 이번 바꿈의 대상이 아닙니다(그대로).
업그레이드 전에 — 외부 식별 쌍의 중복 확인
새 판은 외부 식별 쌍이 둘 다 비어 있지 않은 워크오더끼리 유일하도록 인덱스를 겁니다(Flyway V100, ux_mm_order_external_pair).
이미 같은 쌍을 가진 워크오더가 둘 이상 있으면 그 단계가 실패하므로, 올리기 전에 확인하세요.
아래 SQL 은 올리기 전의 DB 에서 돌리므로 옛 칸 이름 external_order_id 를 씁니다(V103 이 뒤에 external_id 로 바꿉니다).
SELECT external_system, external_order_id, count(*)
FROM pp.mm_order
WHERE external_system <> '' AND external_order_id <> ''
GROUP BY 1, 2
HAVING count(*) > 1;
0 행이어야 합니다. 행이 나오면 한쪽의 external_system 이나 external_order_id 를 고친 뒤 올립니다.
external_order_id 만 있고 external_system 이 비어 있는 워크오더는 쌍이 아니므로 대상이 아닙니다(그대로 남습니다).
첫 기동에 일어나는 일
| 저장소 | 언제 | 하는 일 |
|---|---|---|
| PostgreSQL | server-web 기동(Flyway V99 order_quantity_isa95 · V100 order_external_id_pair_unique) | 수량 칸을 numeric(18,4) 로 바꾸고 워크오더마다 unit_of_measure 를 'EA' 로 채운다. mm_order_oee.total_rework_count 를 더해 REWORK 불량 기록 합으로 채운다. 제품 단위를 코드로 옮기고 priority 를 정수로 바꾼다 |
| PostgreSQL | 이어서 Flyway V103 order_calendar_names_isa95 | 위 이름 바꿈 표의 DB 칸을 RENAME COLUMN 으로 바꾼다 — 데이터·인덱스·제약·뷰는 따라간다. 고객·제품에 external_system 칸과 쌍 유일 인덱스를 더하고(기존 행의 시스템은 비워 둔다), 워크오더 status 가 WAIT·START·PAUSED·END·ABORTED 밖이면 멈춘다. 상태 변경은 개정 행과 함께만 되도록 트리거를 건다 |
| Cassandra · 저장 EQL | 플랫폼 첫 기동(core V21~V23) | tm_asset_order 에 order_name·planned_start_timestamp·planned_end_timestamp 를 더하고 옛 칸 값을 옮긴다(V21·V22, 몇 초, 옛 칸은 지우지 않는다). 저장된 설비 스테이트먼트(EQL)의 옛 속성 이름을 새 이름으로 고쳐 쓴다(V23, SYSTEM 개정) — 고치지 못한 문은 진단 diagnostic.asset.statement_not_renamed 로 남는다 |
| Cassandra | 플랫폼 첫 기동 | 버전 마이그레이션이 작은 표(OEE·EMS 의 current·order, tm_asset_order)에 새 decimal 수량 칸과 unit_of_measure 를 더하고 기존 행을 채운다('EA') — 몇 초. 큰 표(OEE·EMS 의 rolling·rollup·history, tm_kpi_oee_count)는 기동 뒤 백그라운드로 옮긴다. 기동 자체는 평소와 같다 |
- 큰 표를 옮기는 동안 이력 KPI 는 옛 칸에서 같은 값으로 읽힙니다. 표마다 이전이 끝나면 저절로 새 칸만 읽습니다. 진행은 server-web 기동 로그에 «Cassandra migration» 으로 시작하는 줄로 나옵니다(
docker logs plantpulse-server-web | grep 'Cassandra migration'). - 옛 정수 칸(
target_units·total_production_count·total_good_count·total_bad_count)은 이전이 끝나면 더 이상 읽지도 쓰지도 않습니다. 칸을 지우는 것은 이 판이 아니라 나중 판의 일입니다. 새로 설치한 클러스터에는 처음부터 없습니다.
연동이 고칠 것 — 점검표
- SDK —
plantpulse-api를 새 판으로 올린다.setTarget_units(…)/setUnit(…)는 없다 →setPlanned_quantity(Quantity.of("500", "EA")). - V5 응답을 읽는 코드 — 옛 칸 이름(
target_units,total_good_count,total_bad_count,total_production_count,good_count/bad_count(수량 기록),out_of_plan_*_count…)을 새 이름으로. 값은 숫자가 아니라quantity_string문자열이다 — double 로 바꾸지 말고 10진 타입으로 읽는다. - 불량 합계를 쓰던 곳 — 옛
total_bad_count를scrap_quantity로 바꾸기만 하면 재작업이 빠진다. 불량 합이 필요하면scrap_quantity+rework_quantity. - 거절 처리 — 수량 거절은 422
E1002, 분기는 응답의reason(quantity.*)으로. 수량 칸에 숫자를 그대로 넣으면 400E1000. - 실적·보정 입력 — 워크오더의 단위로 보낸다. 개수 단위(
EA·H87·C62·BX·SET)에 소수를 보내면 422quantity.not_integer. - 기간 KPI 를 합치는 화면·리포트 —
good_count등이 null 이면quantities_by_unit을 본다. 단위가 다른 수량을 더하지 않는다. - 이름 바꿈 — V5 요청·응답을 다루는 코드에서
title·fix_start_timestamp·fix_end_timestamp·external_order_id·external_po_id(워크오더),fix_start_timestamp·fix_end_timestamp(캘린더),external_customer_id·external_product_id(고객·제품)를 새 이름으로. 별칭이 없으므로 서버와 같이 올린다. - SDK 세터 이름 —
setTitle→setOrder_name,setFix_start_timestamp→setPlanned_start_timestamp(캘린더는setStart_timestamp),setExternal_order_id→setExternal_id,setExternal_po_id→setCustomer_po_id,OrderQuery.externalOrderId(..)→externalId(..). - MES 실적 수집 — 외부 번호로 찾던 호출을
by-external/{external_system}/{external_id}로. 워크오더를 만들 때external_system도 넣는다. - PostgreSQL 을 직접 읽는 SQL — 계획 수량은
mm_order.planned_quantity(옛target_units)이고 문자열에서numeric이 된다(문자열로 읽으면'1200.0000'꼴).mm_order_oee의 수량 칸도bigint에서numeric(18,4). 단위는 새 칸unit_of_measure. 그 밖에 위 이름 바꿈 표의 DB 칸 이름도 바뀐다. - 플로우 —
OEE_EVENT를 읽는 스크립트는data.payload.good_quantity.quantity_string으로.flow_create_work_order노드에 단위 경로를 채운다. 이 노드에title_field·target_units_field·external_order_id_field·external_po_id_field를 직접 적어 둔 플로우는 그 설정이 읽히지 않고 새 기본 경로(data.order_name…)를 쓰므로, 새 설정 이름으로 다시 적는다. 워크오더·캘린더 이벤트의 옛 키를 읽는 스크립트도 새 이름으로. - 설비 스테이트먼트(EQL) — 업그레이드 뒤 진단 화면에
diagnostic.asset.statement_not_renamed가 있으면 그 문의 옛 이름(fix_start_timestamp·title·external_order_id…)을 손으로 고친다. - 이벤트 소비자 —
plantpulse.order.*·plantpulse.calendar.*는 major 2 의 새 키(planned_start_timestamp·external_id·order_name…, 캘린더는start_timestamp·end_timestamp)를 읽는다.plantpulse.kpi.oee.finalized는*_quantity칸을 읽는다.sq는 폐기만이고 재작업은rq. 정수gq·sq·rq·pq는 소수 단위에서 실리지 않고 다음 major 에서 빠진다.