# Machbase DBMS 매뉴얼 전체 본문
> 현재 published DBMS 8.7.0 한국어 페이지를 navigation 순서로 연결한 Markdown corpus입니다.
- language: `kr`
- document_count: `241`
- document_index: https://docs.machbase.com/kr/llms-chunks.json
---
title: "Machbase DBMS 매뉴얼"
url: https://docs.machbase.com/kr/dbms/
language: kr
kind: section
---
# Machbase DBMS 매뉴얼
Machbase DBMS 매뉴얼에 오신 것을 환영합니다. 이 문서는 Machbase 8.7.0을 기준으로
설치, 테이블 타입별 활용, 애플리케이션 개발, 운영, 보안과 레퍼런스를 제공합니다.
처음 사용하는 독자는 1장에서 SQL 실습을 진행하고 2장에서 데이터 모델과 저장·운영 원리를
익힙니다. 시스템을 설계할 때는 4장의 테이블 선택 기준을 확인한 뒤 필요한 테이블 장으로
이동합니다. 이미 사용 중인 기능의 정확한 문법이나 지원 범위는 16장에서 찾을 수 있습니다.
## 매뉴얼 구성
| 장 | 제목 | 내용 |
|----|------|------|
| 1 | [처음 시작하기](./getting-started/) | 개요, 접속 확인, 빠른 시작, 기본 명령 |
| 2 | [핵심 개념](./core-concepts/) | 테이블 유형, 시간 모델, ROLLUP, Retention Policy |
| 3 | [설치, 배포, 업그레이드](./installation-deployment-upgrade/) | 설치 준비, Standard Edition, Cluster Edition, 업그레이드 |
| 4 | [테이블 타입 선택과 스키마 설계](./data-modeling-table-design/) | 타입 결정, 스키마, 변경 정책과 모델링 패턴 |
| 5 | [TAG 테이블 활용](./tag-table-usage/) | TAG 구조, 메타데이터, 입력, 조회, 보정, 운영 |
| 6 | [TAG 테이블을 위한 ROLLUP 활용](./tag-rollup-usage/) | ROLLUP 설계, 생성, 조회, 재구성, 운영, 성능 튜닝 |
| 7 | [LOG 테이블 활용](./log-table-usage/) | LOG 구조, 입력과 텍스트 검색 |
| 8 | [TRANSACTION 테이블 활용](./rdb-table-usage/) | TRANSACTION 스키마, DML, 트랜잭션, JOIN, 백업·복원 |
| 9 | [LOOKUP 테이블 활용](./lookup-table-usage/) | 기준 정보, PRIMARY KEY, JSON, 일반 predicate DML, JOIN |
| 10 | [VOLATILE 테이블 활용](./volatile-table-usage/) | 메모리 테이블, UPSERT, 상태 캐시, 재시작과 데이터 소실 |
| 11 | [개발 및 애플리케이션 연동](./development-tools-integration/) | 연동 방식, 공통 개념과 언어별 SDK/API |
| 12 | [성능 튜닝](./performance-tuning/) | 쿼리 성능, 수집 성능, 캐시 튜닝 |
| 13 | [운영, 설정, 복구](./operations-configuration-recovery/) | 서버 관리, 백업, Cluster 운영 |
| 14 | [계정, 권한, 접속 제어](./security-access-control/) | 계정, 권한, AUTH KEY와 접속 제어 |
| 15 | [문제 해결](./troubleshooting/) | 오류 진단 및 해결 방법 |
| 16 | [레퍼런스](./reference/) | SQL 문법, 함수, 설정, 시스템 카탈로그 |
---
title: "1. 처음 시작하기"
url: https://docs.machbase.com/kr/dbms/getting-started/
language: kr
kind: section
---
# 1. 처음 시작하기
이 장은 데이터베이스를 처음 다루는 독자와 다른 DBMS를 사용하다 Machbase를 접하는
독자를 위한 출발점입니다. Machbase DBMS 8.7.0에서 어떤 데이터를 어떻게 저장하는지
살펴보고, 이벤트 한 건을 입력하고 조회하는 실습으로 기본 SQL 흐름을 익힙니다.
데이터베이스를 설치하지 않아도 개요부터 읽을 수 있습니다. 실습을 시작할 때는 실행 중인
DBMS 서버와 접속 도구인 `machsql`이 필요합니다. 설치와 접속 준비는
[10분 빠른 시작](./quick-start/)의 실행 전제에서 안내합니다.
## 이 장에서 배울 것
1. 테이블, 행, 컬럼과 SQL의 역할을 이해합니다.
2. 시간에 따라 쌓이는 이력과 현재 상태를 구분합니다.
3. 계측값, 이벤트와 기준 정보에 맞는 Machbase 테이블 유형을 살펴봅니다.
4. 서버에 접속해 LOG 테이블을 만들고 데이터를 입력·조회합니다.
5. 발생 시각과 서버 수신 시각의 차이를 확인하고 다음 학습 경로를 선택합니다.
## 읽는 순서
| 절 | 읽고 나면 할 수 있는 일 |
|---|---|
| [Machbase DBMS 개요](./overview/) | 시계열 데이터베이스의 목적과 테이블 유형별 역할을 설명합니다. |
| [10분 빠른 시작](./quick-start/) | 서버 접속부터 데이터 입력·조회·실습 정리까지 실행합니다. |
| [기본 명령 치트시트](./command-cheatsheet/) | 셸 명령과 SQL을 구분하고 자주 쓰는 명령을 찾습니다. |
| [다음에 읽을 문서 선택하기](./choose-next-doc/) | 자신의 데이터와 개발·운영 업무에 맞는 문서로 이동합니다. |
실습에서는 동작을 쉽게 확인할 수 있는 소량의 SQL `INSERT`를 사용합니다. 지속적으로
유입되는 대량 데이터의 입력 방식과 오류 처리는
[데이터 입력과 반출](/dbms/development-tools-integration/data-input-load-export/)에서
이어 학습합니다.
---
title: "1.1 Machbase DBMS 개요"
url: https://docs.machbase.com/kr/dbms/getting-started/overview/
language: kr
kind: page
---
# 1.1 Machbase DBMS 개요
Machbase DBMS는 센서 계측값, 설비 이벤트, 애플리케이션 로그처럼 시간에 따라
쌓이는 데이터를 저장하고 SQL로 조회·분석하는 시계열 데이터베이스입니다. 사용자는 데이터의
형태와 변경·조회 방식에 맞는 테이블을 선택하고, 원본 이력과 기준 정보를 함께 관리합니다.
## Machbase DBMS 소개
### 데이터베이스와 SQL의 역할
데이터베이스는 애플리케이션이 수집한 데이터를 일정한 구조로 저장하고 여러 작업에서
다시 사용할 수 있게 합니다. DBMS는 그 데이터를 읽고 쓰는 요청, 사용자 권한과 저장
공간을 관리하는 소프트웨어입니다.
테이블은 같은 구조의 기록을 모은 것입니다. 행은 이벤트 한 건이나 측정값 한 건을
나타내고, 컬럼은 시각·장비 이름·측정값 같은 항목을 나타냅니다. 컬럼에는 숫자, 문자열,
날짜와 시간 같은 데이터 타입을 지정합니다. 이렇게 정한 구조를 스키마라고 합니다.
예를 들어 온도 이력은 다음과 같이 표현할 수 있습니다. 아래 시각과 값은 개념 설명용입니다.
| 측정 대상 | 측정 시각 | 온도 |
|---|---|---:|
| 설비 A의 온도 센서 | 09:00:00 | 23.1 |
| 설비 A의 온도 센서 | 09:00:01 | 23.5 |
| 설비 B의 온도 센서 | 09:00:01 | 18.0 |
SQL은 이 데이터를 다루는 언어입니다. `CREATE`로 테이블을 만들고, `INSERT`로 행을
추가하고, `SELECT`로 필요한 행과 컬럼을 읽습니다. `WHERE`는 조회 조건을,
`GROUP BY`는 집계 기준을, `ORDER BY`는 결과의 정렬 순서를 지정합니다.
같은 SQL을 사용하더라도 지원하는 변경 연산과 저장 특성은 테이블 유형에 따라 다릅니다.
### 현재 상태와 시간 이력
“설비 A의 현재 온도는 얼마인가?”와 “지난 1시간 동안 온도가 어떻게 변했는가?”는
다른 질문입니다. 현재 값 하나를 계속 덮어쓰면 과거 변화는 알 수 없습니다. 각 측정값에
시각을 붙여 새 행으로 저장하면 변화 추이, 최대값과 이상 발생 시점을 분석할 수 있습니다.
시계열 시스템에서는 이런 이력이 계속 증가합니다. 따라서 입력 속도뿐 아니라 어떤
대상을 어느 시간 범위로 조회할지, 원본을 얼마나 보관할지도 설계해야 합니다.
계측값이 항상 일정한 간격으로 도착하는 것은 아닙니다. 네트워크 지연, 장비 중단과
재전송으로 늦게 도착하거나 빠진 값이 생길 수 있습니다.
### 계측값, 이벤트와 기준 정보
계측값은 “어느 대상이 어느 시각에 어떤 값을 가졌는가”를 나타냅니다. 이벤트는 알람,
장비 정지, 서비스 시작처럼 어떤 일이 발생한 사실을 기록합니다. 장비 이름, 설치 위치와
측정 단위는 그 기록을 해석하기 위한 기준 정보입니다.
이 세 가지는 하나의 시스템에서 함께 사용됩니다. 온도 이상을 분석하려면 온도 이력뿐
아니라 같은 시각의 알람과 해당 센서의 설치 위치도 필요합니다. SQL의 조인(`JOIN`)은
장비 코드처럼 공통된 값을 기준으로 이력과 기준 정보를 연결하는 데 사용합니다.
## Machbase가 해결하는 문제
Machbase의 시계열 기능은 다음 작업을 함께 수행할 때 활용할 수 있습니다.
| 작업 | 예 | 관련 기능 |
|---|---|---|
| 원본 이력 수집 | 센서 측정값과 설비 이벤트를 계속 저장 | TAG·LOG, SQL 입력과 SDK Append |
| 필요한 구간 조회 | 설비 A의 최근 1시간 온도 변화 확인 | 태그·시간 조건, 인덱스와 실행 계획 |
| 반복 통계 조회 | 긴 기간의 분·시간 단위 추이 비교 | TAG ROLLUP |
| 데이터 해석 | 센서 코드에 장비명과 위치 연결 | 기준 정보와 JOIN |
| 저장 기간 관리 | 보관 기간이 지난 원본 정리 | 지원 테이블의 Retention Policy |
| 장애에 대비한 데이터 보호 | 백업과 복구 절차 검증 | Edition별 백업·복구 기능 |
ROLLUP은 원본에서 구간 통계를 계산합니다. 압축은 저장에 필요한 공간을 줄이는 기술이고,
Retention Policy는 오래된 데이터를 삭제하는 정책입니다. 세 기능의 목적은 서로 다르며,
집계가 있다는 이유만으로 원본을 삭제해도 되는 것은 아닙니다.
필요한 성능은 데이터 타입, 입력량, 동시 조회, 보관 기간과 서버 자원에 따라 달라집니다.
작은 실습으로 사용법을 익힌 뒤, 실제 데이터와 조회 조건으로 처리량과 지연 시간을
측정하십시오. 기능의 역할은 [핵심 개념](/dbms/core-concepts/)에서 더 자세히 설명합니다.
## 데이터에 맞는 테이블 선택
| 테이블 | 주 용도 | 선택할 때 확인할 점 |
|---|---|---|
| TAG | 태그 이름과 시간·거리 축을 가진 계측 이력 | 특정 태그의 구간 조회와 집계가 중심인지 확인합니다. |
| LOG | 여러 항목을 가진 이벤트·로그 이력 | 새 기록을 계속 추가하고 시간 조건이나 검색 조건으로 읽는지 확인합니다. |
| LOOKUP | 장비 코드, 매핑 같은 영속 기준 정보 | 메모리에 적재해 참조할 데이터의 크기와 변경 방식을 확인합니다. |
| TRANSACTION | 행 단위 변경과 트랜잭션이 필요한 업무 데이터 | Standard Edition에서 관계형 DML과 트랜잭션이 필요한지 확인합니다. |
| VOLATILE | 재시작 후 다시 만들 수 있는 공유 상태 | 서버 종료로 데이터가 사라져도 복원할 수 있는지 확인합니다. |
Machbase DBMS 8.7.0에서 LOG 테이블은 `CREATE LOG TABLE`로 명시합니다. 유형을 생략한
`CREATE TABLE`은 TRANSACTION 테이블을 생성하며, TRANSACTION 테이블은 Standard
Edition에서 지원합니다.
데이터의 산업 분야만으로 테이블을 결정하지는 않습니다. 변경·조인·보관 요구사항을
포함한 최종 선택은
[테이블 타입 선택](/dbms/data-modeling-table-design/table-types-selection-type/)에서
확인합니다.
이제 [10분 빠른 시작](../quick-start/)에서 이벤트 한 건을 저장하고 다시 읽어 봅니다.
---
title: "1.2 10분 빠른 시작"
url: https://docs.machbase.com/kr/dbms/getting-started/quick-start/
language: kr
kind: page
---
# 1.2 10분 빠른 시작
실행 중인 서버에 접속해 서비스 시작 이벤트 한 건을 저장하고 다시 읽어 봅니다.
추가 중심 이벤트에 맞는 LOG 테이블을 사용하며, SQL 실행 결과와 두 시간 컬럼의 의미를
확인합니다. 설치에 필요한 시간은 이 실습의 예상 시간에 포함하지 않습니다.
## 실행 전제
- Machbase DBMS 서버가 `127.0.0.1:5656`에서 실행 중입니다.
- `machsql` 명령을 사용할 수 있습니다.
- 테이블 생성·입력·조회·삭제 권한이 있는 실습 계정으로 접속할 수 있습니다.
- 아래 명령은 초기 실습 계정 `SYS`와 비밀번호 `MANAGER`를 사용합니다. 비밀번호를
변경했다면 실제 값으로 바꿉니다.
- `/tmp`에 SQL 파일을 저장할 수 있고, 실습용 이름 `DBMS_GS_QUICK`을 사용할 수 있습니다.
기존 업무 테이블과 이름이 겹치지 않는 실습 환경을 사용합니다. 예제 마지막의
`DROP TABLE`은 실습 테이블과 입력한 데이터를 삭제합니다.
서버가 아직 준비되지 않았다면 [설치, 배포, 업그레이드](/dbms/installation-deployment-upgrade/)와
[Linux Standard Edition 설치](/dbms/installation-deployment-upgrade/standard-edition/#linux)를 먼저
참고하십시오.
## 대표 실행 예제
서비스 시작 이벤트 한 건을 LOG 테이블에 기록합니다. LOG 테이블은 `CREATE LOG TABLE`로
명시해서 생성하며, `_arrival_time` 컬럼이 자동으로 추가됩니다.
다음 명령으로 SQL 파일을 저장하고 실행합니다.
```bash
cat > /tmp/dbms_gs_quick.sql <<'SQL'
CREATE LOG TABLE DBMS_GS_QUICK (
EVENT_ID INTEGER,
EVENT_TIME DATETIME,
LEVEL VARCHAR(10),
MESSAGE VARCHAR(40)
);
INSERT INTO DBMS_GS_QUICK (EVENT_ID, EVENT_TIME, LEVEL, MESSAGE)
VALUES (
1,
TO_DATE('2026-07-02 09:00:00', 'YYYY-MM-DD HH24:MI:SS'),
'INFO',
'service started'
);
SELECT _arrival_time, EVENT_ID, EVENT_TIME, LEVEL, MESSAGE
FROM DBMS_GS_QUICK
ORDER BY EVENT_ID;
DROP TABLE DBMS_GS_QUICK;
SQL
machsql -s 127.0.0.1 -P 5656 -u SYS -p MANAGER -f /tmp/dbms_gs_quick.sql
```
### 결과 확인
각 SQL 단계에 오류가 없는지 확인합니다. SELECT 결과 한 행에 `EVENT_ID`가 `1`,
`LEVEL`이 `INFO`, `MESSAGE`가 `service started`로 표시되어야 합니다.
`EVENT_TIME`은 애플리케이션이 저장한 실제 이벤트 시각이고, `_arrival_time`은 DBMS가
이 예제에서 자동 기록한 서버 입력 시각입니다. 따라서 예제를 실행할 때마다
`_arrival_time` 값은 달라지며 `EVENT_TIME`과 같을 필요가 없습니다.
`CREATE LOG TABLE`은 구조를 만들고 `INSERT`는 한 행을 추가합니다. `SELECT`는 읽을
컬럼을 지정하며, `ORDER BY EVENT_ID`는 결과 순서를 지정합니다. 마지막 `DROP TABLE`까지
성공하면 실습 테이블이 제거됩니다. 데이터를 더 살펴보려면 실행 전에 마지막 DROP 문을
제외하고, 조회를 마친 뒤 해당 실습 테이블만 정리합니다.
재실행 중 `DBMS_GS_QUICK`이 이미 존재한다는 오류가 나면 이전 실행이 `DROP TABLE`
단계에 도달하지 못했을 수 있습니다. `DESC DBMS_GS_QUICK;`으로 구조를 확인하고, 이전
실습에서 만든 테이블인 경우에만 `DROP TABLE DBMS_GS_QUICK;`을 실행한 뒤 재시도합니다.
접속 오류라면 먼저 서버 주소·포트·기동 상태와 계정 정보를 확인합니다.
## 이 예제에서 확인한 것
| 항목 | 확인 내용 |
| --- | --- |
| 서버 접속 | `machsql`로 `127.0.0.1:5656`에 접속 |
| 테이블 생성 | `CREATE LOG TABLE`로 LOG 테이블을 명시적으로 생성 |
| 데이터 입력 | `INSERT`와 `TO_DATE`로 이벤트 데이터 저장 |
| 데이터 조회 | `SELECT`와 `ORDER BY`로 입력 결과 확인 |
| 자동 컬럼 | LOG 테이블의 `_arrival_time`은 서버가 자동 기록 |
| 정리 | `DROP TABLE`로 실습 테이블 삭제 |
---
title: "1.3 기본 명령 치트시트"
url: https://docs.machbase.com/kr/dbms/getting-started/command-cheatsheet/
language: kr
kind: page
---
# 1.3 기본 명령 치트시트
접속 명령은 운영체제의 터미널에서 실행하고, SQL은 접속한 `machsql`에서 실행합니다.
서버 주소, 포트와 계정은 실제 환경에 맞춥니다. 아래 접속 예제의 `MANAGER`는 빠른 시작에서
사용하는 초기 실습 비밀번호이며, 변경한 환경에서는 해당 계정의 비밀번호를 사용합니다.
## 터미널에서 실행할 명령
| 작업 | 명령 |
|---|---|
| 대화형 접속 | `machsql -s 127.0.0.1 -P 5656 -u SYS -p MANAGER` |
| SQL 파일 실행 | `machsql -s 127.0.0.1 -P 5656 -u SYS -p MANAGER -f file.sql` |
## machsql에서 실행할 SQL
| 작업 | 명령 |
|---|---|
| 테이블 목록 | `SHOW TABLES;` |
| 컬럼과 타입 확인 | `DESC table_name;` |
| TRANSACTION 테이블 생성 | `CREATE TRANSACTION TABLE table_name (...);` |
| LOG 테이블 생성 | `CREATE LOG TABLE table_name (...);` |
| 데이터 입력 | `INSERT INTO table_name VALUES (...);` |
| 조건에 맞는 데이터 조회 | `SELECT ... FROM table_name WHERE ...;` |
| 조회 결과 정렬 | `SELECT ... FROM table_name ORDER BY ...;` |
| 테이블과 데이터 삭제 | `DROP TABLE table_name;` |
`table_name`과 `...`은 실제 이름과 정의로 바꿔야 하는 자리 표시자입니다.
그대로 실행할 수 있는 SQL은 [빠른 시작](../quick-start/)에 있습니다.
`DROP TABLE`은 테이블의 데이터도 삭제하므로 정리할 실습 테이블인지 먼저 확인합니다.
Machbase DBMS 8.7.0의 `CREATE TABLE`은 유형을 생략하면 TRANSACTION 테이블을 생성합니다.
TRANSACTION은 Standard Edition에서 지원합니다. 사용하려는 유형을 명시하면 예제의 의도가
분명해집니다. 전체 명령은 [machsql 레퍼런스](../../reference/command-line-tools/machsql/)를
참고하십시오.
---
title: "1.4 다음에 읽을 문서 선택하기"
url: https://docs.machbase.com/kr/dbms/getting-started/choose-next-doc/
language: kr
kind: page
---
# 1.4 다음에 읽을 문서 선택하기
빠른 시작에서는 한 행을 저장하고 조회했습니다. 실제 시스템을 만들 때는 데이터의
형태뿐 아니라 변경 방식, 시간 기준, 조회 패턴과 보관 기간을 함께 결정합니다.
## 설계 전에 답할 질문
- 같은 대상의 값을 시간에 따라 누적합니까, 현재 상태를 덮어씁니까?
- 측정·발생 시각과 서버 수신 시각 중 어떤 시각이 조회의 기준입니까?
- 특정 태그의 구간 조회, 여러 컬럼 검색, 키 조회와 조인 중 무엇을 자주 수행합니까?
- 늦게 도착하거나 중복된 데이터, 잘못된 값을 어떻게 처리합니까?
- 원본과 집계는 각각 얼마나 오래 필요하며, 재시작 후 반드시 남아야 하는 데이터는 무엇입니까?
- 필요한 기능을 어느 Edition에서 사용할 수 있습니까?
## 다음 경로
| 해야 할 일 | 다음 문서 |
|---|---|
| 시간 이력·저장·조회 원리 이해 | [핵심 개념](../../core-concepts/) |
| 테이블 유형과 변경 정책 결정 | [테이블 타입 선택과 스키마 설계](../../data-modeling-table-design/) |
| 센서·계측 이력 저장 | [TAG 테이블 활용](../../tag-table-usage/) |
| 이벤트와 로그 수집·검색 | [LOG 테이블 활용](../../log-table-usage/) |
| 장기간의 태그 통계 조회 | [TAG 테이블을 위한 ROLLUP 활용](../../tag-rollup-usage/) |
| 기준 정보와 업무 데이터 관리 | [LOOKUP](../../lookup-table-usage/), [TRANSACTION](../../rdb-table-usage/) |
| 다시 생성할 수 있는 상태 관리 | [VOLATILE 테이블 활용](../../volatile-table-usage/) |
| 애플리케이션에서 입력·조회 | [개발 및 애플리케이션 연동](../../development-tools-integration/) |
| SQL 문법과 타입 확인 | [SQL 레퍼런스](../../reference/sql/) |
| 운영·백업·권한 설정 | [운영, 설정, 복구](../../operations-configuration-recovery/), [계정과 권한](../../security-access-control/) |
처음에는 대표 데이터와 자주 쓰는 조회를 정해 작은 규모로 실행합니다. 입력 속도, 조회
지연과 저장 공간을 측정한 뒤 수집 방식, 인덱스, 집계와 보관 정책을 조정합니다.
---
title: "2. 핵심 개념"
url: https://docs.machbase.com/kr/dbms/core-concepts/
language: kr
kind: section
---
# 2. 핵심 개념
1장에서 SQL로 데이터를 저장하고 읽는 흐름을 확인했다면, 이 장에서는 그 동작을
설계와 운영의 관점에서 이해합니다. 같은 데이터라도 시간의 의미, 변경 방식과 조회 범위에
따라 적절한 테이블과 저장 전략이 달라집니다.
예를 들어 설비의 현재 온도와 지난달 온도 이력은 보관할 행의 수가 다릅니다. 월평균
온도와 순간적인 이상값 탐지는 필요한 데이터의 해상도가 다릅니다. 이 차이를 알면
테이블, 인덱스, ROLLUP과 보관 정책을 목적에 맞게 선택할 수 있습니다.
## 이 장의 구성
| 절 | 다루는 질문 |
|---|---|
| [데이터 모델 개념](concepts/) | 무엇을 한 행으로 저장하며, 시간·NULL·중복·변경을 어떻게 해석합니까? |
| [저장 및 실행 구조](storage-execution-architecture/) | 필요한 데이터를 어떻게 찾으며, 저장·인덱스·캐시는 어떤 비용을 줄입니까? |
| [주요 기능과 용어 구분](features-concepts/) | 원본·집계·보관·백업은 어떤 목적을 각각 담당합니까? |
| [Edition 개념](concepts-edition/) | 단일 서버와 분산 구성 중 무엇을 선택하고 어떤 기능 차이를 확인합니까? |
예제는 개념 설명을 위한 작은 데이터 집합을 사용합니다. 기능별 SQL, 지원 범위와 운영
절차는 본문에서 연결한 상세 문서에서 확인합니다. 개념을 익힌 뒤
[테이블 타입 선택과 스키마 설계](../data-modeling-table-design/)에서 자신의 데이터에
적용하십시오.
---
title: "2.1 데이터 모델 개념"
url: https://docs.machbase.com/kr/dbms/core-concepts/concepts/
language: kr
kind: page
---
# 2.1 데이터 모델 개념
데이터 모델은 무엇을 한 건으로 저장하고, 어떤 값으로 구분하며, 어떻게 변경하고
조회할지를 정한 규칙입니다. 시계열 모델에서는 측정 대상과 시각, 값의 의미를 함께
정해야 합니다. 컬럼 이름을 정하기 전에 이 규칙부터 확인하면 입력과 분석 결과의
해석이 일관됩니다.
## 시계열 데이터 이해하기
시계열 데이터는 시간에 따른 관측이나 사건의 기록입니다. 온도 측정, 체결 이력,
서비스 오류 기록이 대표적입니다. 일정 간격으로 수집할 수도 있고 사건이 발생할 때만
기록할 수도 있습니다. 저장 순서가 발생 순서와 같다는 보장은 없습니다.
### 한 행의 의미와 식별자
온도 이력의 한 행을 “센서 한 개가 특정 시각에 측정한 값”으로 정했다면 센서 식별자,
측정 시각과 값이 필요합니다. 식별자는 데이터가 어느 대상에서 나온 것인지 구분하고,
시각은 대상의 변화 순서를 해석하는 기준입니다.
여러 센서가 같은 시각에 측정할 수 있으므로 시각만으로 행을 유일하게 구분할 수는
없습니다. 같은 센서의 같은 시각 데이터도 재전송으로 반복될 수 있습니다.
중복을 허용할지, 제거할지, 별도 이벤트 번호가 필요한지를 결정해야 합니다.
TAG의 `PRIMARY KEY`는 태그를 식별하는 역할이며 일반 관계형 테이블처럼 각 측정 행의
이름이 유일하다는 뜻은 아닙니다. TAG 데이터 중복 처리는
[TAG 테이블 운영](../../tag-table-usage/operations-lifecycle/)에서 확인합니다.
### 값, 단위와 품질
숫자만 저장하면 단위와 의미를 알 수 없습니다. 같은 `23.5`라도 온도, 압력과 전압은
서로 다른 데이터입니다. 태그 정의나 기준 정보에 단위와 측정 위치를 관리하고, 하나의
시계열에서 값의 의미가 바뀌지 않게 합니다.
`NULL`과 `0`도 구분합니다. 장비가 0을 측정한 것과 측정에 실패해 값을 알 수 없는 것은
다릅니다. 필요하면 품질이나 상태를 별도 컬럼으로 기록합니다. 수집되지 않은 구간에는
행 자체가 없을 수 있으므로, NULL 행이 있는 경우와도 구분해야 합니다.
일반적인 `AVG` 같은 집계는 NULL을 제외한 값에 적용됩니다. 누락된 값을 무조건 0으로
채우거나 서로 다른 단위를 함께 집계하면 분석 결과가 달라집니다. 지원 집계 함수의
정확한 NULL 처리는 [함수 레퍼런스](../../reference/sql/functions/functions-full/)를
참고하십시오.
### 시계열 워크로드의 특성
많은 시계열 시스템은 새 행을 지속적으로 입력하고, 특정 대상의 시간 범위를 조회합니다.
최신 값을 확인하는 모니터링과 장기간 통계를 계산하는 분석을 함께 수행하기도 합니다.
이런 패턴이 추가 중심 입력, 구간 조회와 ROLLUP을 사용하는 이유입니다.
그러나 과거 데이터가 항상 불변인 것은 아닙니다. 장비 시각의 오류, 센서 보정이나
중복 수집으로 정정이 필요할 수 있습니다. 최근 데이터보다 오래된 데이터를 자주 읽는
업무도 있습니다. 실제 입력·조회·정정 패턴을 측정해 설계하십시오.
### 테이블 타입의 역할
| 테이블 타입 | 개념적 역할 |
|---|---|
| TAG | 이름과 시간·거리 축을 가진 계측 이력 |
| LOG | 새 기록을 계속 추가하는 이벤트와 로그 |
| TRANSACTION | 트랜잭션과 행 단위 변경이 필요한 업무 데이터 |
| LOOKUP | 메모리에 적재해 참조하는 영속 기준 정보 |
| VOLATILE | 재시작 후 다시 만들 수 있는 공유 메모리 상태 |
이력과 기준 정보를 분리하면 장비 이름이나 위치를 모든 측정 행에 반복 저장할 필요가
줄어듭니다. 다만 최신 기준 정보와 과거 이력을 조인하면 결과에도 최신 이름이나 위치가
표시됩니다. 발생 당시의 정보가 필요하면 해당 값을 이력에 남기거나 기준 정보의 변경
이력을 별도로 설계해야 합니다.
### 관계형 업무 모델과 시계열 모델
관계형 모델과 시계열 모델은 서로 배타적인 개념이 아닙니다. 관계형 DBMS에서도
시계열을 저장하고 인덱스·파티션·집계를 사용할 수 있으며, Machbase도 테이블과 SQL,
조인과 관계형 변경 기능을 제공합니다. 비교할 때는 제품 이름보다 주된 작업을 기준으로
판단합니다.
| 관점 | 관계형 업무 데이터의 예 | 시계열 이력의 예 |
|---|---|---|
| 행의 의미 | 주문 한 건의 현재 상태 | 센서 측정 한 건 또는 이벤트 한 건 |
| 변경 패턴 | 키로 찾은 행의 수정·삭제 | 새 기록 추가, 필요한 범위의 정정·정리 |
| 조회 패턴 | 키 조회, 조건 검색, 업무 테이블 조인 | 대상·시간 범위 조회, 추이와 구간 통계 |
| 일관성 요구 | 여러 변경을 함께 확정하거나 취소 | 수집 누락·중복·지연과 조회 가능 시점 관리 |
| 보관 설계 | 업무 수명과 변경 이력 | 원본 해상도, 집계 주기와 보관 기간 |
Machbase의 LOG·TAG 저장과 입력 특성을 TRANSACTION·LOOKUP·VOLATILE에 그대로 적용하지
마십시오. 최종 선택은 [테이블 타입 선택](../../data-modeling-table-design/table-types-selection-type/)과
[데이터 변경 정책](../../data-modeling-table-design/alter-data-mutation-policy/)에서 확인합니다.
## 쓰기 중심 워크로드와 append-only 모델
append는 기존 값의 덮어쓰기 대신 새 행을 추가하는 방식입니다. 예를 들어 설비 상태가
`RUNNING`에서 `STOPPED`로 바뀔 때 새 이벤트를 기록하면 이전 상태와 전환 시점을
보존할 수 있습니다. 현재 상태를 바로 찾는 테이블과 상태 변화 이력을 저장하는 테이블을
별도로 운영할 수도 있습니다.
### 테이블 타입과 변경 모델
LOG는 추가 중심 이력 테이블이며 일반적인 행 `UPDATE`를 지원하지 않습니다. TAG는
계측 이력을 추가하고, 지원되는 조건과 Edition 범위에서 DATA 값을 보정할 수 있습니다.
태그 이름이나 시간축까지 임의로 수정하는 일반 행 변경과는 다릅니다.
TRANSACTION은 관계형 DML과 명시적 트랜잭션을 제공하고, LOOKUP·VOLATILE은 기준 정보나
상태 변경에 사용합니다. 한 테이블의 변경 기능이 다른 유형에서도 같다고 가정하지 말고
[데이터 변경 정책](../../data-modeling-table-design/alter-data-mutation-policy/)을 확인합니다.
추가 중심 설계는 반복 입력과 과거 행의 임의 갱신을 함께 처리할 때의 경합을 줄이는 데
도움이 됩니다. 그렇다고 DBMS 내부의 동기화나 장애 복구 처리가 모두 없어지는 것은
아닙니다. 저장 구조만으로 잠금 없음이나 특정 처리량을 보장할 수는 없습니다.
### SQL 입력과 SDK Append
SQL `INSERT`는 SQL 문장으로 값을 입력하고 실행 결과를 확인하는 경로입니다.
SDK Append는 연속 수집을 위해 여러 행을 전송하는 입력 API입니다. 버퍼링, 전송, 오류
확인과 종료 절차를 해당 SDK의 계약에 맞게 처리해야 합니다.
데이터를 애플리케이션 버퍼에 넣은 시점, 서버가 처리한 시점과 장애 뒤에도 복구할 수 있는
시점을 같은 것으로 취급하지 마십시오. LOG·TAG의 Append는 TRANSACTION 테이블의 명시적
트랜잭션에서 `ROLLBACK`할 대상이 아닙니다. TRANSACTION 테이블에 대한 Append의
트랜잭션 참여와 오류 처리는 사용하는 SDK의 계약을 확인합니다. 입력 확인과 재시도 방법은
[데이터 입력과 반출](../../development-tools-integration/data-input-load-export/)을
참고하십시오.
### 정정, 스키마 변경과 보관
잘못된 LOG 이벤트는 보정 이벤트를 추가하는 방식으로 이력을 남길 수 있습니다.
삭제 후 다시 입력할 때는 LOG에서 허용하는 시간 기반 삭제 범위와 입력 순서를 먼저
확인해야 합니다. 특정 행 하나만 임의로 지울 수 있다고 가정하지 마십시오.
TAG 값 보정은 [TAG 데이터 보정 설계](../../tag-table-usage/tag-data-update-correction/#design-correction-tag)를
따릅니다.
컬럼 추가·삭제와 타입 변경도 별개의 기능입니다. LOG나 TAG의 모든 스키마 변경이
금지된 것은 아니며, 유형과 DATA·METADATA 영역에 따라 지원 범위가 다릅니다.
[DDL 문법](../../reference/sql/syntax/ddl-syntax/)을 확인한 뒤 적용합니다.
원본을 계속 추가하면 저장 공간이 증가합니다. 원본 보관 기간과 ROLLUP 통계의 사용 목적을
별도로 정하십시오. 집계 생성만으로 원본이 삭제되지는 않습니다.
## 시간 모델과 _arrival_time
### 발생 시각과 수신 시각
발생 시각은 장비에서 측정하거나 사건이 발생한 시각이고, 수신 시각은 DBMS가 기록을
받은 시각입니다. 09:00에 측정한 값을 네트워크 복구 후 09:05에 받았다면 두 시각의
차이는 5분입니다. 측정 추이를 보려면 발생 시각이, 수집 지연을 보려면 두 시각의 비교가
필요합니다.
시간 데이터에는 정밀도, 시간대와 시계 정확도도 구분해야 합니다. 나노초 단위로 표현할 수
있다는 사실이 센서 시계도 나노초 단위로 정확하다는 뜻은 아닙니다. 문자열을 DATETIME으로
해석하거나 표시할 때의 시간대는
[시간대 설정](../../reference/configuration/configuration-timezone/)을 참고하십시오.
### LOG 테이블의 `_arrival_time`
LOG에는 `_arrival_time` DATETIME 컬럼이 자동으로 추가됩니다. 값을 생략하는 일반적인
입력에서는 서버 입력 시각을 기록합니다. 별도의 발생 시각이 필요하면 일반 DATETIME
컬럼을 정의합니다.
```sql
CREATE LOG TABLE device_events (
device_id VARCHAR(20),
event_time DATETIME,
status VARCHAR(20)
);
```
`_arrival_time`을 명시하는 입력 경로도 있으므로 이 컬럼이 언제나 실제 수신 시각이라고
단정할 수는 없습니다. 명시 입력의 시간 순서와 제약은
[LOG 시간 모델](../../log-table-usage/arrival-time-model/)을 확인합니다.
```sql
SELECT device_id, event_time, status
FROM device_events
WHERE _arrival_time >= TO_DATE('2026-07-03 09:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND _arrival_time < TO_DATE('2026-07-03 10:00:00', 'YYYY-MM-DD HH24:MI:SS')
ORDER BY _arrival_time;
```
시작 시각은 포함하고 끝 시각은 제외하는 `[시작, 끝)` 범위를 사용하면 이어지는 구간의
경계값을 두 번 집계하는 실수를 줄일 수 있습니다. `BETWEEN`은 양쪽 경계를 모두
포함하므로 목적에 맞게 선택합니다. LOG의 `DURATION`은 `_arrival_time`을 기준으로
구간을 제한합니다.
### TAG 테이블의 BASETIME 컬럼
시간축 TAG 테이블은 `BASETIME` 속성의 DATETIME 컬럼에 애플리케이션이 시각을
입력합니다. LOG의 자동 `_arrival_time` 컬럼을 사용하지 않습니다.
```sql
CREATE TAG TABLE sensor_values (
name VARCHAR(128) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE SUMMARIZED
);
INSERT INTO sensor_values
VALUES ('temp_sensor_01',
TO_DATE('2026-07-03 08:55:00', 'YYYY-MM-DD HH24:MI:SS'),
23.1);
```
이 예에서 `name`은 센서를, `time`은 측정 시각을, `value`는 측정값을 나타냅니다.
`SUMMARIZED`는 ROLLUP 등 관련 통계 기능이 사용할 대표 숫자 컬럼을 지정합니다.
테이블을 생성한 것만으로 원하는 모든 주기의 집계가 생기는 것은 아닙니다.
TAG에는 시간 대신 수치 축을 사용하는 BASE DISTANCE 모델도 있습니다. 거리·위치에 따른
계측에는 이 모델을 검토하되 시간축 전용 함수와 정책을 그대로 적용하지 마십시오.
[TAG 스키마](../../tag-table-usage/table-structure-schema/)에서 축별 규칙을 설명합니다.
### 두 시간 모델의 비교
| 항목 | LOG | 시간축 TAG |
|---|---|---|
| 특수 시간 컬럼 | 자동 생성되는 `_arrival_time` | 선언한 `BASETIME` 컬럼 |
| 입력 시각의 의미 | 기본 입력에서는 서버 시각, 명시 입력은 해당 값 | 애플리케이션이 지정한 시각 |
| 별도 발생 시각 | 일반 DATETIME 컬럼에 저장 가능 | BASETIME을 발생 시각으로 사용 가능 |
| 주요 설계 질문 | 어떤 이벤트와 필드를 검색할 것인가 | 어떤 태그의 어느 구간을 분석할 것인가 |
발생 시각이 필요하다는 이유만으로 LOG를 배제할 필요는 없습니다. 시간 의미와 함께
태그 구조, 검색·집계, 수정·삭제 조건으로 테이블을 선택하십시오.
LOOKUP·VOLATILE·TRANSACTION의 DATETIME은 일반 컬럼이며 LOG·TAG의 특수 시간축 기능이
자동으로 부여되지 않습니다.
행이 저장된 순서와 결과를 보여 줄 순서도 구분합니다. 결과 순서가 중요하면
`ORDER BY`를 명시하고, 동일 시각의 행까지 구분해야 하면 추가 정렬 기준을 정합니다.
---
title: "2.2 저장 및 실행 구조"
url: https://docs.machbase.com/kr/dbms/core-concepts/storage-execution-architecture/
language: kr
kind: page
---
# 2.2 저장 및 실행 구조
조회 시간은 SQL 문장의 길이만으로 결정되지 않습니다. 얼마나 많은 데이터를 읽는지,
조건으로 읽을 범위를 얼마나 줄일 수 있는지, 정렬·조인·집계에 어떤 처리가 필요한지가
중요합니다. 이 절에서는 저장, 인덱스와 캐시를 각각 어떤 비용을 줄이는 기술인지
설명합니다.
## Machbase 아키텍처 개요
사용자는 `machsql`이나 SDK를 통해 서버에 입력·조회 요청을 보냅니다. 서버는 SQL의 문법,
객체와 권한을 확인하고 실행 방법을 결정한 뒤 저장 데이터에 접근해 결과를 반환합니다.
저장·실행 방식은 테이블 유형과 Edition에 따라 다릅니다.
### Standard Edition 구조
Standard Edition은 단일 데이터베이스 서버에서 SQL 처리와 데이터 저장을 수행합니다.
LOG·TAG의 시계열 입력뿐 아니라 TRANSACTION의 관계형 변경, LOOKUP·VOLATILE의
기준 정보와 상태 관리도 각각의 테이블 특성에 맞는 경로로 처리합니다.
```text
클라이언트: machsql 또는 SDK
│ 입력·조회 요청
▼
Machbase DBMS 서버
SQL 분석과 실행 계획
테이블별 저장·인덱스 접근
메모리 버퍼와 백그라운드 처리
│
▼
테이블 유형에 따른 저장 데이터
```
이 그림은 역할을 나타낸 개념도입니다. 모든 요청이 같은 저장 경로를 거치거나 API 호출
즉시 디스크 파일에 기록된다는 의미는 아닙니다.
### Cluster Edition 구조
Cluster Edition은 여러 노드가 역할을 나누어 수행합니다. 일반적인 애플리케이션 SQL
접속은 Broker를 통해 이루어지며, Warehouse는 시계열 데이터를 저장하고 쿼리를 실행합니다.
Coordinator는 클러스터 메타데이터와 노드 상태를 관리하고, Lookup은 기준 정보 처리를,
Deployer는 배포와 노드 관리를 담당합니다.
데이터를 여러 그룹에 분산하는 것은 처리량과 용량을 나누는 일이고, 같은 그룹에서
복제하는 것은 장애에 대비하는 일입니다. 두 목적은 다릅니다. 노드를 추가한다고 모든
쿼리가 같은 비율로 빨라지거나 모든 장애를 자동으로 극복하는 것은 아닙니다.
구성별 기능 차이는 [Edition 개념](../concepts-edition/)에서, 실제 배포와 장애 대응은
[Cluster 설치](../../installation-deployment-upgrade/cluster-edition/)와
[Cluster 운영](../../operations-configuration-recovery/cluster/)에서 확인합니다.
### 입력과 조회의 흐름
입력은 대상 테이블·컬럼 확인, 값 변환과 검증, 데이터 전달·저장 단계로 생각할 수 있습니다.
SQL과 Append API는 처리 결과를 확인하는 방식이 다르므로 애플리케이션은 선택한 경로의
오류 처리와 완료 조건을 따라야 합니다.
조회는 SQL 분석과 실행 계획 수립, 대상 데이터 접근, 조건 평가와 집계·조인·정렬,
결과 반환으로 이어집니다. 모든 데이터가 반드시 같은 순서로 처리되는 것은 아니며
실제 실행 계획에 따라 접근 순서가 달라집니다.
## 컬럼형 저장과 압축
### 행 지향과 컬럼 지향
행 지향 저장은 한 행에 속한 값을 함께 다루는 방식이고, 컬럼 지향 저장은 같은 컬럼의
값을 묶어 다루는 방식입니다. 아래 그림은 두 방식의 차이를 나타낸 예시이며
실제 파일 배치를 그대로 표현한 것은 아닙니다.
```text
논리적 행:
(시각1, 센서A, 23.1)
(시각2, 센서A, 23.5)
(시각3, 센서B, 18.0)
행 지향: [시각1, 센서A, 23.1] [시각2, 센서A, 23.5] ...
컬럼 지향: [시각1, 시각2, 시각3] [센서A, 센서A, 센서B] [23.1, 23.5, 18.0]
```
Machbase의 LOG·TAG 시계열 저장은 컬럼 단위 접근과 압축을 활용합니다. 많은 행에서 일부
컬럼만 읽는 분석에서는 읽을 데이터의 양을 줄이는 데 도움이 됩니다. 다만 온도 평균을
조회하더라도 센서·시각 조건이 있으면 해당 조건을 평가할 데이터도 필요합니다.
행 지향 시스템도 인덱스나 파티션을 이용해 필요한 범위만 읽을 수 있습니다.
“행 지향은 항상 전체 행을 읽고 컬럼 지향은 언제나 빠르다”는 식으로 비교하지 말고,
읽을 행·컬럼 수와 접근 경로를 함께 살펴보십시오. TRANSACTION의 관계형 저장이나
LOOKUP·VOLATILE의 메모리 특성을 LOG·TAG와 같은 구조로 해석해서도 안 됩니다.
### 압축이 효과적인 조건
같은 컬럼에는 같은 타입의 값이 모이고, 센서 값이나 시각에는 반복되거나 비슷한 패턴이
나타날 수 있습니다. 이런 특성은 압축에 유리합니다. 반면 잡음이 큰 값, 불규칙한 문자열과
입력 순서가 섞인 데이터는 다른 결과를 보일 수 있습니다.
시계열 시각이 반드시 단조 증가하는 것은 아닙니다. 늦게 도착한 측정값과 여러 수집원의
입력이 섞일 수 있습니다. 실제 압축률과 처리 성능은 타입, 값의 분포, 입력 순서와 설정에
따라 측정해야 하며 특정 압축 알고리즘이나 비율을 전제하지 않습니다.
### 파티션과 읽기 범위
파티션은 데이터를 관리 가능한 부분으로 나누는 단위입니다. 조회 조건과 저장된 범위
정보를 활용해 관련 없는 부분을 건너뛰면 읽기 작업을 줄일 수 있습니다. 이를 파티션
가지치기(partition pruning)라고 합니다.
LOG·TAG의 저장 단위가 사용자가 지정한 하루·한 달과 반드시 일치하는 것은 아닙니다.
태그와 축의 조건, 데이터 분포와 테이블별 저장 구조에 따라 실제 접근 범위가 달라집니다.
시간 조건을 작성했다는 이유만으로 필요한 데이터만 읽는다고 단정하지 말고 실행 계획과
측정 결과를 확인합니다.
## 인덱싱 기본 원리
인덱스는 조건에 맞는 데이터를 찾는 접근 경로입니다. 찾을 대상이 전체 데이터의 작은
부분이면 도움이 될 수 있지만, 대부분의 행을 읽는 집계에서는 다른 경로가 유리할 수
있습니다. 인덱스는 저장 공간과 입력·변경 시 관리 비용도 필요합니다.
| 테이블 유형 | 접근 경로를 생각할 때의 기준 |
|---|---|
| TAG | 태그 이름과 시간·거리 축의 범위 |
| LOG | `_arrival_time` 조건과 지원하는 검색 인덱스 |
| TRANSACTION | PRIMARY KEY, UNIQUE와 일반 인덱스 |
| LOOKUP·VOLATILE | 메모리에 적재된 키와 지원하는 보조 인덱스 |
예를 들어 “전체 센서의 한 달 평균”과 “센서 A의 최근 1분 값”은 읽을 데이터의 비율이
다릅니다. 같은 테이블이라도 두 쿼리에 같은 성능을 기대하기 어렵습니다.
조인에서는 각 입력의 행 수와 조인 조건도 중요합니다.
지원 인덱스와 제한은 [스키마 객체 정의](../../data-modeling-table-design/schema-objects-definition/)에서,
측정과 조정 방법은 [인덱스 튜닝](../../performance-tuning/index-tuning/)에서 확인합니다.
## Cache와 실행 계획 개념
### SQL 실행 과정
서버는 SQL의 문법과 타입·객체를 확인하고, 조건과 인덱스 등으로 실행 가능한 접근
경로를 정한 뒤 실제 데이터를 처리합니다. 실행 계획은 이 처리 방법을 나타냅니다.
계획을 만드는 비용과 계획에 따라 데이터를 읽는 비용은 서로 다릅니다.
### 실행 계획 재사용과 PVO Cache
PVO Statement Cache는 재사용 가능한 SQL의 파싱·검증·최적화 결과와 실행 계획을
재사용해 반복 준비 비용을 줄입니다. 조회 결과 행을 저장하는 캐시가 아니므로 계획을
재사용해도 데이터를 읽고 조건을 평가하는 작업은 필요합니다.
Min-Max Cache처럼 저장 데이터의 범위 정보를 활용하는 캐시는 읽을 대상을 줄이는
목적을 가집니다. 실행 계획 캐시와 데이터 접근용 캐시를 하나로 생각하지 마십시오.
어떤 캐시를 크게 할지는 적중률과 메모리 사용량을 확인한 뒤 판단합니다.
### EXPLAIN으로 실행 계획 확인
다음 예제는 [데이터 모델 개념](../concepts/#time-model-arrival-time)에서 만든
`sensor_values` 테이블을 사용합니다.
```sql
EXPLAIN SELECT AVG(value)
FROM sensor_values
WHERE name = 'temp_sensor_01'
AND time >= TO_DATE('2026-07-01', 'YYYY-MM-DD')
AND time < TO_DATE('2026-07-03', 'YYYY-MM-DD');
```
실행 계획으로 데이터 접근과 조건 적용 방법을 확인합니다. 실행 계획이 존재한다는
사실만으로 실제 응답 시간이나 디스크 읽기량까지 알 수 있는 것은 아니므로 대표
데이터에서 실행 시간도 함께 측정합니다. 캐시가 비어 있는 첫 실행과 재사용한 실행의
조건을 구분하면 결과를 해석하기 쉽습니다.
PVO Cache의 적중·제거 통계는 `V$PVO_CACHE_STAT`, 캐시된 SQL은
`V$PVO_CACHE_LIST`에서 확인합니다. 설정과 진단은
[캐시와 메모리 튜닝(영문)](/dbms/performance-tuning/cache-tuning-memory/#pvo-cache)을
참고하십시오.
---
title: "2.3 주요 기능과 용어 구분"
url: https://docs.machbase.com/kr/dbms/core-concepts/features-concepts/
language: kr
kind: page
---
# 2.3 주요 기능과 용어 구분
Machbase DBMS의 장기 데이터 운영에 필요한 ROLLUP, Retention Policy, Backup·Restore·Mount의
역할과 선택 기준을 설명합니다. 생성 구문과 운영 절차는 각 기능의 상세 문서에서 다룹니다.
원본은 개별 사건이나 측정값을 다시 분석하는 데 사용하고, 집계는 많은 원본을 요약해
반복 조회에 사용합니다. 보관 정책은 무엇을 언제 삭제할지 정하고, 백업은 장애가 발생했을 때
복구할 자료를 준비합니다. 이 목적들을 구분해야 집계가 남아 있다는 이유로 필요한 원본을
삭제하거나, 복제가 있다는 이유로 백업을 생략하는 일을 피할 수 있습니다.
- **[ROLLUP 통계의 역할](#role-statistics-rollup)** -- 반복 집계의 조회 비용을 줄이는 방법
- **[Retention Policy의 역할](#role-retention-policy)** -- 기간에 따라 데이터를 자동 정리하는 방법
- **[Backup·Restore·Mount의 관계](#concepts-backup-restore-mount)** -- 보호, 복구, 조회 목적의 구분
## ROLLUP 통계의 역할
TAG 테이블의 장기간 데이터를 매번 원시 행에서 집계하면 조회 범위가 커질수록 처리 비용도
증가합니다. ROLLUP은 시간축 TAG의 지정한 숫자 컬럼 등을 구간별로 집계해 반복 조회에
사용하는 기능입니다. 집계할 컬럼과 집계 방식은 ROLLUP 정의에 따라 결정됩니다.
기본 ROLLUP은 초(SEC), 분(MIN), 시간(HOUR) 계층을 구성합니다. 테이블을 만들 때
`WITH ROLLUP`을 지정하거나, 일반 `CREATE ROLLUP`으로 필요한 주기와 조건을 지정할 수 있습니다.
생성되는 객체의 이름이나 저장 구조에 의존하지 말고 공개된 ROLLUP SQL로 관리하십시오.
### 선택 기준
| 조회 패턴 | 선택 |
| --- | --- |
| 장기간의 분·시간 단위 통계를 반복 조회 | 기본 ROLLUP 검토 |
| 집계 주기나 필터 조건을 직접 지정 | 주기·조건을 지정한 일반 ROLLUP 검토 |
| 직접 작성한 집계 SELECT의 결과를 별도 TAG에 저장 | Standard Edition의 Custom ROLLUP 검토 |
| 첫 값·마지막 값이 필요 | 확장 ROLLUP 검토 |
| 원시 값 조회가 대부분이거나 집계 빈도가 낮음 | ROLLUP 없이 시작하고 실행 시간을 측정 |
ROLLUP은 원시 데이터를 대신하는 보관 정책이 아닙니다. 원시 데이터의 보관 기간은 Retention
Policy로 별도 설계하고, TAG 데이터를 수정했다면 해당 범위의 ROLLUP 재구성 여부도 확인합니다.
### 집계 결과를 해석할 때
집계는 정보의 해상도를 낮춥니다. 1분 평균만 남기면 그 1분 안에 있었던 순간적인 이상값이나
개별 측정 순서를 복원할 수 없습니다. 최대·최소값을 함께 보관하면 범위를 알 수 있지만,
이 역시 원본의 모든 정보를 보존하는 것은 아닙니다.
여러 구간의 평균을 다시 평균 내면 전체 평균과 달라질 수 있습니다. 예를 들어 2건의
평균이 10이고 8건의 평균이 20이면 전체 평균은 `(2 × 10 + 8 × 20) / 10 = 18`이며,
두 평균을 단순히 평균 낸 15가 아닙니다. 재집계에는 합계와 유효 건수 등 필요한 통계를
함께 사용하고, ROLLUP의 지원 조회 함수를 따릅니다.
ROLLUP 처리는 원본 입력과 별도로 진행되므로 최신 원본과 집계의 반영 시점이 다를 수
있습니다. 지연 도착이나 값 보정이 있다면 원본 범위, 집계 진행 상태와 재구성 필요성을
함께 확인합니다.
자세한 생성 구문, 조회 함수, 재구성 절차는 [TAG·ROLLUP 활용](/dbms/tag-rollup-usage/)을
참고하십시오.
## Retention Policy의 역할
Retention Policy는 LOG 또는 TAG 테이블에서 보관 기간을 넘긴 데이터를 정해진 주기로
정리합니다. 데이터가 계속 유입되는 테이블의 저장 공간을 운영자가 반복적인 삭제 작업 없이
관리할 때 사용합니다.
| 요구사항 | 선택 |
| --- | --- |
| 일정 기간이 지난 데이터를 계속 자동 정리 | Retention Policy |
| 잘못 입력한 특정 범위를 즉시 제거 | 테이블 유형이 지원하는 `DELETE` |
| 지원 테이블의 전체 데이터를 비움 | `TRUNCATE TABLE` |
Retention은 적용 즉시 모든 오래된 데이터가 사라진다는 의미가 아닙니다. 정책의 실행 주기,
대상 테이블 지원 범위와 실제 삭제 상태를 함께 확인해야 합니다. LOOKUP, VOLATILE,
TRANSACTION 테이블의 수명 관리는 해당 테이블이 지원하는 명시적 DML로 처리합니다.
보관 기간은 입력량과 함께 저장 용량을 결정합니다. 초당 입력 건수에 보관 초 수를 곱하면
원본 행 수의 대략적인 규모를 예상할 수 있지만, 실제 디스크 용량에는 타입·압축·인덱스·
복제·백업이 영향을 줍니다. 원본을 지우기 전에 집계의 범위와 보관 기간, 감사·재분석에
필요한 해상도를 확인합니다. ROLLUP을 생성했다고 원본의 보관 기간이 자동 변경되지는 않습니다.
정책 생성·적용·해제 구문과 운영 점검은
[데이터 보존 정책](/dbms/operations-configuration-recovery/policy-data-retention/)을
참고하십시오.
## Backup·Restore·Mount의 관계
세 기능은 모두 백업 데이터와 관련되지만 결과가 다릅니다.
| 기능 | 목적 | 운영 서버 | 결과 |
| --- | --- | --- | --- |
| Backup | 복구에 사용할 복사본 생성 | 실행 중 수행 가능 | 별도 경로에 백업 생성 |
| 인스턴스 Restore | 백업으로 인스턴스 복구 | 오프라인 절차 필요 | 운영 데이터베이스를 복구 |
| Mount | 백업 내용을 읽기 전용으로 확인 | 실행 중 수행 가능 | 백업을 별도 이름으로 조회 |
인스턴스 복원과 별개로, 논리 데이터베이스를 복원하는 `RESTORE DATABASE` SQL도 있습니다.
이 명령은 실행 중인 서버에서 수행하므로 오프라인 인스턴스 복원과 대상·절차를 구분합니다.
Backup이 성공했다는 사실만으로 복구 절차까지 검증된 것은 아닙니다. 지원하는 Edition에서
Mount로 내용을 조회하거나 격리 환경에서 Restore를 수행해 백업을 검증합니다. 백업 경로의
권한과 보존 주기도 함께 관리하십시오. Mount는 백업을 운영 데이터로 되돌리지 않으며,
마운트한 데이터에는 쓸 수 없습니다. Restore와 Mount는 Standard Edition 기능이므로
Cluster 환경에서는 해당 Edition의 백업·장애 복구 절차를 확인합니다.
운영 계획에서는 허용 가능한 데이터 손실 구간(RPO)과 서비스 복구에 허용되는 시간(RTO)을
정합니다. 전자는 백업·복제 간격을, 후자는 복구할 데이터 규모와 실제 복원 시간을 검토하는
기준입니다. 특정 Edition이나 백업 주기만으로 두 목표가 자동 보장되지는 않습니다.
복제본에도 잘못된 삭제가 반영될 수 있으므로 복제와 백업의 목적을 구분합니다.
명령, 권한, Edition별 지원 범위와 복구 순서는
[백업·복원·마운트](/dbms/operations-configuration-recovery/backup-restore-mount/)를
참고하십시오. 여러 logical database를 운용한다면
[다중 데이터베이스 운영](/dbms/operations-configuration-recovery/multi-database/)도 함께
확인하십시오.
## 입력 경로 비교
작은 SQL 실습에는 `INSERT`, 애플리케이션의 연속 수집에는 지원 SDK의 Append, 파일 적재에는
`machloader` 같은 도구를 검토합니다. 도구마다 지원 테이블, 입력 형식과 실패 확인 방법이
다르므로 이름이 비슷하다는 이유로 서로 바꿔 사용할 수는 없습니다.
SQL 입력, SDK, 파일 적재 도구의 선택 기준은
[데이터 입력과 반출](/dbms/development-tools-integration/data-input-load-export/#machloader-vs-csvimport-csvexport-tagmetaimport)을
참고하십시오.
---
title: "2.4 Edition 개념"
url: https://docs.machbase.com/kr/dbms/core-concepts/concepts-edition/
language: kr
kind: page
---
# 2.4 Edition 개념
Edition은 배포 구조와 기능 지원 범위를 결정합니다. Standard는 단일 서버에서 시작하는
구성이고, Cluster는 여러 노드가 저장과 처리를 나누는 구성입니다. 현재 데이터 크기뿐
아니라 필요한 SQL 기능, 증가율, 장애 대응과 운영 역량을 함께 고려해 선택합니다.
## Standard Edition과 Cluster Edition 차이
### Standard Edition
하나의 DBMS 서버에서 SQL 처리와 데이터 저장을 수행합니다. 분산 노드 간의 배포·통신을
관리할 필요가 없어 개발과 단일 서버 운영을 시작하기에 적합합니다. TRANSACTION 테이블과
Restore·Mount 등 Standard 전용 기능이 필요한지도 확인합니다.
단일 서버에서 시작한다고 데이터량이 반드시 작아야 하는 것은 아닙니다. 입력량, 조회
부하와 보관 기간이 해당 서버의 CPU·메모리·스토리지 범위에 맞는지를 측정해 판단합니다.
### Cluster Edition
Coordinator, Deployer, Broker, Warehouse와 Lookup 노드가 역할을 나눕니다.
| 노드 유형 | 역할 |
|---|---|
| Coordinator | 클러스터 메타데이터와 노드 상태 관리 |
| Deployer | 패키지 배포와 노드 관리 |
| Broker | 애플리케이션 SQL 접속과 쿼리 분배 |
| Warehouse | 시계열 데이터 저장과 쿼리 실행 |
| Lookup | 클러스터 기준 정보 처리 |
일반적인 SQL 애플리케이션은 Broker에 접속합니다. 관리 도구의 접속 대상과 포트는
역할별로 다르므로 SQL 접속 주소와 관리 주소를 구분합니다.
여러 Warehouse 그룹으로 데이터를 나누는 분산과 같은 그룹 안의 데이터 복제는
서로 다른 목적입니다. 분산은 용량·처리량 확장에, 복제는 장애 대응에 사용합니다.
노드 수, 복제 상태, 클라이언트 재접속과 장애 시 운영 절차를 함께 설계해야 합니다.
### 기능 지원 차이
| 확인할 항목 | Standard Edition | Cluster Edition |
|---|---|---|
| 주요 배포 구조 | 단일 DBMS 서버 | 역할을 나눈 여러 노드 |
| TRANSACTION 테이블 | 지원 | 미지원 |
| Restore·Mount | 지원 | 미지원 |
| 용량·처리 확장 | 서버 자원과 저장 구성 확장 | 분산 그룹과 노드 구성 확장 |
| 장애 대응 설계 | 백업과 복구 절차, 서버 운영 계획 | 노드·그룹 복제와 상태, 접속·복구 절차 |
LOG·TAG·LOOKUP·VOLATILE, ROLLUP과 Retention 등 공통 기능도 세부 DML·DDL·운영 제약이
모두 같지는 않습니다. 전체 기능 지원 여부는
[Edition 지원 범위](../../reference/support-scope-constraints/edition/)와
[테이블 타입별 지원 범위](../../reference/support-scope-constraints/table-types-type/)를
확인합니다.
### 선택 기준
1. 필수 기능을 확인합니다. TRANSACTION이나 Restore·Mount가 필요하면 Standard 지원
범위를 먼저 검토합니다.
2. 대표 입력과 조회를 실행합니다. 데이터 타입, 동시 접속, 조회 구간과 보관 기간을
실제 업무에 가깝게 맞춰 단일 서버의 여유 자원을 확인합니다.
3. 데이터 증가율과 장애 요구사항을 반영합니다. 단일 서버를 넘어 분산이 필요하다면
Cluster의 네트워크, 복제와 노드 운영 비용까지 검토합니다.
4. 장애 시나리오를 시험합니다. 노드 감시가 된다는 사실과 서비스가 중단 없이 복구되는
것은 다르므로, 복구 시간과 애플리케이션의 재접속·재시도 동작을 확인합니다.
운영 중 Edition을 바꿀 때도 단순히 서버 수만 늘리는 작업으로 생각해서는 안 됩니다.
사용 중인 SQL·SDK와 데이터 이전·백업·복구 방법의 호환성을 먼저 확인합니다.
## 관련 문서
- [저장 및 실행 구조](../storage-execution-architecture/)에서 처리 흐름을 설명합니다.
- [설치, 배포, 업그레이드](../../installation-deployment-upgrade/)에서 배포 절차를 안내합니다.
- [버전 및 호환성](../../reference/support-scope-constraints/compatibility-version/)에서
버전별 지원 범위를 확인합니다.
- [관계형 업무 모델과 시계열 모델](../concepts/#differences-rdbms)에서 데이터 모델의
선택 기준을 설명합니다.
---
title: "3. 설치, 배포, 업그레이드"
url: https://docs.machbase.com/kr/dbms/installation-deployment-upgrade/
language: kr
kind: section
---
# 3. 설치, 배포, 업그레이드
이 장에서는 Machbase DBMS 8.7.0을 설치하고, 데이터 입력과 조회가 가능한 상태인지
확인합니다. 새 서버를 준비하는 작업과 기존 데이터를 유지하면서 업그레이드하는 작업은
출발점이 다릅니다. 먼저 필요한 기능과 배포 환경을 정하고 자신의 상황에 맞는 절차를 선택합니다.
2장에서 살펴본 데이터 모델과 Edition의 차이는 설치에도 영향을 줍니다. TRANSACTION
테이블이나 Restore·Mount가 필요하다면 Standard Edition의 지원 범위를 확인합니다.
분산 저장과 복제가 필요하다면 Cluster의 노드 역할, 네트워크와 장애 대응을 함께 설계합니다.
## 설치 경로 선택
| 에디션 | 배포 구조 | 선택할 때 확인할 점 |
|---|---|---|
| Standard Edition | 하나의 DBMS 서버가 SQL 처리와 저장 수행 | 필요한 기능과 입력·조회·보관 부하를 서버 자원으로 처리할 수 있는지 확인 |
| Cluster Edition | Coordinator·Deployer·Lookup·Broker·Warehouse 역할 분리 | 분산 그룹, 복제, 통신 경로와 노드 운영 절차를 함께 준비할 수 있는지 확인 |
단일 서버가 소규모 데이터만 처리할 수 있다는 뜻은 아닙니다. 필요한 저장량과 성능을
대표 데이터로 측정한 뒤 판단합니다. 반대로 Cluster도 노드 수만 늘리면 모든 쿼리가
비례해서 빨라지는 것은 아닙니다. 자세한 선택 기준은
[에디션 차이점](/dbms/core-concepts/concepts-edition/#differences-standard-edition-cluster)을
참고하십시오.
### 설치 전에 구분할 대상
| 대상 | 의미 | 확인할 예 |
|---|---|---|
| 배포 패키지 | 실행 파일, 라이브러리와 샘플 설정 | 버전·Edition·OS·CPU 아키텍처 |
| 설치 홈 | 한 서버나 노드가 사용하는 실행·설정 경로 | `MACHBASE_HOME`, `conf/machbase.conf` |
| 데이터 저장 경로 | DBMS가 실제 데이터를 읽고 쓰는 위치 | `DBS_PATH`, 여유 공간과 접근 권한 |
| 서버 인스턴스·노드 | 해당 설정으로 실행되는 DBMS 프로세스 | 기동 상태, 로그와 접속 포트 |
| 논리 데이터베이스 | 접속 후 SQL 객체를 생성하고 사용하는 공간 | 현재 데이터베이스, 사용자·권한·테이블 |
패키지 압축 해제, 새 인스턴스의 데이터베이스 생성, 서버 시작과 테이블 생성은 서로 다른
단계입니다. 기존 데이터가 있는 홈에 신규 설치용 초기화 명령을 실행하지 마십시오.
설치 홈과 실제 데이터 경로도 다를 수 있으므로 백업·업그레이드 전에 둘 다 확인합니다.
SQL에서 사용하는 논리 데이터베이스는 설치 홈과 별개의 개념입니다.
여러 데이터베이스를 운용할 때의 선택·생성·권한은
[다중 데이터베이스 운영](/dbms/operations-configuration-recovery/multi-database/)에서 설명합니다.
## 설치 순서
### Standard Edition
1. [설치 전 준비](./pre-install-preparation/)에서 패키지, 서버 계정, 자원과 포트를 확인합니다.
2. 운영체제에 맞는 설치 절차에서 전용 홈과 환경 변수를 준비합니다.
- [Linux — Tarball 설치](./standard-edition/#linux-tarball)
- [Linux — Docker 설치](./standard-edition/#linux-docker)
- [Windows — 패키지 설치](./standard-edition/#windows-package)
3. 설정과 데이터 경로를 확인하고, 별도 라이선스를 사용할 경우 최초 기동 전에
[라이선스 설치](./pre-install-preparation/#license)를 수행합니다.
4. 선택한 설치 절차에 따라 새 데이터베이스를 생성하고 서버를 시작합니다.
5. [설치 검증 체크리스트](./validation-checklist/)에서 프로세스·포트·접속·버전·라이선스와
소량 데이터의 입력·조회를 확인합니다.
컨테이너를 사용하는 경우에도 패키지의 버전, 데이터 볼륨과 실행 계정의 쓰기 권한을
확인해야 합니다. 컨테이너 시작과 DBMS 초기화·시작의 관계는 사용하는 이미지에 따라
다르므로 해당 설치 절차를 따릅니다.
### Cluster Edition
1. [설치 전 준비](./pre-install-preparation/)와
[클러스터 환경 준비](./cluster-edition/#preparation-environment-cluster-edition)를 확인합니다.
2. 노드별 호스트, 홈, SQL·관리·노드 간 통신 포트와 Warehouse 복제 그룹을 정합니다.
3. 사용할 패키지와 라이선스를 준비하고 배포 방식을 선택합니다.
- [machclusterctl 배포](./cluster-edition/#machclusterctl): 구성 파일로 배포하고 실행 계획 확인
- [수동 설치](./cluster-edition/#manual-machcoordinatoradmin): 관리 도구로 단계별 등록·기동
4. 노드 역할에 맞는 상태와 복제 구성을 확인하고 Broker에 SQL로 접속합니다.
5. [설치 검증 체크리스트](./validation-checklist/)에서 데이터 입력·조회와 그룹 내 복제 상태를
구분해 점검합니다.
SQL 접속이 성공하는 것과 모든 복제본이 정상인 것은 별개의 확인입니다. 서로 다른
Warehouse 그룹이 반드시 같은 데이터를 가지는 것도 아닙니다. 장애 대응은
[Cluster 운영](/dbms/operations-configuration-recovery/cluster/)으로 이어서 확인합니다.
## 업그레이드
기존 시스템은 [업그레이드](./upgrade/)의 절차를 사용합니다. 새 패키지의 실행 가능 여부뿐
아니라 데이터 파일, SQL·SDK, 설정·라이선스와 백업·복구 경로의 호환성을 점검합니다.
운영 설정을 새 패키지의 샘플로 덮어쓰거나, 실행 파일을 바꾼 것만으로 업그레이드가
완료됐다고 판단하지 마십시오.
업그레이드 전의 기준 측정값과 백업을 확보하고, 격리 환경에서 복구에 필요한 시간도
확인합니다. “이전 버전으로 돌아갈 수 있다”는 판단에는 바이너리뿐 아니라 이전 버전이
읽을 수 있는 데이터와 설정이 필요합니다.
## 다음 단계
설치 검증은 운영 준비의 시작입니다. 먼저 [빠른 시작](/dbms/getting-started/quick-start/)으로
SQL 흐름을 익히고, [테이블 타입 선택과 스키마 설계](../data-modeling-table-design/)에서
실제 데이터를 모델링합니다. 운영 투입 전에는 전용 계정, 수집 오류 처리, 보관·백업,
대표 부하 시험과 관측 지표를 준비합니다.
[관측과 진단](/dbms/operations-configuration-recovery/diagnosis-observability/)과
[성능 튜닝 접근법](/dbms/performance-tuning/performance-approach/)에서 그 과정을 안내합니다.
---
title: "3.1 설치 전 준비"
url: https://docs.machbase.com/kr/dbms/installation-deployment-upgrade/pre-install-preparation/
language: kr
kind: page
---
# 3.1 설치 전 준비
설치 전 준비의 목적은 실행 파일을 복사할 위치뿐 아니라 데이터가 남을 위치, 서버를
실행할 계정과 클라이언트의 접속 경로를 확정하는 것입니다. 패키지와 운영체제의 호환성을
확인한 뒤 저장 공간, 네트워크와 라이선스를 준비합니다. 지원 범위는 제공받은 패키지의
릴리스 정보와 기술 지원 정책을 기준으로 판단합니다.
## 먼저 정할 배포 정보
| 항목 | 정할 내용 | 필요한 이유 |
|---|---|---|
| Edition과 버전 | Standard 또는 Cluster, 서버·SDK 버전 | 사용할 SQL 기능과 배포 방법 결정 |
| OS 계정 | 서버 실행 계정과 파일 소유자 | 설정·데이터·로그 경로의 접근 권한 일치 |
| 설치 홈 | 실행 파일과 설정이 있는 절대 경로 | 관리 명령이 조작할 인스턴스 식별 |
| 데이터 경로 | 실제 `DBS_PATH`, 파일 시스템과 여유 공간 | 재시작·업그레이드 때 보존할 데이터 식별 |
| 접속 정보 | 서버 주소, SQL 포트, 관리 포트와 허용 클라이언트 | 포트 충돌과 잘못된 인스턴스 접속 방지 |
| 복구와 라이선스 | 백업 저장소, 복원 절차, 사용할 라이선스 | 장애 대응과 운영 범위 확인 |
OS 계정 `machbase`와 DB 사용자 `SYS`는 다릅니다. 전자는 프로세스와 파일의 권한을,
후자는 SQL 접속과 데이터베이스 작업 권한을 결정합니다. 서버 시작에 성공해도 파일 권한이나
SQL 권한이 맞지 않으면 적재·백업 같은 작업은 실패할 수 있습니다.
| 항목 | 설명 |
|------|------|
| [설치 전 요구사항](/dbms/installation-deployment-upgrade/pre-install-preparation/#pre-install-requirements) | OS 버전, 하드웨어 최소 사양, 네트워크 포트 |
| [패키지 구성 이해](/dbms/installation-deployment-upgrade/pre-install-preparation/#package) | 패키지 파일 명명 규칙, 디렉터리 구조, 주요 실행 파일 |
| [라이선스 설치](/dbms/installation-deployment-upgrade/pre-install-preparation/#license) | license.dat 파일 배치 방법, 라이선스 상태 확인 방법 |
---
## 설치 전 요구사항
### 운영체제와 패키지
운영체제 종류와 CPU 아키텍처가 설치 패키지의 표기와 일치해야 합니다. 지원 운영체제와 최소
버전은 릴리스마다 바뀔 수 있으므로, 고정된 버전 표 대신 패키지와 함께 제공되는 릴리스 정보로
확인하십시오.
### 시스템 자원
CPU, 메모리, 디스크와 네트워크 요구량은 입력률, 보관 기간, 인덱스와 ROLLUP 구성에 따라
달라집니다. 설치 공간뿐 아니라 예상 원시 데이터, 백업과 운영 여유 공간을 포함해 산정하고,
실제 워크로드로 용량과 처리량을 검증하십시오. 초당 행 수와 보관 기간으로 원본 행 수를
예상하고, 대표 데이터를 적재해 행 크기·압축·인덱스의 실제 저장 비용을 측정합니다.
Cluster에서는 복제본 공간도 포함합니다. 같은 디스크의 다른 디렉터리는 별도의 장애
영역이나 독립적인 I/O 장치가 아닙니다.
### 기본 포트
| 포트 | 용도 |
|------|------|
| **5656** | SQL 클라이언트 접속 (Native TCP) |
SQL 클라이언트 포트를 변경하려면 `$MACHBASE_HOME/conf/machbase.conf`의 `PORT_NO`를
설정합니다. `MACHBASE_PORT_NO` 환경 변수도 사용하므로 서버를 시작하는 셸이나 서비스의
환경 변수를 함께 확인합니다. 변경한 포트는 클라이언트에도 명시하고, 이미 실행 중인
서버에 새 환경 변수가 소급 적용된다고 가정하지 마십시오.
방화벽이 있는 환경에서는 위 포트를 인바운드 허용으로 열어야 합니다. Cluster Edition은 Coordinator link/admin 포트와 Broker, Warehouse, Deployer 포트도 추가로 열어야 합니다.
### 시스템 커널 파라미터 (Linux)
Linux에 설치할 경우 아래 항목을 설치 전에 점검합니다.
#### 파일 디스크립터 한도
다수의 파일을 동시에 여는 워크로드에서는 낮은 파일 디스크립터 한도가 병목이 될 수 있습니다.
기본 한도는 운영체제와 계정 설정에 따라 다르므로 서버를 실행할 계정에서 확인합니다.
```bash
# 현재 값 확인
ulimit -Sn
```
이 설치 예제에서는 65535를 사용합니다. 한도가 이보다 작으면 `/etc/security/limits.conf`를
수정한 뒤 새 로그인 세션에서 적용 여부를 확인합니다.
```
* hard nofile 65535
* soft nofile 65535
```
서버를 실행할 계정으로 다시 로그인한 뒤 값을 확인합니다. 서비스 관리자를 통해 서버를
시작한다면 해당 서비스의 파일 디스크립터 한도도 별도로 확인합니다.
```bash
ulimit -Sn
# 출력: 65535
```
#### 포트 예약
Machbase 서비스 포트가 운영체제의 임시 포트 자동 할당에 사용되지 않도록 예약합니다.
이 설정은 다른 프로세스가 같은 포트를 명시적으로 사용하는 것까지 차단하지는 않습니다.
```bash
current=$(cat /proc/sys/net/ipv4/ip_local_reserved_ports)
ports=5656
sudo sysctl -w net.ipv4.ip_local_reserved_ports="${current:+$current,}$ports"
```
기존 예약 포트가 있으면 덮어쓰지 말고 쉼표로 구분해 병합합니다. 영구 적용은 `/etc/sysctl.conf`의 `net.ipv4.ip_local_reserved_ports` 항목을 다음 값과 병합합니다.
```
net.ipv4.ip_local_reserved_ports = 5656
```
### 시간 동기화
시계열 데이터를 처리하므로 서버의 시간이 정확해야 합니다. NTP 또는 `chrony`로 시스템 시간을 동기화하십시오. Cluster Edition에서는 모든 노드의 시간을 일치시켜야 합니다.
```bash
# 타임존 확인
ls -l /etc/localtime
date
```
---
## 패키지 구성 이해
### 패키지 파일 명명 규칙
패키지 파일 이름은 에디션에 따라 다음 형식을 따릅니다.
```
machbase-EDITION-VERSION-OS-CPU-BIT-MODE.EXT
```
| 항목 | 설명 | 예시 |
|------|------|------|
| EDITION | 에디션 구분 | `SDK`, `cluster` |
| VERSION | 버전 (Major.Minor.Fix.AUX) | `8.7.0.official` |
| OS | 운영체제 | `LINUX`, `WINDOWS` |
| CPU | CPU 아키텍처 | `X86` |
| BIT | 아키텍처 비트 수 | `64` |
| MODE | 빌드 모드 | `release` |
| EXT | 확장자 | `tgz` (Linux), `zip` 또는 설치 실행 파일 (Windows) |
Standard Edition Linux tarball은 `machbase-SDK-...tgz` 이름으로 생성됩니다.
예시:
- Standard Edition: `machbase-SDK-8.7.0.official-LINUX-X86-64-release.tgz`
- Cluster Edition: `machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz`
Minor 버전이 다르면 DB 파일과 프로토콜의 호환성이 달라질 수 있습니다. Fix 버전 변경을
포함한 실제 업그레이드 가능 경로는 대상 릴리스의 호환성 안내와
[업그레이드 절차](../upgrade/)에서 확인합니다.
### 설치 디렉터리 구조
tarball 압축을 해제하면 `$MACHBASE_HOME` 아래에 다음 구조가 생성됩니다.
```
$MACHBASE_HOME/
├── bin/ 실행 파일
├── conf/ 설정 파일 (machbase.conf 등)
├── dbs/ 데이터 저장 공간
├── doc/ 라이선스 문서
├── include/ C/C++ 헤더 파일
├── install/ Makefile용 mk 파일
├── lib/ 공유 라이브러리
├── package/ Cluster Edition 추가 패키지 경로
├── sample/ 예제 파일
├── trc/ 서버 로그 및 트레이스 파일
├── tutorials/ 튜토리얼
├── utility/ 유틸리티 파일
└── 3rd-party/ Grafana 플러그인 등
```
### 주요 실행 파일
| 실행 파일 | 설명 |
|-----------|------|
| `machbased` | 서버 데몬 |
| `machadmin` | 서버 관리 (시작·종료·DB 생성) |
| `machsql` | CLI 쿼리 도구 |
| `machloader` | 대용량 파일 적재·추출 도구 |
| `csvimport` | CSV 파일 가져오기 |
| `csvexport` | CSV 파일 내보내기 |
| `tagmetaimport` | TAG 메타 데이터 일괄 등록 |
Cluster Edition 패키지에는 `machcoordinatoradmin`, `machdeployeradmin` 등의 관리 도구가 추가됩니다.
`machclusterctl`은 해당 도구를 포함하도록 빌드된 패키지에서 사용할 수 있습니다.
### 설정 파일
`$MACHBASE_HOME/conf/` 아래에 에디션별 샘플 설정 파일이 있습니다.
```bash
ls $MACHBASE_HOME/conf/
# machbase.conf
# machbase.conf.sample.standard
# machbase.conf.sample.edge
# machloader.conf.sample
```
실제 사용 파일은 `machbase.conf`입니다. Standard full 패키지는 빌드 과정에서 `machbase.conf.sample.standard`를 복사해 `machbase.conf`를 포함합니다. 실제 파일이 없는 패키지에서는 에디션에 맞는 샘플 파일을 복사하여 수정합니다.
Standard/Edge 샘플에는 TRANSACTION 쓰기 충돌과 내구성 정책을 제어하는 `TRANSACTION_BUSY_TIMEOUT_MS`,
`TRANSACTION_SYNCHRONOUS`, `TRANSACTION_JOURNAL_MODE` 설정이 포함됩니다. TRANSACTION 테이블을 사용하는 환경에서는
기본값을 먼저 사용하고, 동시 쓰기와 내구성 요구를 검토한 뒤 값을 조정합니다.
---
## 라이선스 설치
라이선스 파일이 없으면 서버는 기본 `COMMUNITY` 라이선스 정보를 사용합니다. 설치 전에
이 범위가 예정한 기능·용량에 맞는지 확인하고, 별도 라이선스가 필요한 환경에서는
첫 서버 시작 전에 해당 파일을 준비합니다. 서버가 기동된다는 사실만으로 운영에 필요한
라이선스 조건을 충족했다고 판단하지 마십시오.
### 라이선스 상태 확인
설치된 라이선스의 상태와 제한 위반 여부는 `V$LICENSE_INFO`의 `VIOLATE_STATUS`와
`VIOLATE_MSG`로 확인합니다. 라이선스 파일의 본문은 수정하지 마십시오.
### 설치 방법
#### 방법 1: 파일 복사 (서버 시작 전)
`license.dat` 파일을 `$MACHBASE_HOME/conf/`에 복사합니다. 서버가 시작될 때 자동으로 인식합니다.
```bash
cp license.dat $MACHBASE_HOME/conf/license.dat
```
#### 방법 2: machadmin 명령
`machadmin`으로 라이선스 파일을 검증하고 설치합니다. 서버가 실행 중이면 라이선스 reload 요청을 함께 보냅니다.
```bash
machadmin -t /path/to/license.dat
```
#### 방법 3: SQL 쿼리 (서버 실행 중)
서버가 이미 실행 중인 경우 machsql에서 쿼리로 설치합니다.
```sql
ALTER SYSTEM INSTALL LICENSE = '/path/to/license.dat';
```
### 설치 확인
#### machadmin 확인
```bash
machadmin -f
```
#### V$LICENSE_INFO 뷰 조회
```sql
SELECT ID, ISSUE_DATE, TYPE, CUSTOMER, VIOLATE_STATUS, VIOLATE_MSG
FROM V$LICENSE_INFO;
```
`VIOLATE_STATUS`가 0이면 정상입니다.
machsql에서는 다음 명령으로도 라이선스 정보를 확인합니다.
```sql
SHOW LICENSE;
```
---
title: "3.2 Standard Edition 설치"
url: https://docs.machbase.com/kr/dbms/installation-deployment-upgrade/standard-edition/
language: kr
kind: page
---
# 3.2 Standard Edition 설치
Standard Edition은 한 서버에서 SQL 처리와 데이터 저장을 수행합니다. 설치는 패키지 준비,
서버 실행 환경 설정, 데이터베이스 생성, 라이선스 확인, 시작과 SQL 검증 순서로 진행합니다.
단일 서버라고 해서 소량 데이터만 다루는 것은 아니며, 처리량과 보관 기간에 필요한 자원을
실제 워크로드로 확인해야 합니다.
## 설치 경로
운영체제에 따라 아래 경로 중 하나를 선택합니다.
| OS | 설치 방식 | 링크 |
|----|-----------|------|
| Linux | Tarball (.tgz) | [Tarball 설치](/dbms/installation-deployment-upgrade/standard-edition/#linux-tarball) |
| Linux | Docker 컨테이너 | [Docker 설치](/dbms/installation-deployment-upgrade/standard-edition/#linux-docker) |
| Windows | ZIP 또는 설치 실행 파일 | [Windows 패키지 설치](/dbms/installation-deployment-upgrade/standard-edition/#windows-package) |
설치 전에 [Linux 환경 준비](/dbms/installation-deployment-upgrade/standard-edition/#linux-preparation-environment-linux) 또는 [Windows 환경 준비](/dbms/installation-deployment-upgrade/standard-edition/#windows-preparation-environment-windows)를 먼저 확인하십시오.
---
## Linux 설치
Linux에서 Standard Edition을 설치하는 방법은 두 가지입니다.
| 방식 | 적합한 상황 |
|------|------------|
| [Tarball 설치](/dbms/installation-deployment-upgrade/standard-edition/#linux-tarball) | 실제 서버 환경, 데이터 디렉터리를 직접 관리해야 하는 경우 |
| [Docker 설치](/dbms/installation-deployment-upgrade/standard-edition/#linux-docker) | 개발·테스트 환경, 빠른 구동이 필요한 경우 |
Tarball 설치는 [설치 전 준비](../pre-install-preparation/)를 먼저 완료해야 합니다. Docker
설치는 Docker Engine과 볼륨·포트 권한을 준비하십시오.
---
### Linux 환경 준비
파일 디스크립터 한도, 시간 동기화, 포트 예약과 방화벽 설정은 Edition 공통 작업입니다.
[설치 전 준비](../pre-install-preparation/)에서 서버를 실행할 계정과 운영 환경에 맞게
설정한 뒤 다음 설치 절차를 진행합니다.
---
### Tarball 설치
Linux 환경에 tarball(.tgz)을 압축 해제하여 Standard Edition을 설치하는 절차입니다.
#### 1. 사용자 생성
Machbase 전용 OS 사용자를 생성합니다.
```bash
sudo useradd -m -d /home/machbase machbase
sudo passwd machbase
```
이후 `machbase` 계정으로 로그인하여 작업합니다.
#### 2. 패키지 다운로드 및 압축 해제
예제에서는 패키지를 `/home/machbase/packages/`에 다운로드한 것으로 가정합니다.
아래 패키지 이름을 실제 배포본으로 바꾸고, 아직 인스턴스가 없는 새 설치 디렉터리에
압축을 해제합니다. 기존 설치의 갱신은 [업그레이드](../upgrade/) 절차를 사용합니다.
```bash
machbase_package=/home/machbase/packages/machbase-SDK-8.7.0.official-LINUX-X86-64-release.tgz
test -r "$machbase_package" &&
mkdir /home/machbase/machbase_home &&
tar zxf "$machbase_package" -C /home/machbase/machbase_home &&
cd /home/machbase/machbase_home
```
압축 해제 후 디렉터리 구조를 확인합니다.
```bash
ls -l
# bin/ conf/ dbs/ doc/ include/ lib/ trc/ ...
```
#### 3. 환경 변수 설정
`~/.bashrc`에 환경 변수를 추가합니다.
```bash
export MACHBASE_HOME=/home/machbase/machbase_home
export PATH="$MACHBASE_HOME/bin:$PATH"
export LD_LIBRARY_PATH="$MACHBASE_HOME/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
```
적용합니다.
```bash
source ~/.bashrc
```
#### 4. 데이터베이스 생성
`machadmin -c`는 물리 인스턴스의 데이터베이스 파일을 생성합니다. SQL의
`CREATE DATABASE`로 논리 데이터베이스를 추가하는 작업과 구분하십시오. 실행 전에
`MACHBASE_HOME`, `conf/machbase.conf`와 실제 `DBS_PATH`가 새 설치 대상인지 확인합니다.
기존 데이터베이스가 있다는 오류가 나면 삭제·재생성하지 말고 대상 경로부터 확인합니다.
```bash
machadmin -c
# Database created successfully.
```
#### 서버 시작 전 설정과 라이선스 확인
`conf/machbase.conf`에서 `PORT_NO`와 `DBS_PATH`를 확인합니다. 별도 라이선스를 사용하는
경우 [라이선스 설치](../pre-install-preparation/#license)의 파일 복사 또는
`machadmin -t` 방법으로 설치하고 `machadmin -f`로 확인한 뒤 시작합니다.
#### 5. 서버 시작
```bash
machadmin -u
# Machbase server started successfully.
```
프로세스 확인:
```bash
machadmin -e
```
#### 6. 접속 테스트
`machsql`로 서버에 접속합니다. 기본 관리자 계정은 `SYS / MANAGER`입니다.
```bash
machsql
# Machbase server address (Default:127.0.0.1) :
# Machbase user ID (Default:SYS)
# Machbase User Password :
# MACHBASE_CONNECT_MODE=INET, PORT=5656 EDITION=STANDARD
# Mach>
```
간단한 테스트를 수행합니다.
```sql
CREATE LOG TABLE install_check (id INTEGER, val DOUBLE);
INSERT INTO install_check (id, val) VALUES (1, 3.14);
SELECT id, val FROM install_check;
DROP TABLE install_check;
```
`id=1`, `val=3.14` 한 행이 반환되고 마지막 DROP이 성공하는지 확인합니다.
`install_check`가 이미 있으면 기존 객체를 삭제하지 말고 사용하지 않는 실습 이름으로 바꿉니다.
#### 서버 종료
설치 확인이 끝난 뒤 서버를 중지해야 할 때만 실행합니다.
```bash
machadmin -s
# Machbase server shut down successfully.
```
#### 포트 변경
기본 포트(5656)를 변경하려면 `$MACHBASE_HOME/conf/machbase.conf`의 `PORT_NO`를 수정하거나 환경 변수를 설정합니다.
```bash
export MACHBASE_PORT_NO=7878
```
서버를 종료한 상태에서 설정을 적용하고 다시 시작합니다. 이 환경 변수는 현재 셸에만
적용되므로 서비스로 시작한다면 서비스의 환경에도 반영합니다. 접속에는
`machsql -s 127.0.0.1 -P 7878 -u SYS`처럼 바뀐 포트를 지정합니다.
---
### Docker 설치
Docker 이미지를 사용하면 서버와 실행 환경을 컨테이너로 배포할 수 있습니다. 개발·테스트
환경에서도 호스트의 Docker Engine, 저장 볼륨, 포트와 파일 디스크립터 한도를 준비해야 합니다.
Docker가 사전에 설치되어 있어야 합니다. 배포 튜토리얼은 `machbase/machbase` 이미지를 사용합니다. 소스에서 Docker 이미지를 직접 빌드한 경우에는 로컬 이미지 이름(`machbase:latest` 등)으로 바꾸십시오.
#### 이미지 확인
```bash
docker pull machbase/machbase
docker image ls machbase/machbase
```
#### 컨테이너 실행
```bash
docker create \
--name machbase \
--ulimit nofile=65535 \
-p 5656:5656 \
-v /data/machbase:/home/machbase/machbase/dbs \
machbase/machbase
```
| 옵션 | 설명 |
|------|------|
| `-p 5656:5656` | 호스트 SQL 포트:컨테이너 SQL 포트 매핑 |
| `--ulimit nofile=65535` | 컨테이너 안에서 서버가 사용할 파일 디스크립터 한도 |
| `-v /data/machbase:...` | 데이터 디렉터리를 호스트에 보존하는 볼륨 마운트 |
데이터가 컨테이너의 쓰기 계층에만 있으면 컨테이너 삭제 시 함께 제거됩니다. 실제 데이터
경로가 볼륨에 연결되었는지 확인하고, 볼륨 자체의 삭제나 디스크 장애에 대비한 백업도 준비합니다.
`docker create`는 아직 서버를 시작하지 않습니다. `/data/machbase`는 컨테이너의 서버
실행 계정이 쓸 수 있어야 하며, 기존 DB 파일이 있으면 버전과 인스턴스가 맞는지 확인합니다.
이미지의 실제 버전과 `MACHBASE_HOME` 경로를 확인한 뒤 운영에서는 검증한 이미지 태그나
digest를 고정합니다. 태그 없는 공개 이미지가 항상 8.7.0이라고 가정하지 마십시오.
별도 라이선스를 사용하는 경우 시작 전에 다음처럼 준비한 파일을 복사합니다.
```bash
docker cp /path/to/license.dat machbase:/home/machbase/machbase/conf/license.dat
```
준비가 끝나면 컨테이너를 시작합니다.
```bash
docker start machbase
```
#### 컨테이너 상태 확인
```bash
docker ps
docker logs machbase
```
#### 접속 테스트
##### machsql (컨테이너 내부)
```bash
docker exec -it machbase machsql
# Mach>
```
##### 호스트에서 접속
호스트에 machsql이 설치된 경우:
```bash
machsql -s 127.0.0.1 -P 5656 -u SYS -p MANAGER
```
#### 컨테이너 종료 및 재시작
```bash
docker stop machbase
docker start machbase
```
#### 라이선스 설치
실행 중 라이선스를 갱신할 때는 컨테이너에 파일을 복사한 뒤 라이선스 설치 명령을
실행합니다. 컨테이너의 설정 파일과 라이선스는 데이터 볼륨과 별개이므로 컨테이너를
재생성할 때도 다시 적용할 수 있도록 원본을 보관합니다.
```bash
docker cp /path/to/license.dat machbase:/tmp/license.dat
docker exec machbase machadmin -t /tmp/license.dat
docker exec machbase machadmin -f
```
---
## Windows 설치
### 설치 전 확인
- **지원 OS**: 제공받은 Windows 패키지의 릴리스 정보에서 지원 버전을 확인합니다.
- **아키텍처**: 배포 패키지의 비트 수와 운영체제 아키텍처가 일치해야 합니다.
설치 전에 [Windows 환경 준비](/dbms/installation-deployment-upgrade/standard-edition/#windows-preparation-environment-windows)를 먼저 완료하십시오.
### 설치 방식
| 방식 | 설명 |
|------|------|
| [Windows 패키지 설치](/dbms/installation-deployment-upgrade/standard-edition/#windows-package) | 설치 마법사를 통한 설치. 환경 변수와 바로가기 생성 |
---
### Windows 환경 준비
Windows에 설치하기 전에 방화벽 설정을 확인합니다.
#### 방화벽 포트 열기
Machbase가 사용하는 포트를 Windows 방화벽 인바운드 규칙에 추가합니다.
| 포트 | 프로토콜 | 용도 |
|------|---------|------|
| 5656 | TCP | SQL 클라이언트 접속 |
##### 설정 방법
1. **제어판 → Windows Defender 방화벽 → 고급 설정**을 엽니다.
2. 왼쪽 패널에서 **인바운드 규칙**을 선택하고, 오른쪽 패널에서 **새 규칙**을 클릭합니다.
3. 규칙 유형으로 **포트**를 선택하고 **다음**을 클릭합니다.
4. **TCP**를 선택하고, **특정 로컬 포트** 필드에 `5656`을 입력한 후 **다음**을 클릭합니다.
5. **연결 허용**을 선택하고 **다음**을 클릭합니다.
6. 접속을 허용할 네트워크의 프로필(**도메인**, **개인**, **공용**)만 선택하고 **다음**을 클릭합니다.
7. 규칙 이름(예: `Machbase`)을 입력하고 **마침**을 클릭합니다.
규칙을 만든 뒤 속성의 원격 주소 범위도 실제 클라이언트 주소로 제한합니다.
원격 접속을 사용하지 않는 환경에서는 인바운드 허용 규칙을 만들 필요가 없습니다.
##### PowerShell로 설정 (관리자 권한)
GUI 대신 PowerShell로 빠르게 설정합니다.
```powershell
New-NetFirewallRule -DisplayName "Machbase SQL" -Direction Inbound -Protocol TCP -LocalPort 5656 -Action Allow
```
#### Visual C++ 재배포 패키지
실행에 Visual C++ Redistributable이 필요합니다. 설치 실행 파일을 사용하는 경우 자동으로 처리될 수 있지만, 문제가 발생하면 Microsoft 공식 사이트에서 최신 버전을 수동으로 설치하십시오.
---
### Windows 패키지 설치
Windows 버전은 ZIP 패키지 또는 설치 실행 파일로 제공됩니다. ZIP 패키지는 압축 파일 루트에 `bin\`, `conf\`, `dbs\`, `trc\` 등을 포함하며, 설치 실행 파일은 설치 경로 아래에 `machbase_home\`을 만들고 환경 변수와 실행 바로가기를 생성합니다.
#### 설치 절차
1. Windows 배포 패키지를 다운로드합니다.
2. ZIP 패키지를 사용하는 경우 원하는 설치 디렉터리에 압축을 해제합니다.
```cmd
mkdir C:\machbase
tar -xf machbase-SDK-8.7.0.official-WINDOWS-X86-64-release.zip -C C:\machbase
```
3. 설치 실행 파일이 제공된 경우 파일을 실행합니다. 설치 시작 화면이 표시되면 **Next**를 클릭합니다.
4. 설치 경로를 선택합니다. 기본값은 `C:\machbase-\` 형식입니다. 변경이 필요하면 경로를 수정한 후 **Next**를 클릭합니다.
5. 설치가 진행됩니다. 완료되면 **Next** → **Close**를 클릭합니다.
#### ZIP 패키지 초기화
ZIP을 해제했다면 명령 프롬프트에서 해당 디렉터리를 설치 홈으로 지정합니다. 다음은
`C:\machbase`에 새로 설치한 예제입니다. 실제 설정 파일과 라이선스를 먼저 확인하고,
기존 DB가 없는 새 인스턴스에서만 `-c`를 실행합니다.
```cmd
set "MACHBASE_HOME=C:\machbase"
set "PATH=%MACHBASE_HOME%\bin;%PATH%"
machadmin.exe -c
machadmin.exe -f
machadmin.exe -u
machadmin.exe -e
```
위 `set` 명령은 현재 창에 적용됩니다. 이후 다른 창이나 서비스에서 실행할 때도 같은
설치 홈과 실행 파일 경로를 사용해야 합니다. 설치 실행 파일이 이미 데이터베이스를
만들었다면 ZIP용 생성 명령을 반복하지 마십시오.
#### 서버 시작과 종료
설치 실행 파일을 사용한 경우 바탕화면과 시작 메뉴에 바로가기가 생성됩니다.
- **start Machbase**: `machadmin.exe -u`를 실행해 서버를 시작합니다.
- **stop Machbase**: `machadmin.exe -s`를 실행해 서버를 종료합니다.
- **machsql**: SQL 콘솔을 실행합니다.
#### 명령줄 접속
설치 후 명령 프롬프트에서 `machsql`을 실행하여 서버에 접속합니다. 설치 실행 파일을 사용한 경우 `<설치 경로>\machbase_home\bin`이 시스템 `PATH`에 추가됩니다. ZIP 패키지를 사용한 경우 압축을 해제한 디렉터리의 `bin\`을 `PATH`에 추가하거나 전체 경로로 실행합니다.
```cmd
machsql -s 127.0.0.1 -P 5656 -u SYS -p MANAGER
```
기본 관리자 계정: `SYS` / `MANAGER`
#### 설치 경로 구조
ZIP 패키지는 압축 해제 경로 바로 아래에 `bin\`, `conf\`, `dbs\`, `trc\` 등을 배치합니다.
설치 실행 파일은 설치 경로 아래의 `machbase_home\`에 같은 구성을 생성합니다.
[패키지 구성](/dbms/installation-deployment-upgrade/pre-install-preparation/#package) 참고.
---
title: "3.3 Cluster Edition 설치와 배포"
url: https://docs.machbase.com/kr/dbms/installation-deployment-upgrade/cluster-edition/
language: kr
kind: page
---
# 3.3 Cluster Edition 설치와 배포
Cluster Edition은 SQL 접속, 데이터 저장, 복제와 노드 관리를 여러 역할로 나눕니다.
설치 전에 각 역할을 어느 호스트에 배치할지, 어떤 포트와 저장 경로를 사용할지 정합니다.
일반 SQL 접속은 Broker로, 운영 명령은 해당 관리 노드로 보내야 합니다.
## 노드 역할
| 노드 | 역할 |
|------|------|
| **Coordinator** | 클러스터 메타 정보 관리, 노드 상태 감시 |
| **Deployer** | 패키지 배포 및 노드 초기화 중계 |
| **Lookup** | 참조 데이터와 조회 처리 |
| **Broker** | SQL 파싱 및 쿼리 분배, 클라이언트 접점 |
| **Warehouse** | 실제 데이터 저장 및 쿼리 실행 |
아래 YAML 예시는 세 호스트에 Coordinator 2개, Deployer 3개, Lookup 2개(master 1개,
monitor 1개), Broker 2개와 Warehouse 2개(하나의 복제 그룹)를 배치합니다.
실제 노드 수와 배치는 가용성, 처리량과 장애 시 남아 있어야 할 용량을 기준으로 정합니다.
## 배포 방식
| 방식 | 설명 | 적합한 경우 |
|------|------|------------|
| [machclusterctl](/dbms/installation-deployment-upgrade/cluster-edition/#machclusterctl) | cluster.yaml 기반 자동 배포 | 권장. 신규 구축 |
| [수동 (machcoordinatoradmin)](/dbms/installation-deployment-upgrade/cluster-edition/#manual-machcoordinatoradmin) | Coordinator 명령 기반 노드 등록·배포 | 세밀한 제어가 필요한 경우 |
## 설치 순서
1. [Cluster Edition 구성 개요](/dbms/installation-deployment-upgrade/cluster-edition/#overview) 숙지
2. [환경 준비](/dbms/installation-deployment-upgrade/cluster-edition/#preparation-environment-cluster-edition) (SSH 키, 커널 파라미터, NTP)
3. 패키지·경로와 [라이선스](../pre-install-preparation/#license) 적용 방식 준비
4. 배포 방식 선택 후 설치·기동하고 라이선스 확인
5. [설치 검증](/dbms/installation-deployment-upgrade/validation-checklist/)
---
## Cluster Edition 구성 개요
역할이 분리된 Coordinator, Deployer, Lookup, Broker, Warehouse 노드로 구성됩니다. 각 노드의 역할과 상호 관계를 이해한 후 배포 계획을 세우십시오.
### 노드 역할 상세
#### Coordinator
클러스터 메타데이터, 노드 등록과 상태 감시를 담당합니다. Primary/Secondary 이중화를
검토하십시오. 실행 중인 데이터 처리 경로와 관리 경로는 구분되지만, Coordinator 장애만으로
모든 SQL의 지속 동작을 보장할 수는 없습니다. 다른 노드 상태와 클라이언트 영향을 함께
확인하고 검증한 장애 대응 절차를 사용합니다.
- 설정 파일: `$MACHBASE_COORDINATOR_HOME/conf/machbase.conf`
- 관리 도구: `machcoordinatoradmin`
- 주요 포트: `CLUSTER_LINK_PORT_NO`, `HTTP_ADMIN_PORT`
기본값은 `CLUSTER_LINK_PORT_NO=3868`, `HTTP_ADMIN_PORT=5779`입니다. 이 장의 예제에서는 운영 중 포트 충돌을 피하기 위해 Coordinator link/admin 포트로 `5101`/`5102`를 명시합니다.
#### Deployer
Coordinator의 지시에 따라 각 노드에 패키지를 배포하고 초기화를 중계합니다. 각 노드 호스트에 하나씩 배치하거나, 별도 배포 서버로 운영합니다.
- 관리 도구: `machdeployeradmin`
#### Lookup
참조 데이터와 조회 처리를 위한 노드입니다. 구성에 따라 `master`, `monitor`, `slave`
역할을 지정합니다.
#### Broker
클라이언트의 SQL 요청을 받아 파싱하고 적절한 Warehouse로 분배합니다. 일반 애플리케이션은
Broker 주소로 연결합니다. Warehouse 직접 연결은 복제 상태 비교 같은 관리 진단 절차에서만
사용하며, 애플리케이션의 접속 경로와 구분합니다. Broker 이중화를 권장합니다.
- 클라이언트 접속 포트: 기본 5656
#### Warehouse
실제 데이터를 저장하고 쿼리를 실행합니다. 같은 그룹의 Warehouse끼리 데이터를 복제하여 고가용성을 제공합니다. 그룹당 2개 이상을 권장합니다.
### 구성 예시
```
[클라이언트]
│ (SQL, 5656)
▼
[Broker ×2] ──────────────────────────────────────────
│ (쿼리 분배)
├─► [Warehouse group1-node1] ◄──복제──► [Warehouse group1-node2]
└─► [Warehouse group2-node1] ◄──복제──► [Warehouse group2-node2]
[Coordinator Primary] ◄──HA──► [Coordinator Secondary]
│ (메타 관리, 노드 감시)
[Deployer]
│
[Lookup master / monitor]
```
### 에디션 비교
Standard Edition과의 상세 비교는 [에디션 차이점](/dbms/core-concepts/concepts-edition/#differences-standard-edition-cluster)을 참고하십시오.
---
## Cluster Edition 설치 환경 준비
배포 전에 모든 노드에 다음 환경을 준비합니다.
### 파일 디스크립터 한도
모든 노드에서 아래 설정을 적용합니다.
```bash
sudo vi /etc/security/limits.conf
```
```
* hard nofile 65535
* soft nofile 65535
```
서버 실행 계정으로 새 로그인 세션을 연 뒤 확인합니다. 서비스 관리자로 시작한다면
서비스 자체의 파일 디스크립터 한도도 확인합니다.
```bash
ulimit -Sn
# 65535
```
### OS 사용자 생성
모든 노드에 `machbase` 계정을 생성합니다.
```bash
sudo useradd -m machbase --home-dir /home/machbase
sudo passwd machbase
```
### SSH 키 기반 인증
`machclusterctl`을 사용하는 경우, 배포 서버에서 모든 노드로 비밀번호 없이 SSH 접속이 가능해야 합니다.
```bash
# 배포 서버에서 SSH 키 생성 (이미 있으면 생략)
ssh-keygen -t rsa -b 4096
# 각 노드에 공개 키 등록
ssh-copy-id machbase@192.168.1.11
```
등록 후 비밀번호 없이 접속이 되는지 확인합니다.
```bash
ssh machbase@192.168.1.11 'hostname'
```
### 네트워크 커널 파라미터
다음은 네트워크 버퍼 조정 예시이며 모든 서버에 적용할 필수값이 아닙니다. 현재 커널 설정,
메모리 사용량과 네트워크 병목을 먼저 측정합니다. 값을 늘린 뒤 처리량뿐 아니라 메모리와
지연도 비교하고 효과가 확인된 항목만 영구 적용합니다.
```bash
sudo sysctl -w net.core.rmem_default=33554432
sudo sysctl -w net.core.wmem_default=33554432
sudo sysctl -w net.core.rmem_max=268435456
sudo sysctl -w net.core.wmem_max=268435456
sudo sysctl -w 'net.ipv4.tcp_rmem=262144 33554432 268435456'
sudo sysctl -w 'net.ipv4.tcp_wmem=262144 33554432 268435456'
sudo sysctl -w 'net.ipv4.tcp_mem=8388608 8388608 8388608'
```
영구 적용은 `/etc/sysctl.conf`에 추가합니다.
### 시간 동기화 (NTP)
모든 노드의 시스템 시간이 일치해야 합니다. NTP 또는 `chrony`로 동기화하십시오.
```bash
# chrony 사용 예
sudo systemctl enable chronyd
sudo systemctl start chronyd
chronyc tracking
```
타임 서버를 사용할 수 없는 격리된 설치 환경에서는 초기 시각을 직접 설정할 수 있습니다.
다음 값은 형식 예시이므로 실제 현재 시각으로 바꾸십시오. 수동 설정만으로 노드 간 시간 차이가
지속적으로 보정되지는 않으므로 운영 전에는 시간 동기화 경로를 마련합니다.
```bash
sudo date -s "2025-01-02 12:34:56"
```
### 포트 예약
각 노드에서 Machbase가 사용할 포트를 예약합니다.
```bash
current=$(cat /proc/sys/net/ipv4/ip_local_reserved_ports)
ports=5101-5110,5201-5202,5301-5302,5401,5500-5503,5656
sudo sysctl -w net.ipv4.ip_local_reserved_ports="${current:+$current,}$ports"
```
기존 예약 포트가 있으면 덮어쓰지 말고 쉼표로 구분해 병합합니다. 클러스터 구성에 따라 포트 범위를 조정하십시오. Cluster link, Coordinator/Deployer 관리, 서비스, Warehouse 복제 관리자 포트 등을 모두 포함해야 합니다.
---
## machclusterctl 기반 배포
`machclusterctl`은 `cluster.yaml` 파일 하나로 전체 클러스터를 자동으로 배포하고 관리하는 도구입니다. SSH를 통해 각 노드에 원격 접속하여 패키지 배포, 초기화, 시작·종료를 일괄 처리합니다.
### 사전 조건
- Primary Coordinator를 설치할 호스트에서 명령을 실행하며, 패키지와 SSH 개인키를
해당 호스트에서 읽을 수 있어야 합니다.
- Primary 호스트에서 모든 대상 호스트로 SSH 키 기반 인증이 설정되어 있어야 합니다.
- 각 노드의 `home_path`와 `dbs_path` 상위 경로를 생성·기록할 권한이 있어야 합니다.
- 별도 라이선스가 필요하면 자동 기동 전에 배포 패키지와 라이선스 적용 절차를 준비합니다.
YAML에 임의의 라이선스 속성을 추가하지 않습니다.
- [Cluster Edition 설치 환경 준비](/dbms/installation-deployment-upgrade/cluster-edition/#preparation-environment-cluster-edition) 완료
### 작업 순서
| 단계 | 문서 |
|------|------|
| 1. cluster.yaml 작성 | [cluster.yaml 작성](/dbms/installation-deployment-upgrade/cluster-edition/#machclusterctl-cluster-yaml) |
| 2. YAML 유효성 검사 | [YAML 검증](/dbms/installation-deployment-upgrade/cluster-edition/#machclusterctl-validation-yaml) |
| 3. 최초 설치 및 시작 | [최초 설치](/dbms/installation-deployment-upgrade/cluster-edition/#machclusterctl-initial) |
| 4. 상태 확인 | [최초 설치와 상태 확인](/dbms/installation-deployment-upgrade/cluster-edition/#machclusterctl-initial) |
| 5. (이후) 구성 변경 | [Cluster 운영](/dbms/operations-configuration-recovery/cluster/) |
| 6. (장애 시) 복구 | [Cluster 문제 해결](/dbms/troubleshooting/cluster/) |
---
### cluster.yaml 작성
`cluster.yaml`은 설치할 노드 구성과 경로를 선언합니다. 아래 주소는 예시이므로 실제 호스트로
바꿉니다. `origin_path`는 명령을 실행하는 Primary Coordinator 호스트에서 읽는 압축 파일,
`home_path`와 `dbs_path`는 해당 노드 호스트의 경로입니다. `deployer`는 그 노드를
배치·제어할 Deployer를 참조합니다.
#### 파일 구조 예시
```yaml
version: "1"
cluster:
name: mc-prod
hosts:
node1:
address: machbase@192.168.1.10
node2:
address: machbase@192.168.1.11
node3:
address: machbase@192.168.1.12
package:
name: machbase
origin_path: /home/machbase/packages/machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz
ssh:
key_file: /home/machbase/.ssh/id_rsa
defaults:
coordinator:
home_path: /home/machbase/coordinator
cluster_link_port: 5101
http_admin_port: 5102
deployer:
home_path: /home/machbase/deployer
cluster_link_port: 5201
http_admin_port: 5202
lookup:
home_path: /home/machbase/lookup
cluster_link_port: 5301
broker:
home_path: /home/machbase/broker
cluster_link_port: 5401
service_port: 5656
warehouse:
home_path: /home/machbase/warehouse
cluster_link_port: 5501
service_port: 5500
coordinators:
- alias: coord-primary-1
host: node1
role: primary
- alias: coord-secondary-1
host: node2
role: secondary
deployers:
- alias: deployer-1
host: node1
- alias: deployer-2
host: node2
- alias: deployer-3
host: node3
lookup:
- alias: lookup-master-1
host: node1
deployer: deployer-1
type: master
- alias: lookup-monitor-1
host: node2
deployer: deployer-2
type: monitor
brokers:
- alias: broker-1
host: node1
deployer: deployer-1
dbs_path: /data/machbase/broker-1/dbs
- alias: broker-2
host: node2
deployer: deployer-2
warehouse_groups:
- name: group1
nodes:
- alias: warehouse-group1-1
host: node2
deployer: deployer-2
dbs_path: /data/machbase/warehouse-group1-1/dbs
- alias: warehouse-group1-2
host: node3
deployer: deployer-3
dbs_path: /data/machbase/warehouse-group1-2/dbs
```
#### 주요 항목 설명
| 항목 | 설명 |
|------|------|
| `version` | YAML 스키마 버전입니다. 현재 `"1"`을 사용합니다. |
| `cluster.name` | 클러스터 이름입니다. `destroy` 확인 등에서 사용됩니다. |
| `cluster.hosts` | 노드에서 참조할 호스트 별칭과 SSH 접속 주소입니다. `address`는 `user@host` 형식을 사용합니다. |
| `cluster.package.name` | Coordinator에 등록할 패키지 이름입니다. |
| `cluster.package.origin_path` | `install`, `apply`, `upgrade` 실행 때 입력으로 사용할 패키지 보관 파일 경로입니다. |
| `cluster.package.registered_path` | `export`가 기록하는 Coordinator 패키지 저장소의 관찰 경로입니다. 실행 입력으로 사용하지 않습니다. |
| `cluster.ssh.key_file` | 대상 서버 접속에 사용할 개인키 경로입니다. 비밀번호 필드는 사용하지 않습니다. |
| `cluster.defaults` | 노드 타입별 `home_path`, `cluster_link_port`, `service_port` 기본값입니다. |
| `cluster.coordinators` | Coordinator 노드 목록입니다. `role`은 `primary` 또는 `secondary`를 사용합니다. |
| `cluster.deployers` | Deployer 노드 목록입니다. |
| `cluster.lookup` | Lookup 노드 목록입니다. `type`은 `master`, `monitor`, `slave`를 사용합니다. |
| `cluster.brokers` | Broker 노드 목록입니다. 클라이언트 SQL 접속 포트는 `service_port`입니다. |
| `cluster.warehouse_groups` | Warehouse 그룹과 그룹별 노드 목록입니다. |
#### 권장 구성
- Coordinator: 2개 (Primary + Secondary HA)
- Deployer: 1개 이상
- Lookup: `master` 1개, `monitor` 1개 이상
- Broker: 2개 이상 (부하 분산)
- Warehouse 그룹: 그룹당 2개 (복제를 통한 고가용성)
같은 서버에 같은 타입의 노드를 2개 이상 배치할 때는 두 번째 노드부터 `home_path`와 포트를 명시적으로 지정하여 충돌을 피합니다.
Coordinator와 Deployer의 관리 포트는 `http_admin_port`를 사용합니다.
Broker와 Warehouse 노드에는 선택적으로 `dbs_path`를 지정합니다. `dbs_path`는 노드가 설치되는 서버 기준의 데이터 파일 경로이며, 생략하면 `machcoordinatoradmin --add-node`의 기본 `DBS_PATH` 동작을 따릅니다.
기존 `cluster.package.path`는 하위 호환 입력으로 사용할 수 있지만, 새로 작성하는 YAML에서는 `origin_path`를 사용합니다.
`http_admin_port`는 Coordinator와 Deployer에만 사용합니다. Broker, Lookup, Warehouse에는
HTTP 포트 필드를 지정하지 않습니다.
작성이 완료되면 유효성을 검사합니다.
---
### YAML 검증
`cluster.yaml`을 실제 설치에 사용하기 전에 유효성 검사를 수행합니다. `validate`는 YAML 문법과 필수 값, 별칭, 포트 충돌, 토폴로지 관계를 정적으로 검사합니다.
#### 검증 명령
```bash
machclusterctl validate -f cluster.yaml
```
#### 검증 항목
| 항목 | 설명 |
|------|------|
| YAML 문법 | 파일 파싱 오류 여부 |
| 환경변수 치환 | `${VAR}` 또는 `${VAR:-default}` 표현식 해석 가능 여부 |
| 필수 필드 | 클러스터 이름, 호스트, 패키지, 노드별 필수 값 누락 여부 |
| 별칭 | 노드 별칭 중복 여부 |
| 포트 충돌 | 같은 호스트 안에서 선언된 포트 충돌 여부 |
| 토폴로지 | Primary Coordinator, Lookup master/monitor, Deployer 참조 관계 |
#### 출력 예시
```text
Validation passed.
```
오류가 있으면 메시지에 표시된 항목을 수정한 뒤 재검증합니다.
#### 설치 전 실행 계획 확인
신규 설치 전에 SSH 접속, 패키지 파일 존재 여부, 원격 디렉터리 권한까지 확인하려면 `install --dry-run --verbose`를 사용합니다.
```bash
machclusterctl install -f cluster.yaml --dry-run --verbose
```
설치 후 구성 변경을 검증할 때는 `apply --dry-run`을 사용합니다. 현재 클러스터 상태와 YAML의 차이를 계산해 실행 계획을 보여주며, 실제 원격 변경 작업은 수행하지 않습니다.
```bash
machclusterctl apply -f cluster.yaml --dry-run --verbose
```
#### 일반적인 오류와 해결
| 오류 | 원인 | 해결 |
|------|------|------|
| `field ... not found` | 지원하지 않는 YAML 키 사용 | 현행 스키마의 `cluster.*` 항목으로 수정 |
| `required field ...` | 필수 값 누락 | 메시지에 표시된 필드를 추가 |
| `duplicate alias` | 노드 별칭 중복 | 모든 노드 별칭을 고유하게 변경 |
| `port conflict` | 같은 호스트에서 동일 포트 사용 | 충돌한 포트를 다르게 지정. 홈 경로만 바꾸어도 포트 충돌은 해결되지 않음 |
| `deployer ... not found` | Lookup/Broker/Warehouse가 존재하지 않는 Deployer를 참조 | `deployer` 값을 Deployer 별칭 또는 호스트:포트로 수정 |
---
### 최초 설치
`cluster.yaml` 작성과 유효성 검사가 완료되면 설치 계획을 확인한 뒤 클러스터를 설치합니다.
#### 1. 클러스터 설치
패키지를 각 노드에 배포하고 초기화하기 전에 실행 계획과 사전 점검 결과를 확인합니다.
```bash
machclusterctl install -f cluster.yaml --dry-run --verbose
```
문제가 없으면 실제 설치를 실행합니다.
```bash
machclusterctl install -f cluster.yaml --yes --verbose
```
이 명령은 다음을 자동으로 처리합니다.
1. 패키지를 각 노드에 복사하고 압축 해제
2. 각 노드의 `machbase.conf` 생성 및 포트 설정
3. Coordinator 데이터베이스 초기화
4. 노드 등록 (Coordinator에 각 노드 추가)
#### 2. 클러스터 시작
`install`은 Coordinator, Deployer, Lookup, Broker, Warehouse를 준비하고 기동합니다. 설치 후 전체 클러스터를 다시 시작해야 할 때만 다음 명령을 사용합니다.
```bash
machclusterctl start
```
#### 3. 상태 확인
```bash
export MACHBASE_COORDINATOR_HOME=/home/machbase/coordinator
machclusterctl status
```
한 서버에서 여러 Coordinator 홈을 번갈아 확인할 때는 직접 지정합니다.
```bash
machclusterctl status --coordinator /home/machbase/coordinator
```
출력은 `machcoordinatoradmin --cluster-status-full --verbose` 형식입니다. 아래는 출력 열을
보여 주는 일부 행의 예시이며 YAML의 전체 노드 목록이 아닙니다. Coordinator와 Broker는
`primary`, `leader` 같은 역할 상태를 가질 수 있으므로 모든 행이 `normal`인지가 아니라
등록 노드 수와 역할별 목표 상태·실제 상태가 맞는지 확인합니다.
```
+-------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------+-----------------+----------+
| Node Type | Node Name | Group Name | Group State | Desired & Actual State | RP State | Disk(%) (00/00) | Ping(μs) |
+-------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------+-----------------+----------+
| coordinator | coord-1(192.168.1.10:5101) | Coordinator | normal | primary | primary | ----------- | --------------- | 214 |
| deployer | deployer-1(192.168.1.10:5201) | Deployer | normal | running | running | ----------- | --------------- | 100 |
| broker | broker-1(192.168.1.11:5401) | Broker | normal | leader | leader | ----------- | --------------- | 100 |
| warehouse | wh-g1-1(192.168.1.13:5501) | group1 | normal | normal | normal | running | 26.9 | 100 |
+-------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------+-----------------+----------+
```
#### 4. 클라이언트 접속 테스트
Broker 노드의 IP와 포트로 접속합니다.
```bash
machsql -s 192.168.1.11 -P 5656 -u SYS -p MANAGER
# Mach>
```
#### 클러스터 종료
```bash
machclusterctl stop
```
---
### 설치 후 운영
최초 설치와 접속 검증이 끝난 뒤의 노드 구성 변경, 노드 추가·제거와 상태 복구는
[Cluster 운영](../../operations-configuration-recovery/cluster/)을 따릅니다. 장애 원인 분류와
복구 판단은 [Cluster 문제 해결](../../troubleshooting/cluster/)을 참고하십시오.
## machcoordinatoradmin 기반 수동 배포
`machclusterctl`을 사용할 수 없을 때는 Coordinator와 Deployer를 직접 준비하고 패키지와
노드를 등록합니다. 자동 배포가 끝난 환경에 아래 생성 명령을 다시 실행하지 마십시오.
수동 예제는 YAML과 별개의 구성입니다. 역할별 실행 위치를 바꾸어 명령을 수행합니다.
| 호스트 | 역할 |
|---|---|
| `192.168.1.10` | Primary Coordinator, Deployer, Lookup master |
| `192.168.1.11` | Deployer, Lookup monitor, Broker |
| `192.168.1.13` | Deployer, Warehouse group1 첫 노드 |
| `192.168.1.14` | Deployer, Warehouse group1 복제 노드 |
| `192.168.1.20` | 선택적인 Secondary Coordinator |
같은 호스트의 역할마다 홈과 포트를 분리합니다. Deployer는 데이터 처리 노드가 설치될
각 호스트에서 준비하며, 아래 `--deployer` 주소도 대상 호스트와 맞춥니다.
### 수동 배포 순서
1. [Package 준비와 등록](/dbms/installation-deployment-upgrade/cluster-edition/#manual-machcoordinatoradmin-package) — 전체 패키지 설치와 경량 패키지 등록 준비
2. [Coordinator / Deployer 설치](/dbms/installation-deployment-upgrade/cluster-edition/#manual-machcoordinatoradmin-coordinator-deployer) — 핵심 관리 노드 구동
3. [Package 등록](/dbms/installation-deployment-upgrade/cluster-edition/#manual-machcoordinatoradmin-package) — 실행 중인 Coordinator에 경량 패키지 등록
4. [Lookup / Broker / Warehouse 설치](/dbms/installation-deployment-upgrade/cluster-edition/#manual-machcoordinatoradmin-lookup-broker-warehouse) — 데이터 처리 노드 등록 및 구동
5. [전체 상태 확인](/dbms/installation-deployment-upgrade/cluster-edition/#manual-machcoordinatoradmin-lookup-broker-warehouse) — 등록·구동 후 최초 상태 검증
### machclusterctl 대비 차이점
| 항목 | machclusterctl | 수동 배포 |
|------|---------------|----------|
| 설정 방식 | cluster.yaml 단일 파일 | 각 노드 machbase.conf 직접 편집 |
| 패키지 배포 | 자동 원격 복사 | Coordinator/Deployer는 직접 설치, Broker/Warehouse는 Deployer가 배포 |
| 노드 등록 | 자동 | `machcoordinatoradmin --add-node` 수동 실행 |
| 일괄 시작·종료 | `machclusterctl start/stop` | 노드별 개별 실행 |
수동 배포는 유연성이 높지만 실수 가능성도 높습니다. 신규 구축에는 `machclusterctl`을 권장합니다.
---
### Package 준비와 등록
Cluster Edition 수동 배포의 패키지 준비와 등록 절차입니다. Coordinator와 Deployer에는 전체 패키지를 설치하고 환경 변수를 설정합니다. Broker와 Warehouse 배포에 사용할 경량 패키지는 Coordinator가 기동된 후에 등록합니다.
#### 패키지 종류
Cluster Edition에는 두 가지 패키지가 있습니다.
| 패키지 | 대상 노드 | 특징 |
|--------|-----------|------|
| 전체 패키지 | Coordinator, Deployer | 모든 실행 파일 포함 |
| 경량 패키지 (lightweight) | Broker, Warehouse | 데이터 처리에 필요한 파일만 포함, 크기 작음 |
파일명 예시:
- 전체: `machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz`
- 경량: `machbase-cluster-8.7.0.official-LINUX-X86-64-release-lightweight.tgz`
#### Coordinator와 Deployer에 패키지 배포
전체 패키지 파일을 Coordinator와 Deployer 노드에 복사하고 압축 해제합니다.
##### Coordinator 노드
```bash
# Coordinator 노드에서 실행
mkdir -p /home/machbase/coordinator
scp machbase@package-host:/path/to/machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz /home/machbase/
tar zxf /home/machbase/machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz -C /home/machbase/coordinator
```
##### Deployer 노드
```bash
mkdir -p /home/machbase/deployer
scp machbase@package-host:/path/to/machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz /home/machbase/
tar zxf /home/machbase/machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz -C /home/machbase/deployer
```
Broker와 Warehouse 노드에는 이 단계에서 경량 패키지를 직접 압축 해제하지 않습니다. 경량 패키지를 Coordinator에 등록하면, 이후 `--add-node`로 지정한 Deployer가 대상 노드의 `--home-path`에 패키지를 배포합니다.
#### Coordinator에 패키지 등록
Broker와 Warehouse를 Coordinator에서 기동하려면 경량 패키지를 Coordinator에 등록해야 합니다. Coordinator와 Deployer를 설치하고 Coordinator가 실행 중인 상태에서 다음 명령을 실행합니다.
```bash
$MACHBASE_COORDINATOR_HOME/bin/machcoordinatoradmin --add-package=machbase \
--file-name="/home/machbase/machbase-cluster-8.7.0.official-LINUX-X86-64-release-lightweight.tgz"
```
등록된 패키지는 이후 Broker와 Warehouse를 `--add-node`로 등록할 때 `--package-name=machbase`로 참조합니다.
#### 환경 변수 설정
`package-host`와 `/path/to/`는 실제 패키지 보관 위치로 바꿉니다. Coordinator와 Deployer를
같은 호스트에서 관리하더라도 별도의 셸에서 역할에 맞는 환경을 사용합니다.
다음은 각 셸에 적용할 값이며, 서비스나 로그인 초기화 파일에도 같은 값을 반영합니다.
```bash
# Coordinator 노드
export MACHBASE_COORDINATOR_HOME=/home/machbase/coordinator
export MACHBASE_HOME=$MACHBASE_COORDINATOR_HOME
export PATH=$MACHBASE_HOME/bin:$PATH
export LD_LIBRARY_PATH=$MACHBASE_HOME/lib:$LD_LIBRARY_PATH
```
```bash
# 별도 Deployer 관리 셸
export MACHBASE_DEPLOYER_HOME=/home/machbase/deployer
export MACHBASE_HOME=$MACHBASE_DEPLOYER_HOME
export PATH="$MACHBASE_HOME/bin:$PATH"
export LD_LIBRARY_PATH="$MACHBASE_HOME/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
```
---
### Coordinator / Deployer 설치
패키지 배포가 완료되면 Coordinator를 먼저 설치·시작한 후 Deployer를 등록합니다.
#### Coordinator 설치
##### 1. machbase.conf 설정
`$MACHBASE_COORDINATOR_HOME/conf/machbase.conf`를 편집합니다.
```bash
vi $MACHBASE_COORDINATOR_HOME/conf/machbase.conf
```
주요 설정:
```
CLUSTER_LINK_HOST = 192.168.1.10 # 이 노드의 IP
CLUSTER_LINK_PORT_NO = 5101
HTTP_ADMIN_PORT = 5102
```
##### 2. 메타 데이터베이스 생성 및 서비스 시작
```bash
machcoordinatoradmin -c
machcoordinatoradmin -u
```
##### 3. 자기 자신을 Coordinator 노드로 등록
```bash
machcoordinatoradmin --add-node="192.168.1.10:5101" \
--node-type=coordinator \
--http-admin-port=5102
```
##### 4. 등록 확인
```bash
machcoordinatoradmin --cluster-status
```
#### Secondary Coordinator 설치 (선택)
고가용성을 위해 Secondary Coordinator를 추가합니다.
Secondary 노드에서 패키지를 배포하고 `machbase.conf`를 설정한 후, **Primary Coordinator에서** 먼저 노드를 등록합니다.
```bash
# Primary Coordinator에서 먼저 등록
machcoordinatoradmin --add-node="192.168.1.20:5101" \
--node-type=coordinator \
--http-admin-port=5102
# 그 다음 Secondary 노드에서 시작 (--primary 옵션으로 Primary 지정)
machcoordinatoradmin -u --primary=192.168.1.10:5101
```
Secondary를 시작하기 전에 반드시 Primary에서 노드 등록을 완료해야 합니다.
#### Deployer 설치
수동 구성표의 각 Deployer 호스트에서 이 절차를 수행합니다. 아래 설정의
`CLUSTER_LINK_HOST`는 그 호스트 자신의 IP로 바꿉니다. 다른 호스트의 주소를 그대로
복사하면 노드의 통신 주소와 실제 실행 위치가 달라집니다.
##### 1. machbase.conf 설정
```
CLUSTER_LINK_HOST = 192.168.1.10 # Deployer 노드 IP
CLUSTER_LINK_PORT_NO = 5201
HTTP_ADMIN_PORT = 5202
```
##### 2. 시작
```bash
machdeployeradmin -c
machdeployeradmin -u
```
##### 3. Coordinator에 Deployer 노드 등록
모든 Deployer가 시작되면 Primary Coordinator 관리 셸에서 등록합니다.
```bash
machcoordinatoradmin --add-node="192.168.1.10:5201" \
--node-type=deployer \
--http-admin-port=5202
machcoordinatoradmin --add-node="192.168.1.11:5201" \
--node-type=deployer --http-admin-port=5202
machcoordinatoradmin --add-node="192.168.1.13:5201" \
--node-type=deployer --http-admin-port=5202
machcoordinatoradmin --add-node="192.168.1.14:5201" \
--node-type=deployer --http-admin-port=5202
```
---
### Lookup / Broker / Warehouse 설치
Coordinator와 Deployer가 준비된 후 Lookup, Broker, Warehouse 노드를 Coordinator에 등록하고 시작합니다. Broker와 Warehouse를 등록하기 전에 경량 패키지를 Coordinator에 `--add-package`로 등록해야 합니다.
#### Lookup 노드
Lookup master와 monitor를 각각 등록하고 시작합니다. 다음 예제는 Primary 호스트와 Broker
호스트에 배치하며, 각 호스트의 Deployer를 사용합니다. 같은 홈이 기존 Lookup에 사용 중이
아닌지 확인합니다.
```bash
machcoordinatoradmin --add-node="192.168.1.10:5301" \
--node-type=lookup \
--lookup-type=master \
--deployer="192.168.1.10:5201" \
--home-path="/home/machbase/lookup"
machcoordinatoradmin --add-node="192.168.1.11:5301" \
--node-type=lookup \
--lookup-type=monitor \
--deployer="192.168.1.11:5201" \
--home-path="/home/machbase/lookup"
machcoordinatoradmin --startup-node="192.168.1.10:5301"
machcoordinatoradmin --startup-node="192.168.1.11:5301"
```
#### Broker 설치
##### 1. 등록 파라미터 확인
Broker 설정 파일은 `--add-node` 실행 때 생성되어 Deployer를 통해 대상 노드에 배포됩니다.
등록 전에 클러스터 통신 포트와 서비스 포트를 확정합니다. Broker에는 HTTP 관리 포트를
지정하지 않습니다.
```
CLUSTER_LINK_HOST = 192.168.1.11 # Broker 노드 IP
CLUSTER_LINK_PORT_NO = 5401
PORT_NO = 5656 # 클라이언트 접속 포트
```
##### 2. Coordinator에 노드 등록
Coordinator 노드에서 실행합니다.
```bash
machcoordinatoradmin --add-node="192.168.1.11:5401" \
--node-type=broker \
--deployer="192.168.1.11:5201" \
--package-name=machbase \
--home-path="/home/machbase/broker" \
--dbs-path="/data/machbase/broker_dbs" \
--port-no=5656
```
| 파라미터 | 설명 |
|---------|------|
| `--add-node` | 등록할 노드의 IP:CLUSTER_LINK_PORT_NO |
| `--node-type` | `broker` / `warehouse` / `lookup` |
| `--deployer` | 해당 노드를 설치하고 제어할 Deployer의 IP:CLUSTER_LINK_PORT_NO |
| `--package-name` | Coordinator에 등록한 패키지 이름 |
| `--home-path` | 노드 홈 디렉터리 |
| `--dbs-path` | Broker/Warehouse의 데이터 파일 경로. 생략하면 기본 `DBS_PATH`를 사용 |
| `--port-no` | 클라이언트 또는 노드 서비스 포트 |
| `--replication` | Warehouse 복제 관리자 주소입니다. `host:port` 형식을 사용합니다. |
##### 3. 노드 시작
Coordinator에서 해당 노드를 시작합니다.
```bash
machcoordinatoradmin --startup-node="192.168.1.11:5401"
```
#### Warehouse 설치
Warehouse 노드는 그룹 단위로 구성합니다. 같은 그룹의 노드끼리 데이터를 복제합니다.
##### 1. 등록 파라미터 확인
Warehouse 설정 파일은 `--add-node` 실행 때 생성되어 Deployer를 통해 대상 노드에 배포됩니다. 등록 전에 클러스터 통신 포트, 서비스 포트, 복제 관리자 주소를 확정합니다.
```
CLUSTER_LINK_HOST = 192.168.1.13
CLUSTER_LINK_PORT_NO = 5501
PORT_NO = 5500
```
##### 2. 노드 등록
```bash
machcoordinatoradmin --add-node="192.168.1.13:5501" \
--node-type=warehouse \
--deployer="192.168.1.13:5201" \
--package-name=machbase \
--home-path="/home/machbase/warehouse_g1_1" \
--dbs-path="/data/machbase/warehouse_g1_1_dbs" \
--port-no=5500 \
--replication=192.168.1.13:5502 \
--group=group1 \
--no-replicate
machcoordinatoradmin --add-node="192.168.1.14:5501" \
--node-type=warehouse \
--deployer="192.168.1.14:5201" \
--package-name=machbase \
--home-path="/home/machbase/warehouse_g1_2" \
--dbs-path="/data/machbase/warehouse_g1_2_dbs" \
--port-no=5500 \
--replication=192.168.1.14:5502 \
--group=group1
```
별도 `--add-group` 명령은 사용하지 않습니다. Warehouse 그룹 이름은 각 Warehouse 노드를 등록할 때 `--group`으로 지정합니다.
##### 3. 노드 시작
```bash
machcoordinatoradmin --startup-node="192.168.1.13:5501"
machcoordinatoradmin --startup-node="192.168.1.14:5501"
```
#### 전체 상태 확인
모든 노드 등록 후 상태를 확인합니다.
```bash
machcoordinatoradmin --cluster-status
```
Coordinator, Lookup, Broker, Warehouse가 각 역할에 맞는 정상 상태로 표시되면 클러스터가 정상 구동 중입니다. Coordinator는 `primary`, Broker는 `leader`, Warehouse는 `normal`, `sync-active`, `sync-standby` 등으로 표시됩니다.
#### 최초 접속 확인
Broker 네이티브 포트로 접속해 `SELECT CURRENT_DATABASE();`와 표본 조회를 실행합니다. 최초 설치 검증이 끝난 뒤의 개별 노드 시작·종료와 상태 복구는 [Cluster 운영](../../operations-configuration-recovery/cluster/)과 [Cluster 문제 해결](../../troubleshooting/cluster/)을 사용하십시오.
---
title: "3.4 업그레이드"
url: https://docs.machbase.com/kr/dbms/installation-deployment-upgrade/upgrade/
language: kr
kind: page
---
# 3.4 업그레이드
업그레이드는 실행 파일 교체뿐 아니라 기존 데이터, 설정과 애플리케이션이 새 버전에서
같은 의미로 동작하는지 확인하는 작업입니다. 먼저 지원되는 버전 간 경로를 확인하고,
복원 가능한 백업과 서비스 재개 기준을 준비합니다. 다음 예제의 8.7.0 패키지명과 경로는
제공받은 실제 배포본에 맞춥니다.
## 업그레이드 전 확인사항
- 현재 버전과 대상 버전의 호환성을 확인합니다. Minor 버전이 다르면 DB 파일 형식이 변경될 수 있습니다.
- 업그레이드 전 백업을 수행합니다. [백업 방법](/dbms/operations-configuration-recovery/backup-restore-mount/#backup) 참고.
- 진행 중인 INSERT·APPEND 클라이언트를 확인합니다.
### 8.7.0 업그레이드 사전 점검
8.5에서 8.7.0으로 업그레이드할 때는 바이너리를 교체하기 전에 다음 의존성을 조사하고
지원되는 방식으로 전환합니다.
1. 제거된 `HTTP_AUTH`, `HTTP_ENABLE`, `HTTP_MAX_MEM`, `HTTP_PORT_NO`, `RS_CACHE_*`,
`STREAM_THREAD_COUNT`, `STREAM_WAIT_MS` 설정을 현재 파일에서 찾아 지원 목록과 대조합니다.
제거된 설정은 삭제하고 유지 설정은 보존합니다. 특히 Cluster의 `HTTP_ADMIN_PORT`는
현행 관리 포트이므로 `HTTP_*`라는 이유로 함께 제거하지 마십시오.
2. `/machbase`, `/machiot`를 호출하는 애플리케이션은 지원되는 SDK를 사용하는 백엔드로
전환합니다.
3. `STREAM_*` 프로시저와 `FLUSH RESULT_CACHE`를 실행하는 SQL 또는 운영 스크립트를
변경합니다.
4. `machcli.h`와 `MachCLI*()`를 사용하는 C/C++ 애플리케이션을 Machbase SQLCLI 또는
ODBC로 이전합니다. SQLCLI와 ODBC는 서로 다른 API 집합입니다.
5. WebAdmin/MWA에 의존하는 운영 절차와 대시보드는 명령행 도구 또는 별도 애플리케이션으로
전환합니다.
제거 항목 전체와 유지 기능은
[버전 및 호환성](/dbms/reference/support-scope-constraints/compatibility-version/#removed-features-870)을
참고하십시오.
## 업그레이드 경로
| 에디션 | 방식 | 링크 |
|--------|------|------|
| Standard Edition | 서버 종료 후 패키지 교체 | [Standard Edition 업그레이드](/dbms/installation-deployment-upgrade/upgrade/#standard-edition) |
| Cluster Edition | Broker/Warehouse 순차 업그레이드 | [온라인 업그레이드](/dbms/installation-deployment-upgrade/upgrade/#cluster-edition-online) |
| Cluster Edition | 전체 중지 | [전체 중지 업그레이드](/dbms/installation-deployment-upgrade/upgrade/#cluster-edition-full-stop) |
Cluster Edition의 경우 데이터 가용성 요구사항에 따라 온라인 또는 전체 중지 방식을 선택합니다.
---
## Standard Edition 업그레이드
서버를 종료하고 패키지를 교체한 후 재시작합니다. 물리 DB 파일을 그대로 여는 것이
지원되는 버전 간 경로에만 이 절차를 적용합니다. 데이터 변환이나 export/import가 필요한
경로는 해당 릴리스의 마이그레이션 절차를 먼저 수행합니다.
### 업그레이드 전 준비
1. **백업 수행**: 지원되는 BACKUP 명령으로 백업을 만들고 별도 환경에서 복원 여부를
확인합니다. 실행 중인 데이터 디렉터리의 단순 복사만으로 복구 가능한 백업을 확보했다고
판단하지 마십시오.
2. **클라이언트 연결 종료**: 진행 중인 Append 또는 INSERT 작업을 모두 완료합니다.
3. **현재 버전 확인**:
```bash
machbased -v
```
### 업그레이드 절차
#### 1. 서버 종료
```bash
machadmin -s
# Machbase server shut down successfully.
```
#### 2. 기존 패키지와 설정 보관
실행 파일과 라이브러리뿐 아니라 현재 설정과 라이선스도 별도 위치에 보관합니다.
아래 경로에 이전 백업이 없는지 확인한 뒤 실행합니다.
```bash
cp -a "$MACHBASE_HOME/bin" "$MACHBASE_HOME/bin.bak"
cp -a "$MACHBASE_HOME/lib" "$MACHBASE_HOME/lib.bak"
cp -a "$MACHBASE_HOME/conf" "$MACHBASE_HOME/conf.bak"
```
**데이터 디렉터리(`dbs/`)와 별도로 지정한 `DBS_PATH`는 보존합니다.** 위 복사는 실행 파일과
설정의 보관이며 DB 백업을 대체하지 않습니다. 새 버전이 데이터를 변경한 뒤에는 이전
실행 파일만 되돌려 복구할 수 있다고 가정하지 말고, 검증한 백업 복원 경로를 사용합니다.
#### 3. 새 패키지 압축 해제
새 패키지는 별도 작업 디렉터리에 해제하여 구성과 설정 변경 사항을 먼저 확인합니다.
```bash
upgrade_stage=$(mktemp -d)
tar zxf machbase-SDK-8.7.0.official-LINUX-X86-64-release.tgz -C "$upgrade_stage"
```
압축 해제만으로 기존 설치의 실행 파일이 바뀌지는 않습니다. 다음은 `bin/`, `lib/`,
`include/`를 포함하는 Standard tarball에서 실행 파일·라이브러리·헤더를 반영하는 예입니다.
먼저 서버가 종료되었는지 확인하고, 명령이 실패하면 다음 시작 단계로 넘어가지 않습니다.
```bash
(
set -e
test -n "$MACHBASE_HOME"
test -x "$upgrade_stage/bin/machbased"
test -d "$upgrade_stage/lib"
test -d "$upgrade_stage/include"
test -d "$MACHBASE_HOME/bin"
test -d "$MACHBASE_HOME/lib"
test -d "$MACHBASE_HOME/include"
cp -a "$upgrade_stage/bin/." "$MACHBASE_HOME/bin/"
cp -a "$upgrade_stage/lib/." "$MACHBASE_HOME/lib/"
cp -a "$upgrade_stage/include/." "$MACHBASE_HOME/include/"
"$MACHBASE_HOME/bin/machbased" -v
)
```
기존 `conf/machbase.conf`, 라이선스와 실제 `DBS_PATH`의 데이터는 보존하고 새 설정 항목은
기존 설정에 병합합니다. 복사는 같은 이름의 배포 파일을 교체하며 이전 버전에서만 있던
파일을 자동 삭제하지 않습니다. SDK나 플러그인은 새 버전에 맞는 파일을 명시적으로 선택하고,
추가 교체 대상과 제거 항목은 릴리스 안내를 확인합니다. 출력된 바이너리 버전과 설정 검토가
완료된 뒤에만 서버를 시작합니다.
#### 4. 서버 시작
```bash
machadmin -u
# Machbase server started successfully.
```
#### 5. 버전 확인
```bash
machbased -v
# 서버 접속
machsql -s 127.0.0.1 -P 5656 -u SYS -p MANAGER
```
```sql
SELECT EDITION, BINARY_DB_MAJOR_VERSION, BINARY_DB_MINOR_VERSION FROM V$VERSION;
```
### 주의사항
- `dbs/` 디렉터리를 절대 삭제하거나 초기화(`machadmin -d`)하지 마십시오.
- Minor 버전 간 업그레이드는 DB 파일 마이그레이션이 필요할 수 있습니다. 릴리스 노트를 반드시 확인하십시오.
- Windows 환경에서는 새 패키지 또는 설치 실행 파일을 적용하기 전에 Machbase 서비스를 중지합니다.
---
## Cluster Edition 업그레이드
서비스 중단 여부에 따라 두 가지 방식을 선택합니다.
| 방식 | 서비스 중단 | 적합한 상황 |
|------|-----------|------------|
| [온라인 업그레이드](/dbms/installation-deployment-upgrade/upgrade/#cluster-edition-online) | Broker/Warehouse 순차 재기동 | Broker와 Warehouse만 교체하는 운영 환경 |
| [전체 중지 업그레이드](/dbms/installation-deployment-upgrade/upgrade/#cluster-edition-full-stop) | 있음 | 유지보수 창이 허용되는 경우, Major 버전 변경 |
### 업그레이드 전 공통 주의사항
- 업그레이드 중에는 DDL 또는 DELETE를 실행하지 마십시오.
- 업그레이드 중 노드 추가·시작·종료·삭제 작업을 병행하지 마십시오.
- 온라인 업그레이드는 Broker와 Warehouse를 대상으로 합니다. Coordinator, Deployer, Lookup까지 교체하려면 전체 중지 업그레이드를 사용합니다.
- 업그레이드 전 백업을 권장합니다.
---
### 온라인 업그레이드
실행 중인 클러스터에서 Broker와 Warehouse를 순차적으로 업그레이드합니다. Coordinator, Deployer, Lookup까지 포함한 전체 바이너리 교체가 필요하면 [전체 중지 업그레이드](/dbms/installation-deployment-upgrade/upgrade/#cluster-edition-full-stop)를 사용합니다.
#### 업그레이드 절차
##### 1. cluster.yaml의 패키지 변경
`cluster.package.name`과 `cluster.package.origin_path`를 새 패키지로 변경합니다. 패키지 내용이
바뀌면 패키지 이름과 압축 파일 이름도 함께 고유하게 바꿉니다.
```yaml
cluster:
package:
name: machbase-v8.7.0
origin_path: /home/machbase/packages/machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz
```
`registered_path`는 `machclusterctl export`가 기록하는 Coordinator 패키지 저장소 경로입니다.
업그레이드할 압축 파일을 지정할 때는 `origin_path`를 사용합니다.
##### 2. 실행 계획 확인
```bash
machclusterctl upgrade -f cluster.yaml --online --dry-run --verbose
```
##### 3. 온라인 업그레이드 실행
```bash
machclusterctl upgrade -f cluster.yaml --online --yes --verbose
```
`--online`을 생략해도 온라인 모드로 처리되지만, 운영 절차를 명확히 하기 위해 옵션을 명시하는 것을 권장합니다.
##### 4. 전체 상태 확인
```bash
machclusterctl status
```
#### 수동 업그레이드 참고
`machcoordinatoradmin --upgrade-node`를 직접 사용하는 경우에는 대상 노드와 패키지 이름을 함께 지정합니다.
```bash
machcoordinatoradmin --upgrade-node=192.168.1.11:5401 --package-name=machbase-v8.7.0
```
온라인 대상은 Broker와 Warehouse로 제한합니다. Broker가 하나만 남아 있을 때 해당 Broker를 업그레이드하면 그 시간 동안 클라이언트 접속이 끊길 수 있습니다.
온라인 모드는 전체 클러스터 중지를 생략하는 방식이며 무중단을 보장하는 HA-aware rolling
upgrade는 아닙니다. Warehouse 그룹이 일시적으로 읽기 전용이 될 수 있으므로 애플리케이션의
재접속·재시도와 쓰기 지연을 검증해야 합니다. 프로토콜 호환성이 달라지거나 모든 역할의
바이너리를 맞춰야 한다면 전체 중지 방식을 사용합니다.
모든 대상 노드의 역할 상태와 버전을 확인한 뒤 대표 조회·입력과 복제 상태까지 검증합니다.
---
### 전체 중지 업그레이드
클러스터를 완전히 종료한 후 모든 노드를 일괄 업그레이드합니다. Coordinator, Deployer, Lookup까지 포함해 전체 바이너리를 교체해야 하거나 DB 파일 형식 변경이 수반되는 경우에 사용합니다.
#### 업그레이드 절차
##### 1. 클라이언트 연결 종료
모든 INSERT·APPEND·SELECT 작업이 완료되었는지 확인합니다.
##### 2. cluster.yaml의 패키지 변경
`cluster.package.name`과 `cluster.package.origin_path`를 새 패키지로 변경합니다. 업그레이드 전에는 노드 추가, 삭제, 포트 변경 같은 토폴로지 변경이 없어야 합니다. 토폴로지 변경이 있으면 먼저 `apply`로 반영한 뒤 업그레이드를 수행합니다.
`machclusterctl upgrade --full-stop`은 Coordinator와 Deployer를 포함한 모든 노드 홈에
같은 패키지를 교체 반영합니다. 따라서 `origin_path`에는 `machcoordinatoradmin`과
`machdeployeradmin`이 포함된 전체 Cluster 패키지를 지정합니다.
##### 3. 실행 계획 확인
```bash
machclusterctl upgrade -f cluster.yaml --full-stop --dry-run --verbose
```
##### 4. 전체 중지 업그레이드 실행
```bash
machclusterctl upgrade -f cluster.yaml --full-stop --yes --verbose
```
`machclusterctl`은 전체 클러스터 중단을 전제로 패키지를 임시 준비 경로에 해제한 뒤
노드 홈에 교체 반영합니다.
#### 수동 배포 참고
수동 배포 환경에서 직접 교체해야 하는 경우, Coordinator에 새 패키지를 등록합니다.
```bash
machcoordinatoradmin --add-package=machbase-v8.7.0 \
--file-name=/home/machbase/packages/machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz
```
Warehouse → Broker → Lookup → Deployer → Coordinator 순으로 종료합니다.
```bash
machcoordinatoradmin --shutdown-node=192.168.1.13:5501
machcoordinatoradmin --shutdown-node=192.168.1.14:5501
machcoordinatoradmin --shutdown-node=192.168.1.11:5401
machcoordinatoradmin --shutdown-node=192.168.1.10:5301
machdeployeradmin --shutdown
machcoordinatoradmin --shutdown
```
각 노드 홈을 새 패키지로 교체할 때는 기존 `conf/machbase.conf`, `dbs/`, `meta/`, `package/`
디렉터리를 보존합니다. 별도 작업 경로에 새 패키지를 해제한 뒤 보존 대상 경로를 제외하고
교체합니다.
Coordinator → Deployer → Lookup → Broker → Warehouse 순으로 시작합니다.
```bash
machcoordinatoradmin --startup
machdeployeradmin --startup
machcoordinatoradmin --startup-node=192.168.1.10:5301
machcoordinatoradmin --startup-node=192.168.1.11:5401
machcoordinatoradmin --startup-node=192.168.1.13:5501
machcoordinatoradmin --startup-node=192.168.1.14:5501
```
재시작 후 Broker와 Warehouse의 패키지 메타데이터를 새 패키지 이름으로 동기화합니다.
```bash
machcoordinatoradmin --upgrade-node=192.168.1.11:5401 --package-name=machbase-v8.7.0
machcoordinatoradmin --upgrade-node=192.168.1.13:5501 --package-name=machbase-v8.7.0
machcoordinatoradmin --upgrade-node=192.168.1.14:5501 --package-name=machbase-v8.7.0
```
##### 5. 상태 확인
```bash
machclusterctl status
```
노드 상태뿐 아니라 Broker 접속, 대표 데이터 조회·입력, 복제, 라이선스와 설정값을 확인한
뒤 서비스를 재개합니다. [설치 검증](../validation-checklist/) 결과를 변경 전 기록과
비교하고, 검증이 끝날 때까지 이전 패키지·설정과 백업을 보존합니다.
#### 주의사항
- Major 버전 업그레이드는 DB 파일 형식이 변경될 수 있습니다. 릴리스 노트를 반드시 확인하고, 업그레이드 전 백업을 수행하십시오.
- `conf/machbase.conf`, `dbs/`, `meta/`, `package/` 경로를 절대 삭제하거나 초기화하지 마십시오.
---
title: "3.5 설치 검증 체크리스트"
url: https://docs.machbase.com/kr/dbms/installation-deployment-upgrade/validation-checklist/
language: kr
kind: page
---
# 3.5 설치 검증 체크리스트
설치 검증은 프로세스가 살아 있는지 확인하는 것에서 끝나지 않습니다. 올바른 버전과 설정의
서버에 실제 클라이언트가 접속하고, 권한 범위에서 데이터를 기록·조회할 수 있어야 합니다.
아래 항목을 서버 상태 → 접속 → 버전·라이선스 → SQL → Cluster 복제 순서로 확인합니다.
각 결과에 실행 호스트, 설치 홈, 접속 주소·포트, 시각과 오류를 기록합니다. 업그레이드라면
변경 전 기록과 비교하십시오. `SYS`/`MANAGER`는 초기 실습 접속 예시이며 실제 계정으로
바꾸어야 합니다. 실습 테이블 이름이 기존 업무 객체와 겹치지 않는지도 먼저 확인합니다.
## Standard Edition 체크리스트
### 1. 서버 프로세스 확인
```bash
machadmin -e
# Machbase server is running with PID().
```
프로세스를 직접 확인할 수도 있지만 다른 설치 홈에서 실행된 서버인지 구분해야 합니다.
`MACHBASE_HOME`이 검사할 인스턴스를 가리키는지 확인합니다.
```bash
ps -ef | grep machbased | grep -v grep
```
### 2. 포트 리스닝 확인
```bash
ss -tlnp | grep 5656
# LISTEN 0 128 0.0.0.0:5656 ...
```
현재 운영체제에 맞는 포트 확인 도구를 사용합니다. 예시의 `5656`은 실제 SQL 포트로 바꾸고,
수신 주소가 의도한 인터페이스인지 확인합니다. 리스너 확인과 원격 클라이언트의 접속 성공은
별개의 검사이므로 애플리케이션이 실행될 호스트에서도 접속을 시험합니다.
### 3. machsql 접속 테스트
```bash
machsql -s 127.0.0.1 -P 5656 -u SYS -p MANAGER
# MACHBASE_CONNECT_MODE=INET, PORT=5656 EDITION=STANDARD
# Mach>
```
로컬 접속은 성공하고 원격 접속만 실패하면 주소·포트, 리스너 설정과 방화벽을 확인합니다.
인증 오류는 사용자명·인증 방식·만료 상태를 확인하며, 접속 성공 뒤 객체 접근 오류는
현재 데이터베이스와 SQL 권한을 별도로 점검합니다.
### 4. 버전 확인
```sql
SELECT * FROM V$VERSION;
SELECT CURRENT_DATABASE();
```
### 5. 라이선스 확인
```sql
SELECT ID, ISSUE_DATE, TYPE, VIOLATE_STATUS FROM V$LICENSE_INFO;
```
`VIOLATE_STATUS`가 0인지 확인하고 설치된 라이선스 유형·기간이 운영 계획과 맞는지 봅니다.
위반 상태라면 `VIOLATE_MSG`와 서버 로그에서 원인을 확인합니다.
### 6. 기본 쿼리 테스트
```sql
CREATE LOG TABLE check_test (id INTEGER, ts DATETIME);
INSERT INTO check_test (id, ts) VALUES (1, NOW);
SELECT id, ts FROM check_test;
DROP TABLE check_test;
```
입력한 `id=1` 한 행과 시각이 반환되고 DROP이 성공하는지 확인합니다. 이 검사는 기본
LOG 입력 경로만 확인합니다. 실제 서비스에서 TAG·TRANSACTION·Append 등을 사용한다면
해당 테이블과 SDK로 대표 입력·조회도 수행합니다.
## Cluster Edition 추가 체크리스트
### 7. 클러스터 노드 상태 확인
```bash
machcoordinatoradmin --cluster-status
# 배포 기록의 노드 수와 역할별 상태를 비교
```
### 8. Broker 접속 테스트
```bash
machsql -s 192.168.1.11 -P 5656 -u SYS -p MANAGER
```
```sql
SELECT * FROM V$NODE_STATUS;
```
Coordinator의 `primary`, Broker의 `leader`, Warehouse의 복제 상태처럼 역할마다 정상
상태 표기가 다릅니다. 모든 노드를 단일 문자열과 비교하지 말고 목표 상태와 실제 상태,
그룹 구성과 주소가 배포 기록과 맞는지 확인합니다.
### 9. 데이터 복제 확인
먼저 Broker를 통한 입력·조회를 확인한 뒤 Warehouse 그룹의 복제 상태를 확인합니다.
Broker에서 한 행이 조회된다는 사실만으로 모든 복제본의 동기화를 증명할 수는 없습니다.
```sql
-- Broker를 통해 데이터 입력
CREATE LOG TABLE cluster_check_test (id INTEGER, ts DATETIME);
INSERT INTO cluster_check_test (id, ts) VALUES (1, NOW);
-- Broker에서 입력 결과 확인
SELECT COUNT(*) FROM cluster_check_test;
```
Warehouse 직접 SQL 접속은 일반 애플리케이션 경로가 아니라 복제 진단용 관리 절차입니다.
`machcoordinatoradmin --cluster-status`에서 같은 복제 그룹의 active·standby peer를
확인한 뒤, 필요한 경우에만 각 peer의 네이티브 포트에 관리 계정으로 접속해 동일한 조회 결과를
비교합니다. 서로 다른 Warehouse 그룹의 모든 노드가 같은 행을 가진다고 가정하지 마십시오.
검증이 끝나면 Broker 연결에서 테이블을 정리합니다.
```sql
DROP TABLE cluster_check_test;
```
---
## 완료 기준과 문제 발생 시
서버·접속·권한·대표 SQL과 필요한 복제 검사가 모두 통과하면 서비스 인수를 진행합니다.
영속성을 검증해야 한다면 별도 검증 환경에서 데이터를 남긴 뒤 정상 재시작 전후 결과를
비교합니다. 업무 데이터가 있는 서버를 단순 설치 점검 목적으로 재초기화하지 마십시오.
실패하면 최초 오류와 관련 로그를 보존하고 실패한 단계부터 원인을 좁힙니다.
- 서버 로그: `$MACHBASE_HOME/trc/machbase.trc`
- [운영·장애 진단](/dbms/operations-configuration-recovery/diagnosis-observability/) 참고
---
title: "4. 테이블 타입과 스키마 설계"
url: https://docs.machbase.com/kr/dbms/data-modeling-table-design/
language: kr
kind: section
---
# 4. 테이블 타입과 스키마 설계
이 장에서는 저장할 데이터의 의미와 변경·조회 요구사항을 Machbase DBMS의 테이블과
컬럼으로 구체화합니다. 데이터베이스가 실행되는 것을 확인했다면, 이제 무엇을 한 행으로
저장할지부터 정합니다. 익숙한 테이블 유형을 먼저 고른 뒤 모든 데이터를 거기에 맞추면
시간 이력, 수정 범위와 보관 정책이 서로 충돌할 수 있습니다.
각 테이블 타입의 역할을 처음 접한다면 [데이터 모델 개념](/dbms/core-concepts/concepts/)을
먼저 읽으십시오. 이 장에서는 그 개념을 실제 스키마와 검증 가능한 설계로 연결합니다.
## 설계의 출발점
테이블 이름보다 먼저 한 행의 의미를 문장으로 적습니다. 예를 들어 “센서 하나의 측정값
한 건”, “설비 상태가 바뀐 사건 한 건”, “장비 하나의 현재 설치 정보”는 서로 다른
행의 단위입니다. 같은 설비에서 발생한 데이터라도 저장 역할이 다릅니다.
다음 질문에 답하면 테이블을 선택할 근거가 생깁니다.
| 설계 질문 | 결정할 내용 |
|---|---|
| 한 행은 무엇인가? | 측정·이벤트·현재 상태·기준 정보 중 무엇을 저장하는지 |
| 대상을 어떻게 구분하는가? | 태그 이름, 업무 키와 중복 수집 식별 기준 |
| 시간이나 축은 무엇을 뜻하는가? | 측정 시각, 수신 시각 또는 거리·위치 |
| 값을 어떻게 해석하는가? | 단위, 타입 범위, 정밀도와 NULL·결측 처리 |
| 어떤 변경이 필요한가? | 새 행 추가, 과거 값 보정, 키 변경과 삭제 범위 |
| 어떤 조회를 자주 하는가? | 태그·시간 구간, 조건 검색, 키 조회, 집계와 조인 |
| 얼마나 남겨 두어야 하는가? | 원본·집계·기준 정보 이력의 보관 기간과 재시작 후 복구 |
| 작업이 실패하면 무엇을 되돌리는가? | 테이블·API별 트랜잭션 범위와 재시도·재구성 절차 |
데이터 크기나 입력 빈도 하나만으로 타입을 결정하지 않습니다. 한 행에 모은 값들이
같은 관측이나 사건을 설명하는지 확인하고, 연결이 필요한 데이터는 공통 식별자로 조인할 수
있게 설계합니다. 조인 가능한 키가 있다는 사실과 DBMS가 모든 업무 관계를 자동으로 강제한다는
뜻도 구분합니다. 지원 제약 조건은 테이블별로 확인합니다.
## 이 장의 구성
| 순서 | 섹션 | 설계 결과 |
|-----:|---|---|
| 4.1 | [테이블 타입 선택](./table-types-selection-type/) | 변경·조회·영속성·Edition 요구에 맞는 타입 |
| 4.2 | [스키마 객체 정의](./schema-objects-definition/) | 컬럼과 타입, 식별자, 기본값·제약, 인덱스와 VIEW |
| 4.3 | [데이터 변경 정책](./alter-data-mutation-policy/) | 허용할 보정·삭제와 실패 처리의 범위 |
| 4.4 | [안티패턴](./table-types-patterns-type-anti/) | 요구와 맞지 않는 선택을 찾아 수정할 근거 |
| 4.5 | [모델링 패턴](./patterns-modeling/) | 이력·상태·기준 정보를 함께 사용하는 구체적인 모델 |
4.1에서 타입을 선택하고 4.2와 4.3에서 구조와 변경 정책을 정합니다. 이어서 4.4의
안티패턴을 점검하고 4.5의 패턴을 자신의 데이터에 맞게 적용합니다. 표의 비교 결과는
출발점이며 실제 SQL과 지원 조건은 연결된 테이블별 문서와 레퍼런스에서 확인합니다.
## 설비 모니터링을 예로 생각하기
온도, 알람, 장비 정보와 화면용 상태를 모두 같은 방식으로 저장할 필요는 없습니다.
| 데이터 | 한 행의 의미 | 검토할 저장 역할 |
|---|---|---|
| 온도 이력 | 한 센서가 특정 시각에 측정한 값 | 태그·시간 범위 조회와 집계를 위한 TAG |
| 알람 이력 | 특정 시각에 발생한 알람 사건 | 추가 중심 이벤트를 위한 LOG |
| 장비 기준 정보 | 장비 하나의 이름·위치·허용 기준 | LOOKUP 또는 여러 변경을 트랜잭션으로 묶어야 하는 경우 TRANSACTION |
| 화면용 현재 상태 | 다시 계산할 수 있는 최근 상태 | 원본과 재구성 경로가 있는 경우 VOLATILE |
이 표는 예시이지 고정된 정답은 아닙니다. 이벤트에 여러 측정 항목이 함께 들어오거나
수정·보관 요구가 다르면 다른 설계가 적합할 수 있습니다. TRANSACTION은 Standard Edition
지원 여부도 함께 확인합니다.
온도 이력에 최신 장비 정보를 조인하면 결과는 현재 위치와 이름으로 해석됩니다.
측정 당시 위치나 허용 기준이 필요하면 그 정보를 원본에 남기거나 기준 정보의 변경
이력을 설계해야 합니다. 현재 상태 캐시를 함께 갱신하는 경우에도 원본 입력과 캐시 갱신이
한 트랜잭션이라고 가정하지 말고, 실패 후 캐시를 다시 만들 방법을 준비합니다.
## 작은 데이터로 설계 검증하기
운영 규모로 늘리기 전에 대표 데이터와 자주 쓰는 쿼리로 다음을 확인합니다.
1. 정상값뿐 아니라 NULL, 누락, 경계값과 중복·지연 도착 데이터를 입력합니다.
2. 원본 조회와 집계 결과가 같은 시간 기준·단위·NULL 정책을 사용하는지 확인합니다.
3. 보정·삭제·재입력 후 결과를 비교하고 다른 행이나 관련 집계에 미치는 영향을 확인합니다.
4. 재시작 시 보존돼야 할 데이터와 다시 생성해야 할 상태를 구분해 복구 순서를 점검합니다.
5. 입력량·조회 범위·동시성을 늘려 처리량, 지연과 저장 공간을 측정합니다.
입력 오류, 오래된 스키마를 사용하는 애플리케이션과 재시작 복구는 정상 경로와 함께
설계할 대상입니다. 스키마를 변경할 때는 기존 데이터와 뷰·롤업·클라이언트의 의존성을
확인하고, 필요하면 새 테이블로 옮겨 검증한 뒤 전환합니다.
구현은 [테이블별 활용 장](/dbms/)과
[개발 및 애플리케이션 연동](/dbms/development-tools-integration/)으로,
측정은 [성능 튜닝](/dbms/performance-tuning/)으로 이어집니다.
---
title: "4.1 테이블 타입 선택"
url: https://docs.machbase.com/kr/dbms/data-modeling-table-design/table-types-selection-type/
language: kr
kind: page
---
# 4.1 테이블 타입 선택
잘못된 타입 선택은 성능 저하와 기능 제한으로 이어지므로, 설계 초기에 데이터 성격에 맞는 타입을 결정해야 합니다.
- **[타입 선택 결정 가이드](/dbms/data-modeling-table-design/table-types-selection-type/#selection-decision)**
- **[타입 비교표](/dbms/data-modeling-table-design/table-types-selection-type/#comparison-tag-log-rdb-volatile-lookup)**
- **[TRANSACTION vs LOOKUP 비교](/dbms/data-modeling-table-design/table-types-selection-type/#comparison-rdb-vs-lookup)**
테이블 타입의 역할과 저장 개념은 [데이터 모델 개념](../../core-concepts/concepts/#time-series)을
참고하십시오. 같은 데이터라도 이력을 누적할지, 현재 상태를 갱신할지에 따라 적합한 타입이
달라집니다. 변경·조회·영속성 요구사항을 함께 검토합니다.
### 선택 전에 작성할 데이터 설명
테이블 이름보다 먼저 한 행이 나타내는 사실을 한 문장으로 적습니다. 같은 설비에서 나온
데이터라도 “온도 측정 한 건”, “현재 운전 상태 한 건”, “정비 작업 한 건”은 서로 다른
행의 단위이며 키와 변경 방식도 달라집니다.
| 설계 질문 | 설비 모니터링에서 정할 내용 |
|---|---|
| 한 행은 무엇인가? | 센서 한 개의 측정 한 건인지, 설비의 현재 상태인지 구분 |
| 무엇으로 찾는가? | 센서 이름과 발생 시각, 설비 ID, 정비 작업 번호 |
| 값은 어떻게 변하는가? | 새 이력 추가, 잘못된 값 보정, 현재 행 덮어쓰기 |
| 함께 확정할 변경이 있는가? | 정비 작업 등록과 부품 수량 변경을 한 트랜잭션으로 묶을지 결정 |
| 얼마나 보관하는가? | 원본 기간, 집계 기간, 재시작 후 재생성 가능 여부 |
| 어느 정도의 크기인가? | 태그 수, 초당 행 수, 행 크기, 기준 정보와 인덱스의 메모리 사용량 |
예를 들어 온도 이력은 TAG, 알람 사건은 LOG, 설비 코드표는 LOOKUP이 후보입니다.
부품 재고 변경과 작업 등록을 함께 확정해야 하면 Standard Edition의 TRANSACTION을
검토합니다. 현재 상태 캐시는 원본에서 재구성할 수 있을 때 VOLATILE로 분리할 수 있습니다.
이들을 한 테이블로 합치기보다 각 행의 의미와 실패 시 복구 방법을 먼저 맞춥니다.
## 타입 선택 결정 가이드
아래 흐름으로 후보를 좁힌 뒤, 필요한 DML과 트랜잭션, 메모리 사용량, Edition 지원 여부를
비교표에서 확인합니다. 예를 들어 기준 정보라도 여러 변경을 하나의 트랜잭션으로 묶어야
한다면 LOOKUP 대신 TRANSACTION을 검토합니다.
### 결정 흐름
```
데이터가 센서/기기 계측값인가?
├── YES → 시간축인가? YES → TAG TABLE (BASETIME)
│ 거리축인가? YES → TAG TABLE (BASEDISTANCE)
└── NO ↓
데이터가 이벤트/로그/패킷인가? (추가 전용)
├── YES → LOG TABLE
└── NO ↓
데이터가 코드 테이블/기준 정보인가? (반복 조회·갱신)
├── YES → LOOKUP TABLE
└── NO ↓
데이터가 서버 재시작 시 폐기 가능한 인메모리 상태/캐시인가?
├── YES → VOLATILE TABLE
└── NO ↓
일반 관계형 업무 데이터 (UPDATE/DELETE/SELECT/INSERT 모두 필요)
└── TRANSACTION TABLE
```
### 주요 판단 기준
| 질문 | 타입 |
|------|------|
| 시간 또는 거리 기반 계측값인가? | TAG |
| 원본 이벤트를 추가하고 오래된 구간만 정리하는가? | LOG |
| PRIMARY KEY가 필요한 기준 정보이며 반복 조회·갱신하는가? | LOOKUP |
| 서버 재시작 시 데이터가 사라져도 되는가? | VOLATILE |
| 일반 관계형 업무 (INSERT/UPDATE/DELETE/SELECT)? | TRANSACTION |
### 주의사항
- 이벤트마다 고유한 이름을 태그 식별자로 사용하면 태그 수와 메타데이터가 계속 늘어납니다.
반복 계측 대상이 없는 이벤트는 LOG 테이블을 검토합니다.
- LOG 테이블은 UPDATE와 일반 조건 DELETE가 불가하므로 수정 가능성이 있는 데이터에는 부적합합니다. 보존/정리 목적의 `BEFORE`, `OLDEST`, `EXCEPT` DELETE만 사용합니다.
- TRANSACTION 테이블은 Standard Edition 전용입니다. Cluster Edition 환경에서는 LOOKUP(소규모) 또는 외부 RDBMS를 활용합니다.
- VOLATILE 테이블은 서버 재시작 시 데이터가 소멸됩니다.
## 타입 비교표
### 기능 비교
| 항목 | TAG | LOG | TRANSACTION | VOLATILE | LOOKUP |
|------|-----|-----|-----|----------|--------|
| DDL | `CREATE TAG TABLE` | `CREATE LOG TABLE` | `CREATE TABLE` / `CREATE TRANSACTION TABLE` / `CREATE TXN TABLE` | `CREATE VOLATILE TABLE` | `CREATE LOOKUP TABLE` |
| 주 용도 | 센서·계측값 | 이벤트·로그 | 관계형 업무 | 임시 집계 | 코드·기준 |
| INSERT | O | O | O | O | O |
| UPDATE | O (Standard, 태그/BASETIME 조건) | X | O | O | O |
| DELETE | O (BEFORE/조건/전체) | O (BEFORE/OLDEST/EXCEPT/전체) | O | O (PK 일치/전체) | O (일반 조건식/전체 삭제) |
| PRIMARY KEY | 필수 | X | 선택 | 선택 | 필수 |
| BASETIME | 필수 (시간축) | X | X | X | X |
| _arrival_time | X | 자동 추가 | X | X | X |
| 인덱스 | 태그·축 접근, 지원되는 보조 인덱스 | BITMAP/KEYWORD/LSM | BTREE PK + 보조 인덱스 | 키·보조 인덱스 | 키·보조 인덱스 |
| 영속성 | O | O | O | X (메모리) | O |
| Cluster Edition | O | O | X | O | O |
Append API는 테이블뿐 아니라 SDK와 입력 경로에 따라 지원 범위가 달라집니다.
[SDK Append 지원표](../../development-tools-integration/sdk-support-scope/#append-table-type-matrix)에서
사용하는 드라이버와 테이블 조합을 확인합니다.
### 스토리지 특성
| 항목 | TAG | LOG | TRANSACTION | VOLATILE | LOOKUP |
|------|-----|-----|-----|----------|--------|
| 스토리지 | 컬럼형 | 컬럼형 | 행 기반 (관계형) | 메모리 | 영속 저장 + 전체 행 메모리 상주 |
| 용량 검토 기준 | 태그 수·원본·ROLLUP·보관 기간 | 원본·검색 인덱스·보관 기간 | 행·인덱스·트랜잭션 부하 | 전체 행·인덱스의 메모리 크기 | 전체 행·인덱스의 메모리 크기와 재시작 적재 시간 |
메모리 테이블의 “작다”는 고정 행 수를 의미하지 않습니다. 행 폭, 가변 길이 값과 보조
인덱스를 포함해 실제 메모리 사용량을 측정합니다. 디스크 기반 테이블도 압축률이나
서버 사양만으로 처리량을 보장할 수 없으므로 대표 입력과 조회를 함께 실행합니다.
### TRANSACTION 테이블 제약
TRANSACTION 테이블은 다음 제약이 있습니다.
- **Cluster Edition 미지원**: Standard Edition 전용
- **최소 컬럼 수**: 1개 이상
## TRANSACTION vs LOOKUP 비교
TRANSACTION 테이블과 LOOKUP 테이블은 모두 관계형 데이터를 저장하지만, 대상 규모와 기능에 차이가 있습니다.
### 비교표
| 항목 | TRANSACTION 테이블 | LOOKUP 테이블 |
|------|-----------|--------------|
| DDL | `CREATE TRANSACTION TABLE` | `CREATE LOOKUP TABLE` |
| PRIMARY KEY | 선택 | 필수 |
| INSERT | O | O |
| UPDATE (WHERE 포함) | O | O |
| UPDATE (WHERE 없음) | O (전체 행) | X |
| DELETE | O | O |
| 명시적 트랜잭션 | 여러 문장을 COMMIT/ROLLBACK으로 제어 | 참여하지 않음, 문장별 변경 |
| 인덱스 | BTREE PK + 보조 인덱스 | 메모리 키·보조 인덱스 |
| 데이터 규모 | 디스크 용량과 트랜잭션 부하로 검증 | 참조 데이터 조회·갱신 부하로 검증 |
| JOIN 대상 | O | O |
| Cluster Edition | X | O |
### 선택 가이드
**TRANSACTION 테이블을 선택하는 경우**
- 명시적 트랜잭션과 관계형 DML이 필요한 데이터
- UPDATE·DELETE·INSERT·SELECT가 모두 필요한 일반 관계형 워크로드
- PRIMARY KEY 없이 다양한 컬럼 조합으로 조회하는 경우
- Standard Edition 환경
**LOOKUP 테이블을 선택하는 경우**
- 코드 테이블과 기준 정보
- PRIMARY KEY 조회와 단건 UPDATE/DELETE가 필요한 경우
- Cluster Edition 환경에서도 사용해야 하는 경우
- 실시간 기준 정보 갱신이 필요한 경우
### 예시
```sql
-- LOOKUP: 국가 코드 테이블 (PK 조회와 조건 기반 UPDATE)
CREATE LOOKUP TABLE country_code (
code VARCHAR(4) PRIMARY KEY,
name VARCHAR(64)
);
INSERT INTO country_code VALUES ('KR', 'Republic of Korea');
UPDATE country_code SET name = 'Korea' WHERE code = 'KR';
SELECT code, name FROM country_code WHERE code = 'KR';
-- TRANSACTION: 주문 이력 (대규모, 일반 UPDATE/DELETE 지원)
CREATE TRANSACTION TABLE order_history (
order_id LONG PRIMARY KEY,
item_id INTEGER,
qty INTEGER,
amount DECIMAL(18,2)
);
INSERT INTO order_history VALUES (12345, 501, 1, 12000.00);
UPDATE order_history SET qty = 10 WHERE order_id = 12345;
SELECT order_id, qty, amount FROM order_history WHERE order_id = 12345;
DELETE FROM order_history WHERE order_id = 12345;
SELECT COUNT(*) FROM order_history WHERE order_id = 12345;
```
첫 SELECT는 변경된 국가명을, 주문 SELECT는 수량 `10`을 반환합니다. 마지막 COUNT는
`0`입니다. 이 예제의 `amount`는 수량 변경과 별도로 유지하는 예시 금액이며 자동 재계산되지
않습니다. 실제 주문 모델에서는 단가·수량·합계의 관계와 함께 변경할 컬럼을 명시합니다.
실습을 끝내면 이 예제에서 만든 테이블만 `DROP TABLE`로 정리합니다.
---
title: "4.2 스키마 객체 정의"
url: https://docs.machbase.com/kr/dbms/data-modeling-table-design/schema-objects-definition/
language: kr
kind: page
---
# 4.2 스키마 객체 정의
이 페이지는 테이블, 컬럼, 인덱스와 VIEW를 설계할 때 결정할 항목을 정리합니다. SQL 문법과
옵션을 중복해서 나열하지 않고 [SQL 문법 사전](/dbms/reference/sql/syntax/)을
정본으로 사용합니다.
- **[테이블 생성과 삭제](#create-delete)**
- **[테이블 변경](#alter)**
- **[컬럼과 데이터 타입 선택](#selection-type-column-data-types)**
- **[제약 조건과 기본값](#constraints-defaults-condition)**
- **[인덱스 설계](#index-create-delete)**
- **[VIEW 설계](#create-view)**
## 테이블 생성과 삭제
먼저 [테이블 타입 선택](../table-types-selection-type/)을 완료합니다. 이 페이지는 선택한
타입에 맞게 테이블 이름, 컬럼, 키, 제약 조건, 인덱스와 VIEW를 정의합니다.
### 행의 단위와 컬럼의 역할
테이블 하나에서 행의 단위를 일정하게 유지합니다. 설비별 하루 요약과 초 단위 원본을
같은 의미의 행처럼 섞으면 `COUNT`나 `AVG`를 해석하기 어렵습니다. 원본과 집계에는
각각 명확한 행의 단위와 조회 이름을 부여합니다.
| 컬럼 역할 | 예 | 설계 원칙 |
|---|---|---|
| 대상 식별 | `sensor_id`, `equipment_id` | 표시 이름과 분리한 안정적인 값 사용 |
| 발생 기준 | `measured_at`, `event_time` | 실제 발생 시각인지 수신 시각인지 명시 |
| 측정값 | `temperature_c`, `pressure_kpa` | 단위, 유효 범위와 보정 방법 정의 |
| 품질 | `quality_code` | 값 누락, 측정 실패와 정상 0 구분 |
| 기준 속성 | 위치, 설비 종류 | 태그 메타데이터 또는 별도 기준표에서 관리할지 결정 |
외부 장비 코드처럼 업무에서 이미 사용하는 키는 자연키이고, 별도로 발급한 번호는
대리키입니다. 코드가 바뀔 수 있거나 여러 수집원이 같은 코드를 사용하면 식별 범위를
명확히 하거나 대리키를 검토합니다. 자동 증가 번호는 생성 순서를 위한 값이지 발생 시각이나
무중복 수집을 자동 보장하는 값이 아닙니다. TAG의 이름은 측정 행이 아닌 태그를 식별합니다.
테이블 이름은 영문자, 숫자와 밑줄을 사용하고 영문자로 시작하도록 정합니다. 예약어 또는
시스템 객체와 혼동할 수 있는 이름은 피하십시오. 삭제 전에는 의존하는 VIEW, 인덱스,
ROLLUP과 보존 정책을 확인합니다.
타입별 생성 예제는 다음 문서를 참고하십시오.
- [TAG 테이블 생성](/dbms/tag-table-usage/create-alter-drop/)
- [LOG 테이블 생성](/dbms/log-table-usage/create-alter-drop/)
- [TRANSACTION 테이블](/dbms/rdb-table-usage/)
- [LOOKUP 테이블](/dbms/lookup-table-usage/)
- [VOLATILE 테이블](/dbms/volatile-table-usage/)
## 테이블 변경
테이블 타입과 데이터 유무에 따라 컬럼 추가·삭제·이름 변경·타입 변경의 지원 범위가
다릅니다. 운영 테이블을 변경하기 전에 다음 순서로 판단하십시오.
1. 대상 Edition과 테이블 타입이 해당 `ALTER TABLE` 동작을 지원하는지 확인합니다.
2. 기존 데이터, 인덱스, VIEW와 애플리케이션의 컬럼 순서 의존성을 확인합니다.
3. 운영과 같은 스키마·데이터량의 검증 환경에서 실행 시간과 잠금 영향을 측정합니다.
4. 되돌리기 어렵다면 새 테이블을 만들고 검증 후 전환하는 방식을 사용합니다.
새 컬럼을 추가한 뒤에는 기존 행과 이후 입력한 행을 각각 조회해 NULL·DEFAULT 결과를
확인합니다. 모든 테이블에서 기존 행이 DEFAULT로 채워지는 것은 아닙니다. 예를 들어
VOLATILE의 기존 행과 TAG 메타데이터의 자동 등록 행에는 별도 규칙이 있습니다.
[ARRAY의 DEFAULT 규칙](/dbms/reference/sql/types/array/#default와-기존-row)을
포함해 실제 타입의 DDL 계약을 확인하십시오.
애플리케이션 배포와 스키마 변경의 순서도 정합니다. 가능한 입력 경로에서는 컬럼 목록을
명시하고, 위치 순서에 의존하는 Append와 바인딩 코드는 새 스키마와 대조합니다.
새 테이블로 이전하면 행 수뿐 아니라 키별 건수, 시간 범위, NULL 비율과 대표 집계도
비교하고, 전환 중 새로 들어오는 행의 누락·중복을 어떻게 처리할지 정합니다.
정확한 지원 범위와 구문은 [ALTER TABLE 사전](/dbms/reference/sql/syntax/)과
[테이블 타입별 지원 범위](/dbms/reference/support-scope-constraints/table-types-type/)를
참고하십시오.
## 컬럼과 데이터 타입 선택
값의 실제 범위와 연산을 기준으로 가장 작은 적합 타입을 선택합니다. 표시 형식을 저장 타입과
혼동하지 마십시오.
| 데이터 | 권장 검토 타입 | 주의점 |
| --- | --- | --- |
| 정수 계측값·코드 | `SHORT`·`INTEGER`·`LONG` 계열 | NULL 예약값과 범위 확인 |
| 실수 계측값 | `FLOAT`·`DOUBLE` | 정밀도, 집계 오차와 NULL 예약값 확인 |
| 정확한 소수 계산이 필요한 값 | `DECIMAL` | precision·scale과 반올림 규칙을 먼저 결정 |
| 시각 | `DATETIME` | 표현 범위와 시간대를 클라이언트·세션 정책과 함께 설계 |
| 짧은 문자열 | `VARCHAR` | 최대 길이와 인코딩 확인 |
| 긴 본문 | `TEXT` | 지원 테이블 타입, 정렬·집계 제약과 인덱스 비용 확인 |
| 네트워크 주소 | `IPV4`·`IPV6` | 문자열 대신 주소 타입 사용 검토 |
| 구조화 문서 | `JSON` | 크기 제한, 키 승격 기준과 테이블 타입별 지원 확인 |
| 이진 데이터 | `BINARY` | 테이블 타입에 따라 가변·고정 길이가 다름 |
| 고정 개수의 숫자 묶음 | 숫자 `ARRAY` | 요소 타입·길이, 요소별 NULL과 전체 NULL 구분 |
`VARCHAR(n)`의 길이는 바이트 수입니다. 한글이나 이모지를 포함하면 문자 수와 다를 수
있습니다. 정수 타입은 일부 경계값을 NULL 표현으로 예약하므로 일반 프로그래밍 언어의
정수 범위를 그대로 적용하지 않습니다. FLOAT과 DOUBLE도 양수 최대값을 NULL로 인식하므로
실수 타입이라고 예외는 아닙니다. “작은 타입”보다 유효 범위와 연산 의미가 우선입니다.
### 정확한 소수가 필요한 값: DECIMAL
금액, 세율, 정산 값처럼 10진 정확성이 필요한 값에는 `DECIMAL`을 사용합니다. 오차가
허용되고 지수 범위가 넓은 계측값에는 `FLOAT`·`DOUBLE`을 사용합니다. 두 계열의 차이는
저장 크기가 아니라 값의 의미이므로, 반올림 결과를 업무에서 그대로 사용하는 값이라면
DECIMAL을 선택합니다.
설계 시 다음을 먼저 정합니다.
| 결정 항목 | 내용 |
|---|---|
| precision | 전체 유효 숫자 수, 1~65 |
| scale | 소수 자릿수, 0~30이며 precision보다 클 수 없음 |
| 생략 규칙 | `DECIMAL`은 `DECIMAL(10,0)`, `DECIMAL(M)`은 `DECIMAL(M,0)` |
scale을 넘는 소수 자릿수는 입력 시점에 0에서 멀어지는 방향의 절반 올림으로 저장됩니다.
반올림이 저장 시점에 확정되므로 업무 규칙과 같은 방향인지 확인합니다. precision을
넘는 값은 잘라내거나 부동소수점으로 바꾸지 않고 오류로 처리하므로, 자릿수는 여유를
두고 정합니다.
`SUM`, `AVG`, `MIN`, `MAX`와 `GROUP BY`, `ORDER BY`, `DISTINCT`는 exact 경로로
동작합니다. 반면 percentile이나 고급 통계 함수처럼 exact DECIMAL 경로가 없는 연산은
DOUBLE로 변환해 계산하므로 결과가 근삿값입니다. 정확성이 필요한 집계와 참고용 통계를
구분해 설계합니다.
애플리케이션 경로에서 값을 부동소수점으로 경유시키면 저장 타입과 무관하게 정확성이
사라집니다. JDBC는 `BigDecimal`, Python은 `decimal.Decimal`, ODBC는 `SQL_NUMERIC`처럼
각 언어의 decimal 표현이나 문자열로 전달합니다. 선언 규칙, 인덱스, 클라이언트 매핑은
[DECIMAL과 NUMERIC 고정소수점 타입](/dbms/reference/sql/types/decimal-numeric-fixed-point/)을
참고하십시오.
### 구조화 문서: JSON
수집원마다 키가 다르거나 항목이 계속 늘어나는 부가 속성에는 `JSON` 컬럼이 적합합니다.
스키마 변경 없이 항목을 추가할 수 있기 때문입니다. 반대로 자주 `WHERE`나 `GROUP BY`에
사용하는 값은 JSON 안에 두지 말고 별도 컬럼으로 승격합니다. JSON 컬럼은 primary key로
선언할 수 없고, LOOKUP 테이블에서는 JSON path 인덱스를 지원하지 않습니다.
크기 제한도 설계에 반영합니다. 문서 하나는 최대 32,768 바이트, JSON path는 최대 512
바이트입니다. 원본 payload 전체를 보관하는 용도가 아니라 조회에 필요한 속성을 담는
용도로 사용합니다.
| 테이블 타입 | JSON 컬럼 | 확인할 점 |
|---|:---:|---|
| TAG·LOG·TRANSACTION | O | JSON 함수와 path 조회 지원 |
| LOOKUP | O | 일반 컬럼으로 지원, JSON path 인덱스는 미지원 |
| VOLATILE | X | JSON 컬럼을 만들 수 없음 |
조회는 `->` 연산자와 `JSON_EXTRACT_*` 계열을, 갱신은 `JSON_SET` 계열을 사용합니다.
갱신 함수는 해당 테이블 타입이 `UPDATE`를 지원할 때만 의미가 있으므로, LOG나 TAG처럼
행 수정이 없는 테이블에서는 입력 시점에 문서를 완성합니다. 함수별 지원 범위는
[JSON 타입의 테이블 타입별 지원 범위](/dbms/reference/sql/types/table-types-type-support-scope-json/)를
참고하십시오.
### 타입 선택 전에 확인할 제약
| 타입 | 설계 단계에서 확인할 내용 |
|---|---|
| `TEXT` | LOG와 Standard Edition의 TRANSACTION에서만 지원합니다. LOG의 TEXT 컬럼에는 `ORDER BY`와 `GROUP BY`를 사용할 수 없고, `MODIFY COLUMN`으로 VARCHAR로 바꿀 수도 없습니다. 정렬·집계에 쓸 값은 VARCHAR나 숫자 컬럼으로 따로 둡니다. |
| `BINARY` | LOG는 가변 길이 최대 64MB이지만 TAG는 `BINARY(n)` 고정 길이 1~32,767 바이트로 성격이 다릅니다. LOOKUP과 VOLATILE에서는 지원하지 않습니다. |
| `DATETIME` | 표현 범위는 1970-01-01부터 2262-04-11까지이며 나노초까지 저장합니다. 만료일이나 무기한을 뜻하는 먼 미래 시각을 임의로 넣지 않습니다. |
| `ARRAY` | 고정 길이 1차원 숫자 배열이며 cardinality는 1~1024입니다. 전체 NULL과 요소 NULL을 구분해 설계합니다. |
전체 범위와 테이블 타입별 지원 여부는
[데이터 타입 사전](/dbms/reference/sql/types/)을 기준으로 확인하십시오.
## 제약 조건과 기본값
`PRIMARY KEY`, `NOT NULL`, `DEFAULT` 지원은 테이블 타입마다 다릅니다. 특히 TAG의
`PRIMARY KEY`는 태그 식별자, LOOKUP·VOLATILE·TRANSACTION의 `PRIMARY KEY`는 행 식별과
갱신 경로를 결정합니다.
NULL은 알 수 없거나 없는 값이고 0이나 빈 구간과 다릅니다. 측정 실패를 DEFAULT 0으로
대체하면 평균과 정상값 판정이 왜곡될 수 있습니다. `COUNT(*)`는 행 수, `COUNT(value)`는
그 컬럼이 NULL이 아닌 행 수이므로 표본 수를 보고할 때 구분합니다.
기본값은 생략한 입력에 대한 정책이며 값의 유효성 검사를 대신하지 않습니다. 테이블이
제공하지 않는 제약은 수집·업무 애플리케이션에서 검증합니다. 예를 들어 외래 키처럼
다른 DBMS의 제약을 이름만 보고 지원한다고 가정하지 말고
[TRANSACTION 지원 범위](/dbms/reference/support-scope-constraints/rdb/)를 확인합니다.
시스템이 제공하는 `_ARRIVAL_TIME`이나 `_RID` 같은 컬럼을 애플리케이션의 업무 키로 사용하지
마십시오. 공개된 조회 의미가 필요한 경우에만 참조하고, 시스템 컬럼의 저장 구조나 생성
방식에는 의존하지 않습니다.
## 인덱스 설계
인덱스는 조회 비용을 낮추지만 입력과 저장 비용을 추가합니다.
대표 조회를 먼저 적고 조건에 맞는 행의 비율(선택도)을 확인합니다. 전체 설비의 한 달
평균과 설비 한 대의 특정 주문 조회는 접근 방식이 다릅니다. 필터·조인 컬럼마다 무조건
인덱스를 만들기보다 `EXPLAIN`과 실제 실행 시간을 비교해 도움이 되는 인덱스를 남깁니다.
- 시간 범위와 태그 식별자로 충분한 TAG 조회에는 추가 인덱스를 먼저 만들지 않습니다.
- LOG에서 자주 필터링하는 컬럼은 실제 실행 계획과 선택도를 측정한 뒤 인덱스를 검토합니다.
- LOOKUP·VOLATILE·TRANSACTION은 키 조회와 조인 조건을 기준으로 설계합니다.
- 긴 텍스트의 단어 검색은 해당 타입이 지원하는 `KEYWORD` 인덱스를 검토합니다.
생성·삭제 구문과 지원 타입은 [인덱스 SQL 사전](/dbms/reference/sql/syntax/)을
참고하십시오.
## VIEW 설계
VIEW는 반복해서 사용하는 조회에 이름을 부여하지만 결과 자체를 저장하지 않습니다. VIEW 정의에서
시간 범위가 고정되거나 불필요한 전체 컬럼을 읽지 않도록 하고, 기반 테이블이나 컬럼을
변경하기 전에 의존 VIEW를 확인하십시오.
자주 쓰는 컬럼 목록과 단위 변환을 VIEW로 일관되게 제공할 수 있습니다. 그러나 VIEW를
만든 것만으로 데이터가 복사되거나 조회 비용이 줄지는 않습니다. 과거 이벤트를 최신 기준표와
조인한 VIEW는 기준표가 바뀌면 과거 결과도 바뀔 수 있으므로, 발생 당시의 속성이 필요하면
버전별 기준 정보나 원본에 기록한 속성을 사용합니다.
VIEW 생성·조회·삭제와 제한은 [VIEW SQL 사전](/dbms/reference/sql/syntax/)을
참고하십시오.
---
title: "4.3 데이터 변경 정책"
url: https://docs.machbase.com/kr/dbms/data-modeling-table-design/alter-data-mutation-policy/
language: kr
kind: page
---
# 4.3 데이터 변경 정책
테이블 타입에 따라 `UPDATE`, `DELETE`, `TRUNCATE` 지원 범위가 다릅니다. 이 페이지는
데이터 모델을 선택할 때 필요한 정책만 요약합니다. 정확한 문법과 제약은 연결된 SQL
레퍼런스를 기준으로 확인하십시오.
변경 정책은 “이 값을 수정할 수 있는가”뿐 아니라 누가 어떤 범위를 바꾸고, 실패하면
어디까지 되돌릴 수 있는지를 정하는 것입니다. 현재 상태의 수정, 원본 계측값 보정,
스키마 변경과 보관 기간 만료에 따른 삭제를 별도 작업으로 구분합니다.
## 테이블 타입별 데이터 변경 지원 범위
| 테이블 타입 | UPDATE | DELETE | TRUNCATE |
|------------|--------|--------|----------|
| TAG | Standard의 DATA 보정: 태그 선택 조건과 BASETIME 조건 필요 | `BEFORE`, 태그/축 조건 또는 전체 삭제 | X |
| LOG | X | `BEFORE`, `OLDEST`, `EXCEPT`, 전체 삭제 | O |
| TRANSACTION | O | O | O |
| VOLATILE | 기본 키 조건 | 기본 키 조건 또는 전체 삭제 | X |
| LOOKUP | 일반 조건식, PK 변경 불가 | 일반 조건식 또는 전체 삭제 | X |
LOG 테이블은 적재한 이벤트를 수정하지 않는 구조입니다. 수정이 잦은 상태·설정 정보는
VOLATILE, LOOKUP 또는 TRANSACTION 테이블에 저장하십시오.
## 변경 단위와 실패 처리
| 작업 | 모델에서 정할 사항 |
|---|---|
| 현재 설정값 갱신 | 키, 허용 값과 동시 변경자 처리 |
| 잘못된 계측값 보정 | 태그·시간 범위, 보정 사유와 ROLLUP 재계산 |
| LOG 이벤트 정정 | 원본을 남기고 보정 이벤트로 연결할지 결정 |
| 기준 키 교체 | 참조하는 데이터의 전환 순서와 중간 실패 처리 |
| 기간 삭제 | 보관 기준 시각, 삭제 범위와 필요한 백업 |
TRANSACTION의 여러 DML은 명시적 트랜잭션으로 묶을 수 있습니다. LOOKUP·VOLATILE의
변경과 LOG·TAG 입력이 그 트랜잭션에 함께 참여한다고 가정하지 마십시오. 예를 들어
TAG 이력을 저장한 뒤 VOLATILE 캐시 갱신이 실패하면 이력은 남아 있을 수 있습니다.
캐시 재구성이나 재시도 절차가 필요합니다. Append의 트랜잭션 참여는 API와 대상 유형에
따라 다르므로 [SDK 지원 범위](/dbms/development-tools-integration/sdk-support-scope/)에서 확인합니다.
여러 행을 변경하기 전에는 같은 조건으로 건수와 대표 행을 조회합니다. 이 사전 조회가
행을 잠그거나 이후 변경 범위를 고정하는 것은 아니므로, 동시 입력·갱신이 있다면 작업
시간대와 대상 범위를 함께 통제합니다. 실행 결과의 영향 행 수와 변경 후 값도 확인합니다.
## UPDATE 정책
### TRANSACTION, VOLATILE, LOOKUP
- TRANSACTION은 일반 관계형 `UPDATE`와 트랜잭션을 지원합니다.
- VOLATILE은 기본 키 일치 조건으로 대상을 지정합니다.
- LOOKUP은 일반 조건식을 사용할 수 있지만 기본 키 컬럼 자체는 바꿀 수 없습니다.
LOOKUP UPDATE에는 WHERE 조건이 필요합니다. LOOKUP·VOLATILE의 기본 키를 바꾸려면
삭제와 새 키 입력을 별도 문장으로 처리해야 하므로, 원본 보관과 참조 전환 순서를 먼저
정합니다. 이 두 문장을 묶어 롤백할 필요가 있다면 TRANSACTION 모델을 검토합니다.
LOOKUP의 지원 predicate와 표현식은
[LOOKUP predicate UPDATE](/dbms/reference/sql/syntax/dml-syntax/lookup-predicate-update-syntax/)를
참고하십시오.
### TAG data UPDATE
TAG의 실제 시계열 데이터와 메타데이터는 서로 다른 구문으로 수정합니다.
| 대상 | 구문 | 핵심 제약 |
|------|------|-----------|
| 시계열 데이터 | `UPDATE tag_table SET ... WHERE ...` | 태그 선택 조건과 BASETIME 조건이 모두 필요 |
| 메타데이터 | `UPDATE tag_table METADATA SET ...` | TAG 메타데이터 전용 구문 사용 |
TAG data UPDATE는 Standard Edition의 논리 TAG 테이블에서 지원합니다. 태그 이름,
BASETIME, 메타데이터 컬럼은 `SET` 대상이 될 수 없습니다. 수정한 구간에 구체화된 롤업이
있다면 `ROLLUP_REBUILD`로 다시 생성하십시오.
TAG DATA의 SET 우변은 기존 행 컬럼을 참조할 수 없습니다. 따라서
`SET value = value + 1`처럼 일괄 가산하는 방식으로 보정하지 않습니다. 계산한 보정값을
상수나 매개변수로 전달하고, 필요한 범위를 한정합니다. 여러 번 보정한 이력을 남겨야 하면
현재 값만 덮어쓰지 말고 변경 전후 값과 사유를 별도 이력으로 저장합니다.
문법과 허용 표현식은 다음 레퍼런스가 정본입니다.
- [TAG data UPDATE](/dbms/reference/sql/syntax/dml-syntax/tag-data-update-syntax/)
- [TAG data UPDATE WHERE/SET 제약](/dbms/reference/sql/syntax/dml-syntax/tag-data-update-where-set-constraints/)
- [TAG 메타데이터](/dbms/tag-table-usage/tag-metadata/)
LOG 테이블은 `UPDATE`를 지원하지 않습니다.
## DELETE 정책
### TRANSACTION, VOLATILE, LOOKUP
- TRANSACTION은 일반 `WHERE` 조건으로 삭제할 수 있습니다.
- VOLATILE은 기본 키 조건으로 삭제하거나 조건 없이 전체 행을 삭제할 수 있습니다.
- LOOKUP은 일반 조건식으로 삭제하거나 `WHERE` 없이 전체 행을 삭제할 수 있습니다.
LOOKUP의 지원 predicate는
[LOOKUP predicate DELETE](/dbms/reference/sql/syntax/dml-syntax/lookup-predicate-delete-syntax/)를
참고하십시오.
### LOG
LOG는 임의의 일반 `WHERE` 조건 대신 로그 보존형 삭제 구문을 사용합니다.
`OLDEST`, `EXCEPT`, `BEFORE` 또는 전체 삭제 중 목적에 맞는 형식을 선택합니다. 정확한 구문과
실행 예제는 [LOG 데이터 생명주기](/dbms/log-table-usage/operations-lifecycle/)를 참고하십시오.
### TAG/KV
TAG/KV는 `BEFORE`로 오래된 데이터를 정리하거나, 태그 이름과 축 조건을 사용해 대상을
지정합니다. `BEFORE` 시각은 현재보다 과거여야 합니다. 실행 구문은
[TAG 데이터 변경](/dbms/tag-table-usage/data-input-mutation/)을 참고하십시오.
수동 보존 삭제를 반복해야 한다면
[Retention Policy](/dbms/operations-configuration-recovery/policy-data-retention/)를
사용하십시오.
### TAG 메타데이터
TAG 메타데이터는 `DELETE FROM table_name METADATA` 형식으로 삭제합니다. 대상 중 실제
데이터가 있는 태그가 하나라도 있으면 문장 전체가 실패합니다.
상세 조건은 [TAG 메타데이터](/dbms/tag-table-usage/tag-metadata/)를 참고하십시오.
## TRUNCATE 정책
`TRUNCATE TABLE`은 LOG와 TRANSACTION에서만 지원합니다. 스키마와 인덱스 정의는
유지하고 모든 행을 제거합니다.
| 항목 | TRUNCATE | DELETE |
|------|----------|--------|
| 대상 | 테이블 전체 | 타입에 따라 전체 또는 조건 지정 |
| `WHERE` | 불가 | 지원 범위 안에서 가능 |
| TRANSACTION 롤백 | 명시적 트랜잭션에서 가능 | 명시적 트랜잭션에서 가능 |
삭제 전 백업과 재입력 경로를 확인하십시오. TAG는 지원되는 `BEFORE` 또는 태그/축 조건을,
VOLATILE과 LOOKUP의 전체 삭제는 조건 없는 `DELETE`를 사용합니다.
TRANSACTION의 롤백 가능 여부를 LOG의 삭제에도 적용하지 마십시오. 이미 확정한 변경은
이후 ROLLBACK으로 되돌릴 수 없습니다. 또한 삭제와 물리 디스크 공간 반환은 같은 시점의
작업이라고 가정하지 말고 저장 사용량과 해당 테이블의 정리 상태를 확인합니다.
---
title: "4.4 안티패턴"
url: https://docs.machbase.com/kr/dbms/data-modeling-table-design/table-types-patterns-type-anti/
language: kr
kind: page
---
# 4.4 안티패턴
안티패턴은 특정 테이블을 사용했다는 사실보다 데이터의 의미와 요구사항에 맞지 않는
방식으로 사용한 경우를 뜻합니다. 아래 예제의 전제가 자신의 업무에도 적용되는지 확인한 뒤
대안을 선택하십시오. 같은 스키마라도 현재 상태 관리에는 맞고 이력 누적에는 맞지 않을 수 있습니다.
- **[LOOKUP에 무제한 이력 누적](/dbms/data-modeling-table-design/table-types-patterns-type-anti/#high-frequency-lookup)**
- **[센서별 테이블 생성](/dbms/data-modeling-table-design/table-types-patterns-type-anti/#per-sensor-create)**
- **[잘못된 타입 선택](/dbms/data-modeling-table-design/table-types-patterns-type-anti/#table-types-selection-type-wrong)**
- **[VOLATILE 영속 저장 오용](/dbms/data-modeling-table-design/table-types-patterns-type-anti/#storage-persistent-volatile)**
- **[시계열 데이터 TRANSACTION 오용](/dbms/data-modeling-table-design/table-types-patterns-type-anti/#time-series-storage-misuse-rdb)**
## LOOKUP에 무제한 이력 누적
### 문제
문제는 조회 빈도 자체가 아니라, 계속 늘어나는 계측 이력을 LOOKUP에 저장하는 설계입니다.
LOOKUP은 전체 행과 인덱스를 메모리에 유지하므로 장기 시계열 이력이 쌓일수록 메모리 부담이
커집니다. 작은 기준 정보를 키로 반복 조회하는 용도에는 적합합니다.
### 안티패턴 예시
```sql
-- 잘못된 설계: 센서 계측값을 LOOKUP 테이블에 저장
CREATE LOOKUP TABLE sensor_data_wrong (
sensor_id VARCHAR(64) PRIMARY KEY,
value DOUBLE,
ts DATETIME
);
-- 이 스키마는 sensor_id가 PK이므로 센서마다 현재 한 행만 저장
-- 측정 이력을 그대로 추가하면 같은 PK와 충돌
INSERT INTO sensor_data_wrong VALUES ('TEMP-01', 25.3, NOW);
INSERT INTO sensor_data_wrong VALUES ('TEMP-01', 25.5, NOW); -- PK 중복 오류
```
### 올바른 패턴
태그별 계측 이력 수집과 조회가 중심이면 TAG를 검토합니다. LOOKUP에서도 측정마다 다른
키를 부여할 수 있지만 전체 이력이 메모리에 상주하는 비용은 남습니다. 최신값 캐시는
별도의 성능상 필요가 있고 원본에서 복구할 수 있을 때 VOLATILE로 추가합니다. TAG의
최신값 조회로 요구사항을 만족한다면 캐시를 별도로 유지할 필요가 없습니다.
```sql
-- 올바른 설계: 이력은 TAG 테이블
CREATE TAG TABLE sensor_data (
name VARCHAR(64) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE
);
-- 최신값 캐시는 VOLATILE 테이블
CREATE VOLATILE TABLE sensor_latest (
sensor_id VARCHAR(64) PRIMARY KEY,
value DOUBLE,
updated_at DATETIME
);
```
### 결과
| | 안티패턴 (LOOKUP) | 올바른 설계 (TAG) |
|-|-----------------|-----------------|
| 같은 센서 키로 이력 누적 | X (예제의 센서 키가 행을 식별) | O (태그 이름 아래 여러 계측 행 저장) |
| 지속 입력 경로 | 행 식별자 중심 | 시계열 Append API 사용 가능 |
| 시간 범위 조회 | 일반 조건 조회 | 태그·시간 축 조회 |
## 센서별 테이블 생성
### 문제
센서(태그)마다 별도 테이블을 생성하는 패턴입니다. 센서 수가 늘어날수록 DDL, 권한과 조회
대상도 함께 늘어나 운영 비용이 커집니다.
### 안티패턴 예시
```sql
-- 잘못된 설계: 센서마다 테이블 생성
CREATE TAG TABLE sensor_temp_01 (
name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE
);
CREATE TAG TABLE sensor_temp_02 (
name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE
);
CREATE TAG TABLE sensor_temp_03 (
name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE
);
-- ... 센서가 10,000개면 테이블도 10,000개
```
### 문제점
| 문제 | 설명 |
|------|------|
| 관리 복잡도 | 테이블 수만큼 DDL 관리 필요 |
| 쿼리 불편 | 여러 테이블을 결합해야 센서 간 집계 가능 |
| 메타데이터 증가 | 시스템 카탈로그 부하 |
| 신규 센서 추가 | 매번 DDL 실행 필요 |
### 올바른 패턴
센서 이름을 PRIMARY KEY로 하는 하나의 TAG 테이블에 모든 센서 데이터를 저장합니다.
이 원칙은 같은 컬럼 구조·권한·보관 정책을 공유하는 센서 집합에 적용합니다. 측정 단위,
스키마, 접근 권한이나 보관 기간을 독립적으로 관리해야 한다면 테이블을 나누는 것이
적절할 수 있습니다. 센서 수 자체를 테이블 분리 기준으로 삼지 않는 것이 핵심입니다.
```sql
-- 올바른 설계: 모든 온도 센서를 하나의 테이블로
CREATE TAG TABLE temperature_sensor (
name VARCHAR(128) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE
);
-- 모든 센서 데이터를 하나의 테이블에 삽입
INSERT INTO temperature_sensor VALUES ('TEMP-01', NOW, 23.5);
INSERT INTO temperature_sensor VALUES ('TEMP-02', NOW, 24.1);
INSERT INTO temperature_sensor VALUES ('TEMP-10000', NOW, 22.9);
```
### 장점
- 신규 센서 추가 시 DDL 불필요 (새 태그 이름으로 INSERT만 하면 됨)
- 크로스-센서 집계 용이
- 운영 관리 포인트 최소화
## 잘못된 타입 선택
데이터 특성에 맞지 않는 테이블 타입을 선택하는 대표적인 안티패턴입니다.
### 안티패턴 1: 이벤트 로그를 TAG 테이블에 저장
```sql
-- 잘못됨: 이벤트 로그를 TAG로 저장
CREATE TAG TABLE error_log_wrong (
name VARCHAR(256) PRIMARY KEY, -- 이벤트 내용이 태그 이름이 됨
time DATETIME BASETIME,
level SHORT
);
-- 문제: 이벤트마다 고유 이름 → 태그 수 폭발
```
**올바른 설계**: LOG 테이블 사용
```sql
CREATE LOG TABLE error_log (
level SHORT,
msg VARCHAR(512),
src VARCHAR(128)
);
```
### 안티패턴 2: 센서 값을 LOG 테이블에 저장
LOG에 센서 값이 있다는 사실이 오류는 아닙니다. 문제는 아래처럼 실제 측정 시각을
생략하고, 요구사항은 태그별 측정 시간 집계인데 수신 시각만 남기는 경우입니다.
여러 필드로 된 장비 이벤트를 검색하는 것이 주목적이면 LOG가 적합할 수 있습니다.
```sql
-- 측정 시각이 필요한 요구사항에 부족한 스키마
CREATE LOG TABLE sensor_wrong (
sensor_id VARCHAR(64),
value DOUBLE
-- 실제 측정 시각이 없고 서버 수신 시각만 자동 저장됨
);
```
**올바른 설계**: TAG 테이블 사용
```sql
CREATE TAG TABLE sensor_measurements (
name VARCHAR(64) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE
);
```
### 안티패턴 3: 대용량 이력을 LOOKUP에 저장
```sql
-- 잘못됨: 관계형 트랜잭션이 필요한 주문 이력을 LOOKUP에 저장
CREATE LOOKUP TABLE order_history_wrong (
order_id LONG PRIMARY KEY,
customer VARCHAR(64)
-- 전체 행과 인덱스의 메모리 비용, 명시적 트랜잭션 요구를 확인
);
```
**올바른 설계**: TRANSACTION 테이블 사용
```sql
CREATE TRANSACTION TABLE order_history (
order_id LONG,
customer VARCHAR(64),
item_id INTEGER,
amount DOUBLE,
status VARCHAR(16)
);
-- UPDATE/DELETE/SELECT 모두 지원
UPDATE order_history SET status = 'SHIPPED' WHERE order_id = 1001;
```
### 안티패턴 4: 시계열 데이터를 TRANSACTION 테이블에 저장
TRANSACTION에도 시간 컬럼과 Append API를 사용할 수 있지만 TAG 전용 시간축 저장 구조와
ROLLUP은 제공되지 않습니다. 관계형 변경보다 계측 이력 수집과 집계가 중심이면 TAG를
검토합니다. 자세한 내용은 [시계열 데이터 TRANSACTION 오용](/dbms/data-modeling-table-design/table-types-patterns-type-anti/#time-series-storage-misuse-rdb)을
참고하십시오.
## VOLATILE 영속 저장 오용
### 문제
VOLATILE 테이블에 영구 보존이 필요한 데이터를 저장하는 패턴입니다.
### 안티패턴 예시
```sql
-- 잘못됨: 중요 설정을 VOLATILE에 저장
CREATE VOLATILE TABLE critical_config (
key_name VARCHAR(64) PRIMARY KEY,
value VARCHAR(256)
);
INSERT INTO critical_config VALUES ('license_key', 'XXXX-XXXX-XXXX');
INSERT INTO critical_config VALUES ('max_connections', '1000');
-- 서버 재시작 시 모든 설정 소멸!
```
### 문제점
- 서버가 종료되거나 재시작되면 테이블과 데이터가 소멸합니다.
- 서버 프로세스가 종료되는 장애에서도 메모리의 데이터를 복구할 수 없으므로,
유일한 원본을 VOLATILE에 저장하면 데이터 손실로 이어집니다.
### 올바른 패턴
영구 보존이 필요한 데이터는 LOOKUP 또는 TRANSACTION 테이블에 저장합니다.
```sql
-- 올바름: 설정은 LOOKUP 테이블
CREATE LOOKUP TABLE app_config (
key_name VARCHAR(64) PRIMARY KEY,
value VARCHAR(256)
);
INSERT INTO app_config VALUES ('max_connections', '1000');
-- 서버 재시작 후에도 데이터 유지
```
### VOLATILE의 올바른 용도
VOLATILE에는 재시작 후 재생성하거나 폐기할 수 있는 데이터를 저장합니다. 조회용 캐시뿐
아니라 수명이 명확한 작업 상태도 포함할 수 있습니다. 업무상 반드시 보존해야 하는
결과를 메모리에만 두지 않습니다.
| 적합 | 부적합 |
|------|--------|
| 센서 최신값 캐시 | 원본 트랜잭션 데이터 |
| 실시간 집계 결과 | 중요 설정 값 |
| 세션 임시 상태 | 감사 로그 |
| 대시보드 캐시 | 사용자 정보 |
## 시계열 데이터 TRANSACTION 오용
### 문제
센서·IoT 계측값 같은 지속적인 시계열 데이터를 TRANSACTION 테이블에 저장하는 패턴입니다.
관계형 갱신이 필요하지 않다면 TAG 테이블의 태그·시간 축과 ROLLUP을 활용할 수 없으므로
해당 기능이 필요한 조회와 운영 요구에 맞지 않을 수 있습니다. 반대로 측정값 등록을
다른 업무 변경과 하나의 트랜잭션으로 묶어야 하면 TRANSACTION을 선택할 이유가 있습니다.
### 안티패턴 예시
```sql
-- 잘못됨: 센서 시계열 데이터를 TRANSACTION 테이블에 저장
CREATE TRANSACTION TABLE sensor_timeseries (
sensor_id VARCHAR(64),
ts DATETIME,
value DOUBLE,
unit VARCHAR(16)
);
```
### 문제점
| 문제 | 설명 |
|------|------|
| 입력 의미 불일치 | 관계형 트랜잭션이 필요하지 않은 값에도 관계형 쓰기 경로 사용 |
| 시간 축 부재 | TAG의 BASETIME 기반 조회 구조를 사용할 수 없음 |
| 집계 기능 차이 | TAG 전용 ROLLUP을 사용할 수 없음 |
### 올바른 패턴
센서 계측값은 TAG 테이블에 저장합니다.
```sql
-- 올바름: TAG 테이블 사용
CREATE TAG TABLE sensor_history (
name VARCHAR(64) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE,
unit VARCHAR(16)
);
-- Append API 고속 버퍼로 대량 입력 가능
-- 시간 단위 집계와 TAG 전용 최적화 활용 가능
SELECT name, DATE_TRUNC('hour', time, 1) AS hour, AVG(value), MAX(value)
FROM sensor_history
WHERE time >= NOW - 86400000000000
GROUP BY name, hour;
```
### TRANSACTION 테이블이 적합한 경우
TRANSACTION 테이블은 관계형 구조의 업무 데이터(주문, 재고, 설비 이력 등)에 씁니다. 시간 컬럼이 있더라도 UPDATE/DELETE가 필요한 업무 이력이라면 TRANSACTION 테이블을, 수정 없이 계속 쌓이는 고빈도 계측값이라면 TAG를 선택합니다.
## 의미가 다른 값을 같은 통계로 계산
스키마가 같아도 단위나 행의 의미가 다르면 단순 집계할 수 없습니다. 누적 전력량(kWh)의
평균은 소비 전력(kW)이 아니며, 여러 구간 평균의 단순 평균은 전체 표본 평균과 다를 수
있습니다. NULL을 0으로 채우면 측정 실패가 정상적인 0으로 바뀝니다.
타입을 정할 때 단위, 표본 수와 품질 규칙도 함께 기록합니다. 구간별 통계를 재집계할 때는
합계와 유효 건수 같은 필요한 통계를 유지하고, 원본을 지우기 전에 향후 분석에 필요한
해상도를 확인합니다.
---
title: "4.5 모델링 패턴"
url: https://docs.machbase.com/kr/dbms/data-modeling-table-design/patterns-modeling/
language: kr
kind: page
---
# 4.5 모델링 패턴
실제 운영 환경에서 자주 쓰이는 데이터 모델링 패턴을 다룹니다.
각 절의 SQL은 서로 다른 모델을 보여 주는 예제입니다. 필요한 절을 선택해 별도 실습
환경에서 실행하며, 같은 이름의 테이블이 있는지 먼저 확인합니다. 스키마 생성만으로
수집·집계·캐시 갱신 작업이 자동 실행되지는 않습니다. 입력 애플리케이션이나 작업
스케줄러가 수행할 일과 실패 처리도 함께 설계합니다.
- **[시간축 모델링](/dbms/data-modeling-table-design/patterns-modeling/#time-axis-modeling)**
- **[거리축 모델링](/dbms/data-modeling-table-design/patterns-modeling/#distance-axis-modeling)**
- **[상태·캐시 모델링](/dbms/data-modeling-table-design/patterns-modeling/#state-cache-status-modeling)**
- **[이벤트·로그 모델링](/dbms/data-modeling-table-design/patterns-modeling/#event-log-modeling-logs)**
- **[참조·마스터 모델링](/dbms/data-modeling-table-design/patterns-modeling/#reference-master-modeling)**
- **[영속·임시 혼합 패턴](/dbms/data-modeling-table-design/patterns-modeling/#persistent-temporary)**
- **[INSERT·UPDATE 패턴](/dbms/data-modeling-table-design/patterns-modeling/#insert-update)**
- **[JOIN·메타데이터 설계](/dbms/data-modeling-table-design/patterns-modeling/#join-metadata-design)**
- **[복합 타입 조합 패턴](/dbms/data-modeling-table-design/patterns-modeling/#table-types-patterns-combined-type)**
## 시간축 모델링
시간을 기준 축으로 삼는 패턴으로, 센서 계측값·에너지 모니터링·환경 데이터 등에 적합합니다.
한 행은 계량기 한 대의 한 번의 관측입니다. `time`은 수신 시각이 아닌 측정 시각으로
정하고, `kwh`는 누적 계량값인지 구간 사용량인지 수집 계약에 기록합니다. 다음 스키마는
전압과 전류도 같은 관측에 포함된다는 전제입니다. 측정 주기나 시각이 서로 다르면 같은 행에
억지로 맞추기보다 별도 시계열로 저장하거나 결측 처리 규칙을 정합니다.
### 기본 패턴: TAG 테이블
```sql
CREATE TAG TABLE power_meter (
meter_id VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
kwh DOUBLE,
voltage DOUBLE,
current DOUBLE
) METADATA (
location VARCHAR(64),
phase SHORT,
rating_kw DOUBLE
);
```
### 시간 범위 집계
```sql
-- 1시간 단위 계량값 평균과 최댓값 (최근 24시간)
SELECT meter_id,
DATE_TRUNC('hour', time, 1) AS hour,
AVG(kwh) AS avg_kwh,
MAX(kwh) AS peak_kwh
FROM power_meter
WHERE time >= NOW - 86400000000000
GROUP BY meter_id, hour
ORDER BY meter_id, hour;
```
`kwh`가 누적 전력량이면 위 평균은 계량기 지시값의 평균이며 시간당 소비량이 아닙니다.
구간 소비량은 시작·종료 계량값의 차이와 계량기 초기화·교체·최댓값 초과 처리 규칙을
함께 적용해 계산합니다. 전력(kW)과 전력량(kWh)도 구분해 컬럼 이름과 단위를 정합니다.
### 다중 해상도 저장 패턴
원시 데이터(고해상도)와 집계 데이터(저해상도)를 별도 테이블에 나누어 저장합니다.
```sql
-- 원시 데이터 (초 단위)
CREATE TAG TABLE power_raw (
meter_id VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
kwh DOUBLE
);
-- 1분 집계 (VOLATILE 또는 별도 TAG로 캐싱)
CREATE VOLATILE TABLE power_1min (
key_id VARCHAR(80) PRIMARY KEY,
meter_id VARCHAR(32),
ts DATETIME,
avg_kwh DOUBLE,
max_kwh DOUBLE
);
```
위 DDL은 저장 공간만 만듭니다. `power_1min`의 `key_id`에는 계량기와 구간을 유일하게
식별할 값을 애플리케이션이 지정하고 집계값을 채워야 합니다. VOLATILE의 결과는 재시작하면
사라지므로 장기 집계 보관소로 사용하지 않습니다. 원본 삭제 후에도 필요한 통계는 영속
TAG 테이블이나 지원되는 ROLLUP으로 보관하고 각각의 보존 정책을 확인합니다.
### 시간대 처리
DATETIME은 시점을 나타내며 문자열 입력과 출력은 접속 환경의 시간대 영향을 받습니다.
한국 시각으로 표시하려면 클라이언트의 시간대 설정을 맞춥니다. 저장된 시각에 9시간을
더하면 같은 시점을 다른 시간대로 표시하는 것이 아니라 값 자체를 9시간 뒤로 바꾸므로
표시용 변환과 구분해야 합니다.
```sql
-- 클라이언트 시간대를 확인한 뒤 원래 시각을 조회
SELECT meter_id,
time,
kwh
FROM power_meter
WHERE meter_id = 'MTR-001'
AND time >= '2024-01-01 00:00:00';
```
입력 시각의 시간대와 접속 시간대가 다르면 입력 전에 기준을 통일합니다. JDBC의
`TIMEZONE` 설정 예는 [JDBC 연결](/dbms/development-tools-integration/jdbc/)을 참고하십시오.
## 거리축 모델링
거리(위치)를 기준 축으로 삼는 패턴으로, 파이프라인 검사·도로 센서·레이저 스캔 등에 적합합니다.
거리축은 시간축의 다른 표시 형식이 아닙니다. 시간축 전용 ROLLUP과 Retention을 그대로
적용할 수 없습니다. 같은 파이프를 반복 검사한다면 `pipe_id`만으로 서로 다른 검사 회차를
섞지 않도록 회차 식별자나 테이블 분리 기준을 정합니다. 아래 예제는 한 파이프의 한 회차를
가정하며 거리 단위는 m, 두께 단위는 mm입니다.
### 기본 패턴
```sql
CREATE TAG TABLE pipeline_thickness (
pipe_id VARCHAR(32) PRIMARY KEY,
distance DOUBLE BASEDISTANCE, -- 단위: 미터
thickness DOUBLE,
temp DOUBLE
);
```
### 구간 데이터 조회
```sql
-- 파이프 ID별 0~50m 구간 데이터 조회
SELECT pipe_id, distance, thickness
FROM pipeline_thickness
WHERE pipe_id = 'PIPE-A'
AND distance BETWEEN 0.0 AND 50.0
ORDER BY distance;
-- 임계치 이하 구간 조회
SELECT pipe_id, distance, thickness
FROM pipeline_thickness
WHERE pipe_id = 'PIPE-A'
AND thickness < 8.0 -- 두께가 8mm 미만인 구간
ORDER BY distance;
```
### 거리 기반 집계
```sql
-- 10m 구간별 평균 두께
SELECT pipe_id,
FLOOR(distance / 10.0) * 10 AS segment_start,
AVG(thickness) AS avg_thickness,
MIN(thickness) AS min_thickness
FROM pipeline_thickness
WHERE pipe_id = 'PIPE-A'
GROUP BY pipe_id, FLOOR(distance / 10.0) * 10
ORDER BY segment_start;
```
### 시간 + 거리 복합 모델링
검사 시각과 위치를 함께 관리해야 하면 시간축 TAG 테이블에 거리 컬럼을 추가합니다.
```sql
-- 시간축 + 위치 정보 함께 저장
CREATE TAG TABLE inspection_data (
inspector VARCHAR(64) PRIMARY KEY,
time DATETIME BASETIME,
distance DOUBLE, -- 위치 컬럼 (축은 아님)
thickness DOUBLE,
defect SHORT
);
-- 특정 날짜·구간 조회
SELECT inspector, time, distance, thickness
FROM inspection_data
WHERE inspector = 'INSPECTOR-01'
AND time BETWEEN '2024-01-01' AND '2024-01-02'
AND distance BETWEEN 100.0 AND 200.0
ORDER BY time;
```
## 상태·캐시 모델링
디바이스나 센서의 현재 상태를 실시간 조회하기 위한 캐시 모델링 패턴입니다.
### 최신 상태 캐시 패턴
VOLATILE 테이블로 각 디바이스의 현재 상태를 캐싱합니다.
```sql
-- 상태 캐시 (VOLATILE)
CREATE VOLATILE TABLE device_status (
device_id VARCHAR(64) PRIMARY KEY,
status VARCHAR(16),
value DOUBLE,
updated_at DATETIME
);
-- 상태 이력 (TAG 또는 LOG)
CREATE TAG TABLE device_status_history (
name VARCHAR(64) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE,
status VARCHAR(16)
);
```
### 상태 업데이트 흐름
다음 SQL은 이력과 캐시를 각각 갱신하는 동작을 보여 줍니다. 두 입력은 하나의 트랜잭션이
아니며, 각각 평가하는 `NOW`도 같은 시각이라고 보장하지 않습니다. 실제 수집에서는 한 번
정한 측정 시각을 두 경로에 전달합니다. 늦게 도착한 과거 값이 최신 캐시를 덮어쓰지 않도록
이벤트 순서 판정과 동시 갱신 처리를 애플리케이션에서 정합니다.
```sql
-- 새 계측값 수신 시:
-- 1. TAG 테이블에 이력 저장
INSERT INTO device_status_history VALUES ('DEV-01', NOW, 78.5, 'WARNING');
-- 2. VOLATILE 캐시 업데이트 (ON DUPLICATE KEY UPDATE)
INSERT INTO device_status VALUES ('DEV-01', 'WARNING', 78.5, NOW)
ON DUPLICATE KEY UPDATE SET status = 'WARNING', value = 78.5, updated_at = NOW;
```
### 대시보드 조회 패턴
```sql
-- 현재 ALARM 상태인 모든 디바이스
SELECT device_id, status, value, updated_at
FROM device_status
WHERE status IN ('ALARM', 'WARNING')
ORDER BY updated_at DESC;
-- 특정 디바이스의 최신 상태
SELECT device_id, status, value, updated_at
FROM device_status
WHERE device_id = 'DEV-01';
```
### 상태 정의 참조 패턴
상태 코드의 의미는 LOOKUP 테이블에서 관리합니다.
```sql
CREATE LOOKUP TABLE status_definition (
code VARCHAR(16) PRIMARY KEY,
label VARCHAR(64),
color VARCHAR(16),
severity SHORT
);
INSERT INTO status_definition VALUES ('NORMAL', '정상', 'green', 0);
INSERT INTO status_definition VALUES ('WARNING', '경고', 'yellow', 1);
INSERT INTO status_definition VALUES ('ALARM', '알람', 'red', 2);
-- JOIN 조회
SELECT d.device_id, s.label, s.color, d.value
FROM device_status d
JOIN status_definition s ON d.status = s.code
ORDER BY s.severity DESC;
```
## 이벤트·로그 모델링
시스템 이벤트, 알람, 감사 로그를 LOG 테이블로 모델링하는 패턴입니다.
### 계층적 이벤트 모델
```sql
-- 알람 이벤트 LOG 테이블
CREATE LOG TABLE alarm_event (
severity SHORT, -- 1=INFO, 2=WARN, 3=ERROR, 4=CRITICAL
category VARCHAR(32), -- 카테고리
source VARCHAR(64), -- 발생 소스
message VARCHAR(512),
src_ip IPV4 -- 발생 IP (있는 경우)
);
-- 시스템 감사 LOG 테이블
CREATE LOG TABLE audit_log (
user_id VARCHAR(64),
action VARCHAR(32), -- INSERT, UPDATE, DELETE, LOGIN 등
target VARCHAR(128), -- 대상 테이블/리소스
detail TEXT, -- 상세 내용 (전문 검색 대상)
result VARCHAR(8) -- SUCCESS, FAILURE
);
CREATE INDEX idx_audit_detail ON audit_log(detail) INDEX_TYPE KEYWORD;
```
### 알람 집계 패턴
```sql
-- 최근 1시간 심각도별 알람 수
SELECT severity, COUNT(*) AS cnt
FROM alarm_event
WHERE _arrival_time >= NOW - 3600000000000
GROUP BY severity
ORDER BY severity DESC;
-- 소스별 알람 현황 (최근 24시간)
SELECT source, COUNT(*) AS total,
SUM(CASE WHEN severity = 4 THEN 1 ELSE 0 END) AS critical_cnt
FROM alarm_event
WHERE _arrival_time >= NOW - 86400000000000
GROUP BY source
ORDER BY total DESC;
```
### 로그 레벨 필터 패턴
```sql
-- ERROR 이상 로그 조회 (최근 10분)
SELECT _arrival_time, source, message
FROM alarm_event
WHERE severity >= 3
AND _arrival_time >= NOW - 600000000000
ORDER BY _arrival_time DESC
LIMIT 100;
```
### 전문 검색 패턴
```sql
-- 특정 키워드를 포함하는 감사 로그 조회
SELECT _arrival_time, user_id, action, target
FROM audit_log
WHERE detail SEARCH 'password'
AND _arrival_time >= NOW - 86400000000000;
```
## 참조·마스터 모델링
코드 테이블, 설비 마스터, 사용자 정보 등 참조 데이터를 LOOKUP 테이블로 모델링하는 패턴입니다.
식별자만 이력에 저장하면 이름과 위치를 반복 저장하는 비용을 줄일 수 있습니다. 하지만
현재 마스터의 위치를 바꾸면 과거 이력을 조인한 결과도 새 위치로 표시됩니다. “발생 당시
어느 라인에 있었는가”가 중요하면 유효 기간이 있는 별도 변경 이력을 설계하거나 원본 행에
당시 속성을 기록합니다. 아래 계층 스키마의 참조 관계가 외래 키로 자동 검증되는 것은
아니므로 존재하지 않는 공장·라인 코드를 입력하지 않도록 애플리케이션에서 확인합니다.
### 계층적 코드 체계
```sql
-- 대분류 코드
CREATE LOOKUP TABLE category_main (
code VARCHAR(8) PRIMARY KEY,
label VARCHAR(64)
);
-- 중분류 코드 (대분류 참조)
CREATE LOOKUP TABLE category_sub (
code VARCHAR(16) PRIMARY KEY,
main_code VARCHAR(8),
label VARCHAR(64)
);
CREATE INDEX idx_sub_main ON category_sub(main_code);
```
### 설비 계층 마스터
```sql
-- 공장 마스터
CREATE LOOKUP TABLE factory (
factory_id VARCHAR(16) PRIMARY KEY,
name VARCHAR(64),
location VARCHAR(128)
);
-- 라인 마스터 (공장 참조)
CREATE LOOKUP TABLE production_line (
line_id VARCHAR(16) PRIMARY KEY,
factory_id VARCHAR(16),
name VARCHAR(64)
);
-- 설비 마스터 (라인 참조)
CREATE LOOKUP TABLE equipment (
equip_id VARCHAR(32) PRIMARY KEY,
line_id VARCHAR(16),
equip_name VARCHAR(128),
equip_type VARCHAR(32),
install_dt DATETIME
);
CREATE INDEX idx_equip_line ON equipment(line_id);
CREATE INDEX idx_equip_type ON equipment(equip_type);
```
### 마스터 조인 조회 패턴
계측 테이블에는 설비 식별자를 저장하고, 공장·라인·설비 이름 같은 속성은 LOOKUP 테이블에
한 번만 저장합니다. 조회할 때 계측 테이블의 시간 범위를 먼저 제한한 뒤 설비 식별자로
LOOKUP 테이블을 조인합니다. 실제 조인 구문과 실행 계획 확인 방법은
[JOIN·서브쿼리](/dbms/tag-table-usage/query-analysis/)을 참고하십시오.
## 영속·임시 혼합 패턴
영속 테이블(TAG, LOG, TRANSACTION, LOOKUP)에 원본을 보관하고 VOLATILE에 조회용 캐시를
유지하는 패턴입니다. 두 저장 경로가 하나의 트랜잭션으로 처리된다고 가정하지 말고,
캐시 지연과 실패 후 재구성 절차를 함께 설계합니다.
### 원본 + 집계 캐시 패턴
원본 데이터는 영속 테이블에, 집계 결과는 VOLATILE 테이블에 저장합니다.
```sql
-- 원본 데이터 (TAG, 영속)
CREATE TAG TABLE sensor_data (
name VARCHAR(64) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE
);
-- 집계 캐시 (VOLATILE, 임시)
CREATE VOLATILE TABLE sensor_recent_avg (
key_id VARCHAR(64) PRIMARY KEY,
sensor_id VARCHAR(64),
base_ts DATETIME,
avg_val DOUBLE,
max_val DOUBLE,
cnt LONG
);
```
### 캐시 갱신 패턴
캐시 한 행은 센서 하나의 최근 2시간 통계입니다. `base_ts`는 집계 창의 경계가 아니라
집계에 포함된 가장 최근 측정 시각입니다. 매번 같은 범위 정의로 계산하고, 정확히 같은
시각의 결과를 비교해야 하면 실행마다 기준 시각을 한 번 정해 사용합니다.
아래 예제는 미래 시각의 측정값을 제외합니다. 결과 비교 시에는 각 SQL의 `NOW` 대신
동일한 고정 기준 시각을 전달해 하한과 상한을 함께 계산합니다.
```sql
-- 주기적 집계 갱신 (1시간마다 실행)
DELETE FROM sensor_recent_avg;
INSERT INTO sensor_recent_avg
SELECT name AS key_id,
name,
MAX(time) AS base_ts,
AVG(value),
MAX(value),
COUNT(*)
FROM sensor_data
WHERE time >= NOW - 3600000000000 * 2 -- 최근 2시간 재계산
AND time <= NOW
GROUP BY name;
```
`INSERT ... SELECT`에는 `ON DUPLICATE KEY UPDATE`를 붙이지 않습니다. 캐시를 재구성할 때는
기존 캐시를 삭제한 뒤 다시 적재합니다.
삭제와 재적재 사이에는 캐시가 비거나 일부만 채워진 상태를 다른 조회가 볼 수 있습니다.
일괄 갱신 중 표시할 결과, 실패 시 재시도와 원본 조회 여부를 애플리케이션에서 정합니다.
항상 일관된 전체 결과가 필요하면 그 요구를 충족하는 별도의 전환·트랜잭션 모델을 검토합니다.
### 대시보드 조회 최적화
```sql
-- 저장된 캐시 조회 (원본 조회로의 전환은 애플리케이션에서 처리)
SELECT sensor_id, base_ts, avg_val, max_val
FROM sensor_recent_avg
WHERE base_ts >= NOW - 3600000000000 * 2
AND base_ts <= NOW
ORDER BY sensor_id, base_ts;
```
이 조건은 최신 측정 시각이 최근 2시간에 속한 캐시 행을 표시합니다. 기존 평균을 조회
시각 기준으로 다시 계산하는 것은 아니므로, 집계 자체의 최신성은 갱신 주기로 관리합니다.
### 장애 복구
서버 재시작으로 VOLATILE 테이블과 캐시가 소멸되면 테이블을 먼저 다시 만든 뒤 원본
TAG 테이블에서 같은 최근 2시간 통계를 재계산합니다. 아래 CREATE는 테이블이 사라진
재시작 이후에만 실행합니다. 원본 보존 기간 안에 재계산할 데이터가 남아 있어야 합니다.
```sql
-- 캐시 재구성 (서버 재시작 후)
CREATE VOLATILE TABLE sensor_recent_avg (
key_id VARCHAR(64) PRIMARY KEY,
sensor_id VARCHAR(64),
base_ts DATETIME,
avg_val DOUBLE,
max_val DOUBLE,
cnt LONG
);
INSERT INTO sensor_recent_avg
SELECT name,
name, MAX(time), AVG(value), MAX(value), COUNT(*)
FROM sensor_data
WHERE time >= NOW - 3600000000000 * 2 -- 정상 갱신과 같은 최근 2시간
AND time <= NOW
GROUP BY name;
```
복구 후 캐시의 센서별 건수·평균·최신 시각을 같은 기준 시각의 원본 집계와 비교합니다.
`cnt`는 NULL 측정값을 포함한 행 수입니다. 평균에 사용한 표본 수가 필요하면
`COUNT(value)`를 별도 컬럼으로 저장합니다.
## INSERT·UPDATE 패턴
모델은 어떤 데이터를 변경 가능하게 둘지 결정해야 하지만, 이 페이지에서 DML 지원표를 다시
정의하지 않습니다. 테이블별 변경 조건은 [데이터 변경 정책](../alter-data-mutation-policy/)을,
INSERT·Append·파일 입력 선택은
[데이터 입력과 반출](/dbms/development-tools-integration/data-input-load-export/)을 사용하십시오.
## JOIN·메타데이터 설계
여러 테이블 타입을 조합할 때는 다음 원칙을 적용합니다.
먼저 조인의 관계를 정합니다. 센서 코드마다 마스터가 정확히 한 행인지, 코드가 공장마다
반복되는지 확인하십시오. 한 원본 행이 여러 기준 행과 일치하면 결과 행이 늘고 SUM 같은
집계도 중복될 수 있습니다. 코드의 이름뿐 아니라 식별 범위와 타입을 맞춰야 합니다.
1. 원본 행 수뿐 아니라 필터 적용 후 행 수와 실행 계획을 보고 조인 순서를 검토합니다.
2. JOIN 조건 컬럼의 타입을 맞추고, 지원되는 인덱스의 사용 여부를 확인합니다.
3. WHERE 절에 시간 범위와 업무 조건을 명시해 조인할 행 수를 줄입니다.
4. TAG 속성을 함께 조회하는 목적이라면 별도 LOOKUP 대신 METADATA가 적합한지 검토합니다.
재현 가능한 조인 예제는 [TAG 조회와 분석](/dbms/tag-table-usage/query-analysis/)과
[LOOKUP 조회와 분석](/dbms/lookup-table-usage/query-analysis/)을 참고하십시오.
## 복합 타입 조합 패턴
실제 운영 시스템에서 여러 테이블 타입을 조합하는 대표적인 설계 패턴입니다.
### 제조 설비 모니터링 시스템
```
┌─────────────────────────────────────────────────┐
│ 설비 모니터링 시스템 │
├──────────────┬──────────────┬───────────────────┤
│ TAG 테이블 │ LOG 테이블 │ LOOKUP 테이블 │
│ sensor_data │ alarm_event │ equipment_master │
│ (계측값 이력) │ (알람 이벤트) │ (설비 기준 정보) │
├──────────────┴──────────────┴───────────────────┤
│ VOLATILE 테이블 │
│ sensor_latest (최신값 캐시) │
└─────────────────────────────────────────────────┘
```
### 물류·주문 관리 시스템 (Standard Edition)
```
┌─────────────────────────────────────────────────┐
│ 물류 관리 시스템 │
├──────────────┬──────────────┬───────────────────┤
│ TRANSACTION 테이블 │ LOG 테이블 │ LOOKUP 테이블 │
│ orders │ delivery_log │ product_master │
│ (주문 관리) │ (배송 이벤트) │ (제품 기준 정보) │
│ UPDATE/DELETE│ │ │
├──────────────┴──────────────┴───────────────────┤
│ VOLATILE 테이블 │
│ order_status_cache (현재 상태 캐시) │
└─────────────────────────────────────────────────┘
```
### 패턴 요약
| 역할 | 권장 타입 | 이유 |
|------|---------|------|
| 고빈도 계측값 이력 | TAG | Append API 고속 버퍼, 시계열 최적화 |
| 이벤트·알람 로그 | LOG | 추가 전용, 도착 시각 자동 |
| 관계형 업무 (UPDATE/DELETE) | TRANSACTION | SELECT/INSERT/UPDATE/DELETE 모두 지원 |
| 기준·코드 정보 | LOOKUP | PK 식별과 일반 조건식 UPDATE/DELETE, 영속 |
| 실시간 상태 캐시 | VOLATILE | 메모리 속도, UPSERT |
---
다음으로 읽을 내용:
- [SELECT의 GROUP BY와 집계](/dbms/reference/sql/syntax/select-syntax/)
- [운영 및 구성](/dbms/operations-configuration-recovery/)
---
title: "5. TAG 테이블 활용"
url: https://docs.machbase.com/kr/dbms/tag-table-usage/
language: kr
kind: section
---
# 5. TAG 테이블 활용
TAG 테이블은 반복 관측하는 대상의 이름과 시간 또는 거리 축으로 계측 이력을 저장합니다.
이 장은 Machbase DBMS 8.7.0의 TAG 구조, 입력·조회, 메타데이터와 보정·운영을 설명합니다.
3·4장에서 정한 배포 환경과 데이터 모델을 실제 SQL로 확인하는 단계입니다.
태그는 측정 대상이고 DATA 행은 관측 한 건입니다. METADATA는 태그마다 한 행인 속성이며,
과거 관측마다 별도로 저장되는 속성이 아닙니다. 원본 측정, 현재 속성, 구간 집계와
수집 시각을 구분하면 조회 결과와 정정 범위를 일관되게 해석할 수 있습니다.
## 이 장의 구성
| 절 | 내용 |
|---|---|
| [개요와 사용 기준](./overview-use-criteria/) | 태그 식별자와 관측 행, 시간·거리 축 선택 |
| [테이블 구조와 스키마](./table-structure-schema/) | 컬럼 순서·타입, LSL/USL, BINARY와 저장 설계 |
| [생성, 변경, 삭제](./create-alter-drop/) | 기본 DDL, METADATA 확장과 객체 정리 |
| [데이터 입력과 변경](./data-input-mutation/) | 자동 태그 등록, SQL·Append·파일 입력 구분 |
| [조회와 분석](./query-analysis/) | 구간·최신값·STAT 조회와 결과 해석 |
| [인덱스와 성능](./index-performance/) | 실제 데이터를 이용한 접근 경로 확인 |
| [운영과 데이터 생명주기](./operations-lifecycle/) | 삭제, 보존 정책과 중복 제거 완료 확인 |
| [제약, 오류, 문제 해결](./constraints-errors-troubleshooting/) | 정상 조건과 의도적 실패 예제 |
| [활용 패턴과 시나리오](./patterns-scenarios/) | 관측 단위·단위계·결측에 맞는 모델 |
| [TAG 메타데이터](./tag-metadata/) | 등록·조회·수정·삭제, JSON과 ARRAY |
| [TAG data UPDATE와 데이터 보정](./tag-data-update-correction/) | 직접 정정, NULL 보정과 감사 이력 |
| [tagmetaimport와 메타데이터 일괄 등록](./tagmetaimport/) | CSV 준비, 입력 대상, 재입력 오류 확인 |
## 실습과 지원 범위
각 페이지는 독립 실습입니다. 준비 SQL의 테이블이 이미 있으면 다른 업무 객체인지
확인하고, 임의로 삭제하지 않습니다. 성공 예제와 실패를 확인하는 예제를 분리해 실행하며
정리 SQL은 해당 실습에서 만든 객체에만 적용합니다.
TAG DATA UPDATE는 Standard Edition 전용입니다. 자동 중복 검사 기간, METADATA ALTER와
LSL/USL의 세부 작업도 Edition별 범위를 확인합니다. SQL INSERT, Append의 처리 응답,
저장 버퍼 flush와 인덱스·통계 처리 완료는 같은 의미가 아닙니다.
ROLLUP의 생성·조회·재구성 전체 절차는 [6장](../tag-rollup-usage/)에서 설명합니다.
이 장에서는 TAG 정정과 삭제가 집계에 미치는 관계만 다룹니다.
---
title: "5.1 개요와 사용 기준"
url: https://docs.machbase.com/kr/dbms/tag-table-usage/overview-use-criteria/
language: kr
kind: page
---
# 5.1 개요와 사용 기준
TAG는 센서·설비처럼 같은 대상을 반복 관측한 이력을 저장할 때 검토합니다. “태그 하나”와
“행 하나”를 구분하는 것이 출발점입니다. 같은 이름으로 여러 시각의 행을 입력할 수 있으며,
태그 이름이 같다는 이유만으로 중복 행이 제거되지는 않습니다.
## TAG 테이블의 특성
`PRIMARY KEY`는 태그 이름을 지정합니다. 관계형 테이블의 행별 고유 키와 역할이 다릅니다.
한 테이블에는 시간축 또는 거리축 하나를 지정합니다.
```sql
-- 시간에 따라 발생한 관측값을 저장하는 시간축 TAG입니다.
CREATE TAG TABLE ch5_overview_time (
name VARCHAR(64) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE SUMMARIZED
);
-- 거리나 위치 구간을 기준으로 관측값을 저장하는 거리축 TAG입니다.
-- 거리축 TAG에는 ROLLUP을 사용할 수 없습니다.
CREATE TAG TABLE ch5_overview_distance (
name VARCHAR(64) PRIMARY KEY,
distance DOUBLE BASEDISTANCE,
value DOUBLE
);
```
두 예제 모두 이름은 첫 번째 컬럼, 축은 두 번째 컬럼입니다. 시간축은 `DATETIME BASETIME`,
거리축은 `DOUBLE`, `LONG`, `ULONG` 중 하나와 `BASEDISTANCE`를 사용합니다.
`SUMMARIZED`를 사용하면 세 번째 컬럼에 지정합니다. 컬럼 순서와 지정 가능한 타입은
[생성, 변경, 삭제](../create-alter-drop/), 모델별 설계 판단은
[테이블 구조와 스키마](../table-structure-schema/)를 참고합니다.
`SUMMARIZED`는 대표 통계·집계 관련 컬럼을 지정하는 속성이며, ROLLUP 객체를 자동으로
만들지는 않습니다.
다음 표는 예제 테이블의 컬럼 목록이 아니라, TAG 테이블을 사용할 때 구분해야 하는 데이터
범위입니다. DATA는 사용자가 입력하는 관측 행이고, METADATA와 STAT는 태그별 속성·통계를
확인할 때 사용하는 별도 범위입니다.
| 데이터 범위 | 의미 |
|---|---|
| 태그 이름 | 센서나 반복 관측 대상을 식별하는 첫 번째 컬럼 |
| DATA | 사용자가 입력하는 관측 행: 축 값과 여러 데이터 컬럼 |
| METADATA | 태그별 위치·단위·설정 등 현재 속성. 일반 DATA 컬럼과 구분 |
| STAT | `V$
_STAT`에서 확인하는 태그별 입력 통계. 예제 테이블의 직접 컬럼이 아님 |
## TAG가 적합한 경우
- 같은 스키마로 여러 대상의 이력을 계속 추가합니다.
- 특정 태그의 시간·거리 범위를 자주 조회합니다.
- 현재 속성을 조건으로 태그를 고르고 해당 이력을 분석합니다.
- 시간축 TAG에서 반복 구간 통계를 저장하고 조회해야 합니다. 이 경우 ROLLUP 설계는
[TAG ROLLUP](../../tag-rollup-usage/)에서 별도로 확인합니다.
단일 값 모델은 측정 항목별로 태그를 나누고, 다중 값 모델은 같은 관측에서 함께 나온
온도·압력 등을 한 행에 모읍니다. 측정 시각이 다른 값을 억지로 같은 행에 넣지 말고
결측·품질 정책을 정합니다. 시계열의 같은 시각 데이터도 중복 수집일 수 있으므로
이름·시각·값의 중복 처리 정책을 별도로 확인합니다.
## 다른 테이블을 검토할 경우
| 요구사항 | 검토할 대안 |
|---|---|
| 관측 대상보다 사건 자체가 중요한 이벤트 검색 | LOG |
| 영속 기준 정보의 일반 조건 조회·변경 | LOOKUP |
| 여러 DML의 명시적 트랜잭션과 관계형 변경 | TRANSACTION(Standard Edition 전용) |
| 원본에서 재구성할 수 있는 공유 상태 캐시 | VOLATILE |
TAG에서도 JOIN과 제한된 값 보정을 사용할 수 있습니다. “JOIN이 필요하면 TAG를 쓸 수 없다”
또는 “계측값은 반드시 TAG여야 한다”는 식으로 판단하지 않습니다.
다만 DATA의 UPDATE는 Standard Edition 전용입니다. Cluster Edition에서는 METADATA
UPDATE만 사용할 수 있으므로, 값 보정이 필요하면 재입력과 재집계 절차를 함께 설계합니다.
## 설계 순서
1. 태그가 센서·장비·검사 회차 중 무엇을 식별하는지 정합니다.
2. 측정 시각 또는 거리·위치의 의미와 단위를 정합니다.
3. 값의 타입, NULL과 품질 표시, 관측 주기를 정합니다.
4. 관측마다 보존할 속성과 현재 METADATA를 구분합니다.
5. 구간 조회·집계·보정·보관 요구를 Edition의 지원 범위와 대조합니다.
예제 테이블의 확인과 정리는 다음과 같습니다.
```sql
-- 예제 테이블의 스키마를 확인합니다.
DESC ch5_overview_time;
DESC ch5_overview_distance;
-- 뒤 예제와 이름이 겹치지 않도록 정리합니다.
DROP TABLE ch5_overview_distance;
DROP TABLE ch5_overview_time;
```
다음은 [스키마 설계](../table-structure-schema/)와
[입력·조회 실습](../data-input-mutation/)입니다.
---
title: "5.2 테이블 구조와 스키마"
url: https://docs.machbase.com/kr/dbms/tag-table-usage/table-structure-schema/
language: kr
kind: page
---
# 5.2 테이블 구조와 스키마
## TAG 테이블 설계
TAG 테이블 설계는 컬럼을 몇 개 둘지 정하는 일이 아니라, 무엇을 하나의 태그로 볼지와
어떤 값을 어느 범위에 둘지 정하는 일입니다.
컬럼 위치에 따라 정해지는 역할, 축 컬럼의 타입, 지정할 수 없는 타입은
[생성, 변경, 삭제](../create-alter-drop/#original-85-creating-tag-tables)를 참고합니다.
이 페이지는 그 규칙 안에서 결정할 항목을 다룹니다.
이 페이지의 DDL은 모델별 독립 예제입니다. 생성 순서가 필요한 실습은 해당 절 안에서
설명하며, 같은 이름의 기존 객체가 있으면 다른 이름으로 실행합니다.
스키마를 정할 때는 다음 항목을 함께 검토합니다.
- [활용 사례](#tag-schema-use-case-summary)
- [태그 이름 컬럼](#tag-name-column-design)
- [시간축과 거리축 선택](#time-axis-design-tag)
- [값 컬럼 설계](#tag-table-design-design-column)
- [METADATA 컬럼 설계](#metadata-column-design-summary)
- [JSON METADATA 컬럼 설계](#json-metadata-column-design-summary)
- [이진 데이터 컬럼 설계](#tag-table-design-design-column-binary)
- [VARCHAR 스토리지 최적화](#tag-table-design-storage-varchar)
- [스토리지 전략](#tag-table-design-strategy)
- [LSL·USL 설계](#tag-table-design-lsl-usl)
- [보정과 중복 정책](#correction-duplication-policy-summary)
- [제약 및 지원 범위](#tag-schema-limitations-summary)
### 활용 사례
TAG는 같은 구조의 관측값이 여러 대상에서 계속 쌓이는 업무에 적합합니다. 센서, 설비,
이동체, 검사 회차처럼 반복 관측 대상을 먼저 정하고, 그 대상별 이력을 시간 또는 거리 축으로
조회할 수 있는지 확인합니다. 이 항목에서는 대상을 TAG로 모델링할지, 다른 테이블 타입으로
나눌지를 확정합니다. 업무 모델 예시는 [활용 사례](../patterns-scenarios/#use-cases-tag)를
참고합니다.
### 태그 이름 컬럼
센서별, 설비별, 검사 회차별 중 어떤 단위를 하나의 태그로 볼지 먼저 정합니다. 잘게
나누면 태그 수가 늘어 메타데이터와 인덱스 부담이 커지고, 크게 묶으면 한 태그 안에 서로
다른 대상의 이력이 섞입니다. 이름 규칙은
[VARCHAR 스토리지 최적화](#tag-table-design-storage-varchar)에서 함께 다룹니다.
### 시간축과 거리축 선택
축은 조회 조건에서 무엇을 범위로 지정할지를 기준으로 고릅니다. 측정 시각으로 구간을
지정하면 시간축, 특정 경로의 누적 위치처럼 거리 구간으로 지정하면 거리축입니다. 한 TAG
테이블은 두 축을 동시에 가질 수 없으므로 나중에 바꾸려면 테이블을 다시 만들어야 합니다.
```sql
-- 측정 시각으로 구간을 지정하는 시간축 TAG입니다.
CREATE TAG TABLE time_sensor (
name VARCHAR(40) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE SUMMARIZED
);
```
```sql
-- 거리·위치로 구간을 지정하는 거리축 TAG입니다.
CREATE TAG TABLE rail_sensor (
name VARCHAR(40) PRIMARY KEY,
distance DOUBLE BASEDISTANCE,
value DOUBLE
);
```
`DURATION`, ROLLUP과 시간 함수는 BASETIME에만 적용됩니다. 거리 범위 조회는 일반 비교와
`BETWEEN`을 사용하며, 실행 예제는 [조회와 분석](../query-analysis/)을 참고합니다.
### 값 컬럼 설계
TAG 테이블의 값 컬럼은 태그 이름과 축 컬럼을 제외한 일반 데이터 컬럼입니다. 계측값,
상태, 품질 코드처럼 측정 행마다 달라지는 값을 저장합니다.
#### 지원 타입
아래는 자주 사용하는 타입의 예입니다. JSON, BINARY, DECIMAL과 숫자 ARRAY를 포함한 전체
지원 범위는 [데이터 타입 사전](/dbms/reference/sql/types/)을 참고하십시오.
| 타입 | 설명 | 저장 크기 |
|------|------|---------|
| `DOUBLE` | 64비트 부동소수점 | 8바이트 |
| `FLOAT` | 32비트 부동소수점 | 4바이트 |
| `LONG` | 64비트 정수 | 8바이트 |
| `INTEGER` (`INT`) | 32비트 정수 | 4바이트 |
| `SHORT` | 16비트 정수 | 2바이트 |
| `VARCHAR(n)` | 가변 문자열 | 최대 n바이트 |
#### 권장 타입 선택
| 데이터 | 권장 타입 |
|--------|---------|
| 온도, 습도, 압력 등 아날로그 값 | `DOUBLE` |
| 카운터, 상태 코드 | `INTEGER` |
| 플래그, 이진 상태 | `SHORT` |
| 에너지, 유량 누적값 | `DOUBLE` 또는 `LONG` |
| 태그 문자열 값 | `VARCHAR(n)` |
#### 다중 값 컬럼 설계
한 테이블에 여러 계측항목을 함께 저장할 때는 NULL 값이 발생할 수 있습니다. 계측항목이 서로 동시에 수집되는 경우에 적합합니다.
```sql
CREATE TAG TABLE weather_station (
name VARCHAR(64) PRIMARY KEY,
time DATETIME BASETIME,
temperature DOUBLE, -- 항상 수집
humidity DOUBLE, -- 항상 수집
wind_speed DOUBLE, -- 옵션
rainfall DOUBLE -- 옵션
);
```
#### NULL 허용 설계
TAG 테이블 값 컬럼은 기본적으로 NULL을 허용합니다. 특정 태그가 일부 항목만 수집하는 경우 나머지 컬럼에 NULL을 삽입합니다.
```sql
-- wind_speed와 rainfall이 없는 경우
INSERT INTO weather_station VALUES ('WS-01', NOW, 22.5, 65.0, NULL, NULL);
```
### METADATA 컬럼 설계
METADATA 컬럼은 DATA 행마다 반복 저장할 값이 아니라 태그별 현재 속성을 저장할 때 사용합니다.
설치 위치, 단위, 장비 설정, 관리 상태처럼 태그 이름에 붙는 속성이 여기에 해당합니다. DATA와
METADATA를 분리하면 관측 이력은 유지하면서 태그의 현재 속성만 따로 조회하거나 변경할 수
있습니다. 이 항목에서는 각 속성을 METADATA로 올릴지 DATA 컬럼으로 남길지 확정합니다.
관측마다 값이 달라지면 DATA, 태그 수명 동안 대체로 고정이면 METADATA입니다. 입력, 조회,
수정 예제는 [METADATA 사용](../tag-metadata/#original-85-tag-metadata)을 참고합니다.
### JSON METADATA 컬럼 설계
태그별 속성이 계층 구조를 갖거나 속성 집합이 자주 바뀌면 JSON METADATA 컬럼을 검토합니다.
예를 들어 설비 위치, 제조사 정보, 설치 옵션을 하나의 JSON 문서로 저장하고 필요한 path만
조회할 수 있습니다. 이 항목에서는 METADATA를 개별 컬럼으로 둘지 JSON 문서 하나로 둘지
확정합니다. 속성 집합이 고정이고 조건 조회가 잦으면 개별 컬럼이, 태그마다 속성 구성이
다르면 JSON이 유리합니다. 자주 조건으로 사용하는 path는 인덱스 설계도 함께 검토합니다.
자세한 문법과 예제는 [JSON METADATA](../tag-metadata/#metadata-design-json)를 참고합니다.
### 이진 데이터 컬럼 설계
TAG 테이블의 `BINARY(n)`은 1~32767바이트의 센서 프레임을 저장할 때 사용합니다. 입력
literal, 길이 제약과 드라이버 동작은 [Binary 컬럼](#original-85-binary-columns)을
참고하십시오. 큰 이미지나 파형은 외부 스토리지에 두고 참조 키만 저장하는 설계도
검토하십시오.
### VARCHAR 스토리지 최적화
`VARCHAR`는 실제 최대 길이에 맞춰 선언합니다. 저장 option의 정확한 문법은
[DDL 사전](/dbms/reference/sql/syntax/ddl-syntax/)을 참고하십시오. 태그 이름에는 사이트,
설비, 센서 식별자를 일관된 구분자로 조합해 범위 조회가 가능하도록 설계합니다.
### 스토리지 전략
데이터는 태그별로 분리된 컬럼 스토리지에 저장됩니다. 데이터 양과 조회 패턴에 따라 적절한 전략을 선택합니다.
#### 단일 테이블 vs 다중 테이블
##### 단일 TAG 테이블 (권장)
같은 종류의 센서는 하나의 TAG 테이블에 모아서 관리합니다.
```sql
-- 권장: 모든 온도 센서를 하나의 테이블로
CREATE TAG TABLE temperature_sensor (
name VARCHAR(64) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE
);
```
**장점**
- 관리 포인트 최소화
- 크로스-태그 집계 용이
- 운영 간소화
##### 다중 TAG 테이블
컬럼 구성이 다르거나 보존 기간·접근 권한·운영 주기를 따로 관리해야 하면 테이블 분리를
검토합니다. 센서 수가 늘었다는 이유만으로 센서별 테이블을 만들지는 않습니다.
```sql
-- 온도·습도 센서 (DOUBLE 값)
CREATE TAG TABLE thermo_sensor (
name VARCHAR(64) PRIMARY KEY,
time DATETIME BASETIME,
temp DOUBLE,
humid DOUBLE
);
-- 진동 센서 (DOUBLE + BINARY 파형)
CREATE TAG TABLE vibration_sensor (
name VARCHAR(64) PRIMARY KEY,
time DATETIME BASETIME,
rms DOUBLE,
waveform BINARY(4096)
);
```
#### 태그 수 관리
- 태그 수 증가에 따른 태그 인덱스와 메타데이터 메모리 사용량을 운영 규모의 데이터로
측정합니다.
- 센서 계층 구조를 태그 이름에 인코딩하여 관리합니다.
- 태그 이름이 매 레코드마다 고유한 값이 되는 설계는 피합니다 (안티패턴).
#### 파티션 전략
시간축 TAG는 `BASETIME`을 기준으로 범위를 제한해 조회합니다. 시스템 저장 객체나 파티션
이름에 의존하지 말고, 보존 기간은 [데이터 보존 정책](/dbms/operations-configuration-recovery/policy-data-retention/)으로
관리하십시오.
### LSL·USL 설계
LSL(Lower Specification Limit)은 하한 규격값, USL(Upper Specification Limit)은 상한
규격값입니다. TAG 테이블의 METADATA 컬럼에 태그별 허용 범위를 설정하면 범위 밖 DATA
입력을 차단할 수 있으므로, 태그마다 다른 입력 품질 기준을 적용할 수 있습니다.
값을 보정하는 기능이 아니라 입력을 거부하는 기능입니다. 범위를 벗어난 입력은 수집 오류
정책에 따라 기록·재처리해야 합니다.
#### 제약 조건
다음 제약 조건이 적용됩니다.
* LSL/USL을 Cluster 전체에서 미지원으로 묶지 않습니다. 테이블 생성 시 한계 정의와
메타데이터 값 설정, DATA INSERT·Append의 한계 검사는 공통 경로입니다.
기존 METADATA에 한계 컬럼을 ALTER로 추가하는 아래 실습은 Standard Edition에서 수행합니다.
* LSL/USL을 설정하려면 Tag 테이블의 세 번째 컬럼인 __Value__가 __SUMMARIZED__로 설정되어야 합니다.
* LSL은 USL보다 작거나 같아야 하며, __Value__ 컬럼의 입력 값은 LSL과 USL 사이에 있어야 합니다 (포함). __(LSL <= Value <= USL)__
* LSL/USL 설정을 부여하기 전에 입력된 데이터는 검증되지 않습니다.
* LSL/USL 컬럼을 NULL로 설정하면 입력 데이터를 검증하지 않습니다.
* LSL/USL 기능은 개별적으로 사용할 수 있습니다. 상한 사양만 일치시키려면 USL만 설정할 수 있습니다.
* USL만 설정하면 상한 초과만 검사하고, LSL만 설정하면 하한 미달만 검사합니다.
#### 지원되는 데이터 타입
한계 컬럼은 대상 __Value__ 컬럼과 타입이 같아야 합니다. 아래는 기본 숫자 타입의
규격 범위 설정을 설명합니다. SUMMARIZED 자체의 전체 지원 타입에는 JSON도 있으므로
SUMMARIZED 선언 가능 여부와 숫자 한계 설정을 같은 조건으로 해석하지 않습니다.
|타입|설명|범위|유효 자릿수|
|----|------|-----|----|
|short|16비트 부호 있는 정수 데이터 타입|-32767 ~ 32767|-|
|ushort|16비트 부호 없는 정수 데이터 타입|0 ~ 65534|-|
|integer|32비트 부호 있는 정수 데이터 타입|-2147483647 ~ 2147483647|-|
|uinteger|32비트 부호 없는 정수 데이터 타입|0 ~ 4294967294|-|
|long|64비트 부호 있는 정수 데이터 타입|-9223372036854775807 ~ 9223372036854775807|-|
|ulong|64비트 부호 없는 정수 데이터 타입|0~18446744073709551614|-|
|float|32비트 부동 소수점 데이터|-|6[^1]|
|double|64비트 부동 소수점 데이터|-|15[^1]|
#### LSL/USL 설정 및 사용
다음 CREATE 예제들은 서로 다른 선택을 보여 줍니다. 기본 `example`만 이후
INSERT·UPDATE 실습에서 사용하고, 대안 테이블은 별도로 생성합니다.
태그 메타데이터 테이블의 컬럼에 `LOWER LIMIT`(LSL) 또는 `UPPER LIMIT`(USL) 키워드를 지정합니다. Tag 테이블 생성 시 또는 메타데이터 컬럼 추가 시 설정할 수 있습니다.
##### CREATE
```sql
CREATE TAG TABLE example (
tag_id VARCHAR(50) PRIMARY KEY,
time DATETIME BASETIME,
value INTEGER SUMMARIZED)
METADATA (
lsl INTEGER LOWER LIMIT,
usl INTEGER UPPER LIMIT
);
```
두 컬럼을 함께 사용하거나 하나만 사용할 수 있습니다.
LSL만 설정하면 `Value >= LSL`을 검사하고 상한은 제한하지 않습니다. USL 값을 `NULL`로
둔 것과 같은 의미입니다.
```sql
CREATE TAG TABLE example_lower_only (
tag_id VARCHAR(50) PRIMARY KEY,
time DATETIME BASETIME,
value INTEGER SUMMARIZED)
METADATA (
lsl INTEGER LOWER LIMIT
);
```
##### ADD COLUMN
데이터가 이미 입력된 후 `ADD COLUMN`을 사용하여 추가하는 경우 기본값은 __NULL__입니다.
```sql
CREATE TAG TABLE example_alter_limits (
tag_id VARCHAR(50) PRIMARY KEY,
time DATETIME BASETIME,
value INTEGER SUMMARIZED
);
ALTER TABLE example_alter_limits METADATA ADD COLUMN (lsl INTEGER LOWER LIMIT);
ALTER TABLE example_alter_limits METADATA ADD COLUMN (usl INTEGER UPPER LIMIT);
```
[CREATE](#create)와 마찬가지로 하나의 속성만 추가할 수도 있습니다.
```sql
CREATE TAG TABLE example_alter_upper (
tag_id VARCHAR(50) PRIMARY KEY,
time DATETIME BASETIME,
value INTEGER SUMMARIZED
);
ALTER TABLE example_alter_upper METADATA ADD COLUMN (usl INTEGER UPPER LIMIT);
```
##### INSERT
특정 TAG ID에 대한 LSL/USL 값을 설정합니다.
```sql
INSERT INTO example metadata VALUES ('TAG_01', 100, 200);
```
설정 후 태그 데이터를 입력하면 다음과 같이 동작합니다.
```sql
INSERT INTO example VALUES ('TAG_01', NOW, 95); -- Failure
```
```text
[ERR-02342: SUMMARIZED value is less than LOWER LIMIT.]
```
```sql
INSERT INTO example VALUES ('TAG_01', NOW, 100); -- Success (Inclusive)
```
```text
1 row(s) inserted.
Elapsed time: 0.000
```
```sql
INSERT INTO example VALUES ('TAG_01', NOW, 150); -- Success
```
```text
1 row(s) inserted.
Elapsed time: 0.000
```
```sql
INSERT INTO example VALUES ('TAG_01', NOW, 200); -- Success (Inclusive)
```
```text
1 row(s) inserted.
Elapsed time: 0.000
```
```sql
INSERT INTO example VALUES ('TAG_01', NOW, 205); -- Failure
```
```text
[ERR-02341: SUMMARIZED value is greater than UPPER LIMIT.]
```
Tag 테이블을 조회하면 규격 범위 내 데이터만 입력된 것을 확인할 수 있습니다.
```sql
SELECT * FROM example;
```
```text
TAG_ID TIME VALUE LSL USL
------------------------------------------------------------------------------------------------------------------------------
TAG_01 2023-09-12 09:31:27 923:289:631 100 100 200
TAG_01 2023-09-12 09:31:27 929:013:232 150 100 200
TAG_01 2023-09-12 09:31:27 939:209:248 200 100 200
[3] row(s) selected.
Elapsed time: 0.001
```
##### UPDATE
LSL/USL 컬럼의 값을 수정합니다. 이미 입력된 데이터에는 소급 적용되지 않으므로 주의가 필요합니다.
```sql
UPDATE example metadata SET lsl = 10, usl = 100 WHERE tag_id = 'TAG_01';
```
```text
1 row(s) updated.
Elapsed time: 0.001
```
```sql
SELECT tag_id, lsl, usl FROM example METADATA;
```
```text
TAG_ID LSL USL
----------------------------------------------------------------------------------------
TAG_01 10 100
[1] row(s) selected.
Elapsed time: 0.001
```
##### DELETE
LSL/USL 컬럼은 `DROP COLUMN`으로 제거하지 않고 값을 NULL로 설정해 제약을 해제합니다.
```sql
UPDATE EXAMPLE METADATA SET lsl = NULL, usl = NULL WHERE tag_id = 'TAG_01';
```
```text
1 row(s) updated.
Elapsed time: 0.001
```
```sql
SELECT tag_id, lsl, usl FROM example METADATA;
```
```text
TAG_ID LSL USL
----------------------------------------------------------------------------------------
TAG_01 NULL NULL
[1] row(s) selected.
Elapsed time: 0.001
```
#### TRACE 로그로 LSL/USL 위반 확인
- 위치: `$MACHBASE_HOME/trc/machbase.trc`
- 빠른 필터:
```bash
grep LIMIT_DROP $MACHBASE_HOME/trc/machbase.trc | tail -n 20
```
- 로그 포맷: `LIMIT_DROP (TYPE=) TABLE=<테이블명> TAG= <컬럼명=값 ...>`
- TYPE=LOWER/UPPER로 어떤 한계가 위반됐는지 구분.
- DATETIME은 `YYYY-MM-DD HH24:MI:SS mmm:uuu:nnn` 형태.
- 실제 예시:
```
[2025-11-29 13:50:34 P-151395 T-126343511537344][QP-INFO] LIMIT_DROP (TYPE=LOWER) TABLE=TAG3 TAG=tag-1 TIME=2020-01-01 00:00:00 000:000:000 VALUE=5.55
[2025-11-29 13:50:34 P-151395 T-126343511537344][QP-INFO] LIMIT_DROP (TYPE=UPPER) TABLE=TAG3 TAG=tag-1 TIME=2020-01-01 00:00:04 000:000:000 VALUE=30.55
[2025-11-29 13:50:35 P-151395 T-126344475694784][QP-INFO] LIMIT_DROP (TYPE=LOWER) TABLE=TAG3 TAG=tag-2 TIME=1998-12-24 09:00:00 000:000:000 VALUE=0
[2025-11-29 13:50:35 P-151395 T-126344475694784][QP-INFO] LIMIT_DROP (TYPE=UPPER) TABLE=TAG3 TAG=tag-2 TIME=1998-12-24 09:00:00 000:000:008 VALUE=45
```
- 활용 포인트
- TAG별로 LOWER/UPPER 위반 시각과 값을 한눈에 파악.
- grep으로 TAG/테이블명을 추가 필터하면 특정 대상만 추적 가능.
- 주의: 한 줄 최대 약 4KB라 컬럼이 많을 때 뒤가 잘릴 수 있으며, 기동 직후 메타 캐시 준비 전에는 테이블명이 ID로 보일 수 있습니다.
[^1]: [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754)
### 보정과 중복 정책
값 보정과 중복 제거는 컬럼 정의만으로 끝나는 항목은 아니지만, 스키마 설계 단계에서 미리
정해야 합니다. 보정이 필요하면 어떤 값 컬럼을 수정할 수 있는지, 원본을 별도 컬럼이나 별도
테이블에 보존할지, 보정 후 ROLLUP을 어떻게 재구성할지 결정합니다. DATA의 UPDATE는
Standard Edition 전용이므로, Cluster Edition에서는 보정 대신 재입력과 재집계
절차를 설계합니다. 자세한 내용은
[데이터 보정](../tag-data-update-correction/#design-correction-tag)을 참고합니다.
같은 태그와 같은 축 값이 반복 입력될 수 있으면 중복을 허용할지, 수집 단계에서 제거할지,
Machbase의 자동 중복 제거 기능을 사용할지 정합니다. 설정 변경과 운영 검증 절차는
[자동 중복 제거](../operations-lifecycle/#original-85-duplication-removal)를 참고합니다.
### 제약 및 지원 범위
TAG 테이블은 반복 관측 이력에 맞춘 구조이므로 모든 SQL 기능을 일반 관계형 테이블처럼
지원하지 않습니다. 축 컬럼, METADATA, 값 보정, ROLLUP, Edition별 지원 범위는 설계 전에
확인해야 합니다. 이 항목에서는 앞에서 정한 설계안이 지원 범위 안에 있는지 점검하고,
벗어나면 해당 항목으로 돌아가 스키마를 조정합니다. 지원하지 않는 기능과 대표 오류는
[제약 및 주의사항](../constraints-errors-troubleshooting/#limitations-tag)을 참고합니다.
## Binary 컬럼
`BINARY(n)`은 Tag 테이블에서 센서 프레임용 고정 길이 바이너리 값을 저장합니다.
TAG의 길이 생략형 `BINARY`는 32767바이트로 처리됩니다. 필요한 프레임 크기를 명시하면
저장·전송 크기를 이해하기 쉽습니다. TAG 외 테이블에는 `BINARY(n)` 길이 지정형을
선언할 수 없습니다. 길이는 1~32K-1
(1~32767)바이트만 유효하며, 인덱스를 생성할 수 없습니다.
명시적 binary literal로 `BINARY` 값을 입력합니다.
### DDL 규칙
```sql
CREATE TAG TABLE t1(
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
frame BINARY(4)
);
```
- 유효 길이: `1 <= n <= 32767` (32K-1).
- 범위를 벗어나면 생성 시 오류가 발생합니다(`BINARY(0)` 등).
- `DESC`와 테이블 메타데이터는 선언된 바이트 길이(헥스 폭 아님)를 표시합니다.
SQL `LENGTH(binary_col)`는 짧은 입력값에 붙은 뒤쪽 0 패딩을 제외한 표시
값 길이를 반환합니다.
### 지원 입력 형식
```sql
X'hex_digits'
x'hex_digits'
B'bit_digits'
b'bit_digits'
O'octal_digits'
o'octal_digits'
```
| 형식 | 의미 | 단위 |
| --- | --- | --- |
| `X'...'`, `x'...'` | 16진수 literal | 16진수 2자리 = 1바이트 |
| `B'...'`, `b'...'` | 2진수 literal | bit 8자리 = 1바이트 |
| `O'...'`, `o'...'` | 8진수 literal | 8진수 3자리 = 1바이트 |
prefix는 대소문자 모두 허용됩니다.
```sql
CREATE TAG TABLE t_bin (
name VARCHAR(20) PRIMARY KEY,
time DATETIME BASETIME,
value BINARY(4)
);
INSERT INTO t_bin VALUES('hex1', '2024-01-01 00:00:00', X'0A');
INSERT INTO t_bin VALUES('hex2', '2024-01-01 00:00:01', x'00010203');
INSERT INTO t_bin VALUES('bit1', '2024-01-01 00:00:02', B'00001010');
INSERT INTO t_bin VALUES('oct1', '2024-01-01 00:00:03', O'012');
```
`X'0A'`, `B'00001010'`, `O'012'`는 모두 1바이트 값 `0x0A`를 의미합니다.
### Binary literal 규칙
#### 16진수 literal
`X'...'`와 `x'...'`에는 `0-9`, `A-F`, `a-f`를 사용할 수 있습니다.
```sql
X'00'
X'0AFF'
x'abcdef'
```
16진수 문자는 반드시 짝수 개여야 합니다. 두 자리가 1바이트에 해당합니다.
#### 2진수 literal
`B'...'`와 `b'...'`에는 `0`과 `1`만 사용할 수 있습니다.
```sql
B'00000000' -- 0x00
B'00001010' -- 0x0A
b'11111111' -- 0xFF
```
bit 수는 반드시 8의 배수여야 합니다. 8자리가 1바이트에 해당합니다.
#### 8진수 literal
`O'...'`와 `o'...'`에는 `0-7`만 사용할 수 있습니다.
```sql
O'000' -- 0x00
O'012' -- 0x0A
o'377' -- 0xFF
```
8진수 문자는 반드시 3자리 단위여야 합니다. 각 3자리 값은 `000`부터
`377`까지의 1바이트 범위여야 합니다.
#### 빈 값
작은따옴표 안을 비워 길이 0인 binary 값을 표현합니다.
```sql
X''
B''
O''
```
### 길이 제한
`BINARY(n)` 컬럼에는 최대 `n`바이트까지만 입력 가능합니다.
```sql
CREATE TAG TABLE t_limit (
name VARCHAR(20) PRIMARY KEY,
time DATETIME BASETIME,
value BINARY(2)
);
INSERT INTO t_limit VALUES('ok_hex', '2024-01-01 00:00:00', X'0AFF');
INSERT INTO t_limit VALUES('ok_bit', '2024-01-01 00:00:01', B'0000101011111111');
INSERT INTO t_limit VALUES('ok_oct', '2024-01-01 00:00:02', O'012377');
INSERT INTO t_limit VALUES('bad_hex', '2024-01-01 00:00:03', X'000102'); -- 실패: 3바이트
```
source 종류와 무관하게 최종 binary 값이 대상 `BINARY(n)` 길이를 초과하면
입력은 실패합니다. 이 규칙은 binary literal뿐 아니라 일반 문자열, 기존
`'0x...'` 문자열 입력, 다른 `BINARY` 컬럼 값을 `INSERT ... SELECT`로
복사하는 경우에도 동일하게 적용됩니다.
```sql
CREATE TAG TABLE t_src (
name VARCHAR(20) PRIMARY KEY,
time DATETIME BASETIME,
value BINARY(8)
);
CREATE TAG TABLE t_dst (
name VARCHAR(20) PRIMARY KEY,
time DATETIME BASETIME,
value BINARY(4)
);
INSERT INTO t_src VALUES('k1', '2024-01-01 00:00:00', X'0102030405060708');
INSERT INTO t_dst SELECT name, time, value FROM t_src; -- 실패: 8바이트 값을 BINARY(4)에 입력
```
`CASE`, `INSERT ... SELECT`, view 등 SQL expression 안에서 사용하더라도
최종 binary 값이 대상 `BINARY(n)` 길이를 초과하면 입력은 실패합니다.
### 올바르지 않은 입력
다음 입력은 유효하지 않습니다.
```sql
X'0' -- 16진수 문자가 홀수 개
X'0G' -- G는 16진수 문자가 아님
B'0101' -- bit 수가 8의 배수가 아님
B'00000002' -- 2는 2진수 문자가 아님
O'12' -- 8진수 문자가 3자리 단위가 아님
O'400' -- 1바이트 범위 초과
X'0102 -- 닫는 작은따옴표가 없음
```
잘못된 값이나 길이 초과 입력은 다음 오류로 실패합니다.
```text
[ERR-02233: Error occurred at column (n): (Invalid insert value.)]
```
### 기존 문자열 입력과의 차이
기존 호환성을 위해 문자열 형태의 `'0x...'` 입력도 사용할 수 있습니다.
`'0x...'`는 문자열에서 `BINARY` 컬럼으로 변환되는 방식이고,
`X'...'`, `B'...'`, `O'...'`는 SQL에서 binary 값임을 명확히 표시하는
binary literal입니다.
일반 문자열을 `BINARY(n)` 컬럼에 입력하는 것도 가능하지만, 문자열 byte 길이가 `n`을
초과하면 실패합니다. 새 SQL 작성 시에는 의미가 명확한 binary literal 형식을
권장합니다.
`'0b...'`, `'0o...'`, 따옴표 없는 `0x...`, `0b...`, `0o...` 형식은
binary literal로 지원하지 않습니다.
### 출력 및 도구 메모
- machsql은 `0x` 없는 대문자 헥스로 출력합니다. 짧은 입력값에 붙은 뒤쪽
0 패딩은 텍스트 출력에 표시되지 않습니다.
- machloader: 스키마에 `BINARY(n)`을 선언하고, 잘못된 값이나 길이 초과는
실패합니다.
- Machbase SQLCLI, ODBC, Java, C#, Node.js 드라이버는 고정 길이 버퍼로 송수신하며 메타데이터
`LENGTH`는 바이트 길이입니다.
## 예제 정리
이 페이지에서 실제로 만든 테이블만 정리합니다. 대안 DDL을 실행하지 않았다면 그 테이블의
DROP 문도 실행하지 않습니다.
```sql
DROP TABLE time_sensor;
DROP TABLE rail_sensor;
DROP TABLE weather_station;
DROP TABLE temperature_sensor;
DROP TABLE thermo_sensor;
DROP TABLE vibration_sensor;
DROP TABLE example;
DROP TABLE example_lower_only;
DROP TABLE example_alter_limits;
DROP TABLE example_alter_upper;
DROP TABLE t1;
DROP TABLE t_bin;
DROP TABLE t_limit;
DROP TABLE t_dst;
DROP TABLE t_src;
```
---
title: "5.3 생성, 변경, 삭제"
url: https://docs.machbase.com/kr/dbms/tag-table-usage/create-alter-drop/
language: kr
kind: page
---
# 5.3 생성, 변경, 삭제
TAG 테이블은 태그 식별자와 하나의 축 컬럼을 필수로 가집니다. 이 페이지에서는 실행 가능한
기본 예제를 제공하고, 전체 옵션은 SQL 레퍼런스로 연결합니다.
## TAG 테이블 생성
TAG 테이블은 앞의 두 컬럼이 고정된 역할을 가집니다. 이름 컬럼과 축 컬럼은 생략할 수
없고, 순서를 바꾸거나 다른 위치에 지정하면 생성에 실패합니다.
| 컬럼 위치 | 용도와 특성 |
| --- | --- |
| 첫 번째 | 태그 이름입니다. `VARCHAR` 컬럼에 `PRIMARY KEY`를 지정하며 다른 타입은 사용할 수 없습니다. 센서·설비·검사 회차처럼 반복해서 관측할 대상을 식별하고, 같은 태그 이름으로 여러 DATA 행을 입력할 수 있으므로 관계형 테이블의 행별 고유 키와 다르게 이해합니다. |
| 두 번째 | 관측값을 시간 또는 거리·위치 기준으로 정렬·조회합니다. 시간축은 `DATETIME BASETIME`, 거리축은 `DOUBLE`, `LONG`, `ULONG` 중 하나에 `BASEDISTANCE`를 지정합니다. 한 TAG 테이블에는 시간축 또는 거리축 중 하나만 정의할 수 있습니다. |
| 세 번째 이후 | 온도, 압력, 상태, 품질 코드처럼 관측마다 달라지는 DATA 컬럼입니다. 숫자형, `VARCHAR`, `DATETIME`, JSON, 숫자 ARRAY, BINARY를 사용할 수 있습니다. 여러 DATA 컬럼 중 그 태그를 대표하는 값 하나를 정해 두면 태그별 값 통계와 자동 ROLLUP의 기준으로 쓸 수 있고, 이때 `SUMMARIZED`를 지정합니다. 선택 사항이며 세 번째 컬럼에만 지정할 수 있습니다. |
ARRAY는 이름 컬럼, 축 컬럼과 `SUMMARIZED` 컬럼에는 사용할 수 없고 그 밖의 DATA
컬럼에만 사용합니다. `TEXT`, `CLOB`, `BLOB`은 LOG 테이블과 달리 TAG 테이블의 DATA 컬럼에 사용할
수 없으므로, 긴 문자열은 `VARCHAR`로, 이진 데이터는 `BINARY`로 저장합니다. 타입별 표기와
값 범위는 [데이터 타입 사전](/dbms/reference/sql/types/)을 참고합니다.
태그마다 한 번만 저장하는 속성은 위 컬럼 위치가 아니라 `METADATA` 절에 따로 선언합니다.
아래 시간축 예제의 `location`이 여기에 해당합니다.
다음 두 테이블은 이 페이지의 독립 실습용입니다. 기존 객체가 없는지 확인한 뒤 순서대로
실행합니다. 시간축과 거리축 중 데이터의 실제 의미에 맞는 축을 선택합니다. 거리축 TAG에는
ROLLUP을 사용할 수 없습니다.
### 시간축 TAG
```sql
CREATE TAG TABLE ch5_tag_ddl (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE SUMMARIZED
) METADATA (
location VARCHAR(64)
);
```
`SUMMARIZED`를 지정하면 두 가지가 따라옵니다. 첫째, 태그별 STAT 뷰에 `MIN_VALUE`,
`MAX_VALUE`처럼 값 자체의 통계가 이 컬럼을 기준으로 쌓입니다. `SUMMARIZED` 컬럼이 없으면
STAT에는 행 수와 축 범위만 남고 값 통계는 저장되지 않습니다. 둘째, `WITH ROLLUP` 자동
생성과 JSON 문서 전체 ROLLUP이 이 컬럼을 대상으로 삼습니다.
반대로 일반 숫자 ROLLUP은 `CREATE ROLLUP ... ON table(column)`에서 컬럼을 직접 지정하므로
`SUMMARIZED`가 없어도 만들 수 있습니다. 값 통계가 필요 없고 ROLLUP도 직접 만들 계획이면
지정하지 않아도 됩니다. 지정할 수 있는 타입은 지원 숫자 타입과 JSON이며, ROLLUP 생성
조건은 [6장](../../tag-rollup-usage/)을 참고합니다.
위치·단위처럼 태그마다 한 번 저장하는 속성은 `METADATA`, 매 측정마다 달라지는 값은 일반
데이터 컬럼에 정의합니다.
### 거리축 TAG
```sql
CREATE TAG TABLE ch5_distance_ddl (
name VARCHAR(32) PRIMARY KEY,
distance DOUBLE BASEDISTANCE,
value DOUBLE
);
```
소수 거리값에는 `DOUBLE`, 정수 축에는 값 범위에 맞는 `LONG` 또는 `ULONG`을 사용합니다.
## TAG 테이블 변경
이 절의 METADATA ADD/DROP 실습은 Standard Edition을 전제로 합니다.
TAG 데이터 컬럼의 임의 변경은 제한됩니다. 스키마를 확장해야 한다면 새 테이블로 전환하는
방법을 먼저 검토하십시오. 메타데이터 컬럼은 지원 구문으로 추가하거나 삭제할 수 있습니다.
```sql
ALTER TABLE ch5_tag_ddl
METADATA ADD COLUMN (team VARCHAR(32));
ALTER TABLE ch5_tag_ddl METADATA
ADD COLUMN (limits DECIMAL(12,4)[2] DEFAULT [0.0000, NULL]);
ALTER TABLE ch5_tag_ddl
METADATA DROP COLUMN (team);
ALTER TABLE ch5_tag_ddl METADATA
DROP COLUMN (limits);
```
Standard Edition에서는 TAG METADATA에 고정 길이 숫자 ARRAY 컬럼을 추가할 수 있습니다.
ALTER 전에 존재한 metadata row에는 명시한 ARRAY DEFAULT를 적용합니다. ALTER 뒤 TAG DATA
입력으로 자동 등록되는 metadata row에는 DEFAULT를 다시 적용하지 않으며 새 ARRAY 컬럼은
whole NULL입니다.
TAG DATA의 일반 ARRAY 컬럼은 `CREATE TABLE`에서 선언할 수 있지만 ALTER로 추가할 수
없습니다. TAG METADATA ARRAY에는 자동 index를 만들지 않으며 명시적 index도 지원하지
않습니다. 자세한 규칙은 [TAG 메타데이터](../tag-metadata/)와
[숫자 ARRAY 타입](/dbms/reference/sql/types/array/)을 참고하십시오.
데이터가 있는 운영 테이블에서는 변경 전에 의존 쿼리, SDK 컬럼 순서와 재입력 경로를
확인합니다. ALTER 후 `DESC ch5_tag_ddl;`과 METADATA 조회로 변경 결과를 확인합니다.
## TAG 테이블 삭제
`DROP TABLE`은 원시 데이터와 메타데이터를 함께 제거합니다. ROLLUP 등 의존 객체가 있으면
먼저 의존 순서에 따라 정리해야 합니다. TAG의 `DROP TABLE ... CASCADE`는 연결된
ROLLUP도 제거할 수 있으므로 단순 정리의 기본 명령으로 사용하지 않습니다.
Custom ROLLUP의 대상 테이블은 별도 의존 제약도 확인합니다.
```sql
DROP TABLE ch5_distance_ddl;
DROP TABLE ch5_tag_ddl;
```
정확한 속성, 허용 범위와 DDL은
[DDL 구문 사전](/dbms/reference/sql/syntax/ddl-syntax/)을 참고하십시오.
다음으로 읽을 내용:
- [TAG 테이블 구조와 스키마](../table-structure-schema/)
- [TAG 데이터 입력과 변경](../data-input-mutation/)
- [TAG 메타데이터](../tag-metadata/)
---
title: "5.4 데이터 입력과 변경"
url: https://docs.machbase.com/kr/dbms/tag-table-usage/data-input-mutation/
language: kr
kind: page
---
# 5.4 데이터 입력과 변경
TAG 데이터는 SQL `INSERT`, Append API 또는 파일 적재 도구로 입력합니다. SQL 예제는 기능
확인과 소량 입력에 사용하고, 지속적인 수집은 Append API를 우선 검토합니다.
## SQL INSERT
다음 예제는 시간축과 거리축 TAG를 각각 만들고 데이터를 확인한 뒤 정리합니다.
실습 이름이 기존 테이블과 겹치지 않는지 먼저 확인합니다. 시간축 이름은 센서,
거리축 이름은 한 검사 대상·회차를 식별합니다.
```sql
CREATE TAG TABLE ch5_input_time (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE
);
INSERT INTO ch5_input_time
VALUES ('TEMP_001', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 25.5);
INSERT INTO ch5_input_time
VALUES ('TEMP_001', TO_DATE('2026-01-01 10:01:00', 'YYYY-MM-DD HH24:MI:SS'), 25.7);
CREATE TAG TABLE ch5_input_distance (
name VARCHAR(32) PRIMARY KEY,
distance DOUBLE BASEDISTANCE,
value DOUBLE,
quality INTEGER
);
INSERT INTO ch5_input_distance VALUES ('PIPE_A', 0.0, 10.1, 100);
INSERT INTO ch5_input_distance VALUES ('PIPE_A', 500.5, 11.2, 100);
EXEC TABLE_FLUSH(ch5_input_time);
EXEC TABLE_FLUSH(ch5_input_distance);
SELECT name, time, value FROM ch5_input_time ORDER BY time;
SELECT name, distance, value, quality
FROM ch5_input_distance
ORDER BY distance;
SELECT COUNT(*) FROM ch5_input_time;
SELECT COUNT(*) FROM ch5_input_distance;
DROP TABLE ch5_input_distance;
DROP TABLE ch5_input_time;
```
두 테이블은 각각 두 행을 반환합니다. 시간축 값은 25.5, 25.7이고 거리축은
0.0, 500.5입니다. 태그를 미리 등록하지 않았으므로 첫 DATA 입력에서 해당 이름이 자동
등록됩니다. 실제 발생 시각과 재전송 여부는 애플리케이션에서 관리합니다.
`TABLE_FLUSH`는 pending storage/input buffer를 명시적으로 flush해야 하는 검증·운영 절차에
사용합니다. transaction commit이나 조회 가시성 보장 수단은 아니며, 일반 수집 루프에서 행마다
실행하지 마십시오. 인자와 오류 계약은
[EXEC procedure 정본](/dbms/reference/sql/syntax/execute-procedure-syntax/#table-flush)을
참고합니다.
## 메타데이터와 함께 입력
사용자 메타데이터가 있는 TAG도 DATA만 입력해 새 태그를 자동 등록할 수 있습니다.
위치·단위 같은 값을 먼저 정해야 하면 `INSERT ... METADATA`로 등록한 뒤 DATA를
입력합니다. 데이터와 메타데이터를 함께 전달하는 지원 구문도 있습니다.
이미 등록된 속성을 일반 DATA 입력마다 갱신하는 것으로 가정하지 마십시오. 시스템 관리 컬럼은 입력
목록에 포함하지 않습니다.
메타데이터 값의 등록·갱신·삭제는 [TAG 메타데이터](../tag-metadata/)를 참고하십시오.
## 입력 경로 선택
| 경로 | 적합한 경우 | 확인할 사항 |
| --- | --- | --- |
| SQL `INSERT` | 기능 확인, 저빈도 입력 | 문장별 파싱·왕복 비용 |
| SDK Append | 지속적인 고처리량 입력 | 배치 크기, flush와 오류 처리 |
| `csvimport` / `machloader` | 클라이언트 파일 일괄 적재 | 컬럼 순서, 날짜 형식, bad 파일 |
| `LOAD DATA INFILE` | 서버가 접근할 수 있는 파일 | 서버 경로·권한, 오류 정책 |
SDK별 연결과 Append 예제는 [개발 도구 연동](/dbms/development-tools-integration/)을,
파일 형식과 명령은
[데이터 입력·적재·반출](/dbms/development-tools-integration/data-input-load-export/)을
참고하십시오.
## 데이터 정정
TAG data UPDATE는 Standard Edition에서 태그 선택 조건과 BASETIME 범위를 함께 지정해
실행합니다. 태그명, 축과 메타데이터 컬럼은 일반 data UPDATE 대상으로 사용하지 않습니다.
정정 후 이미 계산된 ROLLUP은 자동으로 바뀌지 않으므로, ROLLUP이 있다면 대상 범위를
명시적으로 재구성합니다. 재구성 절차는 [ROLLUP_REBUILD](../../tag-rollup-usage/rollup-rebuild/)를
참고합니다.
성공 응답·실패 행 수와 재시도 정책을 선택한 입력 API에서 확인합니다. SQL의 NULL,
SDK의 NULL 표현과 숫자 0을 구분하고, 같은 관측을 재전송할 때의 중복 정책도 정합니다.
숫자 ARRAY·희소 입력은 [ARRAY Append 예제](../../development-tools-integration/data-input-load-export/array-append/)를
사용합니다.
자세한 절차는 [TAG 데이터 정정](../tag-data-update-correction/)을 참고하십시오.
---
title: "5.5 조회와 분석"
url: https://docs.machbase.com/kr/dbms/tag-table-usage/query-analysis/
language: kr
kind: page
---
# 5.5 조회와 분석
TAG 테이블에서 시계열 데이터를 조회하는 주요 패턴을 다룹니다. 시간축·거리축 범위 조회,
다중 태그 검색과 통계 뷰 활용을 포함합니다.
## Tag 데이터 조회
### 샘플 스키마 (시간축)
다음 예제는 TAG 테이블에 두 태그를 등록하고 태그마다 10개 행을 입력합니다.
`TAG_0001`은 2018년 1월 1일부터 10일까지, `TAG_0002`는 2월 1일부터 10일까지의 데이터를 사용합니다.
```sql
create tag table TAG (name varchar(20) primary key, time datetime basetime, value double summarized);
insert into tag metadata values ('TAG_0001');
insert into tag metadata values ('TAG_0002');
insert into tag values('TAG_0001', '2018-01-01 01:00:00 000:000:000', 1);
insert into tag values('TAG_0001', '2018-01-02 02:00:00 000:000:000', 2);
insert into tag values('TAG_0001', '2018-01-03 03:00:00 000:000:000', 3);
insert into tag values('TAG_0001', '2018-01-04 04:00:00 000:000:000', 4);
insert into tag values('TAG_0001', '2018-01-05 05:00:00 000:000:000', 5);
insert into tag values('TAG_0001', '2018-01-06 06:00:00 000:000:000', 6);
insert into tag values('TAG_0001', '2018-01-07 07:00:00 000:000:000', 7);
insert into tag values('TAG_0001', '2018-01-08 08:00:00 000:000:000', 8);
insert into tag values('TAG_0001', '2018-01-09 09:00:00 000:000:000', 9);
insert into tag values('TAG_0001', '2018-01-10 10:00:00 000:000:000', 10);
insert into tag values('TAG_0002', '2018-02-01 01:00:00 000:000:000', 11);
insert into tag values('TAG_0002', '2018-02-02 02:00:00 000:000:000', 12);
insert into tag values('TAG_0002', '2018-02-03 03:00:00 000:000:000', 13);
insert into tag values('TAG_0002', '2018-02-04 04:00:00 000:000:000', 14);
insert into tag values('TAG_0002', '2018-02-05 05:00:00 000:000:000', 15);
insert into tag values('TAG_0002', '2018-02-06 06:00:00 000:000:000', 16);
insert into tag values('TAG_0002', '2018-02-07 07:00:00 000:000:000', 17);
insert into tag values('TAG_0002', '2018-02-08 08:00:00 000:000:000', 18);
insert into tag values('TAG_0002', '2018-02-09 09:00:00 000:000:000', 19);
insert into tag values('TAG_0002', '2018-02-10 10:00:00 000:000:000', 20);
exec table_flush(tag);
```
예제 마지막의 `TABLE_FLUSH`는 저장 버퍼를 명시적으로 처리하는 절차입니다. 트랜잭션 커밋이나
조회 가시성을 보장하는 명령으로 해석하지 않습니다. 자세한 동작은
[TABLE_FLUSH](/dbms/reference/sql/syntax/execute-procedure-syntax/#table-flush)를
참고하십시오.
### 모든 TAG 데이터 추출
```sql
select * from tag ORDER BY name, time;
```
```text
NAME TIME VALUE
--------------------------------------------------------------------------------------
TAG_0001 2018-01-01 01:00:00 000:000:000 1
TAG_0001 2018-01-02 02:00:00 000:000:000 2
TAG_0001 2018-01-03 03:00:00 000:000:000 3
TAG_0001 2018-01-04 04:00:00 000:000:000 4
TAG_0001 2018-01-05 05:00:00 000:000:000 5
TAG_0001 2018-01-06 06:00:00 000:000:000 6
TAG_0001 2018-01-07 07:00:00 000:000:000 7
TAG_0001 2018-01-08 08:00:00 000:000:000 8
TAG_0001 2018-01-09 09:00:00 000:000:000 9
TAG_0001 2018-01-10 10:00:00 000:000:000 10
TAG_0002 2018-02-01 01:00:00 000:000:000 11
TAG_0002 2018-02-02 02:00:00 000:000:000 12
TAG_0002 2018-02-03 03:00:00 000:000:000 13
TAG_0002 2018-02-04 04:00:00 000:000:000 14
TAG_0002 2018-02-05 05:00:00 000:000:000 15
TAG_0002 2018-02-06 06:00:00 000:000:000 16
TAG_0002 2018-02-07 07:00:00 000:000:000 17
TAG_0002 2018-02-08 08:00:00 000:000:000 18
TAG_0002 2018-02-09 09:00:00 000:000:000 19
TAG_0002 2018-02-10 10:00:00 000:000:000 20
[20] row(s) selected.
```
위 출력은 예제 실행 결과입니다. 결과 순서를 보장해야 하면 `ORDER BY name, time`을
명시합니다. 조건 없는 조회의 출력 순서는 실행 계획과 스캔 방향에 의존할 수 있습니다.
### 특정 tag 이름으로 데이터 추출
TAG 이름이 TAG_0002인 데이터를 조회하는 예제입니다.
```sql
select * from tag where name='TAG_0002' ORDER BY name, time;
```
```text
NAME TIME VALUE
--------------------------------------------------------------------------------------
TAG_0002 2018-02-01 01:00:00 000:000:000 11
TAG_0002 2018-02-02 02:00:00 000:000:000 12
TAG_0002 2018-02-03 03:00:00 000:000:000 13
TAG_0002 2018-02-04 04:00:00 000:000:000 14
TAG_0002 2018-02-05 05:00:00 000:000:000 15
TAG_0002 2018-02-06 06:00:00 000:000:000 16
TAG_0002 2018-02-07 07:00:00 000:000:000 17
TAG_0002 2018-02-08 08:00:00 000:000:000 18
TAG_0002 2018-02-09 09:00:00 000:000:000 19
TAG_0002 2018-02-10 10:00:00 000:000:000 20
[10] row(s) selected.
```
### 시간 범위 조회
TAG_0002에 대해 시간 범위를 지정해 데이터를 조회하는 예제입니다.
> `BETWEEN`은 양쪽 경계를 모두 포함하며 `>=`와 `<=`를 함께 쓴 조건과 같습니다.
> 아래 예제는 경계 시각에 데이터가 없어서 `>`·`<` 조건도 같은 결과를 반환합니다.
> 연속된 조회 구간이 경계 행을 중복해서 읽지 않도록 하려면 `time >= 시작 AND time < 종료`를
> 사용합니다.
```sql
select * from tag where name = 'TAG_0002' and time between to_date('2018-02-01') and to_date('2018-02-05') ORDER BY name, time;
```
```text
NAME TIME VALUE
--------------------------------------------------------------------------------------
TAG_0002 2018-02-01 01:00:00 000:000:000 11
TAG_0002 2018-02-02 02:00:00 000:000:000 12
TAG_0002 2018-02-03 03:00:00 000:000:000 13
TAG_0002 2018-02-04 04:00:00 000:000:000 14
[4] row(s) selected.
```
```sql
select * from tag where name = 'TAG_0002' and time > to_date('2018-02-01') and time < to_date('2018-02-05') ORDER BY name, time;
```
```text
NAME TIME VALUE
--------------------------------------------------------------------------------------
TAG_0002 2018-02-01 01:00:00 000:000:000 11
TAG_0002 2018-02-02 02:00:00 000:000:000 12
TAG_0002 2018-02-03 03:00:00 000:000:000 13
TAG_0002 2018-02-04 04:00:00 000:000:000 14
[4] row(s) selected.
```
### 거리축 샘플 스키마
거리축(`BASE DISTANCE`) Tag 테이블 기준의 예제입니다.
```sql
CREATE TAG TABLE trip_tag (
name VARCHAR(20) PRIMARY KEY,
distance_m DOUBLE BASE DISTANCE,
value DOUBLE,
quality INTEGER
);
INSERT INTO trip_tag VALUES('ODO_A', 0, 10.1, 100);
INSERT INTO trip_tag VALUES('ODO_A', 500, 11.2, 101);
INSERT INTO trip_tag VALUES('ODO_A', 1000, 12.3, 102);
INSERT INTO trip_tag VALUES('ODO_B', 1000.1, 21.5, 100);
INSERT INTO trip_tag VALUES('ODO_B', 1500, 22.1, 101);
INSERT INTO trip_tag VALUES('ODO_B', 2000, 22.9, 102);
EXEC TABLE_FLUSH(trip_tag);
```
### 거리 구간 조회
```sql
SELECT name, distance_m, value, quality
FROM trip_tag
WHERE name = 'ODO_A'
AND distance_m BETWEEN 0 AND 1000
ORDER BY distance_m;
```
시간축과 마찬가지로 거리축도 축 범위를 좁히는 것이 기본 조회 패턴입니다.
### DOUBLE 거리축의 소수 경계 조회
```sql
SELECT name, distance_m, value, quality
FROM trip_tag
WHERE name = 'ODO_B'
AND distance_m BETWEEN 1000.1 AND 2000
ORDER BY distance_m;
```
숫자값 그대로 비교하므로 `1000`은 제외되고 `1500`, `2000`은 포함됩니다.
### 거리축 실행 계획 확인
대용량 거리축 조회에서는 `EXPLAIN`으로 거리 조건이 key range로 들어가는지 확인합니다.
```sql
EXPLAIN
SELECT name, distance_m, value
FROM trip_tag
WHERE name = 'ODO_B'
AND distance_m BETWEEN 1000.1 AND 2000
ORDER BY distance_m;
```
확인할 포인트:
- `KEYVALUE INDEX SCAN` 또는 유사한 인덱스 스캔이 보이는지
- `KEY RANGE` 아래에 `distance_m between ...` 조건이 보이는지
### 거리 버킷 집계
아래 예제는 0 이상인 거리를 500 단위 구간으로 나눕니다. TRUNC는 0 방향으로 자르므로
음수 좌표의 구간이 필요하면 FLOOR 등 원하는 경계 규칙을 따로 선택합니다.
```sql
SELECT TRUNC(distance_m / 500, 0) * 500 AS dist_bucket,
COUNT(*) AS sample_count,
MIN(value) AS min_v,
MAX(value) AS max_v,
AVG(value) AS avg_v
FROM trip_tag
WHERE name = 'ODO_B'
GROUP BY TRUNC(distance_m / 500, 0) * 500
ORDER BY dist_bucket;
```
예를 들어 `1750`은 `1500` 버킷에 집계됩니다.
### 다중 tag에 대한 시간 범위 검색
두 개 이상의 태그에 같은 시간 범위를 적용하는 예제입니다. 대상 이름 목록이 정해져 있으면
`IN`으로 표현합니다. 대상 태그가 많을 때의 성능은 목록 크기와 시간 범위를 함께 측정합니다.
```sql
select * from tag where name in ('TAG_0002', 'TAG_0001') and time between to_date('2018-01-05') and to_date('2018-02-05') ORDER BY name, time;
```
```text
NAME TIME VALUE
--------------------------------------------------------------------------------------
TAG_0001 2018-01-05 05:00:00 000:000:000 5
TAG_0001 2018-01-06 06:00:00 000:000:000 6
TAG_0001 2018-01-07 07:00:00 000:000:000 7
TAG_0001 2018-01-08 08:00:00 000:000:000 8
TAG_0001 2018-01-09 09:00:00 000:000:000 9
TAG_0001 2018-01-10 10:00:00 000:000:000 10
TAG_0002 2018-02-01 01:00:00 000:000:000 11
TAG_0002 2018-02-02 02:00:00 000:000:000 12
TAG_0002 2018-02-03 03:00:00 000:000:000 13
TAG_0002 2018-02-04 04:00:00 000:000:000 14
[10] row(s) selected.
```
### 특정 값 이상의 데이터 검색
tag 값에 대한 조건도 지정할 수 있습니다. TAG_0002의 값 중 12보다 크고 15보다 작은 값에 대해 필터링한 결과입니다.
```sql
select * from tag where name = 'TAG_0002' and value > 12 and value < 15 and time between to_date('2018-02-01') and to_date('2018-02-05') ORDER BY name, time;
```
```text
NAME TIME VALUE
--------------------------------------------------------------------------------------
TAG_0002 2018-02-03 03:00:00 000:000:000 13
TAG_0002 2018-02-04 04:00:00 000:000:000 14
[2] row(s) selected.
```
### TAG별 통계 뷰 `V$
_STAT`
tag 테이블을 생성하면, tag ID별 통계 정보를 집계하는 가상 테이블이 자동으로 만들어집니다. 이 가상 테이블의 이름은 v${tag 테이블 이름}_stat입니다.
태그명·축 관련 통계와 세 번째 SUMMARIZED 컬럼의 값 통계를 구분합니다.
STAT는 백그라운드 인덱스·통계 처리 상태를 반영하므로 직전 입력의 검증은 원본 SELECT와
함께 수행합니다. 즉시 통계 확인이 필요한 실습에서는 TABLE_FLUSH 후 INDEX_FLUSH로
처리를 기다립니다.
BASE DISTANCE 축별 STAT 스키마는 Machbase 8.7.0부터 지원
축 관련 컬럼 이름과 타입은 TAG 테이블의 축에 따라 달라집니다.
| TAG 축 | 최소/최대 축 | 최소/최대값 발생 축 | 최근 입력 row 축 | 축 통계 타입 |
|--------|--------------|----------------------|------------------|--------------|
| `DATETIME BASE TIME` | `MIN_TIME`, `MAX_TIME` | `MIN_VALUE_TIME`, `MAX_VALUE_TIME` | `RECENT_ROW_TIME` | `DATETIME` |
| `DOUBLE/LONG/ULONG BASE DISTANCE` | `MIN_DISTANCE`, `MAX_DISTANCE` | `MIN_VALUE_DISTANCE`, `MAX_VALUE_DISTANCE` | `RECENT_ROW_DISTANCE` | 원본 BASE DISTANCE 타입 |
두 Edition의 공통 컬럼은 `NAME`, `ROW_COUNT`, `MIN_VALUE`, `MAX_VALUE`입니다. Cluster
Edition에서는 스키마 맨 앞에 `HOSTNAME VARCHAR(64)`가 추가됩니다.
#### BASE TIME STAT 스키마
```sql
DESC v$tag_stat;
```
```text
[ COLUMN ]
----------------------------------------------------------------------------------------------------
NAME NULL? TYPE LENGTH
----------------------------------------------------------------------------------------------------
NAME varchar 100
ROW_COUNT ulong 20
MIN_TIME datetime 31
MAX_TIME datetime 31
MIN_VALUE double 17
MIN_VALUE_TIME datetime 31
MAX_VALUE double 17
MAX_VALUE_TIME datetime 31
RECENT_ROW_TIME datetime 31
```
세 번째 컬럼에 SUMMARIZED 키워드가 없으면 VALUE 관련 정보(MIN_VALUE, MAX_VALUE, MIN_VALUE_TIME, MAX_VALUE_TIME)는 저장되지 않습니다.
수집되는 통계 정보는 다음과 같습니다.
|컬럼 이름|정보|
|--|--|
|NAME|Tag ID의 이름|
|ROW_COUNT|행 수|
|MIN_TIME|해당 tag ID 행 중 가장 작은 basetime 컬럼 값|
|MAX_TIME|해당 tag ID 행 중 가장 큰 basetime 컬럼 값|
|MIN_VALUE|해당 tag ID 행 중 가장 작은 summarized 컬럼 값|
|MIN_VALUE_TIME|MIN_VALUE와 함께 삽입된 basetime 컬럼 값|
|MAX_VALUE|해당 tag ID 행 중 가장 큰 summarized 컬럼 값|
|MAX_VALUE_TIME|MAX_VALUE와 함께 삽입된 basetime 컬럼 값|
|RECENT_ROW_TIME|가장 최근에 삽입된 basetime 컬럼 값|
다음 통계 실습은 앞의 20행 조회 예제와 분리한 테이블을 사용합니다. 따라서 아래 두 태그만
조회되며, 앞의 TAG_0001·TAG_0002가 통계 예상 결과에 섞이지 않습니다.
1. SUMMARIZED 컬럼이 존재하는 경우
```sql
CREATE TAG TABLE ch5_stat_time (name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED);
```
```sql
INSERT INTO ch5_stat_time VALUES('tag-0', TO_DATE('2021-08-12'), 10);
INSERT INTO ch5_stat_time VALUES('tag-0', TO_DATE('2021-08-13'), 10);
INSERT INTO ch5_stat_time VALUES('tag-0', TO_DATE('2021-08-14'), 20);
INSERT INTO ch5_stat_time VALUES('tag-0', TO_DATE('2021-08-11'), 5);
INSERT INTO ch5_stat_time VALUES('tag-1', TO_DATE('2022-08-12'), 100);
INSERT INTO ch5_stat_time VALUES('tag-1', TO_DATE('2022-08-11'), 200);
INSERT INTO ch5_stat_time VALUES('tag-1', TO_DATE('2022-08-10'), 50);
```
```sql
EXEC TABLE_FLUSH(ch5_stat_time);
EXEC INDEX_FLUSH(ch5_stat_time);
SELECT * FROM v$ch5_stat_time_stat ORDER BY name;
```
```text
NAME ROW_COUNT MIN_TIME MAX_TIME MIN_VALUE
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
MIN_VALUE_TIME MAX_VALUE MAX_VALUE_TIME RECENT_ROW_TIME
---------------------------------------------------------------------------------------------------------------------------------
tag-0 4 2021-08-11 00:00:00 000:000:000 2021-08-14 00:00:00 000:000:000 5
2021-08-11 00:00:00 000:000:000 20 2021-08-14 00:00:00 000:000:000 2021-08-11 00:00:00 000:000:000
tag-1 3 2022-08-10 00:00:00 000:000:000 2022-08-12 00:00:00 000:000:000 50
2022-08-10 00:00:00 000:000:000 200 2022-08-11 00:00:00 000:000:000 2022-08-10 00:00:00 000:000:000
[2] row(s) selected.
```
2. SUMMARIZED 컬럼이 존재하지 않는 경우
```sql
CREATE TAG TABLE other_tag (name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE);
```
```text
Executed successfully.
```
```sql
INSERT INTO other_tag VALUES('tag-0', TO_DATE('2021-08-12'), 10);
INSERT INTO other_tag VALUES('tag-0', TO_DATE('2021-08-13'), 10);
INSERT INTO other_tag VALUES('tag-0', TO_DATE('2021-08-14'), 20);
INSERT INTO other_tag VALUES('tag-0', TO_DATE('2021-08-11'), 5);
INSERT INTO other_tag VALUES('tag-1', TO_DATE('2022-08-12'), 100);
INSERT INTO other_tag VALUES('tag-1', TO_DATE('2022-08-11'), 200);
INSERT INTO other_tag VALUES('tag-1', TO_DATE('2022-08-10'), 50);
```
```sql
EXEC TABLE_FLUSH(other_tag);
EXEC INDEX_FLUSH(other_tag);
SELECT * FROM v$other_tag_stat ORDER BY name;
```
```text
NAME ROW_COUNT MIN_TIME MAX_TIME MIN_VALUE
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
MIN_VALUE_TIME MAX_VALUE MAX_VALUE_TIME RECENT_ROW_TIME
---------------------------------------------------------------------------------------------------------------------------------
tag-0 4 2021-08-11 00:00:00 000:000:000 2021-08-14 00:00:00 000:000:000 NULL
NULL NULL NULL 2021-08-11 00:00:00 000:000:000
tag-1 3 2022-08-10 00:00:00 000:000:000 2022-08-12 00:00:00 000:000:000 NULL
NULL NULL NULL 2022-08-10 00:00:00 000:000:000
[2] row(s) selected.
```
#### BASE DISTANCE STAT 스키마
거리축 TAG 테이블의 통계 뷰는 거리값을 숫자 타입으로 제공합니다.
```sql
CREATE TAG TABLE distance_sensor (
name VARCHAR(32) PRIMARY KEY,
odometer_m DOUBLE BASE DISTANCE,
value DOUBLE SUMMARIZED
);
INSERT INTO distance_sensor VALUES('sensor', 20.5, 8);
INSERT INTO distance_sensor VALUES('sensor', 10.25, 3);
INSERT INTO distance_sensor VALUES('sensor', 30.75, 5);
EXEC TABLE_FLUSH(distance_sensor);
EXEC INDEX_FLUSH(distance_sensor);
```
Standard Edition에서 `DOUBLE BASE DISTANCE` 테이블의 스키마는 다음과 같습니다.
```sql
DESC V$DISTANCE_SENSOR_STAT;
```
```text
[ COLUMN ]
----------------------------------------------------------------------------------------------------
NAME NULL? TYPE LENGTH
----------------------------------------------------------------------------------------------------
NAME varchar 100
ROW_COUNT ulong 20
MIN_DISTANCE double 17
MAX_DISTANCE double 17
MIN_VALUE double 17
MIN_VALUE_DISTANCE double 17
MAX_VALUE double 17
MAX_VALUE_DISTANCE double 17
RECENT_ROW_DISTANCE double 17
```
거리축 통계 컬럼 다섯 개의 타입은 원본 BASE DISTANCE 컬럼 타입을 따릅니다.
| BASE DISTANCE 타입 | STAT 컬럼 타입 | `DESC` 길이 |
|--------------------|----------------|-------------|
| `DOUBLE` | `double` | 17 |
| `LONG` | `long` | 20 |
| `ULONG` | `ulong` | 20 |
```sql
SELECT name,
row_count,
min_distance,
max_distance,
min_value,
min_value_distance,
max_value,
max_value_distance,
recent_row_distance
FROM V$DISTANCE_SENSOR_STAT
WHERE name = 'sensor';
```
- `MIN_DISTANCE`와 `MAX_DISTANCE`는 해당 통계 row의 최소·최대 거리입니다.
- `MIN_VALUE_DISTANCE`와 `MAX_VALUE_DISTANCE`는 각각 최소·최대 summarized value가
발생한 거리입니다.
- `RECENT_ROW_DISTANCE`는 가장 큰 거리가 아니라 가장 최근에 입력된 row의 거리입니다.
- `SUMMARIZED` 컬럼이 없으면 `MIN_VALUE_DISTANCE`와 `MAX_VALUE_DISTANCE`는 `NULL`입니다.
##### Cluster Edition에서 조회
Cluster Edition의 통계 뷰에는 `HOSTNAME`이 추가되고 warehouse별 통계 row가 반환될 수
있습니다. 먼저 warehouse별 값을 확인합니다.
```sql
SELECT hostname, name, row_count,
min_distance, max_distance,
min_value, min_value_distance,
max_value, max_value_distance,
recent_row_distance
FROM V$DISTANCE_SENSOR_STAT
ORDER BY hostname, name;
```
row 수와 거리 경계는 tag 이름으로 안전하게 집계할 수 있습니다.
```sql
SELECT name,
SUM(row_count) AS row_count,
MIN(min_distance) AS min_distance,
MAX(max_distance) AS max_distance
FROM V$DISTANCE_SENSOR_STAT
GROUP BY name;
```
{{< callout type="warning" >}}
`MIN_VALUE`와 `MIN_VALUE_DISTANCE`, `MAX_VALUE`와 `MAX_VALUE_DISTANCE`는 같은 warehouse
row의 짝을 유지해야 합니다. 두 컬럼을 각각 독립적으로 `MIN` 또는 `MAX`하면 서로 다른
warehouse의 값이 결합될 수 있습니다. `RECENT_ROW_DISTANCE`도 warehouse별 최근 입력
거리이므로 `MAX(RECENT_ROW_DISTANCE)`를 전체 cluster의 최근 입력 row로 해석하지 않습니다.
{{< /callout >}}
##### 8.7.0 호환성
BASE DISTANCE 통계 뷰의 기존 이름은 alias로 제공되지 않습니다. 기존 테이블도 8.7.0
서버가 재시작되면 새 스키마로 구성됩니다.
| 8.7.0 이전 이름 | 8.7.0 이름 |
|-----------------|------------|
| `MIN_TIME` | `MIN_DISTANCE` |
| `MAX_TIME` | `MAX_DISTANCE` |
| `MIN_VALUE_TIME` | `MIN_VALUE_DISTANCE` |
| `MAX_VALUE_TIME` | `MAX_VALUE_DISTANCE` |
| `RECENT_ROW_TIME` | `RECENT_ROW_DISTANCE` |
BASE TIME TAG 테이블은 기존 `*_TIME DATETIME` 스키마를 유지합니다.
### scan 방향 hint
축 방향의 순회와 결과 정렬을 구분합니다. 역방향 축 조회에서의 최신값은 가장 큰 축 값이며
마지막에 입력한 행을 나타내는 STAT의 RECENT_ROW 값과 다를 수 있습니다.
같은 축 값의 여러 행까지 순서를 구분해야 하면 애플리케이션의 추가 기준이 필요합니다.
```sql
SELECT *
FROM tag
WHERE name = 'TAG_0001'
ORDER BY time
LIMIT 10;
SELECT /*+ SCAN_FORWARD(tag) */ name, time, value
FROM tag
WHERE name = 'TAG_0001'
LIMIT 10;
SELECT /*+ SCAN_BACKWARD(tag) */ name, time, value
FROM tag
WHERE name = 'TAG_0001'
LIMIT 10;
```
hint가 없을 때의 기본 방향은
[TABLE_SCAN_DIRECTION](/dbms/reference/configuration/configuration/)을 참고합니다.
## 정리
```sql
DROP TABLE ch5_stat_time;
DROP TABLE distance_sensor;
DROP TABLE trip_tag;
DROP TABLE other_tag;
DROP TABLE tag;
```
---
title: "5.6 인덱스와 성능"
url: https://docs.machbase.com/kr/dbms/tag-table-usage/index-performance/
language: kr
kind: page
---
# 5.6 인덱스와 성능
TAG 조회는 태그명과 축 범위를 먼저 제한하는 것이 기본입니다. 추가 인덱스는 실제 조회 조건과
실행 계획을 측정한 뒤 선택합니다.
## 기본 조회 경로
TAG 테이블은 `PRIMARY KEY` 태그명과 `BASETIME` 또는 `BASEDISTANCE` 축을 기준으로 조회할 수
있도록 필요한 구조를 자동 관리합니다. 애플리케이션은 생성되는 시스템 객체의 이름이나
저장 단계에 의존하지 마십시오.
| 조회 조건 | 조정 방향 |
| --- | --- |
| 한 태그의 축 범위 | 태그명과 축 범위를 모두 명시 |
| 여러 태그의 같은 시간 범위 | 시간 범위를 먼저 제한하고 대상 태그 수를 관리 |
| 메타데이터 속성 | TAG `METADATA` 컬럼으로 정의 |
| 반복적인 시간 집계 | ROLLUP 검토 |
| 값 조건 중심 조회 | 값 컬럼 보조 인덱스를 실행 계획으로 검증 |
태그명이나 축 범위 없이 넓은 데이터를 조회하면 읽어야 할 범위가 커집니다. “항상 빠르다”는
가정 대신 실제 데이터량으로 `EXPLAIN` 결과와 실행 시간을 확인하십시오.
## METADATA 컬럼
설치 위치, 장치 유형처럼 태그마다 한 번 정의하는 속성은 `METADATA` 컬럼으로 설계합니다.
일반 스칼라 METADATA 컬럼에는 검색 인덱스가 자동으로 제공됩니다. 다만 JSON 컬럼 자체에는
자동 인덱스가 없으므로 필요한 JSON 경로에 인덱스를 정의합니다. 숫자 ARRAY 메타데이터
컬럼에는 자동·명시적 인덱스가 모두 지원되지 않습니다. 자세한 규칙은
[TAG 메타데이터](../tag-metadata/)를 참고하십시오.
시계열 값과 자주 바뀌는 상태를 METADATA에 넣으면 갱신 경로와 의미가 불명확해집니다.
값의 성격에 따라 TAG 데이터 컬럼, LOOKUP, VOLATILE 또는
TRANSACTION(Standard Edition 전용) 테이블을 검토하십시오.
## 값 컬럼 보조 인덱스
값 조건을 자주 사용하는 경우 `INDEX_TYPE TAG` secondary index를 검토할 수 있습니다. 추가
인덱스는 입력·저장 비용을 늘리므로 생성 전후의 대표 쿼리를 비교합니다.
다음 예제는 격리된 이름을 사용해 값 및 JSON path 인덱스를 만들고, 실행 계획을 확인한 뒤
모든 객체를 정리합니다. 작은 표본은 문법과 결과 검증용이며 성능 우위를 증명하지 않습니다.
이름이 겹치지 않는 별도 환경에서 실행합니다.
```sql
CREATE TAG TABLE ch5_index_tag (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE,
payload JSON
) METADATA (
location VARCHAR(64)
);
INSERT INTO ch5_index_tag METADATA VALUES ('TEMP-01', 'LINE-A');
INSERT INTO ch5_index_tag METADATA VALUES ('TEMP-02', 'LINE-B');
INSERT INTO ch5_index_tag VALUES
('TEMP-01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'),
10.0, '{"state":"normal"}');
INSERT INTO ch5_index_tag VALUES
('TEMP-01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'),
90.0, '{"state":"alarm"}');
INSERT INTO ch5_index_tag VALUES
('TEMP-02', TO_DATE('2026-01-01 00:02:00', 'YYYY-MM-DD HH24:MI:SS'),
95.0, '{"state":"alarm"}');
CREATE INDEX idx_ch5_index_tag_value
ON ch5_index_tag (value) INDEX_TYPE TAG;
CREATE INDEX idx_ch5_index_tag_json
ON ch5_index_tag (payload->'$.state');
EXPLAIN SELECT name, time, value
FROM ch5_index_tag
WHERE name = 'TEMP-01'
AND time BETWEEN TO_DATE('2026-01-01', 'YYYY-MM-DD')
AND TO_DATE('2026-01-02', 'YYYY-MM-DD')
AND value > 80.0;
SELECT name, value FROM ch5_index_tag
WHERE location = 'LINE-A' AND value > 80.0
ORDER BY name, time;
SELECT name, value FROM ch5_index_tag
WHERE payload->'$.state' = 'alarm'
ORDER BY name, time;
DROP INDEX idx_ch5_index_tag_json;
DROP INDEX idx_ch5_index_tag_value;
DROP TABLE ch5_index_tag;
```
첫 SELECT는 TEMP-01의 90.0 한 행, 두 번째는 TEMP-01의 90.0과 TEMP-02의 95.0을
반환합니다. 인덱스 생성 전후에 같은 조회 결과를 확인하고, 운영 규모의 데이터에서는
조회 시간뿐 아니라 입력 비용과 인덱스 크기도 비교합니다.
JSON path 연산자의 반환 타입과 비교 값의 타입을 맞추고, 원하는 경로가 실제로 인덱스를
사용하는지 `EXPLAIN`으로 확인합니다.
## 사용하지 않는 조정
- TAG의 축 컬럼에는 별도 인덱스를 만들지 않습니다.
- LOG용 `MINMAX_CACHE_SIZE` 설정을 TAG 값 컬럼에 적용하지 않습니다.
- 넓은 기간의 반복 집계를 secondary index만으로 해결하려 하지 말고 ROLLUP을 검토합니다.
정확한 인덱스 구문은 [SQL 문법 사전](/dbms/reference/sql/syntax/)을,
측정과 튜닝 절차는 [쿼리 튜닝](/dbms/performance-tuning/performance-query-tuning/)을 참고하십시오.
---
title: "5.7 운영과 데이터 생명주기"
url: https://docs.machbase.com/kr/dbms/tag-table-usage/operations-lifecycle/
language: kr
kind: page
---
# 5.7 운영과 데이터 생명주기
TAG 데이터의 수명 주기는 수동 삭제, Retention Policy와 중복 입력 방지로 관리합니다. 이
페이지는 운영 선택 기준을 설명하며 전체 SQL 문법은 관련 레퍼런스로 연결합니다.
## TAG 데이터 삭제
TAG 데이터는 태그 식별자와 축 조건을 사용해 삭제 범위를 제한합니다. 시간축 TAG의 대표적인
선택은 다음과 같습니다.
| 목적 | 조건 |
| --- | --- |
| 한 태그의 전체 데이터 삭제 | 태그 `PRIMARY KEY` 일치 |
| 한 태그의 시간 범위 삭제 | 태그 일치 + BASETIME 범위 |
| 모든 태그의 과거 데이터 삭제 | BASETIME 조건 또는 `BEFORE` |
| 테이블의 전체 데이터 삭제 | 조건 없는 TAG `DELETE` |
다음 예제는 별도 검증 테이블을 만들고 삭제 범위를 확인한 뒤 정리합니다.
```sql
CREATE TAG TABLE ch5_lifecycle (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE
);
INSERT INTO ch5_lifecycle VALUES
('TAG_0001', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 1.0);
INSERT INTO ch5_lifecycle VALUES
('TAG_0001', TO_DATE('2026-01-02 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 2.0);
INSERT INTO ch5_lifecycle VALUES
('TAG_0002', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 3.0);
DELETE FROM ch5_lifecycle
WHERE name = 'TAG_0001'
AND time < TO_DATE('2026-01-02 00:00:00', 'YYYY-MM-DD HH24:MI:SS');
SELECT name, time, value
FROM ch5_lifecycle
ORDER BY name, time;
DELETE FROM ch5_lifecycle;
SELECT COUNT(*) FROM ch5_lifecycle;
DROP TABLE ch5_lifecycle;
```
첫 삭제 후 TAG_0001의 2026-01-02 값 2.0과 TAG_0002의 값 3.0이 남습니다.
전체 삭제 후 COUNT는 0입니다. DATA 삭제와 METADATA 삭제는 별개입니다.
태그 등록 정보까지 제거하려면 [METADATA 삭제](../tag-metadata/) 조건도 확인합니다.
삭제 조건의 연산자와 Edition별 지원 범위는
[TAG DELETE 구문](/dbms/reference/sql/syntax/dml-syntax/)을
참고하십시오. 수동 삭제를 주기적으로 반복해야 한다면
[Retention Policy](/dbms/operations-configuration-recovery/policy-data-retention/)를
사용하십시오.
### ROLLUP 데이터 처리
원시 TAG 데이터의 삭제와 이미 계산된 ROLLUP의 처리는 별개입니다. 원시 데이터를 정정하거나
삭제한 뒤 집계도 바뀌어야 한다면 대상 범위의 ROLLUP을 재구성합니다. ROLLUP 삭제 구문을
보관 정책처럼 반복 실행하지 마십시오.
- [ROLLUP 부분 삭제와 재구성](/dbms/tag-rollup-usage/rollup-rebuild/)
- [TAG 데이터 정정 후 ROLLUP 재구성](../tag-data-update-correction/)
## 자동 중복 제거
`TAG_DUPLICATE_CHECK_DURATION`은 중복 검사 기간을 분 단위로 설정합니다.
Standard Edition에서는 0~43200분을 지정할 수 있으며 0은 비활성화입니다.
Cluster Edition에서는 0만 허용하므로 아래 활성화 실습은 Standard Edition 전용입니다.
서버 시각을 기준으로 검사 기간에 해당하는 데이터에서 태그·축·데이터 값이 같은 행을
중복으로 판정합니다. 인덱스 처리와 중복 행 정리 과정에서 수행되므로 Append 성공 응답을
“이미 중복 제거된 행 수”로 해석하지 않습니다. 늦게 도착한 데이터가 검사 기간 밖이면
기대한 중복 제거가 되지 않을 수 있으며 업무 키의 유일성 제약을 대신하지 않습니다.
```sql
CREATE TAG TABLE ch5_dedup (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE
) TAG_DUPLICATE_CHECK_DURATION = 1440;
INSERT INTO ch5_dedup VALUES ('TAG_0001', NOW, 1.0);
INSERT INTO ch5_dedup SELECT name, time, value FROM ch5_dedup;
EXEC TABLE_FLUSH(ch5_dedup);
EXEC INDEX_FLUSH(ch5_dedup);
SELECT name, time, value
FROM ch5_dedup
WHERE name = 'TAG_0001';
ALTER TABLE ch5_dedup SET TAG_DUPLICATE_CHECK_DURATION = 60;
DROP TABLE ch5_dedup;
```
두 번째 입력은 첫 행을 복사하므로 축 시각도 정확히 같습니다. 이 실습은 운영 중 동시
입력이 없는 전제입니다. 저장 버퍼와 인덱스 처리를 기다린 뒤 한 행이 남는지 확인합니다.
일반 수집 루프에서 행마다 두 flush를 호출하는 패턴은 사용하지 않습니다.
전체 속성과 제약은 현재 버전의
[CREATE TAG TABLE 구문](/dbms/reference/sql/syntax/ddl-syntax/)을 기준으로
확인하십시오. 이미 보관 정책으로 삭제된 데이터는 중복 판정 대상에 남아 있지 않습니다.
## 운영 점검 순서
1. 원시 데이터와 ROLLUP의 보관 기간을 각각 정합니다.
2. 늦게 도착하는 데이터의 최대 지연을 측정해 중복 검사 기간을 정합니다.
3. 삭제·정정 전에 대상 태그와 시간 범위를 `SELECT`로 확인합니다.
4. 대량 변경 후 ROLLUP과 대표 조회 결과를 검증합니다.
5. 입력량, 디스크 사용량과 Retention 실행 상태를 함께 모니터링합니다.
---
title: "5.8 제약, 오류, 문제 해결"
url: https://docs.machbase.com/kr/dbms/tag-table-usage/constraints-errors-troubleshooting/
language: kr
kind: page
---
# 5.8 제약, 오류, 문제 해결
이 페이지의 UPDATE 실습은 Standard Edition을 전제로 합니다. 먼저 다음 테이블을 만들고,
정상 SQL과 의도적으로 실패하는 SQL을 구분해 실행합니다. 실패 예제를 한꺼번에 정상
스크립트에 넣지 마십시오. 이름이 겹치는 기존 객체를 삭제하지 않습니다.
```sql
CREATE TAG TABLE ch5_error_time (
name VARCHAR(64) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE,
status INTEGER
) METADATA (location VARCHAR(64));
INSERT INTO ch5_error_time METADATA VALUES ('sensor-01', 'zone-1');
INSERT INTO ch5_error_time METADATA VALUES ('sensor-02', 'zone-2');
INSERT INTO ch5_error_time VALUES
('sensor-01', TO_DATE('2026-07-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 0);
INSERT INTO ch5_error_time VALUES
('sensor-02', TO_DATE('2026-07-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 0);
CREATE TAG TABLE ch5_error_distance (
name VARCHAR(64) PRIMARY KEY,
distance DOUBLE BASEDISTANCE,
value DOUBLE SUMMARIZED
);
```
## TAG data UPDATE WHERE 조건 오류
WHERE 절에 태그 선택 조건과 BASETIME 조건이 모두 있어야 합니다.
조건이 모호하거나 허용되지 않는 형태이면 UPDATE가 거부됩니다.
{{< callout type="warning" >}}
**필수 조건**
`WHERE name ...` 형태의 태그 선택 조건과 `time ...` 형태의 BASETIME 조건을 함께 지정합니다.
조건 없는 전체 UPDATE, 태그 조건만 있는 UPDATE, 시간 조건만 있는 UPDATE는 허용되지 않습니다.
{{< /callout >}}
### 증상
다음과 같은 UPDATE가 오류로 거부됩니다.
```sql
-- 시간 조건 없음
UPDATE ch5_error_time SET value = 99.9
WHERE name = 'sensor-01';
-- 태그 선택 조건 없음
UPDATE ch5_error_time SET value = 99.9
WHERE time >= TO_DATE('2026-07-01', 'YYYY-MM-DD');
-- OR 조건 사용
UPDATE ch5_error_time SET value = 99.9
WHERE name = 'sensor-01'
OR name = 'sensor-02';
```
### 원인
대상 태그와 시간 범위를 명확하게 제한할 수 없는 조건은 거부됩니다.
| 조건 형태 | 지원 여부 |
|-----------|:--------:|
| `name = 'sensor-01' AND time >= ...` | O |
| `name IN ('sensor-01', 'sensor-02') AND time BETWEEN ...` | O |
| `name LIKE 'sensor-%' AND time < ...` | O |
| `value > 10`만 사용 | X |
| `name = 'sensor-01'`만 사용 | X |
| `time >= ...`만 사용 | X |
| `OR`, 서브쿼리, 집계 조건 | X |
### 해결 방법
태그 선택 조건과 시간 조건을 함께 명시합니다.
```sql
UPDATE ch5_error_time
SET value = 99.9
WHERE name = 'sensor-01'
AND time = TO_DATE('2026-07-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS');
UPDATE ch5_error_time
SET status = 1
WHERE name IN ('sensor-01', 'sensor-02')
AND time >= TO_DATE('2026-07-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND time < TO_DATE('2026-07-02 00:00:00', 'YYYY-MM-DD HH24:MI:SS');
```
### NAME 또는 TIME bind가 `ERR-02190`으로 거부될 때
Machbase 8.7.0부터 Standard Edition에서는 다음과 같이 NAME과 BASETIME 조건 값에 bind
parameter를 사용할 수 있습니다.
```sql
UPDATE ch5_error_time
SET value = ?
WHERE name = ?
AND time = ?;
```
태그 선택 조건과 BASETIME 조건이 모두 있는데도 이 문장이
`ERR-02190: Invalid UPDATE/DELETE condition. Specify it as (primary key column) = (value)`로
거부되면 서버 버전을 확인합니다. 구버전 서버는 TAG data UPDATE의 NAME/TIME
bind를 지원하지 않습니다. 서버를 8.7.0 이상으로 업그레이드하고, named marker를 사용하면
해당 이름 기반 API를 지원하는 8.7.0 SDK를 함께 사용합니다.
`?` 예제는 SDK에서 준비·바인딩할 SQL이며 machsql에 값 없이 그대로 실행할 문장은 아닙니다.
같은 prepared statement를 재사용할 때는 SET, NAME, TIME 값을 모두 다시 바인딩합니다.
자세한 marker 규칙은
[TAG data UPDATE bind](/dbms/reference/sql/syntax/dml-syntax/tag-data-update-syntax/#tag-data-update-predicate-bind)를
참고하십시오.
대량 UPDATE 전에는 같은 WHERE 조건으로 `SELECT COUNT(*)`를 실행해 수정 대상 row 수를
확인합니다.
## TAG data UPDATE SET 대상 컬럼 오류
SET 절은 실제 데이터 컬럼만 대상으로 합니다. PRIMARY KEY, BASETIME,
메타데이터 컬럼을 SET 대상으로 지정하면 오류가 발생합니다.
{{< callout type="warning" >}}
**SET 대상**
`value`와 사용자 데이터 컬럼은 UPDATE할 수 있습니다. `name`, `time`, METADATA 블록의 컬럼은
TAG data UPDATE의 SET 대상이 아닙니다.
{{< /callout >}}
### 증상
```sql
-- 오류: PRIMARY KEY 컬럼(name) 업데이트 시도
UPDATE ch5_error_time
SET name = 'new-sensor'
WHERE name = 'old-sensor'
AND time >= TO_DATE('2026-07-01', 'YYYY-MM-DD');
-- 오류: BASETIME 컬럼(time) 업데이트 시도
UPDATE ch5_error_time
SET time = NOW
WHERE name = 'sensor-01'
AND time >= TO_DATE('2026-07-01', 'YYYY-MM-DD');
-- 오류: 메타데이터 컬럼을 data UPDATE에서 수정
UPDATE ch5_error_time
SET location = 'zone-2'
WHERE name = 'sensor-01'
AND time >= TO_DATE('2026-07-01', 'YYYY-MM-DD');
```
### 컬럼 유형별 UPDATE 가능 여부
| 컬럼 유형 | 설명 | data UPDATE |
|-----------|------|:-----------:|
| PRIMARY KEY (`name`) | TAG를 식별하는 고유 키 | X |
| BASETIME (`time`) | 시계열 데이터의 타임스탬프 | X |
| 데이터 컬럼 (`value`, 보조 컬럼) | 실제 row 값 | O |
| `SUMMARIZED` 데이터 컬럼 | 통계 대상 데이터 컬럼 | O |
| 메타데이터 컬럼 | METADATA 블록의 태그 속성 | X |
### 해결 방법
데이터 값은 일반 UPDATE로 수정합니다.
```sql
UPDATE ch5_error_time
SET value = 99.9,
status = 1
WHERE name = 'sensor-01'
AND time = TO_DATE('2026-07-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS');
```
메타데이터는 별도 구문을 사용합니다.
```sql
UPDATE ch5_error_time METADATA
SET location = 'zone-2'
WHERE name = 'sensor-01';
```
태그 이름이나 시간 축을 변경해야 하는 경우에는 새 `name`/`time` 값으로 데이터를 삽입한 뒤
운영 정책에 따라 기존 데이터를 삭제합니다.
## BASE DISTANCE TAG STAT 컬럼 오류
Machbase 8.7.0은 BASE DISTANCE TAG의 `V$
_STAT` 축 컬럼을 거리 이름과 숫자
타입으로 제공합니다. 업그레이드 전 SQL이 `MIN_TIME`, `MAX_TIME`, `RECENT_ROW_TIME`
등을 조회하면 컬럼을 찾을 수 없다는 오류가 발생할 수 있습니다.
### 증상
- 업그레이드 후 BASE DISTANCE 통계 뷰에서 기존 `*_TIME` 컬럼을 조회할 수 없습니다.
- 구버전 서버에서는 거리값이 `DATETIME`으로 해석되어 의미 없는 날짜처럼 보일 수 있습니다.
- Cluster Edition에서 Standard와 같은 ordinal result mapping을 사용하면 선두의
`HOSTNAME` 때문에 이후 컬럼이 어긋날 수 있습니다.
### 진단
대상 테이블의 축과 실제 통계 뷰 스키마를 함께 확인합니다.
```sql
DESC ch5_error_distance;
DESC V$CH5_ERROR_DISTANCE_STAT;
```
BASE DISTANCE 테이블이면 `MIN_DISTANCE`, `MAX_DISTANCE`, `MIN_VALUE_DISTANCE`,
`MAX_VALUE_DISTANCE`, `RECENT_ROW_DISTANCE`가 원본 거리축 타입으로 표시되어야 합니다.
Cluster Edition에서는 `HOSTNAME VARCHAR(64)`가 첫 컬럼에 추가됩니다.
### 해결 방법
1. SQL의 기존 `*_TIME` 이름을 대응하는 `*_DISTANCE` 이름으로 변경합니다.
2. SDK result mapping을 `DATETIME`이 아니라 원본 `DOUBLE`, `LONG`, `ULONG` 타입으로
변경합니다.
3. Cluster 결과는 컬럼 이름으로 읽거나 `HOSTNAME`을 포함한 ordinal을 다시 확인합니다.
4. BASE TIME TAG 쿼리는 기존 `*_TIME DATETIME` mapping을 유지합니다.
전체 변환표와 Cluster 집계 주의사항은
[TAG별 통계 뷰](../query-analysis/#tag-stat-axis-schema)를 참고하십시오.
## 제약 및 주의사항
### 기능 지원 범위
| 기능 | 상태 |
|------|------|
| 실제 시계열 데이터 UPDATE | Standard Edition에서 지원 (태그/BASETIME 조건 필요) |
| 메타데이터 UPDATE | 지원 (`UPDATE ... METADATA`) |
| DELETE | 지원 (`BEFORE`, 태그/축 조건 또는 전체 삭제) |
| 다중 PRIMARY KEY | 미지원 (단일 컬럼만) |
| BASETIME과 BASEDISTANCE 동시 사용 | 미지원 |
| TAG DATA 일반 컬럼 ALTER ADD/DROP | 미지원 |
| TAG METADATA 컬럼 ALTER ADD/DROP | 지원 (Standard Edition) |
### 태그 수 제한
- 단일 TAG 테이블에 생성 가능한 태그 수는 시스템 설정에 따라 제한됩니다.
- 태그 수가 늘면 태그 인덱스와 메타데이터의 메모리 사용량도 증가하므로 운영 규모의 데이터로
조회와 입력 성능을 측정합니다.
- 태그 이름이 레코드마다 고유한 값이 되도록 설계하면 안 됩니다 (안티패턴 — [센서별 테이블 생성](/dbms/data-modeling-table-design/table-types-patterns-type-anti/#per-sensor-create) 참고).
### 지연 도착 데이터
- BASETIME 컬럼에는 임의의 과거 시각을 삽입할 수 있습니다.
- 늦게 도착한 데이터가 많은 워크로드는 실제 입력률과 조회 성능을 별도로 측정합니다.
### Cluster Edition 지원
TAG 테이블은 Cluster Edition에서 지원됩니다.
단, TAG data UPDATE는 Standard Edition 전용이며 Cluster Edition에서는 지원되지 않습니다.
### 요약
```
TAG 테이블 = 센서 이름 (PK) + 시간/거리 축 + 계측값
- INSERT/APPEND: O
- UPDATE: 실제 DATA는 Standard Edition의 태그/BASETIME 조건, METADATA는 별도 SQL
- DELETE: O (BEFORE 또는 태그/축 조건)
- METADATA: O (별도 속성 저장, UPDATE 가능)
```
---
**다음 읽을 내용**
- [TRANSACTION 테이블 설계](/dbms/rdb-table-usage/)
## 실습 정리
정상 수정 결과는 SELECT로 확인하고 이번 실습 테이블만 제거합니다.
```sql
SELECT name, time, value, status FROM ch5_error_time ORDER BY name, time;
DROP TABLE ch5_error_time;
DROP TABLE ch5_error_distance;
```
---
title: "5.9 활용 패턴과 시나리오"
url: https://docs.machbase.com/kr/dbms/tag-table-usage/patterns-scenarios/
language: kr
kind: page
---
# 5.9 활용 패턴과 시나리오
각 예제는 별도 실습입니다. 테이블을 생성하기 전에 같은 이름의 객체가 없는지 확인합니다.
태그 이름, 관측 한 건의 의미, 값의 단위와 결측 정책을 먼저 정하고 DDL을 적용합니다.
## 활용 사례
### IoT 센서 데이터
공장, 빌딩, 인프라에 설치된 다양한 센서 데이터를 단일 TAG 테이블에서 관리합니다.
```sql
CREATE TAG TABLE factory_sensor (
name VARCHAR(128) PRIMARY KEY,
time DATETIME BASETIME,
temperature DOUBLE,
vibration DOUBLE,
current DOUBLE
);
-- One row groups measurements from the same equipment observation.
INSERT INTO factory_sensor VALUES (
'F01/LINE-A/MOTOR-01',
NOW,
75.3, 0.15, 2.4
);
```
이 모델은 한 장비의 같은 시각 관측을 여러 컬럼에 저장합니다. 항목별 태그
`.../TEMP`, `.../VIBRATION`을 사용하려면 `name, time, value` 형태의 단일 값 모델을
검토합니다. 항목별 측정 시각이 다르면 NULL과 품질 상태로 그 차이를 표현하십시오.
### 에너지 모니터링
전력, 가스, 수도 계량기 데이터를 시간별로 수집합니다.
```sql
CREATE TAG TABLE energy_meter (
meter_id VARCHAR(64) PRIMARY KEY,
time DATETIME BASETIME,
kwh DOUBLE,
voltage DOUBLE,
current DOUBLE
);
```
`kwh`가 누적 계량값이면 AVG(kwh)는 지시값의 평균이지 구간 사용량이 아닙니다.
소비량은 구간 경계값의 차이에 초기화·교체·누락 처리 규칙을 적용해 계산합니다.
전력 kW와 전력량 kWh를 구분하고 메타데이터나 수집 계약에 단위를 명시합니다.
### 차량·이동체 추적
GPS 좌표와 속도를 시계열로 기록합니다.
```sql
CREATE TAG TABLE vehicle_track (
vehicle_id VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
lat DOUBLE,
lon DOUBLE,
speed DOUBLE,
heading DOUBLE
);
```
좌표계, 위도·경도와 속도의 단위를 수집 계약에 명시합니다. 위치 결측을 숫자 0으로
대체하면 실제 좌표와 구분할 수 없습니다. 이 테이블을 만들었다고 공간 인덱스나 경로 매칭이
자동 제공되는 것은 아니며, 차량·시간 구간으로 먼저 조회하고 필요한 분석을 수행합니다.
### 부적합한 경우
- 태그 이름이 매 레코드마다 달라지는 경우 (태그 수 폭발)
- 태그/시간 범위 없이 전체 데이터를 자주 UPDATE해야 하는 경우
- 단순 이벤트 로그 (LOG 테이블 권장)
## 결과 확인과 정리
IoT 예제는 다음 조회에서 세 측정값이 같은 한 행으로 나오는지 확인합니다.
```sql
SELECT name, time, temperature, vibration, current FROM factory_sensor;
DROP TABLE factory_sensor;
DROP TABLE energy_meter;
DROP TABLE vehicle_track;
```
정리는 이 페이지에서 실제로 생성한 테이블에만 적용합니다.
---
title: "5.10 TAG 메타데이터"
url: https://docs.machbase.com/kr/dbms/tag-table-usage/tag-metadata/
language: kr
kind: page
---
# 5.10 TAG 메타데이터
## Tag 메타데이터
### 개요
METADATA는 태그마다 한 행인 현재 속성입니다. 일반 TAG 조회에서는 같은 속성이 각
DATA 행에 함께 나타납니다. 현재 속성을 바꾸면 과거 DATA를 조회한 결과에도 새 속성이
보일 수 있으므로 발생 당시 속성이 필요하면 DATA나 별도 속성 이력에 보존합니다.
아래는 기본 메타데이터, JSON 메타데이터와 전체 예제를 서로 다른 테이블로 분리합니다.
태그의 정적 속성을 저장하는 영역으로 사용하며 센서 위치, 장비 상태, 설치 정보, 외부 식별자, JSON 문서형 속성을 메타데이터에 둘 수 있습니다.
메타데이터 전용 SQL로 다음 작업을 처리할 수 있습니다. 아래 예제의 `TAG`는 테이블 이름이며,
사용할 때 실제 TAG 테이블 이름으로 바꿉니다.
- 메타데이터 전용 조회
- 메타데이터 조건 기반 `UPDATE` / `DELETE`
- metadata row 마지막 변경 시각 조회
- ARRAY metadata 컬럼 ADD/DROP과 기존 row DEFAULT
- `JSON` 타입 메타데이터 컬럼 선언
- JSON path 조회와 JSON path 인덱스
- JSON 문서 일부만 수정하는 부분 갱신
사용자는 내부 저장 테이블을 직접 다룰 필요 없이 `TAG METADATA` 문법만 사용하면 됩니다.
### 메타데이터 컬럼 정의
메타데이터 컬럼은 `CREATE TAG TABLE` 의 `METADATA (...)` 절에서 정의합니다.
```sql
CREATE TAG TABLE ch5_meta (
name VARCHAR(20) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE
)
METADATA (
location VARCHAR(100),
status VARCHAR(20),
srcip IPV4
);
```
메타데이터 컬럼은 태그 이름별로 1행만 저장됩니다.
### ARRAY 메타데이터 컬럼 추가와 삭제
Standard Edition에서는 기존 TAG 테이블의 METADATA 영역에 고정 길이 숫자 ARRAY 컬럼을
추가하고 삭제할 수 있습니다.
```sql
INSERT INTO ch5_meta (name, time, value)
VALUES ('TEMP_OLD', TO_DATE('2026-09-05 00:00:00'), 10.0);
ALTER TABLE ch5_meta METADATA
ADD COLUMN (limits DECIMAL(12,4)[2] DEFAULT [0.0000, NULL]);
INSERT INTO ch5_meta (name, time, value)
VALUES ('TEMP_NEW', TO_DATE('2026-09-05 00:00:01'), 20.0);
SELECT name, limits
FROM ch5_meta METADATA
ORDER BY name;
```
ALTER 전에 존재한 `TEMP_OLD` metadata row에는 `[0.0000, NULL]`을 backfill합니다. ALTER
뒤 TAG DATA 입력으로 자동 등록된 `TEMP_NEW` metadata row에는 ADD COLUMN DEFAULT를 다시
적용하지 않으며 `limits`는 whole NULL입니다. DEFAULT가 없는 경우에는 ALTER 전 row도
whole NULL입니다.
추가한 ARRAY metadata 컬럼은 일반 TAG 조회의 명시 projection과 `SELECT *`에도
포함됩니다. ARRAY metadata 컬럼에는 자동 index를 만들지 않으며 다음과 같은 명시적
index도 지원하지 않습니다.
```sql
-- 지원하지 않으며 오류를 반환합니다.
CREATE INDEX idx_sensor_limits ON ch5_meta METADATA(limits);
```
컬럼을 제거할 때도 `METADATA`를 지정합니다.
```sql
ALTER TABLE ch5_meta METADATA DROP COLUMN (limits);
```
TAG DATA의 일반 ARRAY 컬럼은 `CREATE TAG TABLE`에서 선언할 수 있지만 ALTER로 추가할 수
없습니다. 지원 요소 타입, cardinality와 DEFAULT 규칙은
[숫자 ARRAY 타입](/dbms/reference/sql/types/array/)을 참고하십시오.
### 메타데이터 입력
메타데이터는 `INSERT INTO ... METADATA` 로 입력합니다.
```sql
INSERT INTO ch5_meta METADATA VALUES (
'TEMP_001',
'Building-A/F1',
'READY',
'192.168.0.11'
);
```
컬럼 목록을 지정할 수도 있습니다.
```sql
INSERT INTO ch5_meta METADATA (name, status, srcip, location)
VALUES ('TEMP_002', 'STOP', '192.168.0.12', 'Building-A/F2');
```
주의사항:
- 컬럼 목록을 생략한 VALUES는 태그명 다음에 메타데이터 선언 순서를 따릅니다.
- 컬럼 목록을 지정했다면 그 목록의 순서대로 값을 전달합니다.
- 생략한 입력의 NULL·DEFAULT 처리는 해당 DDL과 입력 경로의 규칙을 따릅니다.
- 식별자는 TAG의 태그명 컬럼입니다. 이 예제에서는 그 이름을 `name`으로 선언했습니다.
- 메타데이터 행이 생성되면 `_LAST_UPDATE_TIME` 이 서버 시각으로 자동 기록됩니다.
### 메타데이터 조회
#### 메타데이터만 조회
메타데이터 전용 조회는 `FROM TAG METADATA` 를 사용합니다.
```sql
SELECT name, location, status, srcip
FROM ch5_meta METADATA
ORDER BY name;
```
이 조회는 태그 이름당 1행을 반환합니다.
```sql
SELECT *
FROM ch5_meta METADATA
ORDER BY name;
```
`SELECT *` 와 `table_alias.*` 는 `NAME` 과 메타데이터 컬럼만 반환합니다.
`_LAST_UPDATE_TIME` 같은 시스템 관리 컬럼은 `SELECT *` 결과에 표시되지 않습니다. 필요한 경우 컬럼명을 명시합니다.
#### 마지막 변경 시각 조회
TAG metadata에는 각 metadata row의 마지막 변경 시각을 나타내는 시스템 관리 컬럼 `_LAST_UPDATE_TIME` 이 있습니다.
`_LAST_UPDATE_TIME` 은 tag data row의 마지막 입력 시각이 아니라, tag metadata row가 생성되거나 실제 metadata 값이 변경된 시각입니다.
##### 조회 방법
`_LAST_UPDATE_TIME` 은 명시적으로 컬럼명을 지정해서 조회합니다.
```sql
SELECT name, _last_update_time
FROM ch5_meta METADATA;
```
다른 metadata 컬럼과 함께 조회하거나 조건에 사용할 수 있습니다.
```sql
SELECT name, location, status, _last_update_time
FROM ch5_meta METADATA
WHERE name = 'TEMP_001';
```
`SELECT *` 와 `table_alias.*` 결과에는 `_LAST_UPDATE_TIME` 이 표시되지 않습니다.
##### 자동 기록 및 갱신 규칙
metadata row가 새로 생성되면 `_LAST_UPDATE_TIME` 이 자동으로 기록됩니다.
```sql
INSERT INTO ch5_meta METADATA(name, location, status)
VALUES('TEMP_003', 'Building-A/F3', 'READY');
```
사용자 metadata 값이 실제로 변경되면 `_LAST_UPDATE_TIME` 이 갱신됩니다.
```sql
UPDATE ch5_meta METADATA
SET status = 'DONE'
WHERE name = 'TEMP_003';
```
같은 값으로 update하거나 JSON missing path 제거처럼 저장 결과가 바뀌지 않는 update는 실제 변경으로 보지 않습니다. 이 경우 `_LAST_UPDATE_TIME` 은 유지됩니다.
```sql
UPDATE ch5_meta METADATA
SET status = 'DONE'
WHERE name = 'TEMP_003';
```
JSON의 존재하지 않는 경로 제거도 저장값을 바꾸지 않으면 no-op입니다. 실행 예제는
아래 JSON 테이블을 생성한 뒤 확인합니다.
##### 직접 입력 및 수정 제한
`_LAST_UPDATE_TIME` 은 시스템 관리 컬럼이므로 사용자가 직접 값을 넣거나 수정할 수 없습니다.
다음 문장은 허용되지 않습니다.
```sql
INSERT INTO ch5_meta METADATA(name, location, status, _last_update_time)
VALUES('TEMP_004', 'Building-A/F4', 'READY', now);
```
```sql
UPDATE ch5_meta METADATA
SET _last_update_time = now
WHERE name = 'TEMP_003';
```
또한 TAG name 컬럼명, TAG metadata 컬럼명, `ALTER TABLE ... METADATA ADD COLUMN` 대상 컬럼명으로 `_LAST_UPDATE_TIME` 을 사용할 수 없습니다. `ALTER TABLE ... METADATA DROP COLUMN` 으로 `_LAST_UPDATE_TIME` 을 삭제할 수도 없습니다.
```sql
CREATE TAG TABLE invalid_sensor (
_last_update_time VARCHAR(128) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE
);
```
```sql
CREATE TAG TABLE invalid_sensor_meta (
name VARCHAR(128) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE
)
METADATA (
_last_update_time DATETIME
);
```
`_LAST_UPDATE_TIME2` 처럼 prefix만 같은 이름은 별도 사용자 컬럼으로 사용할 수 있습니다.
##### 시간 조건 조회 및 자동 인덱스
`_LAST_UPDATE_TIME` 에는 시간 조건 조회를 위한 인덱스가 자동으로 제공됩니다.
```sql
SELECT name, location, _last_update_time
FROM ch5_meta METADATA
WHERE _last_update_time >= TO_DATE('2026-06-08 00:00:00')
ORDER BY _last_update_time;
```
따라서 같은 컬럼에 사용자가 별도 인덱스를 중복 생성할 필요가 없습니다.
##### machloader / tagmetaimport 사용 시 주의사항
TAG metadata를 import할 때 입력 파일이나 form 파일에는 `NAME` 과 사용자 metadata 컬럼만 포함합니다. 내부 컬럼인 `_ID` 와 시스템 관리 컬럼 `_LAST_UPDATE_TIME` 은 입력 대상이 아닙니다.
metadata가 `location`, `status` 인 경우 입력 데이터는 다음 형태를 사용합니다.
```text
TEMP_001,Building-A/F1,READY
TEMP_002,Building-A/F2,STOP
```
`_LAST_UPDATE_TIME` 은 import 시 서버가 자동으로 채웁니다.
일반 LOG, LOOKUP, VOLATILE 테이블에서 사용자가 `_LAST_UPDATE_TIME` 이라는 이름의 컬럼을 정의한 경우에는 일반 사용자 컬럼으로 동작합니다. 예약 동작은 TAG metadata 시스템 컬럼에만 적용됩니다.
#### 데이터와 함께 조회
메타데이터 조건으로 시계열 데이터를 조회할 때는 일반 `FROM TAG` 를 사용합니다.
```sql
SELECT name, status, time, value
FROM ch5_meta
WHERE status = 'READY'
ORDER BY name, time;
```
이 조회는 데이터 row 기준으로 반환되므로, 같은 tag의 메타데이터 값은 각 데이터 row마다 반복됩니다.
주의사항:
- `FROM TAG METADATA` 에서는 `TIME`, `VALUE` 같은 데이터 컬럼을 조회할 수 없습니다.
- `FROM TAG` 는 데이터 조회 모드이고, `FROM TAG METADATA` 는 메타데이터 조회 모드입니다.
- `TAG METADATA` 에서는 내부 컬럼 `_ID`, `_RID` 를 사용할 수 없습니다.
### 메타데이터 수정
메타데이터 수정은 `UPDATE TAG METADATA` 를 사용합니다.
```sql
UPDATE ch5_meta METADATA
SET status = 'DONE',
srcip = '10.0.0.20'
WHERE name = 'TEMP_001';
```
메타데이터 조건으로 여러 태그를 한 번에 수정할 수도 있습니다.
```sql
UPDATE ch5_meta METADATA
SET status = 'DONE'
WHERE status = 'READY';
```
주의사항:
- 수정 대상은 `NAME` 과 메타데이터 컬럼입니다.
- `TIME`, `VALUE` 같은 데이터 컬럼은 `UPDATE ... METADATA` 에서 수정할 수 없습니다.
- 내부 컬럼은 수정할 수 없습니다.
- 실제 metadata 값이 바뀐 경우에만 `_LAST_UPDATE_TIME` 이 갱신됩니다.
### 메타데이터 삭제
메타데이터 삭제는 `DELETE FROM TAG METADATA` 를 사용합니다.
특정 태그의 메타데이터를 삭제하려면 `WHERE` 절에서 tag name 조건을 지정합니다.
```sql
DELETE FROM ch5_meta METADATA
WHERE name = 'TEMP_002';
```
메타데이터 조건으로 여러 태그를 한 번에 삭제할 수도 있습니다.
```sql
DELETE FROM ch5_meta METADATA
WHERE status = 'STOP';
```
`WHERE` 절을 생략하면 전체 메타데이터가 대상입니다. 이 실습에는 앞서 입력한
TEMP_OLD·TEMP_NEW의 DATA가 남아 있으므로 아래 전체 삭제는 의도적으로 실패합니다.
```sql
DELETE FROM ch5_meta METADATA;
```
주의사항:
- 삭제 대상 중 하나라도 실제 데이터 row를 가지고 있으면 문장 전체가 실패합니다.
- 즉, 사용 중인 tag의 메타데이터는 삭제할 수 없습니다.
- 전체 삭제 시에도 사용 중인 tag가 하나라도 있으면 일부만 삭제하지 않고 문장 전체가 실패합니다.
사용 중인 tag의 메타데이터를 삭제하려면 먼저 해당 tag의 데이터 row를 삭제한 뒤
메타데이터 삭제를 다시 수행합니다.
```sql
DELETE FROM ch5_meta
WHERE name = 'TEMP_001';
DELETE FROM ch5_meta METADATA
WHERE name = 'TEMP_001';
```
### JSON 메타데이터 컬럼
메타데이터에 `JSON` 컬럼을 선언할 수 있습니다.
```sql
CREATE TAG TABLE ch5_meta_json (
name VARCHAR(20) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE
)
METADATA (
status VARCHAR(20),
info JSON
);
```
JSON 메타데이터 입력 예:
```sql
INSERT INTO ch5_meta_json METADATA VALUES (
'SHIP_001',
'READY',
'{"name":"alpha","ship":{"status":"READY"}}'
);
```
주의사항:
- `JSON` 메타데이터 컬럼은 길이를 지정하지 않습니다.
- 유효하지 않은 JSON 문자열은 오류가 발생합니다.
- raw JSON 컬럼 자체에는 자동 인덱스가 생성되지 않습니다.
### JSON 값이 바뀌지 않는 경우
```sql
SELECT name, _last_update_time FROM ch5_meta_json METADATA;
UPDATE ch5_meta_json METADATA
SET info = JSON_REMOVE(info, '$.missing')
WHERE name = 'SHIP_001';
SELECT name, _last_update_time FROM ch5_meta_json METADATA;
```
경로가 없어 저장값이 그대로이면 변경 시각도 유지됩니다.
### JSON path 조회
JSON 메타데이터는 `->` 연산자로 조회할 수 있습니다.
```sql
SELECT name,
info->'$.name',
info->'$.ship.status'
FROM ch5_meta_json METADATA
WHERE info->'$.ship.status' = 'READY'
ORDER BY name;
```
데이터 조회에서도 같은 방식으로 사용할 수 있습니다.
```sql
SELECT name, time, value
FROM ch5_meta_json
WHERE info->'$.ship.status' = 'READY'
ORDER BY name, time;
```
#### 경로 표기 규칙
조회와 부분 갱신에서 사용하는 path는 full JSONPath를 사용합니다.
- 일반 key: `$.name`
- 중첩 key: `$.ship.status`
- key 이름에 `.` 또는 `-` 가 포함되면 bracket 표기 사용
```sql
SELECT info->'$[''ship.owner'']'
FROM ch5_meta_json METADATA;
SELECT info->'$[''ship-owner'']'
FROM ch5_meta_json METADATA;
```
### JSON path 인덱스
#### 테이블 생성 시 함께 선언
자주 조회하는 JSON path는 메타데이터 정의 시 함께 인덱싱할 수 있습니다.
```sql
CREATE TAG TABLE ch5_meta_json_indexed (
name VARCHAR(20) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE
)
METADATA (
status VARCHAR(20),
info JSON INDEX('name', 'ship.status')
);
```
`INDEX(...)` 안의 문자열은 다음 규칙으로 해석합니다.
- `'name'` 은 `$.name`
- `'ship.status'` 는 `$.ship.status`
- 특수 문자가 있는 key나 복잡한 경로는 full JSONPath를 직접 사용
```sql
INFO JSON INDEX('$[''ship.owner'']')
```
#### 생성 후 인덱스 추가
테이블 생성 후에도 JSON path 인덱스를 추가할 수 있습니다.
```sql
CREATE INDEX idx_ship_owner
ON ch5_meta_json METADATA (info->'$.owner');
```
#### 인덱스 삭제
인덱스 이름만으로 삭제합니다.
```sql
SHOW INDEX idx_ship_owner;
DROP INDEX idx_ship_owner;
```
명시적으로 만든 인덱스는 생성 시 정한 이름으로 관리합니다. `SHOW INDEX idx_ship_owner;`는
DROP 전에 실행합니다. 삭제 후 같은 이름을 조회하면 존재하지 않는 객체입니다.
#### 인덱스 사용 시 주의사항
현재 JSON path 인덱스는 문자열 비교 중심으로 동작합니다.
```sql
SELECT name
FROM ch5_meta_json METADATA
WHERE info->'$.status' = 'READY';
```
문자열 literal 비교는 인덱스를 사용할 수 있습니다. 반면 숫자 literal 비교는 full scan으로 처리될 수 있습니다.
예:
- `info->'$.num' = '10'` : 인덱스 사용 가능
- `info->'$.num' = 10` : full scan 가능
### JSON 부분 갱신
JSON 함수는 지정 경로를 바꾼 새 문서 값을 반환합니다. UPDATE는 그 결과를 컬럼에 저장합니다.
이는 경로 단위의 논리적 갱신이며 저장 파일 일부만 제자리에서 수정한다는 성능 보장은 아닙니다.
#### JSON_SET
SQL scalar 값을 JSON scalar로 저장합니다.
```sql
UPDATE ch5_meta_json METADATA
SET info = JSON_SET(info, '$.ship.status', 'DONE')
WHERE name = 'SHIP_001';
```
#### JSON_SET_JSON
입력 문자열을 JSON으로 해석해 object 또는 array를 저장합니다.
```sql
UPDATE ch5_meta_json METADATA
SET info = JSON_SET_JSON(info, '$.owner', '{"name":"machbase","team":"db"}')
WHERE name = 'SHIP_001';
```
#### JSON_REMOVE
특정 멤버 또는 하위 경로를 삭제합니다.
```sql
UPDATE ch5_meta_json METADATA
SET info = JSON_REMOVE(info, '$.owner.team')
WHERE name = 'SHIP_001';
```
#### 부분 갱신 규칙
- `JSON_SET(..., path, NULL)` 은 JSON `null` 을 저장합니다.
- `JSON_SET_JSON(..., path, NULL)` 의 결과는 SQL `NULL` 입니다.
- JSON 문서 인자가 `NULL` 이면 함수 결과는 SQL `NULL` 입니다.
- path 가 `NULL` 이거나 빈 문자열이면 오류가 발생합니다.
- 존재하지 않는 경로에 대한 `JSON_REMOVE` 는 오류가 아니라 no-op 입니다.
- `JSON_REMOVE(..., '$')` 는 허용되지 않습니다.
- 부분 갱신은 object 경로 중심으로 지원합니다.
- array element 경로 갱신 예: `$.items[0]` 는 지원하지 않습니다.
### 전체 예제
```sql
CREATE TAG TABLE ch5_meta_complete (
name VARCHAR(20) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE
)
METADATA (
status VARCHAR(20),
srcip IPV4,
info JSON INDEX('name', 'ship.status')
);
INSERT INTO ch5_meta_complete METADATA VALUES (
'SHIP_001',
'READY',
'192.168.0.11',
'{"name":"alpha","ship":{"status":"READY"}}'
);
INSERT INTO ch5_meta_complete VALUES ('SHIP_001', '2026-04-01 00:00:00', 10.5);
SELECT name, status, info
FROM ch5_meta_complete METADATA;
SELECT name, time, value
FROM ch5_meta_complete
WHERE info->'$.ship.status' = 'READY';
CREATE INDEX idx_ship_owner
ON ch5_meta_complete METADATA (info->'$.owner');
UPDATE ch5_meta_complete METADATA
SET info = JSON_SET(info, '$.ship.status', 'DONE')
WHERE name = 'SHIP_001';
DROP INDEX idx_ship_owner;
```
### 요약
- 메타데이터 전용 조회는 `FROM TAG METADATA`
- 데이터 조회는 `FROM TAG`
- 메타데이터 수정/삭제는 `UPDATE/DELETE ... METADATA`
- ARRAY metadata 컬럼 변경은 `ALTER TABLE ... METADATA ADD/DROP COLUMN`
- JSON 메타데이터는 `INFO JSON`
- `_LAST_UPDATE_TIME` 은 metadata row의 마지막 변경 시각이며 명시적으로 조회할 수 있습니다
- JSON path 인덱스는 `INFO JSON INDEX(...)` 또는 `CREATE INDEX ... ON TAG METADATA (...)`
- JSON 부분 갱신은 `JSON_SET`, `JSON_SET_JSON`, `JSON_REMOVE`
- `_LAST_UPDATE_TIME` 은 서버가 자동 관리하며 standard와 cluster 환경에서 동일하게 동작합니다
## 실습 정리
전체 삭제 실패 예제와 달리 DROP은 테이블과 DATA·METADATA를 모두 제거합니다.
아래 이름이 이번 실습에서 만든 객체인지 확인한 뒤 실행합니다.
```sql
DROP TABLE ch5_meta;
DROP TABLE ch5_meta_json;
DROP TABLE ch5_meta_json_indexed;
DROP TABLE ch5_meta_complete;
```
---
title: "5.11 TAG data UPDATE와 데이터 보정"
url: https://docs.machbase.com/kr/dbms/tag-table-usage/tag-data-update-correction/
language: kr
kind: page
---
# 5.11 TAG data UPDATE와 데이터 보정
TAG DATA 보정은 원본 값을 직접 바꾸는 방식과 원본을 보존하고 조회 시 보정값을 적용하는
방식으로 나눕니다. 두 방식은 ROLLUP에 미치는 영향도 다릅니다. 이 페이지의 UPDATE 실습은
Machbase DBMS 8.7.0 Standard Edition을 전제로 합니다.
## 직접 값 정정
태그명과 BASETIME 조건을 함께 지정합니다. SET 우변은 기존 행 컬럼을 참조할 수 없으므로
`SET value = value + 1` 같은 식 대신 계산한 값이나 바인딩 매개변수를 전달합니다.
이름이 겹치지 않는 다음 테이블을 준비합니다. ROLLUP 정정까지 확인하도록 기본 ROLLUP도
생성합니다.
```sql
CREATE TAG TABLE ch5_correction (
name VARCHAR(64) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE SUMMARIZED,
status INTEGER
) WITH ROLLUP;
INSERT INTO ch5_correction VALUES
('TEMP-01', TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 0);
INSERT INTO ch5_correction VALUES
('TEMP-01', TO_DATE('2026-01-01 12:30:00', 'YYYY-MM-DD HH24:MI:SS'), 99.0, 0);
INSERT INTO ch5_correction VALUES
('TEMP-02', TO_DATE('2026-01-01 12:30:00', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 0);
```
### 범위 확인, 수정, 재조회
```sql
SELECT COUNT(*), MIN(value), MAX(value)
FROM ch5_correction
WHERE name = 'TEMP-01'
AND time >= TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND time < TO_DATE('2026-01-01 13:00:00', 'YYYY-MM-DD HH24:MI:SS');
UPDATE ch5_correction SET value = 25.0, status = 1
WHERE name = 'TEMP-01'
AND time >= TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND time < TO_DATE('2026-01-01 13:00:00', 'YYYY-MM-DD HH24:MI:SS');
SELECT name, time, value, status
FROM ch5_correction ORDER BY name, time;
```
첫 조회는 2건, 최솟값 10.0, 최댓값 99.0입니다. UPDATE 후 TEMP-01의 두 행은
value=25.0, status=1이고 TEMP-02의 20.0은 유지됩니다. 사전 COUNT는 대상을 잠그지
않으므로 동시 입력이 있는 작업은 기준 시각과 범위를 별도로 통제합니다.
### 여러 태그와 오류 처리
태그 선택에는 `=`, `IN`, `LIKE`를 사용할 수 있습니다. 넓은 패턴이나 긴 시간 범위를
수정하기 전에 대상 이름과 건수를 확인하고 작은 범위로 나눕니다. 여러 태그는 순차
처리될 수 있으므로 실패했을 때 문장 전체가 원자적으로 취소됐다고 가정하지 않습니다.
수정된 값과 남은 대상을 다시 조회한 뒤 재시도합니다.
연속된 시간 구간은 `>= 시작 AND < 끝`으로 정의하면 경계 행을 중복 처리하지 않습니다.
하루 전체를 `23:59:59`까지로 지정하면 그 뒤의 소수 초 데이터를 놓칠 수 있습니다.
정확한 허용 조건과 바인딩은 [TAG UPDATE](../../reference/sql/syntax/dml-syntax/tag-data-update-syntax/)를
참고합니다.
### ROLLUP 재구성
원본 수정은 이미 계산된 ROLLUP을 자동으로 바꾸지 않습니다. 위 실습의 수정 구간을
다음처럼 재구성하고 [ROLLUP 재구성](../../tag-rollup-usage/rollup-rebuild/)의 진행 상태와
조회 검증 절차를 따릅니다.
```sql
EXEC ROLLUP_REBUILD(ch5_correction, 'TEMP-01',
TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS'),
TO_DATE('2026-01-01 13:00:00', 'YYYY-MM-DD HH24:MI:SS'));
```
## 원본 보존과 NULL 보정
원본과 보정값을 별도 컬럼에 저장하면 최초 값을 유지할 수 있습니다. 보정값이 NULL일 수도
있으므로 `corrected_value IS NOT NULL`만으로 보정 여부를 판정하지 않고 플래그를 사용합니다.
```sql
CREATE TAG TABLE ch5_correction_overlay (
name VARCHAR(64) PRIMARY KEY,
time DATETIME BASETIME,
raw_value DOUBLE,
corrected_value DOUBLE,
is_corrected SHORT
);
INSERT INTO ch5_correction_overlay VALUES
('TEMP-01', TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS'), 99.0, NULL, 0);
UPDATE ch5_correction_overlay
SET corrected_value = NULL, is_corrected = 1
WHERE name = 'TEMP-01'
AND time = TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS');
SELECT name, raw_value, is_corrected,
CASE WHEN is_corrected = 1 THEN corrected_value ELSE raw_value END AS effective_value
FROM ch5_correction_overlay;
```
raw_value는 99.0, is_corrected는 1, effective_value는 NULL입니다. 플래그가 0이면
원본을 사용합니다. 이 방식은 조회식으로 값을 선택하며 raw_value는 바꾸지 않습니다.
기본 ROLLUP이 이 CASE 식을 자동 집계하거나 raw_value의 재구성만으로 보정을 반영한다고
가정하지 말고, 유효값을 집계하는 별도 조회·집계 모델을 정합니다.
## 보정 이력과 검증
여러 번의 변경 사유와 담당자를 남기려면 별도 이력을 기록합니다.
```sql
CREATE LOG TABLE ch5_correction_log (
sensor_name VARCHAR(64),
target_time DATETIME,
old_value DOUBLE,
new_value DOUBLE,
reason VARCHAR(256),
corrected_by VARCHAR(64)
);
```
TAG 수정과 LOG 이력 기록은 하나의 TRANSACTION 트랜잭션으로 묶이지 않습니다.
순서·부분 실패·재시도 시 같은 작업을 식별할 번호를 애플리케이션에서 설계합니다.
테이블 생성만으로 감사 이력이 자동 기록되지는 않습니다.
정정 뒤 원본/유효값, 영향 행 수, 구간 통계와 보고서를 확인합니다. 완료 후 이번 실습에서
만든 객체만 정리합니다. 아래 CASCADE는 ch5_correction의 ROLLUP도 함께 삭제합니다.
```sql
DROP TABLE ch5_correction CASCADE;
DROP TABLE ch5_correction_overlay;
DROP TABLE ch5_correction_log;
```
---
title: "5.12 tagmetaimport와 메타데이터 일괄 등록"
url: https://docs.machbase.com/kr/dbms/tag-table-usage/tagmetaimport/
language: kr
kind: page
---
# 5.12 tagmetaimport와 메타데이터 일괄 등록
## tagmetaimport로 메타데이터 등록
`tagmetaimport`는 CSV의 태그명과 사용자 메타데이터를 가져오는 도구입니다.
일반 SQL의 논리 TAG 이름과 `-t` 입력 대상을 구분해야 합니다. 현재 래퍼는 `-t`를
machloader에 전달합니다. 아래 논리 테이블 `ch5_meta_import`의 메타데이터 입력 대상은
`_CH5_META_IMPORT_META`입니다. `-t ch5_meta_import`가 자동으로 METADATA를
선택한다고 가정하지 마십시오.
이 이름은 도구의 대상 지정에 사용합니다. SQL 조회·변경은 `ch5_meta_import METADATA`를
사용하며 저장 객체를 직접 수정하는 절차로 확장하지 않습니다. 기본 대상에 의존하지 말고
`-t`를 명시합니다.
## 1. 테이블 준비
기존 객체가 없는 실습 데이터베이스에서 다음 SQL을 실행합니다.
```sql
CREATE TAG TABLE ch5_meta_import (
name VARCHAR(40) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE
) METADATA (
location VARCHAR(40),
status VARCHAR(20)
);
```
## 2. CSV 준비
다음 내용을 클라이언트의 `ch5_metadata.csv`로 저장합니다.
```csv
name,location,status
TEMP_001,Building-A/F1,READY
TEMP_002,Building-A/F2,STOP
TEMP_003,Building-B/F3,READY
```
파일에는 태그명 다음에 METADATA 선언 순서대로 값을 넣습니다. DATA의 time·value와
시스템 컬럼 `_ID`·`_LAST_UPDATE_TIME`은 넣지 않습니다. 헤더가 있으면 `-H`를 지정하며,
헤더가 임의의 컬럼 순서를 자동으로 맞춰 준다고 가정하지 않습니다.
## 3. 입력과 결과 확인
주소·계정은 실제 실습 서버에 맞추고, 사용하는 8.7.0 패키지의 `MACHBASE_HOME`과
라이브러리 환경에서 실행합니다.
```bash
tagmetaimport -s 127.0.0.1 -P 5656 -u SYS -p MANAGER \
-t _CH5_META_IMPORT_META -d ch5_metadata.csv -H \
-l ch5_import.log -b ch5_import.bad
```
첫 실행은 성공 3건·실패 0건을 기대합니다. 아래 METADATA 조회는 세 행을 반환하고
DATA COUNT는 0입니다. 메타데이터 등록은 측정값 입력과 다릅니다.
```sql
SELECT name, location, status, _last_update_time
FROM ch5_meta_import METADATA ORDER BY name;
SELECT COUNT(*) FROM ch5_meta_import;
```
## 4. 기존 태그와 재실행
같은 파일을 다시 입력해도 기존 태그가 자동 갱신되지 않습니다. 현재 경로는 일반
METADATA INSERT이므로 중복 태그는 오류 행으로 집계됩니다. 두 번째 실행은 성공
0건·실패 3건을 기대하며 기존 속성은 유지됩니다. 종료 상태만 보지 말고 성공·실패 건수와
bad/log 파일을 함께 확인합니다.
새 행과 잘못된 행이 섞인 파일도 전체가 하나의 트랜잭션이라고 가정하지 않습니다.
이미 반영된 태그를 확인하고 실패 행만 고쳐 재처리합니다. 기존 속성은 명시적인 UPDATE
또는 지원 UPSERT로 바꿉니다.
```sql
UPDATE ch5_meta_import METADATA SET status = 'DONE' WHERE name = 'TEMP_001';
INSERT INTO ch5_meta_import METADATA VALUES ('TEMP_002', 'Building-C/F2', 'READY')
ON DUPLICATE KEY UPDATE;
SELECT name, location, status FROM ch5_meta_import METADATA ORDER BY name;
```
TEMP_001은 DONE으로, TEMP_002는 Building-C/F2·READY로 바뀝니다.
실제 값이 바뀌면 변경 시각이 갱신되고 같은 값의 no-op은 유지됩니다.
`tagmetaimport`에 자동 UPSERT 옵션이 있다고 해석하지 마십시오.
## 정리와 관련 문서
결과 확인 후 `DROP TABLE ch5_meta_import;`로 이번 실습 테이블만 정리합니다.
CSV·로그·bad 파일은 재처리에 필요하지 않은지 확인한 뒤 정리합니다.
상세 옵션은 [tagmetaimport 명령 사전](../../reference/command-line-tools/tagmetaimport/)을,
SQL 등록·변경 규칙은 [TAG 메타데이터](../tag-metadata/)를 참고하십시오.
---
title: "6. TAG 테이블 ROLLUP 활용"
url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/
language: kr
kind: section
---
# 6. TAG 테이블 ROLLUP 활용
ROLLUP은 시간축 TAG의 데이터를 미리 집계하고, 조회할 때 그 통계를 합쳐 반복 분석 비용을
줄이는 기능입니다. 이 장은 Machbase DBMS 8.7.0의 기본·조건·확장·JSON·Custom
ROLLUP을 구분하고 생성부터 결과 검증과 재구성까지 설명합니다.
## 먼저 구분할 세 가지 간격
| 개념 | 의미 | 예 |
|---|---|---|
| 생성 INTERVAL | 저장할 집계 버킷의 간격 | 1 MIN |
| WAKEUP INTERVAL | 집계 작업을 깨우는 주기 | 10 SEC |
| 조회 버킷 | 보고서가 요청하는 결과 구간 | `rollup('min', 5, time)` |
같은 버킷에 여러 차례의 부분 집계가 저장될 수 있습니다. 기본 ROLLUP은 공개 조회 구문이
필요한 통계를 병합하고, Custom 대상 TAG는 사용자가 최종 재집계 쿼리를 작성합니다.
원본 보존 기간과 ROLLUP 보존·재구성 정책은 별도로 정합니다.
## 이 장의 구성
| 절 | 내용 |
|---|---|
| [개요와 사용 기준](./overview-use-criteria/) | 기본 실습과 원본·집계 비교 |
| [대상 TAG 설계](./target-tag-table-design/) | ON/FROM, 계층 제약과 용량 산정 |
| [생성과 삭제](./create-delete-rollup/) | CREATE, WITH ROLLUP, IF NOT EXISTS와 의존성 |
| [조회 문법](./query-syntax-rollup/) | 후보 선택, 시간 단위와 origin |
| [조건 ROLLUP](./conditional-rollup/) | 원본 필터와 명시적인 후보 선택 |
| [Custom ROLLUP](./custom-rollup/) | 증분 결과 재집계와 OHLCV 계층 |
| [확장 ROLLUP](./extension-rollup/) | FIRST/LAST와 OHLC 검증 |
| [JSON ROLLUP](./json-summarized-rollup/) | 경로·문서 전체 집계와 NULL |
| [제어와 상태](./ingestion-control-rollup/) | STOP/START/WAKEUP/FORCE, V$ROLLUP과 gap |
| [REBUILD](./rollup-rebuild/) | 실제 지원 대상, 버킷 경계와 정정 |
| [성능 튜닝](./performance-tuning-rollup/) | 같은 결과를 기준으로 비용 비교 |
| [활용 시나리오](./patterns-scenarios/) | 다중 태그와 원본·집계의 역할 분담 |
각 페이지는 독립 실습이며 객체 이름을 `ch6_`로 구분합니다. 기존 업무 객체와 이름이
겹치지 않는지 확인하고, 의도적 오류 예제는 성공 스크립트와 분리합니다. 고정 시각 데이터는
표시한 고정 구간으로 조회합니다. 실습 테이블·ROLLUP만 정리합니다.
Custom과 REBUILD는 Standard Edition 전용입니다. ROLLUP이 생성됐거나 입력에 성공했다는
사실만으로 집계가 끝났다고 판단하지 않습니다. 실습에서는 이름을 지정한 FORCE로 처리
범위를 따라잡고 결과를 확인합니다.
[지원 범위](../reference/support-scope-constraints/rollup/)와
[문제 해결](../troubleshooting/rollup/)에서 제약과 진단을 이어 확인합니다.
---
title: "6.1 ROLLUP 개요와 사용 기준"
url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/overview-use-criteria/
language: kr
kind: page
---
# 6.1 ROLLUP 개요와 사용 기준
ROLLUP은 원시 행에서 같은 집계를 반복하는 비용을 줄입니다. 원본 보관 정책이나 임의
쿼리 결과 캐시가 아니며, 집계에 저장하지 않은 원본 정보까지 복원할 수는 없습니다.
## 사용 기준
| 요구 | 검토할 방식 |
|---|---|
| 한 숫자 컬럼의 반복 구간 통계 | 일반 ROLLUP |
| 특정 품질 조건을 통과한 표본만 집계 | 조건 ROLLUP |
| 구간의 첫 값·마지막 값 필요 | EXTENSION ROLLUP |
| 여러 집계식을 별도 TAG에 저장 | Custom ROLLUP(Standard Edition 전용) |
| JSON 경로나 문서의 숫자 값 집계 | JSON 경로 또는 문서 전체 ROLLUP |
| 거리축 TAG | 일반 숫자 구간 집계; ROLLUP은 미지원 |
일반 숫자 컬럼을 명시해 만드는 ROLLUP에는 SUMMARIZED가 필수가 아닙니다.
WITH ROLLUP 자동 생성과 JSON 문서 전체 집계에는 별도의 SUMMARIZED 조건이 있습니다.
자세한 차이는 [생성 문법](../create-delete-rollup/)을 참고합니다.
## 기본 실습
### 1. 생성과 입력
```sql
CREATE TAG TABLE ch6_basic (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE,
quality INTEGER
);
CREATE ROLLUP ch6_basic_ru ON ch6_basic(value) INTERVAL 1 MIN;
INSERT INTO ch6_basic VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 1);
INSERT INTO ch6_basic VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 1);
INSERT INTO ch6_basic VALUES ('TEMP_01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'), 30.0, 1);
INSERT INTO ch6_basic VALUES ('TEMP_02', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.0, 1);
```
### 2. 집계 완료 범위 확인
```sql
EXEC TABLE_FLUSH(ch6_basic);
ALTER ROLLUP ch6_basic_ru FORCE;
SHOW ROLLUPGAP;
```
SHOW ROLLUPGAP은 machsql 명령입니다. SDK에서는 V$ROLLUP 등 지원되는 SQL 조회를
사용합니다. TABLE_FLUSH는 저장 버퍼 처리이고 FORCE는 해당 ROLLUP의 처리 범위를
따라잡는 작업입니다. 지속 입력 중 미래에 들어올 행까지 완료시킨다는 뜻은 아닙니다.
### 3. 원본과 비교
```sql
SELECT DATE_TRUNC('minute', time) AS bucket,
COUNT(value), MIN(value), MAX(value), AVG(value)
FROM ch6_basic
WHERE name = 'TEMP_01'
GROUP BY bucket ORDER BY bucket;
SELECT rollup('min', 1, time) AS bucket,
COUNT(value), MIN(value), MAX(value), AVG(value)
FROM ch6_basic
WHERE name = 'TEMP_01'
GROUP BY bucket ORDER BY bucket;
```
| 버킷 | COUNT(value) | MIN | MAX | AVG |
|---|---:|---:|---:|---:|
| 2026-01-01 00:00:00 | 2 | 10 | 20 | 15 |
| 2026-01-01 00:01:00 | 1 | 30 | 30 | 30 |
두 쿼리는 같은 결과를 반환해야 합니다. DATE_TRUNC의 원본 집계가 단지 ROLLUP이 존재한다는
이유로 자동 전환되는 것으로 설명하지 않습니다. ROLLUP 조회에서는 `rollup()`을 명시합니다.
### 4. 정리
```sql
DROP ROLLUP ch6_basic_ru;
DROP TABLE ch6_basic;
```
## 도입 전 확인
대표 태그 수, 입력량, 조회 빈도와 허용 집계 지연을 정합니다. 필요한 가장 세밀한 구간과
원본 보존 기간을 먼저 결정하고 [계층 설계](../target-tag-table-design/)로 이어갑니다.
긴 기간의 성능은 운영과 유사한 데이터로 측정하며 이 작은 표본의 시간으로 추정하지 않습니다.
---
title: "6.2 ROLLUP 대상 TAG 테이블 설계"
url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/target-tag-table-design/
language: kr
kind: page
---
# 6.2 ROLLUP 대상 TAG 테이블 설계
## ON과 FROM
`ON source(column)`은 원본 시간축 TAG의 컬럼을 집계합니다. `FROM rollup_name`은
기존 일반/확장 ROLLUP의 통계를 더 큰 구간으로 합칩니다. Custom의 대상도 TAG이지만,
다음 Custom 단계는 대상 TAG를 SELECT하는 INTO...AS 구문으로 구성합니다.
## 계층 실습
```sql
CREATE TAG TABLE ch6_design (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE,
quality INTEGER
);
CREATE ROLLUP ch6_design_sec ON ch6_design(value) INTERVAL 1 SEC;
CREATE ROLLUP ch6_design_min FROM ch6_design_sec INTERVAL 1 MIN;
CREATE ROLLUP ch6_design_hour FROM ch6_design_min INTERVAL 1 HOUR;
INSERT INTO ch6_design VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 1);
INSERT INTO ch6_design VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 1);
INSERT INTO ch6_design VALUES ('TEMP_01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'), 30.0, 1);
INSERT INTO ch6_design VALUES ('TEMP_02', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.0, 1);
EXEC TABLE_FLUSH(ch6_design);
ALTER ROLLUP ch6_design_sec FORCE;
ALTER ROLLUP ch6_design_min FORCE;
ALTER ROLLUP ch6_design_hour FORCE;
SELECT name, rollup('hour', 1, time) AS bucket,
SUM(value), COUNT(value), AVG(value)
FROM ch6_design
GROUP BY name, bucket ORDER BY name, bucket;
```
TEMP_01은 합계 60, 건수 3, 평균 20이고 TEMP_02는 100, 1, 100입니다.
하위부터 FORCE해야 상위가 새로 생성된 하위 결과까지 처리할 수 있습니다.
### 계층 제약
- 상위 간격은 소스 간격보다 커야 하며 정수배여야 합니다. 같은 간격도 허용되지 않습니다.
- 일반 ROLLUP에서 FROM으로 확장 ROLLUP으로 바꾸거나 그 반대로 바꿀 수 없습니다.
EXTENSION 속성을 계층에서 일치시킵니다.
- JSON 경로·문서 모드도 소스와 맞아야 합니다.
- 필요한 조회의 최소 구간보다 거친 통계로 더 세밀한 원본을 복원할 수 없습니다.
다음은 각각 의도적으로 실패하는 생성 예제입니다.
```sql
CREATE ROLLUP ch6_design_bad_same FROM ch6_design_min INTERVAL 1 MIN;
CREATE ROLLUP ch6_design_bad_divisor FROM ch6_design_min INTERVAL 90 SEC;
CREATE ROLLUP ch6_design_bad_ext FROM ch6_design_sec INTERVAL 1 MIN EXTENSION;
```
## 간격과 저장량 결정
모든 태그가 모든 구간에 값을 갖는다고 가정하면 논리 버킷 수는 대략
`(보관 시간 / 버킷 간격) × 태그 수`입니다. 태그 1만 개의 1초 버킷을 365일 보관하면
약 3,154억 버킷입니다. 실제 저장 행 수는 부분 집계·빈 구간·컬럼 수의 영향을 받고
디스크 크기는 압축과 저장 부가 비용까지 측정해야 합니다.
초 단위 관측을 분 단위로만 조회한다면 처음부터 모든 초 계층이 필요한지 검토합니다.
생성 간격은 SEC/MIN/HOUR로 표현하지만 DAY/WEEK/MONTH/YEAR는 조회 버킷 단위입니다.
특히 일 조회에 24 HOUR 저장 간격을 그대로 적용할 수 있다고 가정하지 말고
[후보 선택 규칙](../query-syntax-rollup/)을 확인합니다.
새 ROLLUP은 소스에 남아 있는 기존 데이터도 초기 집계하므로 초기 처리량과 gap을 확인합니다.
원본 보정은 FORCE로 되감기지 않습니다. [REBUILD 지원 범위](../rollup-rebuild/)를 따릅니다.
## 정리
```sql
DROP ROLLUP ch6_design_hour;
DROP ROLLUP ch6_design_min;
DROP ROLLUP ch6_design_sec;
DROP TABLE ch6_design;
```
---
title: "6.3 ROLLUP 생성과 삭제"
url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/create-delete-rollup/
language: kr
kind: page
---
# 6.3 ROLLUP 생성과 삭제
## 생성 구문
```text
CREATE ROLLUP [IF NOT EXISTS] name
ON source_tag [(column_or_json_path)]
INTERVAL n (SEC|MIN|HOUR)
[WAKEUP INTERVAL m (SEC|MIN|HOUR)]
[EXTENSION]
[WHERE predicate];
CREATE ROLLUP [IF NOT EXISTS] name
FROM source_rollup
INTERVAL n (SEC|MIN|HOUR)
[WAKEUP INTERVAL m (SEC|MIN|HOUR)]
[EXTENSION]
[WHERE predicate];
```
EXTENSION 뒤에 별도 확장 이름은 쓰지 않습니다. CREATE의 단위는 SEC/MIN/HOUR이며,
조회 함수의 DAY/MONTH 등과 구분합니다. 간격은 양수이며 현행 검증 상한은 365일에
해당하는 간격입니다. 소스·계층·집계 모드의 조건도 함께 만족해야 합니다.
| 대상 | 조건 |
|---|---|
| 일반 숫자 컬럼 | 지원 숫자 타입을 지정; SUMMARIZED는 필수가 아님 |
| JSON 경로 | 해당 JSON 컬럼과 유효한 경로 지정 |
| JSON 문서 전체 | JSON SUMMARIZED 컬럼 필요 |
| WITH ROLLUP 자동 생성 | 세 번째 SUMMARIZED 컬럼 필요 |
| METADATA·거리축·비TAG | 일반 ROLLUP 대상이 아님 |
## 생성·중복 확인·조회 실습
```sql
CREATE TAG TABLE ch6_create (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE,
quality INTEGER
);
CREATE ROLLUP IF NOT EXISTS ch6_create_ru ON ch6_create(value) INTERVAL 1 MIN;
CREATE ROLLUP IF NOT EXISTS ch6_create_ru ON ch6_create(value) INTERVAL 1 MIN;
INSERT INTO ch6_create VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 1);
INSERT INTO ch6_create VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 1);
INSERT INTO ch6_create VALUES ('TEMP_01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'), 30.0, 1);
INSERT INTO ch6_create VALUES ('TEMP_02', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.0, 1);
EXEC TABLE_FLUSH(ch6_create);
ALTER ROLLUP ch6_create_ru FORCE;
SELECT DISTINCT ROLLUP_NAME, COLUMN_NAME, INTERVAL_TIME, WAKEUP_INTERVAL
FROM V$ROLLUP WHERE ROLLUP_NAME = 'CH6_CREATE_RU';
SELECT rollup('min', 1, time) AS bucket, AVG(value)
FROM ch6_create WHERE name = 'TEMP_01'
GROUP BY bucket ORDER BY bucket;
```
같은 이름의 재생성은 기존 정의를 유지합니다. IF NOT EXISTS는 정의 변경·동일성 확인 기능이
아니며, 잘못된 SQL이나 소스 검증을 모두 생략해 주는 옵션도 아닙니다.
두 간격 컬럼은 60000ms이고 조회 평균은 00:00에 15, 00:01에 30입니다.
IF NOT EXISTS 없이 같은 이름을 생성하면 오류입니다. 정상 실습과 분리해 확인합니다.
```sql
CREATE ROLLUP ch6_create_ru ON ch6_create(value) INTERVAL 1 MIN;
```
## WITH ROLLUP 자동 생성
다음은 별도 테이블입니다. SEC부터 MIN·HOUR까지의 기본 계층을 자동 생성합니다.
```sql
CREATE TAG TABLE ch6_auto (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE SUMMARIZED
) WITH ROLLUP (SEC);
SELECT DISTINCT ROLLUP_NAME, ROOT_TABLE, INTERVAL_TIME, EXT_TYPE
FROM V$ROLLUP WHERE ROOT_TABLE = 'CH6_AUTO'
ORDER BY INTERVAL_TIME;
```
INTERVAL_TIME이 1000·60000·3600000인 세 행이 조회됩니다.
EXTENSION 자동 생성은 `WITH ROLLUP (SEC) EXTENSION` 형태입니다. 실제 생성 이름은
V$ROLLUP에서 확인하고, 이름 충돌을 자동으로 해소한다고 가정하지 않습니다.
인자에 따라 만들어지는 계층이 달라집니다. `(SEC)`는 SEC·MIN·HOUR 세 개를, `(MIN)`은
MIN·HOUR 두 개를, `(HOUR)`는 HOUR 하나를 만들며, 인자를 생략하면 `(SEC)`와 같습니다.
이때 첫 단계만 원본 TAG를 소스로 삼고 다음 단계는 직전 ROLLUP을 소스로 삼는 계층으로
연결됩니다. 인자에는 SEC·MIN·HOUR만 사용할 수 있으며, 조회 함수가 받는 DAY 같은 단위를
지정하면 오류입니다.
## 삭제와 정의 변경
다른 ROLLUP이 참조하는 소스는 상위 의존 객체부터 제거합니다. Custom 대상 TAG는 작업을
삭제하기 전에 DROP할 수 없습니다. 정의를 바꾸려면 읽는 애플리케이션과 재집계 시간을
고려해 새 객체로 전환하거나 기존 정의를 제거한 뒤 다시 생성합니다.
```sql
DROP ROLLUP ch6_create_ru;
DROP TABLE ch6_create;
DROP TABLE ch6_auto CASCADE;
```
마지막 CASCADE는 이 실습의 자동 ROLLUP도 함께 제거합니다. 일반 운영 정리의 기본값으로
사용하지 않습니다. Custom 소스 CASCADE는 관련 작업을 제거하더라도 사용자 대상 TAG까지
자동 삭제하는 것으로 해석하지 마십시오.
조건·확장·JSON·Custom 실습은 각 절에서 독립적으로 제공합니다. 전체 구문은
[SQL 레퍼런스](../../reference/sql/syntax/rollup-syntax/)를 참고합니다.
---
title: "6.4 ROLLUP 조회 문법"
url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/query-syntax-rollup/
language: kr
kind: page
---
# 6.4 ROLLUP 조회 문법
## 명시적인 ROLLUP 조회
```text
rollup(time_unit, period, basetime_column [, origin])
```
| 인자 | 계약 |
|---|---|
| time_unit | SECOND/SEC, MINUTE/MIN, HOUR, DAY, WEEK, MONTH, YEAR; 대소문자 무관 |
| period | 양의 정수 리터럴; 컬럼이나 매개변수 자리표시자가 아님 |
| basetime_column | 원본 시간축 TAG의 BASETIME 컬럼 |
| origin | 생략 시 시간대 오프셋을 반영한 기본 기준점; 명시 시 단위별 제약 확인 |
반환값은 DATETIME 버킷입니다. 저장된 집계의 최소 간격보다 세밀한 결과를 복원할 수는
없습니다. 적용 가능한 ROLLUP이 없으면 원시 스캔으로 자동 전환하지 않고 오류가 발생합니다.
원본 집계가 필요하면 별도의 DATE_TRUNC/DATE_BIN + GROUP BY 쿼리를 작성합니다.
## 준비와 기본 조회
```sql
CREATE TAG TABLE ch6_query (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE,
quality INTEGER
);
CREATE ROLLUP ch6_query_sec ON ch6_query(value) INTERVAL 1 SEC;
INSERT INTO ch6_query VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 1);
INSERT INTO ch6_query VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 1);
INSERT INTO ch6_query VALUES ('TEMP_01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'), 30.0, 1);
INSERT INTO ch6_query VALUES ('TEMP_02', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.0, 1);
EXEC TABLE_FLUSH(ch6_query);
ALTER ROLLUP ch6_query_sec FORCE;
SELECT name, rollup('min', 1, time) AS bucket,
COUNT(value), SUM(value), MIN(value), MAX(value), AVG(value)
FROM ch6_query
WHERE time >= TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND time < TO_DATE('2026-01-01 00:02:00', 'YYYY-MM-DD HH24:MI:SS')
GROUP BY name, bucket ORDER BY name, bucket;
```
TEMP_01의 00:00은 COUNT=2, SUM=30, AVG=15이고 00:01은 1, 30, 30입니다.
TEMP_02는 00:00의 1, 100, 100입니다. SELECT에서 name을 반환한다면 GROUP BY에도
name을 넣습니다. name을 빼면 여러 태그를 합친 집계이므로 단위가 다른 센서를 섞지 않습니다.
## 후보 선택과 힌트
1. ROLLUP_TABLE 힌트가 있으면 해당 후보의 호환성을 검사합니다.
2. 자동 선택은 집계 컬럼·JSON 경로·모드와 요청 간격이 맞는 후보를 찾습니다.
3. 조건 없는 후보를 먼저 찾고, 없을 때 조건 후보를 포함해 탐색합니다.
4. 적용 가능한 가장 큰 간격을 선택하며 같은 간격에서는 먼저 등록된 후보가 유지됩니다.
일반/확장 여부만으로 “일반이 항상 우선”이라고 단정하지 않습니다.
조건 ROLLUP만 있을 때는 필터된 데이터가 자동 선택될 수 있으므로 결과가 나타내는
표본 집합을 확인합니다. 반드시 특정 집계를 사용해야 하면 힌트를 명시합니다.
```sql
SELECT /*+ ROLLUP_TABLE(ch6_query_sec) */
rollup('min', 1, time) AS bucket, AVG(value)
FROM ch6_query WHERE name = 'TEMP_01'
GROUP BY bucket ORDER BY bucket;
```
### 저장 후보의 간격과 조회 버킷
현재 후보 간격 검사는 SEC 요청을 period초, MIN 요청을 period분으로 계산합니다.
HOUR와 DAY/WEEK/MONTH/YEAR 요청은 후보 선택 단계에서 period시간을 기준으로 검사하고,
선택된 통계를 요청한 달력·시간 버킷으로 다시 합칩니다.
따라서 `rollup('day', 1, time)`을 위해 24 HOUR ROLLUP을 만들면 자동으로 사용할 수
있다고 가정하면 안 됩니다. 해당 요청은 1시간 기준에 맞는 HOUR/MIN/SEC 후보를 검토합니다.
생성 INTERVAL과 결과 버킷의 의미를 구분하고 실제 실행 계획을 확인합니다.
## 집계 함수와 표본
일반 숫자 ROLLUP은 MIN, MAX, SUM, COUNT, AVG, SUMSQ를 지원합니다.
ROLLUP 조회의 FIRST/LAST에는 EXTENSION이 필요합니다. Custom 결과는 일반 TAG에
저장되므로 [Custom 재집계](../custom-rollup/)의 합계·건수 규칙을 사용합니다.
JSON 문서 전체 집계의 COUNT도 [JSON 절](../json-summarized-rollup/)에서 별도로 설명합니다.
### SUMSQ와 분산·표준편차
ROLLUP 조회는 STDDEV, STDDEV_POP, VARIANCE, VAR_POP을 직접 지원하지 않습니다.
`rollup()` 조회에서 이 함수들을 사용하면
`ERR-02816: Only rollup column with aggregate function can be referenced in ROLLUP SELECT query.`
가 발생합니다. 표준편차와 분산은 구간별 결과를 다시 더할 수 없기 때문입니다.
두 구간의 표준편차를 평균해도 전체 구간의 표준편차가 되지 않습니다.
대신 ROLLUP은 값의 제곱합인 SUMSQ를 COUNT, SUM과 함께 저장합니다. 이 세 값은 모두
더할 수 있으므로 저장 간격보다 큰 버킷으로 다시 합쳐도 유효하며, 조회 시점에 분산과
표준편차를 계산할 수 있습니다. SUMSQ는 일반 ROLLUP과 EXTENSION ROLLUP 모두에
포함됩니다.
| 값 | 계산식 |
|---|---|
| 모분산 | `SUMSQ/N - (SUM/N)^2` |
| 모표준편차 | 모분산의 제곱근 |
| 표본분산 | `(SUMSQ - SUM^2/N) / (N-1)` |
| 표본표준편차 | 표본분산의 제곱근 |
N은 `COUNT(value)`이며 NULL을 제외한 유효 값의 개수입니다.
ROLLUP 조회 블록에는 지원되는 집계만 두고, 분산과 표준편차는 인라인 뷰 밖에서
계산합니다. COUNT, SUM, SUMSQ를 한 번만 읽고 파생 계산을 분리하므로 계산식을
바꿔도 ROLLUP 조회 부분은 그대로 사용할 수 있습니다.
```sql
SELECT bucket, n, s, sq,
sq/n - POWER(s/n, 2) AS var_pop,
SQRT(sq/n - POWER(s/n, 2)) AS stddev_pop
FROM (
SELECT rollup('min', 1, time) AS bucket,
COUNT(value) AS n,
SUM(value) AS s,
SUMSQ(value) AS sq
FROM ch6_query WHERE name = 'TEMP_01'
GROUP BY bucket
) t
ORDER BY bucket;
```
00:00 버킷은 값 10과 20이므로 n=2, s=30, sq=500이고 모분산 25, 모표준편차 5입니다.
00:01 버킷은 값이 하나이므로 모분산과 모표준편차가 0입니다.
표본분산은 분모가 `N-1`이므로 값이 하나인 버킷을 먼저 걸러야 합니다. 이 판단도
인라인 뷰 밖에서 수행합니다. `ELSE NULL`을 명시하면 `ERR-02042`가 발생하므로
`ELSE`를 생략합니다.
```sql
SELECT bucket, n,
CASE WHEN n > 1
THEN (sq - POWER(s, 2)/n) / (n - 1)
END AS var_samp
FROM (
SELECT rollup('min', 1, time) AS bucket,
COUNT(value) AS n,
SUM(value) AS s,
SUMSQ(value) AS sq
FROM ch6_query WHERE name = 'TEMP_01'
GROUP BY bucket
) t
ORDER BY bucket;
```
00:00 버킷의 표본분산은 50이고, 값이 하나인 00:01 버킷은 NULL입니다.
값이 하나인 구간을 결과에서 빼려면 인라인 뷰 밖에 `WHERE n > 1`을 사용합니다.
원본 테이블의 `VARIANCE`와 `STDDEV`는 같은 구간에서 NULL이 아니라 0을 반환하므로,
두 결과를 함께 사용할 때는 표시 정책을 맞춥니다.
위 예제의 값에서는 원본 테이블에 `VAR_POP`, `STDDEV_POP`, `VARIANCE`, `STDDEV`를
직접 사용한 결과와 같습니다. 다만 두 계산식은 평균이 크고 편차가 작을수록 자리수가
손실됩니다. 예를 들어 값이 100000 부근에서 소수점 이하로만 흔들리면 `SUMSQ/N`과
`(SUM/N)^2`가 거의 같은 크기여서 뺄셈 결과의 유효 자리수가 줄어듭니다. 부동소수
오차로 분산이 아주 작은 음수가 되면 `SQRT`의 결과도 유효하지 않습니다. 정밀도가
중요한 구간에서는 원본 테이블의 `STDDEV`, `VAR_POP` 결과와 비교해 사용할 수 있는
범위를 확인합니다.
## 달력 단위와 origin
같은 준비 데이터를 사용해 월 단위 결과와 월요일 기준 주 단위를 확인합니다.
```sql
SELECT rollup('month', 1, time, '2000-01-01 00:00:00') AS bucket,
SUM(value), COUNT(value), AVG(value)
FROM ch6_query WHERE name = 'TEMP_01'
GROUP BY bucket ORDER BY bucket;
SELECT rollup('week', 1, time, '1970-01-05 00:00:00') AS bucket, AVG(value)
FROM ch6_query WHERE name = 'TEMP_01'
GROUP BY bucket ORDER BY bucket;
```
월 조회는 2026-01-01 버킷에 SUM=60, COUNT=3, AVG=20을 반환합니다.
월·년 origin은 시간대 해석 후 월의 1일이어야 하며 결과는 해당 월 경계의 자정입니다.
임의 날짜나 시각 오프셋으로 월간 업무 시작 시각을 옮기는 기능으로 해석하지 않습니다.
일·주 같은 고정 간격의 origin과 월·년의 달력 계산을 구분합니다.
문자열의 의미는 접속 시간대와 함께 확인합니다. DST를 자동으로 원하는 업무 달력에
맞춰 준다고 가정하지 말고 경계 전후의 원본 DATE_BIN 집계와 비교합니다. 저장 집계를
쪼개야 하는 origin이나 조회 경계에는 원본과 동일한 결과를 기대할 수 있는지 검증합니다.
## 정리
```sql
DROP ROLLUP ch6_query_sec;
DROP TABLE ch6_query;
```
---
title: "6.5 조건 ROLLUP"
url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/conditional-rollup/
language: kr
kind: page
---
# 6.5 조건 ROLLUP
## 원본을 필터한 뒤 집계
조건 ROLLUP은 원본의 품질·상태 조건을 적용한 통계를 유지합니다. 이미 계산한 평균에서
불량 표본만 나중에 제거하는 것과 다릅니다. 조건에 사용한 quality 컬럼 자체가 집계 결과에
보존되는 것도 아닙니다.
## 1. 테이블과 두 ROLLUP 준비
```sql
CREATE TAG TABLE ch6_condition (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE,
quality INTEGER
);
CREATE ROLLUP ch6_condition_all
ON ch6_condition(value) INTERVAL 1 MIN EXTENSION;
CREATE ROLLUP ch6_condition_good
ON ch6_condition(value) INTERVAL 1 MIN EXTENSION WHERE quality = 1;
INSERT INTO ch6_condition VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 1);
INSERT INTO ch6_condition VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 1);
INSERT INTO ch6_condition VALUES ('TEMP_01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'), 30.0, 1);
INSERT INTO ch6_condition VALUES ('TEMP_02', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.0, 1);
INSERT INTO ch6_condition VALUES
('TEMP_01', TO_DATE('2026-01-01 00:00:50', 'YYYY-MM-DD HH24:MI:SS'), 90.0, 0);
EXEC TABLE_FLUSH(ch6_condition);
ALTER ROLLUP ch6_condition_all FORCE;
ALTER ROLLUP ch6_condition_good FORCE;
```
## 2. 원본 조건과 ROLLUP 비교
```sql
SELECT DATE_TRUNC('minute', time) AS bucket, COUNT(value), AVG(value),
MIN(value), MAX(value), FIRST(time, value), LAST(time, value)
FROM ch6_condition
WHERE name = 'TEMP_01' AND quality = 1
GROUP BY bucket ORDER BY bucket;
SELECT /*+ ROLLUP_TABLE(ch6_condition_good) */
rollup('min', 1, time) AS bucket, COUNT(value), AVG(value),
MIN(value), MAX(value), FIRST(time, value), LAST(time, value)
FROM ch6_condition WHERE name = 'TEMP_01'
GROUP BY bucket ORDER BY bucket;
SELECT /*+ ROLLUP_TABLE(ch6_condition_all) */
rollup('min', 1, time) AS bucket, COUNT(value), AVG(value),
MIN(value), MAX(value), FIRST(time, value), LAST(time, value)
FROM ch6_condition WHERE name = 'TEMP_01'
GROUP BY bucket ORDER BY bucket;
```
| 버킷·집합 | COUNT | AVG | MIN | MAX | FIRST | LAST |
|---|---:|---:|---:|---:|---:|---:|
| 00:00 전체 | 3 | 40 | 10 | 90 | 10 | 90 |
| 00:00 quality=1 | 2 | 15 | 10 | 20 | 10 | 20 |
| 00:01 두 집합 공통 | 1 | 30 | 30 | 30 | 30 | 30 |
이 표본은 불량값이 구간의 마지막에 있으므로 LAST도 달라집니다.
FIRST/LAST는 시각·값 쌍이 아니라 선택한 value를 반환합니다.
## 명시적인 후보 선택
이 예제는 전체 통계와 정상 통계의 결과 집합을 고정하기 위해 힌트를 사용합니다.
자동 선택은 조건 없는 후보를 우선하지만 조건 후보만 있을 때는 필터된 통계를 선택할 수
있습니다. “조건 ROLLUP은 항상 무시된다”거나 “EXTENSION에는 항상 힌트가 필수”라는
규칙으로 해석하지 않습니다.
## 문법과 제약
일반 ROLLUP의 필터는 INTERVAL·EXTENSION 뒤의 WHERE에 작성합니다.
비교, BETWEEN, IN, LIKE, 논리 연산과 지원 스칼라 함수를 사용할 수 있지만 서브쿼리,
집계 함수와 태그 이름 PRIMARY KEY 조건은 지원하지 않습니다.
Custom은 SELECT 내부 WHERE를 사용하므로 구문을 섞지 않습니다.
## 상태 확인과 정리
```sql
SELECT DISTINCT ROLLUP_NAME, PREDICATE, ENABLED
FROM V$ROLLUP WHERE ROOT_TABLE = 'CH6_CONDITION';
DROP ROLLUP ch6_condition_good;
DROP ROLLUP ch6_condition_all;
DROP TABLE ch6_condition;
```
업무 품질 기준이 바뀌면 조건과 재집계 계획도 갱신합니다. 기존 집계가 새로운 조건으로
자동 전환되지는 않습니다. [제어](../ingestion-control-rollup/)와
[재구성 범위](../rollup-rebuild/)를 확인합니다.
---
title: "6.6 Custom ROLLUP"
url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/custom-rollup/
language: kr
kind: page
---
# 6.6 Custom ROLLUP
Standard Edition 전용
## Custom과 일반 ROLLUP
Custom ROLLUP은 SELECT의 증분 집계 결과를 미리 만든 대상 TAG에 누적합니다.
일반 ROLLUP의 내부 통계를 `rollup()`으로 읽는 방식과 달리, 대상 TAG를 직접 조회하면서
부분 집계를 다시 합쳐야 합니다. Cluster Edition에서는 생성할 수 없습니다.
```text
CREATE ROLLUP [IF NOT EXISTS] name
INTO (destination_tag)
AS (SELECT ... FROM source_tag [WHERE ...] GROUP BY ...)
INTERVAL n (SEC|MIN|HOUR)
[WAKEUP INTERVAL m (SEC|MIN|HOUR)];
```
소스는 시간축 TAG 하나이며 JOIN·FROM 서브쿼리는 사용할 수 없습니다. 대상은 미리 만든
TAG이고 SELECT의 컬럼 순서·타입과 호환되어야 합니다. SELECT 내부 WHERE는 사용할 수
있지만 BASETIME 직접 조건은 허용되지 않습니다. 일반 ROLLUP처럼 INTERVAL 뒤에 외부
WHERE를 붙이지 않습니다.
생성 간격과 SELECT가 계산하는 시간 버킷을 일치시키십시오. 작업이 새 입력만 처리하므로
같은 버킷에 여러 결과 행이 있을 수 있습니다. 행 수를 버킷 수로 해석하지 않습니다.
## 1. 합계·유효 건수 실습
```sql
CREATE TAG TABLE ch6_custom_src (
name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE
);
CREATE TAG TABLE ch6_custom_dst (
name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME,
sum_value DOUBLE, valid_count LONG, total_count LONG
);
CREATE ROLLUP ch6_custom_ru INTO (ch6_custom_dst)
AS (
SELECT name, DATE_TRUNC('minute', time) AS time,
SUM(value), COUNT(value), COUNT(*)
FROM ch6_custom_src
GROUP BY name, time
) INTERVAL 1 MIN;
INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10);
INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:05', 'YYYY-MM-DD HH24:MI:SS'), 10);
EXEC TABLE_FLUSH(ch6_custom_src);
ALTER ROLLUP ch6_custom_ru FORCE;
INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:10', 'YYYY-MM-DD HH24:MI:SS'), 20);
INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:15', 'YYYY-MM-DD HH24:MI:SS'), 20);
INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:20', 'YYYY-MM-DD HH24:MI:SS'), 20);
INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:25', 'YYYY-MM-DD HH24:MI:SS'), 20);
INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20);
INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:35', 'YYYY-MM-DD HH24:MI:SS'), 20);
INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:40', 'YYYY-MM-DD HH24:MI:SS'), 20);
INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:45', 'YYYY-MM-DD HH24:MI:SS'), 20);
INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:55', 'YYYY-MM-DD HH24:MI:SS'), NULL);
EXEC TABLE_FLUSH(ch6_custom_src);
ALTER ROLLUP ch6_custom_ru FORCE;
SELECT name, DATE_TRUNC('minute', time) AS bucket,
SUM(value), COUNT(value), COUNT(*), AVG(value)
FROM ch6_custom_src
GROUP BY name, bucket ORDER BY name, bucket;
SELECT name, time, SUM(sum_value) AS sum_value,
SUM(valid_count) AS valid_count, SUM(total_count) AS total_count,
CASE WHEN SUM(valid_count) = 0 THEN NULL
ELSE SUM(sum_value) / SUM(valid_count) END AS avg_value
FROM ch6_custom_dst
GROUP BY name, time ORDER BY name, time;
```
한 버킷의 합계는 180, 유효 값은 10개, 전체 행은 11개, 평균은 18입니다.
부분 평균 10과 20을 단순 평균한 15와 다릅니다. NULL을 평균의 분모에 넣지 않도록
COUNT(value)를 사용하고, NULL 포함 행 수는 COUNT(*)로 따로 보관합니다.
모든 값이 NULL인 버킷까지 처리하려면 유효 건수 0에서 나누지 않는 결과 정책도 정합니다.
### 표본 비율과 시간 가동률
조건을 만족한 표본 수를 전체 표본 수로 나눈 값은 표본 비율입니다. 시간 가동률로 해석하려면
관측 간격·결측 처리와 상태 지속 시간을 반영해야 합니다. 비율도 부분 비율끼리 평균하지
말고 분자와 분모를 각각 합산합니다.
## 2. OHLCV와 1분→10분 Custom 계층
별도 실습입니다. FIRST/LAST 재집계를 위해 원본의 첫·마지막 관측 시각도 저장합니다.
```sql
CREATE TAG TABLE ch6_ticks (
code VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME,
price DOUBLE, volume DOUBLE
);
CREATE TAG TABLE ch6_candle_min (
code VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME,
open_price DOUBLE, high_price DOUBLE, low_price DOUBLE, close_price DOUBLE,
volume DOUBLE, cnt LONG, firsttime DATETIME, lasttime DATETIME
);
CREATE TAG TABLE ch6_candle_10m (
code VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME,
open_price DOUBLE, high_price DOUBLE, low_price DOUBLE, close_price DOUBLE,
volume DOUBLE, cnt LONG, firsttime DATETIME, lasttime DATETIME
);
CREATE ROLLUP ch6_candle_ru_min INTO (ch6_candle_min)
AS (
SELECT code, DATE_TRUNC('minute', time) AS time,
FIRST(time, price), MAX(price), MIN(price), LAST(time, price),
SUM(volume), COUNT(*), MIN(time), MAX(time)
FROM ch6_ticks
GROUP BY code, time
) INTERVAL 1 MIN;
CREATE ROLLUP ch6_candle_ru_10m INTO (ch6_candle_10m)
AS (
SELECT code, DATE_BIN('min', 10, time, TO_DATE('2000-01-01 00:00:00')) AS time,
FIRST(firsttime, open_price), MAX(high_price),
MIN(low_price), LAST(lasttime, close_price),
SUM(volume), SUM(cnt), MIN(firsttime), MAX(lasttime)
FROM ch6_candle_min
GROUP BY code, time
) INTERVAL 10 MIN;
INSERT INTO ch6_ticks VALUES ('AAPL', TO_DATE('2026-01-01 09:00:00'), 100, 2);
INSERT INTO ch6_ticks VALUES ('AAPL', TO_DATE('2026-01-01 09:00:30'), 105, 3);
INSERT INTO ch6_ticks VALUES ('AAPL', TO_DATE('2026-01-01 09:01:00'), 103, 1);
INSERT INTO ch6_ticks VALUES ('AAPL', TO_DATE('2026-01-01 09:01:30'), 99, 4);
EXEC TABLE_FLUSH(ch6_ticks);
ALTER ROLLUP ch6_candle_ru_min FORCE;
EXEC TABLE_FLUSH(ch6_candle_min);
ALTER ROLLUP ch6_candle_ru_10m FORCE;
SELECT code, time,
FIRST(firsttime, open_price), MAX(high_price),
MIN(low_price), LAST(lasttime, close_price), SUM(volume), SUM(cnt)
FROM ch6_candle_10m
GROUP BY code, time ORDER BY code, time;
```
09:00의 10분 버킷은 Open=100, High=105, Low=99, Close=99, volume=10, cnt=4입니다.
가격·거래량이 NULL인 경우에는 어떤 행의 시각과 값을 사용할지 별도 규칙과 테스트가 필요합니다.
동일 시각의 여러 거래도 업무상 순서가 있다면 추가 식별 기준을 설계합니다.
이 10분 Custom은 생성·조회 예제입니다. 현행 REBUILD의 Custom 시간 범위 처리에
지원되는 간격과 같다고 가정하지 마십시오. [REBUILD 제한](../rollup-rebuild/)을 확인합니다.
## 상태와 정리
생성 후 자동 시작되므로 START를 즉시 반복하지 않습니다. STOP/START와 FORCE는 작업
상태에 맞춰 호출합니다. 작업이 존재하는 대상 TAG의 DROP은 차단됩니다.
```sql
SELECT DISTINCT ROLLUP_NAME, ROLLUP_TABLE, ROOT_TABLE, EXT_TYPE,
INTERVAL_TIME, WAKEUP_INTERVAL
FROM V$ROLLUP WHERE ROLLUP_NAME = 'CH6_CUSTOM_RU';
DROP ROLLUP ch6_candle_ru_10m;
DROP ROLLUP ch6_candle_ru_min;
DROP TABLE ch6_candle_10m;
DROP TABLE ch6_candle_min;
DROP TABLE ch6_ticks;
DROP ROLLUP ch6_custom_ru;
DROP TABLE ch6_custom_dst;
DROP TABLE ch6_custom_src;
```
EXT_TYPE=2는 Custom이며 PREDICATE에는 SELECT 본문이 기록됩니다.
정리할 때는 실행한 실습의 객체만 대상으로 합니다. 원본 보정과 상위 재집계의 재시도·완료
확인은 [제어와 상태](../ingestion-control-rollup/) 및 REBUILD 절차를 따릅니다.
---
title: "6.7 확장 ROLLUP과 FIRST/LAST"
url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/extension-rollup/
language: kr
kind: page
---
# 6.7 확장 ROLLUP과 FIRST/LAST
## EXTENSION과 원본 FIRST/LAST의 차이
EXTENSION은 ROLLUP에 첫·마지막 값과 관련 시각 정보를 추가합니다.
일반 원본 GROUP BY에서 FIRST/LAST를 사용하는 것과, 저장된 ROLLUP에서 FIRST/LAST를
조회하는 것은 다릅니다. 후자에는 적용 가능한 확장 ROLLUP이 필요합니다.
EXTENSION이 추가하는 것은 첫·마지막 값과 시각뿐입니다. MIN, MAX, SUM, COUNT와
제곱합인 SUMSQ는 일반 ROLLUP에도 함께 저장됩니다. SUMSQ로 분산과 표준편차를
계산하는 방법은 [SUMSQ와 분산·표준편차](../query-syntax-rollup/#query-sumsq-stddev-rollup)를
참고합니다.
## 준비와 입력
```sql
CREATE TAG TABLE ch6_ext (
code VARCHAR(20) PRIMARY KEY,
time DATETIME BASETIME,
price DOUBLE
);
CREATE ROLLUP ch6_ext_first
ON ch6_ext(price) INTERVAL 1 MIN EXTENSION;
CREATE ROLLUP ch6_ext_plain
ON ch6_ext(price) INTERVAL 1 MIN;
INSERT INTO ch6_ext VALUES ('AAPL', TO_DATE('2026-01-01 09:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100);
INSERT INTO ch6_ext VALUES ('AAPL', TO_DATE('2026-01-01 09:00:10', 'YYYY-MM-DD HH24:MI:SS'), 105);
INSERT INTO ch6_ext VALUES ('AAPL', TO_DATE('2026-01-01 09:00:20', 'YYYY-MM-DD HH24:MI:SS'), 99);
INSERT INTO ch6_ext VALUES ('AAPL', TO_DATE('2026-01-01 09:00:30', 'YYYY-MM-DD HH24:MI:SS'), 103);
EXEC TABLE_FLUSH(ch6_ext);
ALTER ROLLUP ch6_ext_first FORCE;
ALTER ROLLUP ch6_ext_plain FORCE;
```
## 원본과 OHLC 비교
```sql
SELECT DATE_TRUNC('minute', time) AS bucket,
FIRST(time, price) AS open_price, MAX(price) AS high_price,
MIN(price) AS low_price, LAST(time, price) AS close_price
FROM ch6_ext WHERE code = 'AAPL'
GROUP BY bucket ORDER BY bucket;
SELECT /*+ ROLLUP_TABLE(ch6_ext_first) */
rollup('min', 1, time) AS bucket,
FIRST(time, price) AS open_price, MAX(price) AS high_price,
MIN(price) AS low_price, LAST(time, price) AS close_price
FROM ch6_ext WHERE code = 'AAPL'
GROUP BY bucket ORDER BY bucket;
```
두 쿼리는 09:00 버킷에 Open=100, High=105, Low=99, Close=103을 반환합니다.
ROLLUP의 FIRST/LAST 첫 인자는 BASETIME, 두 번째는 집계 대상 컬럼을 사용합니다.
같은 시각의 여러 값은 추가적인 업무 순서가 필요한 경우를 별도로 설계합니다.
## 일반 후보와 확장 후보가 함께 있을 때
일반 ROLLUP이 확장 ROLLUP보다 무조건 우선하는 것은 아닙니다. 같은 조건·간격의 후보는
등록 순서의 영향을 받습니다. 이 예제는 확장을 먼저 만들었지만, 결과가 특정 후보에
의존해야 한다면 위처럼 명시합니다. 확장만 적용 가능한 환경에서는 힌트 없이 선택될 수도 있습니다.
다음은 일반 ROLLUP을 강제하므로 의도적으로 실패하는 조회입니다.
```sql
SELECT /*+ ROLLUP_TABLE(ch6_ext_plain) */
rollup('min', 1, time) AS bucket, FIRST(time, price)
FROM ch6_ext WHERE code = 'AAPL'
GROUP BY bucket;
```
자동 계층에 확장이 필요하면 별도 테이블의 CREATE에서
`WITH ROLLUP (SEC) EXTENSION`을 사용합니다. FROM으로 계층을 만들 때도 확장 속성을
일치시켜야 합니다. Custom OHLCV 재집계는 [Custom 실습](../custom-rollup/)을 참고합니다.
## 정리
```sql
DROP ROLLUP ch6_ext_plain;
DROP ROLLUP ch6_ext_first;
DROP TABLE ch6_ext;
```
---
title: "6.8 JSON SUMMARIZED ROLLUP"
url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/json-summarized-rollup/
language: kr
kind: page
---
# 6.8 JSON SUMMARIZED ROLLUP
## JSON 경로와 문서 전체 집계
JSON 경로 ROLLUP은 지정 경로에서 숫자 값을 집계합니다. 문서 전체 ROLLUP은
JSON SUMMARIZED 컬럼의 숫자 경로별 통계를 유지합니다. 경로가 없는 값, JSON null,
SQL NULL과 배열을 같은 표본으로 해석하지 않습니다.
## 준비
```sql
CREATE TAG TABLE ch6_json (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value JSON SUMMARIZED
);
CREATE ROLLUP ch6_json_metric ON ch6_json(value.metric) INTERVAL 1 MIN;
CREATE ROLLUP ch6_json_whole ON ch6_json(value) INTERVAL 1 MIN;
INSERT INTO ch6_json VALUES ('S1', TO_DATE('2026-01-01 00:00:00'),
'{"metric":10,"nested":{"x":2},"status":"OK","items":[1,2]}');
INSERT INTO ch6_json VALUES ('S1', TO_DATE('2026-01-01 00:00:10'),
'{"metric":20,"nested":{"x":4},"status":"WARN","items":[3,4]}');
INSERT INTO ch6_json VALUES ('S1', TO_DATE('2026-01-01 00:00:20'),
'{"metric":null,"status":false}');
INSERT INTO ch6_json VALUES ('S1', TO_DATE('2026-01-01 00:00:30'), NULL);
EXEC TABLE_FLUSH(ch6_json);
ALTER ROLLUP ch6_json_metric FORCE;
ALTER ROLLUP ch6_json_whole FORCE;
```
## 경로 집계 비교
```sql
SELECT DATE_TRUNC('minute', time) AS bucket,
COUNT(JSON_EXTRACT_DOUBLE(value, '$.metric')),
AVG(JSON_EXTRACT_DOUBLE(value, '$.metric'))
FROM ch6_json WHERE name = 'S1'
GROUP BY bucket ORDER BY bucket;
SELECT /*+ ROLLUP_TABLE(ch6_json_metric) */
rollup('min', 1, time) AS bucket,
COUNT(value.metric), AVG(value.metric)
FROM ch6_json WHERE name = 'S1'
GROUP BY bucket ORDER BY bucket;
SELECT /*+ ROLLUP_TABLE(ch6_json_metric) */
rollup('min', 1, time) AS bucket, AVG(value->'$.metric')
FROM ch6_json WHERE name = 'S1'
GROUP BY bucket ORDER BY bucket;
```
metric의 유효 숫자 표본은 2개이고 평균은 15입니다. dot과 arrow는 같은 경로를 표현합니다.
특정 배열 요소를 경로로 지정하는 것과 문서 전체 집계가 배열을 자동 전개하는 것은 다릅니다.
경로 선언의 예로 `value.items[0]."metric-id"`를 사용할 수 있지만 해당 JSON 구조와 숫자
표본이 실제로 있는지 먼저 확인해야 합니다.
## 문서 전체 집계와 COUNT
```sql
SELECT COUNT(*) AS raw_rows, COUNT(value) AS raw_documents FROM ch6_json;
SELECT /*+ ROLLUP_TABLE(ch6_json_whole) */
rollup('min', 1, time) AS bucket,
COUNT(value), AVG(value), MIN(value), MAX(value), SUM(value)
FROM ch6_json WHERE name = 'S1'
GROUP BY bucket ORDER BY bucket;
```
원본 COUNT(*)는 4이고 COUNT(value)는 3입니다. 문서 전체 ROLLUP의 COUNT(value)는 저장된
문서 집계 건수를 합산합니다. 현재 이 집계의 건수는 원본 COUNT(*)로 만들어지므로 위처럼
숫자 경로를 가진 문서와 SQL NULL이 섞인 버킷에서는 4입니다. 일반 원본 COUNT(value)의
NULL 제외 규칙을 그대로 적용하면 안 됩니다.
AVG 결과의 metric은 15, nested.x는 3이며 각 경로의 유효 숫자 표본으로 계산합니다.
문자열·불리언·JSON null·배열은 숫자 집계에서 제외되고 비숫자 경로는 null 또는 생략으로
나타날 수 있습니다. JSON 직렬화의 키 순서를 고정된 문자열 결과로 비교하지 않습니다.
숫자 경로가 전혀 없는 문서나 SQL NULL만 있는 구간은 별도로 표본을 만들어 확인합니다.
JSON 경로 집계와 문서 전체 집계는 후보 모드가 다릅니다. 서로 다른 모드의 ROLLUP을
힌트로 강제해서 같은 결과가 된다고 가정하지 마십시오. 유효하지 않은 JSON은 입력 오류입니다.
## 정리
```sql
DROP ROLLUP ch6_json_whole;
DROP ROLLUP ch6_json_metric;
DROP TABLE ch6_json;
```
---
title: "6.9 ROLLUP 제어와 상태 확인"
url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/ingestion-control-rollup/
language: kr
kind: page
---
# 6.9 ROLLUP 제어와 상태 확인
## 작업 상태와 처리 완료
ROLLUP은 생성 시 자동 시작됩니다. START를 즉시 반복하거나 이미 중지한 작업을 다시
STOP하면 상태 오류가 날 수 있습니다. 아래 실습은 생성 → STOP → 입력 → START →
WAKEUP → FORCE 순서로 상태를 구분합니다.
```sql
CREATE TAG TABLE ch6_control (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE,
quality INTEGER
);
CREATE ROLLUP ch6_control_ru ON ch6_control(value)
INTERVAL 1 MIN WAKEUP INTERVAL 10 SEC;
ALTER ROLLUP ch6_control_ru STOP;
INSERT INTO ch6_control VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 1);
INSERT INTO ch6_control VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 1);
INSERT INTO ch6_control VALUES ('TEMP_01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'), 30.0, 1);
INSERT INTO ch6_control VALUES ('TEMP_02', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.0, 1);
EXEC TABLE_FLUSH(ch6_control);
SELECT DISTINCT ROLLUP_NAME, ENABLED, INTERVAL_TIME, WAKEUP_INTERVAL
FROM V$ROLLUP WHERE ROLLUP_NAME = 'CH6_CONTROL_RU';
ALTER ROLLUP ch6_control_ru START;
ALTER ROLLUP ch6_control_ru WAKEUP;
ALTER ROLLUP ch6_control_ru FORCE;
SELECT rollup('min', 1, time) AS bucket, AVG(value)
FROM ch6_control WHERE name = 'TEMP_01'
GROUP BY bucket ORDER BY bucket;
```
중지 상태의 ENABLED는 0, INTERVAL_TIME은 60000ms, WAKEUP_INTERVAL은 10000ms입니다.
마지막 조회는 TEMP_01의 00:00 평균 15와 00:01 평균 30을 반환합니다.
| 명령 | 목적 | 완료 의미 |
|---|---|---|
| STOP | 작업 중지 | 이후 처리하지 않은 입력분은 남아 있음 |
| START | 중지한 작업 재개 | 처리 위치부터 계속 진행 |
| WAKEUP | 작업을 깨움 | 처리 완료를 기다리지 않음 |
| FORCE | 대상 소스의 처리 범위를 따라잡도록 기다림 | 과거 수정분의 재계산이나 미래 입력 완료는 아님 |
| ROLLUP_REBUILD | 지원 대상의 과거 버킷 재계산 | 원본 보정 후 집계를 다시 구성 |
SQL ALTER 대신 이름을 지정한 `EXEC ROLLUP_START(name)`, `ROLLUP_STOP(name)`,
`ROLLUP_FORCE(name)`도 사용할 수 있습니다. 동일 전환을 두 형식으로 연속 실행하지 않습니다.
이름 없는 일괄 제어와 특정 작업 제어의 범위를 혼동하지 마십시오.
## WAKEUP INTERVAL
생략하면 생성 INTERVAL과 같습니다. 양수이고 집계 간격보다 크지 않아야 하며 집계 간격을
나누어떨어지게 해야 합니다. 더 자주 깨우면 지연을 줄일 수 있지만 처리 부하도 증가합니다.
```sql
ALTER ROLLUP ch6_control_ru SET WAKEUP INTERVAL 5 SEC;
SELECT DISTINCT ROLLUP_NAME, INTERVAL_TIME, WAKEUP_INTERVAL
FROM V$ROLLUP WHERE ROLLUP_NAME = 'CH6_CONTROL_RU';
```
WAKEUP_INTERVAL이 5000ms로 바뀝니다. 다음은 60초의 약수가 아닌 의도적 오류입니다.
```sql
ALTER ROLLUP ch6_control_ru SET WAKEUP INTERVAL 7 SEC;
```
## V$ROLLUP 읽기
| 컬럼 | 해석 |
|---|---|
| ROLLUP_NAME | 제어할 작업 이름 |
| ROLLUP_TABLE | 집계 대상 테이블; Custom은 사용자 대상 TAG |
| SOURCE_TABLE, ROOT_TABLE | 직접 소스와 작업을 해석할 원본 관계 |
| COLUMN_NAME | 일반·경로 집계 대상 컬럼 |
| INTERVAL_TIME, WAKEUP_INTERVAL | 밀리초 단위 생성·실행 간격 |
| LAST_WAKEUP_TIME, NEXT_WAKEUP_TIME | 직전에 깨어난 시각과 다음 예정 시각 |
| EXT_TYPE | 0 일반, 1 확장, 2 Custom |
| PREDICATE | 일반 조건식 또는 Custom SELECT 본문 |
| ENABLED | 작업 활성화 여부 |
| RUN_STATE | `I` 초기, `S` 대기, `R` 처리 중 |
| END_RID | 소스 처리 위치 |
| LAST_ELAPSED_MSEC | 직전 처리 시간(ms) |
| DATABASE_NAME, USER_ID | 데이터베이스·소유자 구분 |
```sql
SELECT ROLLUP_NAME, ROLLUP_TABLE, SOURCE_TABLE, ROOT_TABLE,
INTERVAL_TIME, WAKEUP_INTERVAL, LAST_WAKEUP_TIME, NEXT_WAKEUP_TIME,
ENABLED, RUN_STATE, LAST_ELAPSED_MSEC
FROM V$ROLLUP WHERE ROLLUP_NAME = 'CH6_CONTROL_RU'
ORDER BY ROLLUP_NAME;
SHOW ROLLUPGAP;
```
SHOW ROLLUPGAP은 machsql 전용 클라이언트 명령이며 SDK의 일반 SQL API에 보내지 않습니다.
GAP은 소스와 ROLLUP 처리 RID의 차이입니다. 시간 지연 자체가 아니고, 모든 계층·관련 노드의
상태와 함께 봅니다. gap=0이어도 이미 집계한 원본 보정이 반영되었다는 뜻은 아닙니다.
지속 입력 중의 값은 관측 시점에 따라 변하므로 재현 실습에서는 입력을 멈춘 상태로 비교합니다.
중지 기간에 원본이 보존 정책으로 삭제되면 START만으로 그 데이터를 되살릴 수 없습니다.
FORCE는 하위에서 상위 순서로 실행하고, 실패하면 최초 오류·상태·소스 접근 가능성을 확인합니다.
## 정리
```sql
DROP ROLLUP ch6_control_ru;
DROP TABLE ch6_control;
```
자세한 명령 계약은 [EXEC 레퍼런스](../../reference/sql/syntax/execute-procedure-syntax/)와
[ROLLUP 문제 해결](../../troubleshooting/rollup/)을 참고합니다.
---
title: "6.10 ROLLUP_REBUILD"
url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/rollup-rebuild/
language: kr
kind: page
---
# 6.10 ROLLUP_REBUILD
ROLLUP_REBUILD는 원본의 과거 값 보정 이후 집계를 다시 계산하는 프로시저이며
Standard Edition 전용입니다. FORCE와 달리 영향 버킷을 삭제·재생성합니다. 모든 ROLLUP 정의를
임의로 재구성하는 범용 명령은 아닙니다.
## 지원 대상을 먼저 확인
| 대상 | 현행 경로 |
|---|---|
| WITH ROLLUP으로 만든 완전한 SEC→MIN→HOUR 계층 | 기본 숫자·확장·문서 전체 JSON 경로 |
| 원본에 연결된 지원 Custom 트리 | 1 SEC·1 MIN·1 HOUR 간격과 재구성 경계에 맞는 SELECT 확인 |
| 임의 이름·간격의 수동 일반 ROLLUP | 자동 계층과 같은 지원을 가정하지 않음 |
| SEC가 없는 MIN/HOUR만의 자동 계층 | 완전한 기본 계층으로 간주하지 않음 |
| 10 MIN 등 다른 간격의 Custom | 현행 시간 경계 생성 경로에 지원되지 않음 |
| Cluster Edition | 미지원 |
Custom의 SELECT가 만드는 버킷과 INTERVAL, 기준 시간대가 재구성 경계와 일치해야 합니다.
지원하지 않는 작업이 트리에 섞였는지 먼저 조사합니다. 함수가 모든 Custom 표현식·간격을
생성 가능하다는 이유만으로 재구성할 수 있다고 가정하지 마십시오.
## 시간 인수와 버킷 경계
문자열 시각 또는 상수 문자열을 사용하는 TO_DATE를 지정합니다. 일반 DATETIME 식 전체를
평가하는 경로가 아니므로 NOW 산술식이나 바인딩 매개변수로 예제를 작성하지 않습니다.
시작과 종료 시각이 속한 버킷을 모두 포함하고 버킷 전체로 넓힙니다. 1분 단계에서
00:00:30~00:01:00을 지정하면 00:00과 00:01 두 버킷, 즉
[00:00:00, 00:02:00) 범위가 재계산됩니다. 시작=종료도 그 시각의 버킷을 재계산하며
시작이 종료보다 크면 오류입니다. 상위 시간 단계는 더 넓은 버킷으로 확장됩니다.
## 기본 계층과 Custom 정정 실습
### 1. 준비와 최초 집계
```sql
CREATE TAG TABLE ch6_rebuild (
name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED
) WITH ROLLUP (SEC);
CREATE TAG TABLE ch6_rebuild_dst (
name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, sum_value DOUBLE, cnt LONG
);
CREATE ROLLUP ch6_rebuild_custom INTO (ch6_rebuild_dst)
AS (
SELECT name, DATE_TRUNC('minute', time) AS time, SUM(value), COUNT(value)
FROM ch6_rebuild GROUP BY name, time
) INTERVAL 1 MIN;
INSERT INTO ch6_rebuild VALUES ('S1', TO_DATE('2026-01-01 00:00:00'), 1);
INSERT INTO ch6_rebuild VALUES ('S1', TO_DATE('2026-01-01 00:00:30'), 200);
INSERT INTO ch6_rebuild VALUES ('S1', TO_DATE('2026-01-01 00:01:00'), 300);
INSERT INTO ch6_rebuild VALUES ('S1', TO_DATE('2026-01-01 00:01:30'), 4);
EXEC TABLE_FLUSH(ch6_rebuild);
SELECT DISTINCT ROLLUP_NAME, INTERVAL_TIME FROM V$ROLLUP
WHERE ROOT_TABLE = 'CH6_REBUILD' ORDER BY INTERVAL_TIME, ROLLUP_NAME;
ALTER ROLLUP _CH6_REBUILD_ROLLUP_SEC FORCE;
ALTER ROLLUP _CH6_REBUILD_ROLLUP_MIN FORCE;
ALTER ROLLUP _CH6_REBUILD_ROLLUP_HOUR FORCE;
ALTER ROLLUP ch6_rebuild_custom FORCE;
SELECT rollup('min', 1, time) AS bucket, AVG(value)
FROM ch6_rebuild WHERE name = 'S1'
GROUP BY bucket ORDER BY bucket;
```
제어 명령의 자동 생성 이름은 위 V$ROLLUP 결과와 일치하는지 확인합니다.
다른 객체의 이름을 추측해 실행하지 않습니다. 최초 분 평균은 100.5와 152입니다.
### 2. 원본 보정과 재구성
```sql
UPDATE ch6_rebuild SET value = 20
WHERE name = 'S1' AND time = TO_DATE('2026-01-01 00:00:30');
UPDATE ch6_rebuild SET value = 30
WHERE name = 'S1' AND time = TO_DATE('2026-01-01 00:01:00');
EXEC ROLLUP_REBUILD(ch6_rebuild, 'S1',
TO_DATE('2026-01-01 00:00:30'),
TO_DATE('2026-01-01 00:01:00'));
```
### 3. 원본·일반·Custom 결과 비교
```sql
SELECT DATE_TRUNC('minute', time) AS bucket, SUM(value), COUNT(value), AVG(value)
FROM ch6_rebuild WHERE name = 'S1'
GROUP BY bucket ORDER BY bucket;
SELECT rollup('min', 1, time) AS bucket, SUM(value), COUNT(value), AVG(value)
FROM ch6_rebuild WHERE name = 'S1'
GROUP BY bucket ORDER BY bucket;
SELECT time, SUM(sum_value), SUM(cnt), SUM(sum_value) / SUM(cnt)
FROM ch6_rebuild_dst WHERE name = 'S1'
GROUP BY time ORDER BY time;
SHOW ROLLUPGAP;
```
세 결과는 00:00에 합계 21·건수 2·평균 10.5, 00:01에 합계 34·건수 2·평균 17입니다.
종료 시각 뒤의 00:01:30 값 4도 같은 버킷을 재계산할 때 포함되어야 합니다.
## 운영 영향과 실패 복구
관련 작업은 처리 위치를 따라잡고 중지·재계산·재시작됩니다. 원본의 안정적인 조회를 위한
내부 처리도 수행하므로 “재구성 중 정상 작업이 그대로 계속된다”고 설명하지 않습니다.
사용자 쿼리·수집의 허용 지연과 중지 시간을 검증 환경에서 먼저 측정합니다.
여러 단계가 하나의 원자적 트랜잭션으로 취소된다고 가정하지 않습니다. 실패 시 상태 복구는
최선 시도로 수행되므로 실제 데이터와 V$ROLLUP을 확인합니다. 원래 중지 상태를 그대로
복원해 준다는 보장도 없습니다. 재실행 전 지원 대상·원본 잔존 여부·재집계 범위를 확인합니다.
존재하지 않는 태그는 유효한 재구성 대상이 준비된 상황에서 no-op일 수 있습니다.
성공 응답만으로 의도한 태그를 처리했다고 판단하지 말고 실제 결과를 비교합니다.
원본이 삭제되어 없으면 원래 통계를 복원할 수 없습니다.
## 정리
```sql
DROP ROLLUP ch6_rebuild_custom;
DROP TABLE ch6_rebuild_dst;
DROP TABLE ch6_rebuild CASCADE;
```
Custom 대상은 따로 제거합니다. 일반 정의 자체를 바꿔야 하거나 이 프로시저 범위를 벗어나면
지원되는 새 정의·데이터 이전 절차를 마련합니다. 내부 저장 테이블을 임의로 삭제하는 SQL을
대체 절차로 제시하지 않습니다. 정확한 인수는
[REBUILD 레퍼런스](../../reference/sql/syntax/rollup-rebuild-syntax/)를 참고합니다.
---
title: "6.11 ROLLUP 성능 튜닝"
url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/performance-tuning-rollup/
language: kr
kind: page
---
# 6.11 ROLLUP 성능 튜닝
## 같은 결과를 만든 뒤 비용 비교
ROLLUP의 효과는 읽는 원시 행을 줄이는 데 있습니다. 먼저 태그, 시간 구간, NULL 처리와
집계 기준이 같은 결과인지 확인한 뒤 실행 시간·CPU·I/O·메모리를 비교합니다.
작은 예제의 실행 시간이 운영 성능을 보장하지는 않습니다.
## 비교 실습
```sql
CREATE TAG TABLE ch6_perf (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE,
quality INTEGER
);
CREATE ROLLUP ch6_perf_ru ON ch6_perf(value) INTERVAL 1 MIN;
INSERT INTO ch6_perf VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 1);
INSERT INTO ch6_perf VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 1);
INSERT INTO ch6_perf VALUES ('TEMP_01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'), 30.0, 1);
INSERT INTO ch6_perf VALUES ('TEMP_02', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.0, 1);
EXEC TABLE_FLUSH(ch6_perf);
ALTER ROLLUP ch6_perf_ru FORCE;
SELECT DATE_TRUNC('minute', time) AS bucket,
SUM(value), COUNT(value), AVG(value), MIN(value), MAX(value)
FROM ch6_perf
WHERE name = 'TEMP_01'
AND time >= TO_DATE('2026-01-01 00:00:00')
AND time < TO_DATE('2026-01-01 00:02:00')
GROUP BY bucket ORDER BY bucket;
SELECT rollup('min', 1, time) AS bucket,
SUM(value), COUNT(value), AVG(value), MIN(value), MAX(value)
FROM ch6_perf
WHERE name = 'TEMP_01'
AND time >= TO_DATE('2026-01-01 00:00:00')
AND time < TO_DATE('2026-01-01 00:02:00')
GROUP BY bucket ORDER BY bucket;
EXPLAIN SELECT rollup('min', 1, time) AS bucket, AVG(value)
FROM ch6_perf WHERE name = 'TEMP_01'
GROUP BY bucket;
```
두 결과는 00:00의 합계 30·건수 2·평균 15·최솟값 10·최댓값 20과,
00:01의 합계 30·건수 1·평균 30·최솟값 30·최댓값 30입니다.
원본 DATE_TRUNC 쿼리의 실행 계획도 같은 방식으로 확인합니다.
## 운영 부하 측정
| 항목 | 함께 기록할 조건 |
|---|---|
| 입력 처리량 | 태그 수·입력률·행 폭·동시 입력자 |
| 조회 지연 | 태그 범위·버킷·동시 쿼리·캐시가 차갑거나 준비된 상태 |
| 집계 지연 | 계층별 gap·작업 상태·처리 시간 |
| 저장량 | 원본·집계·인덱스·압축·복제본 |
| 변경 영향 | WAKEUP 주기와 계층 변경 전후의 입력·조회 비용 |
WAKEUP을 짧게 하면 같은 버킷에 더 자주 부분 집계가 생길 수 있습니다. 집계 버킷을
작게 하면 저장·재집계 양이 늘어납니다. 두 간격을 같은 튜닝 항목으로 취급하지 않습니다.
단일 실행뿐 아니라 입력과 조회가 동시에 진행되는 상황을 측정합니다.
## 계층 크기의 의미
태그 하나가 30일 동안 모든 구간에 데이터를 가진다는 가정의 논리 버킷 수입니다.
| 조회 구간 | 버킷 수 |
|---|---:|
| 1초 | 2,592,000 |
| 1분 | 43,200 |
| 1시간 | 720 |
| 1일 | 30 |
이는 물리 저장 행 수가 아닙니다. 여러 부분 집계, 빈 구간, NULL과 조건 필터를 포함한
실제 저장량은 표본 적재로 측정합니다. 가장 거친 후보가 항상 올바른 결과를 만드는 것도
아니므로 필요한 해상도·origin·후보 선택 제약을 함께 확인합니다.
일 단위 결과에는 적용 가능한 HOUR/MIN/SEC 집계를 재집계합니다.
24 HOUR ROLLUP을 `rollup('day', 1, ...)`의 대체 저장 계층으로 권장하지 않습니다.
[조회 후보 규칙](../query-syntax-rollup/)과 실제 EXPLAIN을 기준으로 합니다.
## 지연·불일치의 분리
gap=0은 처리 위치를 따라잡았다는 뜻이지 원본 보정이 이미 반영됐다는 뜻은 아닙니다.
성능 비교 전에는 집계 진행과 과거 정정 상태를 구분하고, 정의·필터가 같은지 확인합니다.
적용 가능한 ROLLUP이 없는 `rollup()` 쿼리를 원본 성능 측정용으로 사용하지 않습니다.
## 정리
```sql
DROP ROLLUP ch6_perf_ru;
DROP TABLE ch6_perf;
```
[제어와 상태](../ingestion-control-rollup/), [계층 설계](../target-tag-table-design/),
[문제 해결](../../troubleshooting/rollup/)에서 다음 조정을 선택합니다.
---
title: "6.12 ROLLUP 활용 시나리오"
url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/patterns-scenarios/
language: kr
kind: page
---
# 6.12 ROLLUP 활용 시나리오
## 센서별 원본과 구간 통계
두 센서가 같은 시각에 측정해도 단위가 다르면 평균을 섞지 않습니다. 아래는 현재 단위를
메타데이터로 관리하고 태그별 집계를 조회하는 독립 실습입니다.
### 1. 생성과 입력
```sql
CREATE TAG TABLE ch6_scenario (
name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE
) METADATA (unit VARCHAR(16));
INSERT INTO ch6_scenario METADATA VALUES ('TEMP_01', 'celsius');
INSERT INTO ch6_scenario METADATA VALUES ('PRESS_01', 'bar');
INSERT INTO ch6_scenario VALUES ('TEMP_01', TO_DATE('2026-01-01 10:00:00'), 20);
INSERT INTO ch6_scenario VALUES ('TEMP_01', TO_DATE('2026-01-01 10:00:30'), 22);
INSERT INTO ch6_scenario VALUES ('PRESS_01', TO_DATE('2026-01-01 10:00:00'), 1.02);
CREATE ROLLUP ch6_scenario_ru ON ch6_scenario(value) INTERVAL 1 MIN;
EXEC TABLE_FLUSH(ch6_scenario);
ALTER ROLLUP ch6_scenario_ru FORCE;
```
원본 입력 뒤 ROLLUP을 만들었어도 남아 있는 데이터를 초기 집계합니다.
생성 완료만으로 초기 집계까지 끝났다고 판단하지 않습니다.
### 2. 결과 확인
```sql
SELECT name, unit FROM ch6_scenario METADATA ORDER BY name;
SELECT name, DATE_TRUNC('minute', time) AS bucket, COUNT(value), AVG(value)
FROM ch6_scenario GROUP BY name, bucket ORDER BY name, bucket;
SELECT name, rollup('min', 1, time) AS bucket, COUNT(value), AVG(value)
FROM ch6_scenario GROUP BY name, bucket ORDER BY name, bucket;
SHOW ROLLUPGAP;
```
TEMP_01은 2건·평균 21°C, PRESS_01은 1건·평균 1.02bar입니다.
메타데이터 조회는 현재 단위이며 과거 단위 변경 이력을 자동 보존하지 않습니다.
### 3. 개별 관측 확인
고정 시각 데이터를 입력했으므로 같은 고정 범위를 조회합니다. 이 데이터에 현재 시각의
“최근 5분” 조건을 적용하면 실행 날짜에 따라 빈 결과가 됩니다.
```sql
SELECT name, time, value FROM ch6_scenario
WHERE name = 'TEMP_01'
AND time >= TO_DATE('2026-01-01 10:00:00')
AND time < TO_DATE('2026-01-01 10:01:00')
ORDER BY time;
```
두 원본 값은 20과 22입니다. 통계 평균 21에서 각 관측이나 발생 순서를 복원할 수는 없습니다.
### 4. 정리
```sql
DROP ROLLUP ch6_scenario_ru;
DROP TABLE ch6_scenario;
```
## 업무별 적용
| 요구 | 확인할 설계 |
|---|---|
| 정상 품질 통계 | 조건 필터와 후보 힌트를 고정해 원본과 비교 |
| OHLC | 확장 ROLLUP의 FIRST/LAST 또는 Custom의 보조 시각 재집계 |
| 다중 센서 비교 | 태그별 단위와 같은 버킷·조회 범위 사용 |
| 누적 계량기 소비량 | 경계 차이, 초기화·교체·누락 규칙; 표본 평균과 구분 |
| 가동률 | 표본 비율과 시간 비율 구분, 결측 구간 정책 |
| 최신 원본과 장기 집계 결합 | 집계 완료가 확인된 기준점에서 구간을 겹치지 않게 분리 |
원본과 ROLLUP 결과를 UNION ALL로 합칠 때는 경계의 중복·누락과 서로 다른 표본 수를
확인합니다. “최근 2분은 언제나 미집계” 같은 고정 지연을 보장으로 사용하지 않습니다.
부분 결과를 다시 합쳐 평균을 구한다면 합계와 유효 건수를 전달합니다.
기능별 완결 실습은 [조건](../conditional-rollup/), [확장](../extension-rollup/),
[JSON](../json-summarized-rollup/), [Custom](../custom-rollup/)에 있습니다.
문제가 생기면 [진단 순서](../../troubleshooting/rollup/)로 상태와 의미를 먼저 확인합니다.
---
title: "7. LOG 테이블 활용"
url: https://docs.machbase.com/kr/dbms/log-table-usage/
language: kr
kind: section
---
# 7. LOG 테이블 활용
로그를 저장하는 것과 필요한 로그를 찾아내는 것은 다른 일입니다. 장애를 분석하다 보면
“이 시각은 발생 시각인가, 수집 시각인가?”, “메시지에 있는 단어가 왜 검색되지 않는가?”처럼
입력할 때는 생각하지 못했던 문제를 만나게 됩니다.
이 장에서는 LOG 테이블을 선택하고 설계하는 단계부터 입력, 검색, 보존 관리까지 연결해서
살펴봅니다. 명령을 실행하는 방법뿐 아니라 결과를 확인하는 기준도 함께 익혀 보세요.
LOG는 원본 이벤트를 계속 추가하는 모델입니다. 저장한 행을 반복해서 수정하는 업무
테이블과는 사용법이 다릅니다.
## 이 장의 구성
처음이라면 개요와 스키마를 읽고 생성·입력·조회를 순서대로 따라가는 편이 좋습니다.
시간 조건이 헷갈리면 7.10을, 메시지 검색이 목적이라면 7.11을 먼저 읽어도 됩니다.
| 절 | 읽고 나면 할 수 있는 일 |
|---|---|
| [7.1 개요와 사용 기준](./overview-use-criteria/) | LOG와 다른 테이블의 용도 구분 |
| [7.2 테이블 구조와 스키마](./table-structure-schema/) | 발생 시각·검색 필드·원문 분리 |
| [7.3 생성, 변경, 삭제](./create-alter-drop/) | 기존 데이터를 확인하며 스키마 변경 |
| [7.4 데이터 입력](./data-input-mutation/) | INSERT·Append·파일 적재 경로 선택 |
| [7.5 조회와 분석](./query-analysis/) | 시간 범위 조회와 기준 정보 조인 |
| [7.6 인덱스와 성능](./index-performance/) | 인덱스 선택과 실행 계획 확인 |
| [7.7 운영과 데이터 생명주기](./operations-lifecycle/) | 삭제 경계 확인과 보존 정책 적용 |
| [7.8 제약, 오류, 문제 해결](./constraints-errors-troubleshooting/) | 증상에서 원인과 조치 찾기 |
| [7.9 활용 패턴과 시나리오](./patterns-scenarios/) | 애플리케이션 로그 입력·검색·집계 |
| [7.10 _arrival_time 시간 모델](./arrival-time-model/) | 자동 시각·명시 시각·역전 입력 구분 |
| [7.11 텍스트 검색과 KEYWORD 인덱스](./text-search-keyword-index/) | 검색 방식에 따른 결과 차이 이해 |
| [7.12 네트워크 타입 조회](./regex-network-query/) | IPV4·IPV6 주소와 범위 조회 |
## 실습 환경
이 장의 예제는 DBMS 8.7 문서 기준이며, 별도 표시가 없는 SQL 실습은 Standard Edition의
검증용 환경을 기준으로 합니다. 테이블과 인덱스를 만들 수 있는 계정을 사용하세요.
Cluster 환경은 [Edition별 차이](/dbms/reference/support-scope-constraints/edition/)와
해당 운영 절차를 함께 확인해야 합니다.
실습 객체는 `ch7_`로 시작합니다. 각 절에서 필요한 테이블을 직접 만들기 때문에 다른
절을 실행하지 않아도 따라갈 수 있습니다. 같은 절을 재실행할 때는 마지막 정리 SQL까지
실행했는지 확인하세요. 의도적으로 실패하는 SQL은 정상 실습과 분리해 두었습니다.
주의: `DELETE`, `TRUNCATE`, `DROP`은 데이터를 지우는 명령입니다.
예제 이름을 운영 테이블 이름으로 바꾸어 실행하지 마세요.
실습 결과가 다르다면 실행한 SQL과 실제 결과를 함께 살펴보세요. 어느 단계부터 차이가
났는지 확인하면 원인을 훨씬 좁히기 좋습니다.
---
title: "7.1 개요와 사용 기준"
url: https://docs.machbase.com/kr/dbms/log-table-usage/overview-use-criteria/
language: kr
kind: page
---
# 7.1 개요와 사용 기준
시간이 붙은 데이터라고 모두 같은 테이블에 저장할 필요는 없습니다.
주기적으로 측정한 온도와 “장치가 재접속했다”는 이벤트는 분석 방법이 다르기 때문입니다.
LOG를 선택할 때는 시간 컬럼의 유무보다 한 행이 무엇을 나타내는지부터 생각해 보세요.
## LOG 테이블의 특성
애플리케이션 오류, 보안 장비의 차단 기록, 작업 시작·종료 이력처럼 사건을 하나씩 추가하는
데이터에 LOG가 적합합니다. 사용자 컬럼 외에 `_arrival_time`이 자동으로 생기며,
시간 범위 조건과 메시지 검색을 함께 사용할 수 있습니다.
다음 예제로 이벤트 발생 시각과 입력 시각의 차이를 확인해 보겠습니다.
```sql
CREATE LOG TABLE ch7_overview (
event_time DATETIME,
device VARCHAR(32),
message VARCHAR(128)
);
INSERT INTO ch7_overview VALUES (
TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'),
'DEV-01', 'connection restored'
);
SELECT _arrival_time, event_time, device, message FROM ch7_overview;
DROP TABLE ch7_overview;
```
한 행이 조회됩니다. `event_time`은 예제에 넣은 고정 시각이고, `_arrival_time`은
이번 입력에서 자동으로 정해진 시각입니다. 따라서 실행 날짜가 달라도 `event_time`은
바뀌지 않습니다. 명시 입력과 보정 규칙은 [시간 모델](../arrival-time-model/)에서 다룹니다.
## 사용 기준
“지난 한 시간의 오류”, “이 IP 주소가 보낸 요청”, “timeout이 들어간 메시지”를 자주 찾고,
저장한 행을 수정할 필요가 없다면 LOG부터 검토하세요.
지속 입력에는 Append API를, 초기 데이터나 파일 묶음에는 적재 도구를 사용할 수 있습니다.
입력 후 수정할 수 없다는 이유만으로 감사 데이터의 위변조 방지가 보장되지는 않습니다.
삭제 권한, 접근 통제, 보존 정책과 백업은 별도로 설계해야 합니다.
실수하기 쉬운 부분은 재전송입니다. 같은 이벤트를 다시 보내면 또 다른 행으로 저장될 수
있습니다. LOG에는 PRIMARY KEY·UNIQUE 제약이 없으므로 자동 중복 제거를 기대하면 안 됩니다.
원본 이벤트 ID를 보관하고 수집 단계에서 재시도와 중복 처리 정책을 정하세요.
## 다른 테이블과의 비교
| 중심 업무 | 먼저 검토할 테이블 | 판단 기준 |
|---|---|---|
| 센서 이름별 측정값과 시계열 집계 | TAG | 이름·시간축 조회와 ROLLUP이 중심 |
| 장비 이름·설치 위치 같은 현재 기준 정보 | LOOKUP | 작은 참조 데이터를 조회·변경 |
| 주문 상태 변경·행 삭제·트랜잭션 처리 | TRANSACTION | 행을 수정하고 거래 단위로 처리 |
| 재시작 후 사라져도 되는 메모리 상태 | VOLATILE | 영구 보관할 원본 로그와 분리 |
LOG는 일반 `UPDATE`와 임의 조건 `DELETE WHERE`를 지원하지 않습니다.
정정 이벤트를 새로 추가하는 설계는 가능하지만, 원본 ID와 정정 사유를 저장하고
조회 시 어떤 정정을 반영할지 애플리케이션에서 정해야 합니다.
수정이 일상적인 업무라면 처음부터 다른 테이블을 사용하는 편이 명확합니다.
## 설계 기준
분석에 필요한 시간이 발생 시각인지 수집 시각인지 먼저 구분합니다.
이어서 장치·등급·IP처럼 자주 사용하는 조건을 정하세요. 이런 값은 원문에서 매번
잘라내기보다 별도 컬럼으로 두어야 쿼리를 이해하고 관리하기 좋습니다.
보관 기간도 이때 정해 두세요. 입력 경로만 준비하고 삭제를 미루면 디스크 사용량은
계속 증가합니다.
다음 [스키마 설계](../table-structure-schema/)에서는 이 질문을 실제 컬럼으로 옮겨 봅니다.
선택이 애매할 때는 대표 이벤트 몇 건과 자주 사용할 쿼리를 나란히 놓고 비교해 보세요.
---
title: "7.2 테이블 구조와 스키마"
url: https://docs.machbase.com/kr/dbms/log-table-usage/table-structure-schema/
language: kr
kind: page
---
# 7.2 테이블 구조와 스키마
메시지 전체를 한 컬럼에 넣으면 수집을 빨리 시작할 수 있습니다. 하지만 나중에 장비별 오류
건수를 구하려고 매번 원문을 파싱하면 쿼리가 복잡해집니다. 원문은 보존하되, 반복해서
조회하고 집계할 값은 별도 컬럼으로 꺼내 두는 것이 이 절의 핵심입니다.
## 컬럼 구성
다음은 보안 이벤트 한 건을 저장하고 확인하는 독립 실습입니다.
```sql
CREATE LOG TABLE ch7_schema (
event_time DATETIME,
event_id VARCHAR(64),
device VARCHAR(32),
severity SHORT,
src_ip IPV4,
dst_port INTEGER,
message TEXT
);
INSERT INTO ch7_schema VALUES (
TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'),
'evt-0001', 'FW-01', 3, '192.0.2.10', 65535,
'connection blocked by policy'
);
SELECT event_id, device, severity, src_ip, dst_port, message
FROM ch7_schema;
```
`evt-0001` 한 행과 포트 `65535`가 조회됩니다.
여기서 `event_id`는 추적용 값이지, 중복 입력을 막는 키 제약이 아닙니다.
`event_time`에는 원본이 기록한 발생 시각을 저장합니다.
자동 컬럼인 `_arrival_time`을 DDL에 다시 선언하지 마세요.
네트워크 지연이나 일괄 이관이 있으면 두 시각이 달라지는 것은 자연스러운 일입니다.
## 데이터 타입 선택
| 값 | 선택할 타입 | 확인할 점 |
|---|---|---|
| 장치 이름·짧은 코드·이벤트 ID | `VARCHAR(n)` | LOG의 선언 범위는 1~32,767바이트이며 문자 수가 아님 |
| 긴 원문 메시지 | `TEXT` | 최대 64MiB, 원문 자체의 정렬·그룹화는 지원하지 않음 |
| 오류 등급·작은 코드 | `SHORT` 또는 `INTEGER` | 코드별 의미를 수집기와 조회 코드에서 통일 |
| 포트 | `INTEGER` | `USHORT`의 65535는 NULL 예약값이므로 전체 포트 범위에 부적합 |
| 누적 바이트 수 | `LONG` | 예상 최댓값과 NULL 예약값 확인 |
| 주소 | `IPV4` 또는 `IPV6` | 주소 형식과 원본 문자열 보관 필요 구분 |
LOG에서는 VARCHAR와 TEXT 모두 KEYWORD 인덱스로 단어 검색을 할 수 있습니다.
전문 검색을 한다는 이유만으로 짧은 코드까지 TEXT로 만들 필요는 없습니다.
실수하기 쉬운 부분은 문자열 길이입니다. `VARCHAR(100)`은 한글 100글자를 뜻하지 않습니다.
UTF-8 표본의 바이트 수를 확인하고, 길이가 넘는 값을 실제 입력 경로로 보내 보세요.
길이 초과가 잘림이나 오류 중 어떤 형태로 드러나는지는 사용할 SDK·적재 도구까지
함께 확인해야 합니다.
## 정렬·집계 컬럼
위 스키마에서 `device`·`severity`는 검색 조건과 집계 기준이고,
`message`는 원문을 읽거나 단어를 찾는 데 사용합니다.
`ORDER BY message`나 `GROUP BY message`처럼 TEXT 자체를 대상으로 삼으면 오류가 납니다.
메시지를 전부 정렬하기보다 장치, 오류 코드, 발생 시각 중 실제로 필요한 기준을 정하세요.
KEYWORD 인덱스는 형태소 분석기나 검색 점수 기반 검색 엔진과 같지 않습니다.
단어 검색과 원문 부분 문자열 검색의 차이는
[텍스트 검색](../text-search-keyword-index/)에서 확인할 수 있습니다.
## 스키마 변경과 입력 매핑
LOG는 컬럼 추가·삭제·이름 변경과 제한된 속성 변경을 지원합니다.
그렇다고 저장한 행의 값을 UPDATE로 바꿀 수 있는 것은 아닙니다.
특히 컬럼 순서에 맞춰 보내는 Appender와 CSV 매핑은 DDL 변경의 영향을 받으므로
변경 시점과 입력 프로그램 배포를 함께 계획하세요.
실습 테이블을 정리한 뒤 [컬럼 변경 실습](../create-alter-drop/)으로 이어갑니다.
```sql
DROP TABLE ch7_schema;
```
어떤 값을 컬럼으로 꺼낼지 막히면 실제로 답해야 하는 질문부터 적어 보세요.
그 질문을 SQL로 표현해 보면 필요한 컬럼도 더 분명해집니다.
---
title: "7.3 생성, 변경, 삭제"
url: https://docs.machbase.com/kr/dbms/log-table-usage/create-alter-drop/
language: kr
kind: page
---
# 7.3 생성, 변경, 삭제
컬럼을 추가하는 SQL은 짧지만, 운영에서는 “기존 행에 어떤 값이 보이는가?”와
“기존 입력 프로그램이 계속 동작하는가?”까지 확인해야 합니다.
이 절에서는 데이터를 넣어 둔 상태에서 스키마를 바꾸고 그 결과를 살펴봅니다.
## LOG 테이블 생성
테이블 타입을 생략한 `CREATE TABLE`은 TRANSACTION 테이블을 만듭니다.
이 실습에서는 `CREATE LOG TABLE`을 사용하세요.
```sql
CREATE LOG TABLE ch7_ddl (
event_id INTEGER,
category VARCHAR(32),
severity SHORT,
message VARCHAR(128)
);
INSERT INTO ch7_ddl VALUES (1, 'network', 3, 'connection timeout');
```
자동 컬럼 `_arrival_time`은 DDL에 다시 선언하지 않습니다.
실제 발생 시각이 필요하면 별도 DATETIME 컬럼을 두세요.
PRIMARY KEY·UNIQUE 제약은 LOG에서 지원하지 않습니다.
## 컬럼 추가와 기본값
```sql
ALTER TABLE ch7_ddl ADD COLUMN (host_name VARCHAR(64));
ALTER TABLE ch7_ddl ADD COLUMN (source_kind VARCHAR(16) DEFAULT 'agent');
ALTER TABLE ch7_ddl ADD COLUMN (channels INT32[3] DEFAULT [1, NULL, 3]);
SELECT event_id, host_name, source_kind, channels
FROM ch7_ddl
ORDER BY event_id;
```
기존 1번 행의 `host_name`은 NULL, `source_kind`는 `agent`,
`channels`는 `[1, NULL, 3]`으로 조회됩니다.
DEFAULT가 있는 컬럼과 없는 컬럼의 차이를 여기서 확인하세요.
ARRAY DEFAULT는 요소 수가 선언한 길이와 같아야 합니다.
이어서 컬럼 이름을 바꾸고 문자열 길이를 늘립니다.
```sql
ALTER TABLE ch7_ddl RENAME COLUMN category TO event_category;
ALTER TABLE ch7_ddl MODIFY COLUMN (message VARCHAR(4096));
ALTER TABLE ch7_ddl MODIFY COLUMN severity SET MINMAX_CACHE_SIZE = 1048576;
SELECT event_id, event_category, severity, message FROM ch7_ddl;
```
기존 행의 값은 유지됩니다. 이름을 바꾼 뒤에는 조회 SQL도 `event_category`를 사용해야
합니다. MINMAX 예제는 숫자 컬럼에 1MiB를 지정한 것으로, 모든 테이블에 적용할 권장값은
아닙니다.
실수하기 쉬운 부분은 타입 변경입니다. VARCHAR 길이 확장은 TEXT를 VARCHAR로 변환하는
명령이 아닙니다. `MINMAX_CACHE_SIZE` 역시 VARCHAR·TEXT 같은 가변 길이 컬럼에는
설정할 수 없습니다.
## NOT NULL과 기존 데이터
현재 `event_category`에는 값이 있으므로 다음 변경이 가능합니다.
```sql
ALTER TABLE ch7_ddl MODIFY COLUMN event_category NOT NULL;
ALTER TABLE ch7_ddl MODIFY COLUMN event_category NULL;
```
옵션 없는 `NOT NULL`은 기존 행을 검사합니다.
NULL이 있는 `host_name`에는 적용할 수 없습니다. 아래 SQL은 실패를 확인하고 싶을 때만
따로 실행하세요.
```sql
-- 의도적으로 실패: 기존 행의 host_name이 NULL입니다.
ALTER TABLE ch7_ddl MODIFY COLUMN host_name NOT NULL;
```
`NOT NULL NOCHECK`는 기존 NULL 검사를 생략합니다.
기존 NULL을 채우거나 과거 데이터까지 조건을 만족한다고 보장하는 옵션은 아닙니다.
이 실습의 정상 흐름에서는 사용하지 않습니다.
## 인덱스와 컬럼 삭제
```sql
CREATE INDEX ch7_ddl_host_idx ON ch7_ddl(host_name) INDEX_TYPE LSM;
DROP INDEX ch7_ddl_host_idx;
ALTER TABLE ch7_ddl DROP COLUMN (host_name);
ALTER TABLE ch7_ddl DROP COLUMN (source_kind);
ALTER TABLE ch7_ddl DROP COLUMN (channels);
SELECT event_id, event_category, severity, message FROM ch7_ddl;
```
인덱스가 참조하는 컬럼은 인덱스를 먼저 삭제해야 합니다.
내부 컬럼 `_ARRIVAL_TIME`·`_RID`는 삭제·이름 변경·속성 변경 대상이 아니며,
사용자 컬럼은 최소 하나가 남아 있어야 합니다.
VARCHAR 길이는 기존보다 크게만 변경할 수 있고 최대 32,767바이트 범위여야 합니다.
## 데이터 삭제와 테이블 삭제
주의: 다음 명령은 실습 데이터를 지웁니다.
LOG 데이터는 TRANSACTION 테이블의 `ROLLBACK`으로 되돌릴 수 없습니다.
```sql
TRUNCATE TABLE ch7_ddl;
SELECT COUNT(*) AS remaining_rows FROM ch7_ddl;
DROP TABLE ch7_ddl;
```
TRUNCATE 뒤 건수는 0이고 정의는 남습니다. 마지막 DROP은 정의도 삭제합니다.
일부 오래된 데이터만 정리하려면 [보존형 삭제](../operations-lifecycle/)를 사용하세요.
## DDL 운영 주의사항
변경 전에는 입력 작업과 DDL 시점을 조정하고, 변경 후에는 SQL·Appender·파일 매핑의
컬럼 이름·순서·타입을 확인합니다. 리소스 사용 중 오류가 나면 반복 실행하기보다
대상 테이블을 사용하는 작업부터 확인하세요.
어느 변경에서 막혔는지 모르겠다면 변경 전 DDL과 실패한 SQL을 함께 준비해 보세요.
그 두 가지가 있으면 원인을 훨씬 빠르게 좁힐 수 있습니다.
---
title: "7.4 데이터 입력"
url: https://docs.machbase.com/kr/dbms/log-table-usage/data-input-mutation/
language: kr
kind: page
---
# 7.4 데이터 입력
한두 행이 잘 들어간다고 수집 준비가 끝난 것은 아닙니다.
지속 수집에서는 전송 버퍼, 일부 행의 실패, 연결이 끊긴 뒤의 재전송까지 생각해야 합니다.
먼저 SQL로 컬럼과 시간을 확인한 뒤 실제 처리량에 맞는 입력 경로를 선택하세요.
## SQL INSERT
```sql
CREATE LOG TABLE ch7_input (
event_time DATETIME,
event_id VARCHAR(32),
device VARCHAR(32),
message VARCHAR(128)
);
INSERT INTO ch7_input(event_time, event_id, device, message)
VALUES (TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'),
'evt-001', 'DEV-01', 'connection timeout');
SELECT _arrival_time, event_time, event_id, device, message
FROM ch7_input;
```
한 행이 조회되며 `event_time`은 고정된 발생 시각입니다.
`_arrival_time`을 생략했으므로 입력 경로에서 서버 시각을 사용합니다.
기본 설정에서는 시각 역전 시 보정이 일어날 수 있으므로 항상 실제 수신 시각과 정확히
같다고 가정하지 마세요. 자세한 규칙은 [시간 모델](../arrival-time-model/)에서 확인합니다.
같은 이벤트를 다시 넣으면 어떻게 되는지도 확인해 보겠습니다.
```sql
INSERT INTO ch7_input(event_time, event_id, device, message)
VALUES (TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'),
'evt-001', 'DEV-01', 'connection timeout');
SELECT event_id, COUNT(*) AS received_rows
FROM ch7_input
GROUP BY event_id;
DROP TABLE ch7_input;
```
`evt-001`의 건수는 2입니다. 이름이 같아도 LOG가 중복을 제거하지 않습니다.
수집 애플리케이션의 “전송 완료”와 원본 이벤트의 “한 번만 저장”은 별도 문제입니다.
## 입력 경로 선택
| 상황 | 시작할 경로 | 함께 확인할 항목 |
|---|---|---|
| 소량 입력·기능 확인 | SQL INSERT | 컬럼 목록, 타입, 날짜 형식 |
| 애플리케이션의 지속 대량 입력 | SDK Append | 버퍼 전송, 행별 실패, 재접속 정책 |
| 클라이언트에서 읽는 CSV | csvimport·machloader | 컬럼 매핑, 실패 행 파일 |
| 서버에서 읽을 수 있는 적재 파일 | LOAD DATA INFILE | 서버 경로와 파일 접근 권한 |
SQL INSERT는 문장마다 처리 비용이 발생합니다. 지속적인 대량 입력에는 여러 행을 묶어
보내는 Append API를 검토하세요. 언어별 실행 코드는
[개발 및 애플리케이션 연동](/dbms/development-tools-integration/)에서 선택하면 됩니다.
## Append 전송과 오류 처리
Appender에 행을 전달한 시점에는 데이터가 클라이언트 버퍼에 남아 있을 수 있습니다.
사용하는 SDK의 flush·close 동작을 확인하고, 정상 종료뿐 아니라 예외 경로에서도
남은 버퍼와 연결을 처리해야 합니다.
호출이 성공했다고 모든 행이 저장되었다고 단정하지 마세요.
SDK에 따라 반환값, 오류 콜백, 종료 시 성공·실패 건수처럼 결과를 확인하는 경로가 다릅니다.
입력 길이 초과, NULL, 날짜 변환 오류를 일부러 포함한 작은 배치로 먼저 확인하는 편이 좋습니다.
실수하기 쉬운 상황은 응답을 받기 전에 연결이 끊기는 경우입니다.
이미 저장된 배치를 다시 보낼 수 있으므로 원본 이벤트 ID와 처리 위치를 기록하세요.
LOG의 INSERT·Append는 TRANSACTION 테이블 트랜잭션의 ROLLBACK 대상도 아닙니다.
## 파일 적재와 매핑
한글·빈 문자열·NULL·긴 메시지·서로 다른 시간대를 포함한 표본을 준비하세요.
원본 필드 수와 대상 컬럼 순서가 일치하는지 확인한 다음 전체 파일을 처리합니다.
실패 행 파일과 로그도 보관해야 같은 오류를 다시 분석할 수 있습니다.
전체 명령은 [데이터 입력·적재·반출](/dbms/development-tools-integration/data-input-load-export/)을
참고하세요.
과거 데이터 이관에서 `_arrival_time`을 보존하려면 정렬 순서와 기존 대상 데이터까지
확인해야 합니다. 일반 수집의 과거 발생 시각은 별도 `event_time`에 저장하는 편이 안전합니다.
막히면 전체 배치보다 실패한 원본 한 행부터 확인해 보세요.
필드 값, 대상 타입, 사용한 입력 API를 함께 보면 원인을 찾기 수월합니다.
---
title: "7.5 조회와 분석"
url: https://docs.machbase.com/kr/dbms/log-table-usage/query-analysis/
language: kr
kind: page
---
# 7.5 조회와 분석
시간 범위를 조금만 다르게 잡아도 조회 건수가 달라집니다.
특히 하루씩 나누어 집계할 때 끝 시각을 양쪽 구간에 모두 포함하면 경계의 행이 중복됩니다.
이 절에서는 고정 시각 데이터로 범위와 결과 순서를 확인한 뒤 기준 정보를 조인합니다.
## 실습 데이터 준비
```sql
CREATE LOG TABLE ch7_query (
event_id INTEGER,
device VARCHAR(32),
value DOUBLE
);
INSERT INTO ch7_query(_arrival_time, event_id, device, value)
VALUES (TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 1, 'DEV-01', 10);
INSERT INTO ch7_query(_arrival_time, event_id, device, value)
VALUES (TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS'), 2, 'DEV-01', 20);
INSERT INTO ch7_query(_arrival_time, event_id, device, value)
VALUES (TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS'), 3, 'DEV-02', 30);
INSERT INTO ch7_query(_arrival_time, event_id, device, value)
VALUES (TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS'), 4, 'DEV-02', 40);
SELECT _arrival_time, event_id, device, value
FROM ch7_query
ORDER BY _arrival_time, event_id;
```
결과는 1·2·3·4번 순서입니다. 2·3번처럼 같은 시각의 행도 있으므로
순서를 고정하려면 시간뿐 아니라 이벤트 번호까지 정렬 기준에 넣습니다.
이 번호는 예제에서 직접 관리하는 값이며 LOG의 자동 고유 키가 아닙니다.
## 연속 구간과 시간 경계
```sql
SELECT event_id, value
FROM ch7_query
WHERE _arrival_time >= TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND _arrival_time < TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS')
ORDER BY event_id;
```
1·2·3번이 선택됩니다. 다음 구간을 12:00부터 시작하면 4번은 그 구간에서만 집계됩니다.
`BETWEEN`은 양 끝을 포함하므로 이런 연속 구간과 의미가 다릅니다.
사용자 정의 `event_time`을 기준으로 분석할 때도 같은 WHERE 패턴을 사용하면 됩니다.
## DURATION 조회
```sql
SELECT event_id FROM ch7_query
DURATION 1 HOUR BEFORE TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS')
ORDER BY event_id;
SELECT event_id FROM ch7_query
DURATION 1 HOUR AFTER TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS')
ORDER BY event_id;
SELECT event_id FROM ch7_query
DURATION FROM TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS')
TO TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS')
ORDER BY event_id;
```
| 쿼리 | 시간 범위 | 선택되는 event_id |
|---|---|---|
| 12:00 이전 1시간 | 11:00~12:00, 양 끝 포함 | 2, 3, 4 |
| 10:00 이후 1시간 | 10:00~11:00, 양 끝 포함 | 1, 2, 3 |
| 10:00부터 12:00까지 | 양 끝 포함 | 1, 2, 3, 4 |
`DURATION 1 HOUR`처럼 기준 시각을 생략하면 현재 시각을 기준으로 합니다.
따라서 오래된 고정 시각 실습에 그대로 사용하면 결과가 없을 수 있습니다.
문법 위치는 WHERE 다음, GROUP BY·ORDER BY 앞입니다.
실수하기 쉬운 부분은 DURATION이 모든 시간 컬럼에 적용되는 조건이라고 생각하는 것입니다.
DURATION은 LOG 전용이며 `_arrival_time`을 사용합니다. TAG의 시간 컬럼이나 사용자
DATETIME 조건은 WHERE로 지정하세요. 자세한 문법은
[상대 시간·DURATION 사전](/dbms/reference/sql/relative-time/#log-duration)을 참고하세요.
## 스캔 방향과 정렬
DURATION의 BEFORE는 최신 쪽부터, AFTER는 오래된 쪽부터 읽는 방향을 지정합니다.
FROM … TO는 두 시각의 순서에 따라 방향이 달라집니다.
아래는 역순 범위와 같은 시각의 경계를 확인하는 예제입니다.
```sql
SELECT event_id FROM ch7_query
DURATION FROM TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS')
TO TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS')
ORDER BY event_id DESC;
SELECT event_id FROM ch7_query
DURATION FROM TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS')
TO TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS')
ORDER BY event_id;
```
첫 결과는 4·3·2·1번이고, 같은 시각을 지정한 두 번째 결과는 2·3번입니다.
여기서는 스캔 방향과 별개로 ORDER BY를 명시해 출력 순서를 고정했습니다.
하지만 집계·조인 등까지 포함한 최종 결과의 순서가 필요하다면 ORDER BY를 명시하세요.
특히 LIMIT만 사용해서 어떤 행이 나올지 추측하지 않는 것이 좋습니다.
전역 `TABLE_SCAN_DIRECTION`은 다른 쿼리에도 영향을 줄 수 있습니다.
화면 출력 순서를 바꾸기 위해 서버 설정부터 바꾸지는 마세요.
[쿼리 튜닝](/dbms/performance-tuning/performance-query-tuning/)에서 실행 계획을 확인한 뒤
필요한 접근 경로를 조정하세요.
## LOOKUP 조인
```sql
CREATE LOOKUP TABLE ch7_query_device (
device VARCHAR(32) PRIMARY KEY,
label VARCHAR(64)
);
INSERT INTO ch7_query_device VALUES ('DEV-01', 'Boiler');
INSERT INTO ch7_query_device VALUES ('DEV-02', 'Pump');
SELECT q.event_id, d.label, q.value
FROM ch7_query q
JOIN ch7_query_device d ON q.device = d.device
WHERE q._arrival_time >= TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND q._arrival_time < TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS')
ORDER BY q.event_id;
```
결과는 `(1, Boiler, 10)`, `(2, Boiler, 20)`, `(3, Pump, 30)`입니다.
LOG와 LOOKUP을 섞은 쿼리이므로 DURATION 대신 LOG 컬럼의 WHERE 범위를 사용합니다.
이 INNER JOIN에서는 기준 정보가 없는 이벤트가 결과에서 빠진다는 점도 확인하세요.
또한 현재 LOOKUP 값을 조인한다고 과거 시점의 장치 설명이 복원되는 것은 아닙니다.
이력이 필요하면 당시 설명을 이벤트에 저장하거나 유효 기간을 가진 별도 이력을 설계해야 합니다.
```sql
DROP TABLE ch7_query_device;
DROP TABLE ch7_query;
```
조회 건수가 예상과 다르면 시간 경계와 조인 전 건수를 먼저 확인하세요.
조건을 하나씩 추가하면 어디에서 행이 빠지는지 찾기 쉽습니다.
---
title: "7.6 인덱스와 성능"
url: https://docs.machbase.com/kr/dbms/log-table-usage/index-performance/
language: kr
kind: page
---
# 7.6 인덱스와 성능
인덱스를 만들었는데도 조회가 빨라지지 않으면 먼저 쿼리가 그 인덱스를 사용할 수 있는지
확인해야 합니다. 인덱스는 읽기 비용을 줄이는 대신 입력·저장·백그라운드 처리 비용을
늘립니다. 컬럼마다 하나씩 만드는 것보다 대표 조건을 정하는 편이 출발점으로 좋습니다.
## 인덱스 선택
| 조회 조건 | 검토할 인덱스 | 확인할 점 |
|---|---|---|
| 숫자·DATETIME 등의 값과 범위 | LSM | 조건에 맞는 지원 타입과 실제 실행 계획 |
| VARCHAR·TEXT의 단어·토큰 패턴 | KEYWORD | SEARCH·ESEARCH 사용, LIKE와 결과 의미가 다름 |
| 지원 타입의 반복 값 분석 | BITMAP | 값 분포와 인코딩, 입력·저장 비용 |
LOG의 `_arrival_time` 범위는 기본 시간 접근 경로를 먼저 활용합니다.
같은 목적의 인덱스를 관성적으로 추가할 필요는 없습니다.
지원 타입과 속성은 [INDEX 문법](/dbms/reference/sql/syntax/index-syntax/)을
기준으로 확인하세요. 일반적인 RDBMS의 복합 인덱스 설계를 그대로 적용하지 마세요.
## 인덱스 생성 전후 비교
```sql
CREATE LOG TABLE ch7_index (
event_id INTEGER,
event_time DATETIME,
severity SHORT,
message VARCHAR(256)
);
INSERT INTO ch7_index VALUES (
1, TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 1, 'service started');
INSERT INTO ch7_index VALUES (
2, TO_DATE('2026-01-01 10:01:00', 'YYYY-MM-DD HH24:MI:SS'), 3, 'database timeout');
INSERT INTO ch7_index VALUES (
3, TO_DATE('2026-01-01 10:02:00', 'YYYY-MM-DD HH24:MI:SS'), 3, 'connection timeout');
EXPLAIN SELECT event_id FROM ch7_index WHERE severity = 3;
CREATE INDEX ch7_index_time ON ch7_index(event_time) INDEX_TYPE LSM;
CREATE INDEX ch7_index_message ON ch7_index(message) INDEX_TYPE KEYWORD;
CREATE INDEX ch7_index_severity ON ch7_index(severity)
INDEX_TYPE BITMAP BITMAP_ENCODE = RANGE;
EXEC TABLE_FLUSH(ch7_index);
EXEC INDEX_FLUSH(ch7_index);
EXPLAIN SELECT event_id FROM ch7_index WHERE severity = 3;
EXPLAIN SELECT event_id FROM ch7_index WHERE message SEARCH 'timeout';
SELECT event_id FROM ch7_index
WHERE message SEARCH 'timeout'
ORDER BY event_id;
SHOW INDEXES;
```
검색 결과는 2·3번입니다. 실행 계획에서 값 조건과 SEARCH가 사용하는 접근 경로를
비교하고, SHOW INDEXES로 생성된 이름을 확인하세요.
이 세 행은 동작을 이해하기 위한 표본이지 성능 측정 데이터는 아닙니다.
## 데이터와 인덱스 반영
`TABLE_FLUSH`는 테이블 데이터를 반영하는 작업이고, `INDEX_FLUSH`는 인덱스 빌드가
진행될 때까지 기다리는 작업입니다. 실습에서 생성 전후 계획과 시간을 비교할 때는 위처럼
구분해서 사용하세요.
인덱스가 존재하는 것과 입력된 데이터 전체의 인덱스 반영이 끝난 것은 같지 않습니다.
빌드 지연이 검색 비용에 영향을 줄 수 있습니다. 그렇다고 매 행 입력마다 두 명령을
호출하면 배치 입력의 이점을 줄이게 됩니다. 운영에서는 입력률과 백그라운드 처리 속도를
함께 관찰하고 필요한 동기화 시점만 정하세요.
실수하기 쉬운 부분은 인덱스 반영이 늦다는 이유로 같은 데이터를 재입력하는 것입니다.
재입력 전에 원본 조회 건수와 인덱스 상태를 각각 확인해야 중복을 피할 수 있습니다.
## 성능 측정 기준
생성 전후에 같은 데이터량·조건값·동시 입력 부하를 사용하세요.
한 번의 실행 시간뿐 아니라 반복 조회 시간, 입력 처리량, 인덱스 공간과 빌드 지연을
함께 기록합니다. LIKE·REGEXP는 원문 문자열에 조건을 평가하므로 먼저 시간 범위를
제한하면 검사할 대상을 줄일 수 있습니다. KEYWORD 인덱스가 있다고 LIKE가 SEARCH로
바뀌는 것은 아닙니다.
```sql
DROP INDEX ch7_index_severity;
DROP INDEX ch7_index_message;
DROP INDEX ch7_index_time;
DROP TABLE ch7_index;
```
계획을 읽기 어렵다면 쿼리와 EXPLAIN 결과를 함께 비교해 보세요.
[인덱스 튜닝](/dbms/performance-tuning/index-tuning/)의 진단 순서가 다음 확인 지점을
잡는 데 도움이 됩니다.
---
title: "7.7 운영과 데이터 생명주기"
url: https://docs.machbase.com/kr/dbms/log-table-usage/operations-lifecycle/
language: kr
kind: page
---
# 7.7 운영과 데이터 생명주기
입력은 잘 되는데 디스크가 계속 늘어난다면 삭제 기준부터 확인할 차례입니다.
LOG는 업무 조건으로 특정 행을 골라 지우는 모델이 아니라, 오래된 영역부터 정리하는
모델입니다. 보관할 기간과 실제로 삭제되는 경계를 함께 확인해야 합니다.
## 삭제 방법 선택
| 목적 | 명령 | 기준 |
|---|---|---|
| 오래된 N행 삭제 | OLDEST n ROWS | 오래된 입력 행부터 |
| 최근 N행만 남김 | EXCEPT n ROWS | 남길 행 수 |
| 최근 기간만 남김 | EXCEPT n DAY 등 | 서버 현재 시각에서 기간을 뺀 경계 |
| 고정 시각까지 삭제 | BEFORE datetime_expr | 해당 _arrival_time 경계 포함 |
| 전체 데이터 삭제 | 조건 없는 DELETE 또는 TRUNCATE | 전체 행 |
| 주기적인 기간 관리 | Retention Policy | 보존 기간과 실행 주기 |
주의: 삭제는 되돌릴 수 있다고 가정하면 안 됩니다. LOG 데이터는 TRANSACTION 테이블의
ROLLBACK 대상이 아닙니다. 운영에서는 백업과 실제 대상 범위를 확인한 뒤 실행하세요.
## 삭제 방식 비교
명령을 연속해서 실행하면 앞선 삭제가 다음 결과에 영향을 줍니다.
여기서는 같은 세 행을 서로 다른 테이블에 복사해 비교하겠습니다.
```sql
CREATE LOG TABLE ch7_lifecycle (event_id INTEGER);
CREATE LOG TABLE ch7_oldest (event_id INTEGER);
CREATE LOG TABLE ch7_keep (event_id INTEGER);
CREATE LOG TABLE ch7_before (event_id INTEGER);
INSERT INTO ch7_lifecycle(_arrival_time, event_id)
VALUES (TO_DATE('2026-01-01', 'YYYY-MM-DD'), 1);
INSERT INTO ch7_lifecycle(_arrival_time, event_id)
VALUES (TO_DATE('2026-01-02', 'YYYY-MM-DD'), 2);
INSERT INTO ch7_lifecycle(_arrival_time, event_id)
VALUES (TO_DATE('2026-01-03', 'YYYY-MM-DD'), 3);
INSERT INTO ch7_oldest(_arrival_time, event_id)
SELECT _arrival_time, event_id FROM ch7_lifecycle ORDER BY _arrival_time;
INSERT INTO ch7_keep(_arrival_time, event_id)
SELECT _arrival_time, event_id FROM ch7_lifecycle ORDER BY _arrival_time;
INSERT INTO ch7_before(_arrival_time, event_id)
SELECT _arrival_time, event_id FROM ch7_lifecycle ORDER BY _arrival_time;
SELECT COUNT(*) AS delete_candidates FROM ch7_before
WHERE _arrival_time <= TO_DATE('2026-01-02', 'YYYY-MM-DD');
DELETE FROM ch7_oldest OLDEST 1 ROWS;
DELETE FROM ch7_keep EXCEPT 1 ROWS;
DELETE FROM ch7_before BEFORE TO_DATE('2026-01-02', 'YYYY-MM-DD');
SELECT event_id FROM ch7_oldest ORDER BY event_id;
SELECT event_id FROM ch7_keep ORDER BY event_id;
SELECT event_id FROM ch7_before ORDER BY event_id;
```
삭제 전 확인 건수는 2입니다. 각 테이블에 남는 이벤트는 다음과 같습니다.
| 테이블 | 남는 event_id |
|---|---|
| ch7_oldest | 2, 3 |
| ch7_keep | 3 |
| ch7_before | 3 |
실수하기 쉬운 부분은 BEFORE라는 이름입니다.
현재 LOG 삭제는 지정한 시각과 같은 행도 포함합니다.
`WHERE _arrival_time < 경계`로 사전 건수를 세면 삭제 대상과 달라질 수 있으므로
위처럼 `<=`로 확인하세요.
```sql
DELETE FROM ch7_lifecycle;
SELECT COUNT(*) AS remaining_rows FROM ch7_lifecycle;
DROP TABLE ch7_before;
DROP TABLE ch7_keep;
DROP TABLE ch7_oldest;
DROP TABLE ch7_lifecycle;
```
전체 DELETE 뒤 건수는 0이고 테이블 정의는 남습니다.
## 상대 기간 삭제
```sql
CREATE LOG TABLE ch7_period (event_id INTEGER);
INSERT INTO ch7_period(_arrival_time, event_id) VALUES (SYSDATE - 2d, 1);
INSERT INTO ch7_period(_arrival_time, event_id) VALUES (SYSDATE, 2);
DELETE FROM ch7_period EXCEPT 1 DAY;
SELECT event_id FROM ch7_period ORDER BY event_id;
DROP TABLE ch7_period;
```
생성부터 조회까지 바로 실행하면 2번만 남습니다.
기준은 “마지막으로 들어온 행의 시각”이 아니라 서버 현재 시각입니다.
입력이 멈춰 있어도 시간은 계속 흐른다는 점을 보존 정책에도 반영하세요.
## Retention Policy
다음은 1일 보존·1분 주기의 검증용 예제입니다.
운영 권장값이 아니며, 정책 생성과 테이블 연결에 필요한 권한이 있는 계정을 사용하세요.
```sql
CREATE LOG TABLE ch7_retention (event_id INTEGER);
INSERT INTO ch7_retention(_arrival_time, event_id) VALUES (SYSDATE - 2d, 1);
INSERT INTO ch7_retention(_arrival_time, event_id) VALUES (SYSDATE, 2);
CREATE RETENTION ch7_policy DURATION 1 DAY INTERVAL 1 MIN;
ALTER TABLE ch7_retention ADD RETENTION ch7_policy;
SELECT * FROM M$RETENTION WHERE POLICY_NAME = 'CH7_POLICY';
SELECT TABLE_NAME, POLICY_NAME, STATE, LAST_DELETED_TIME
FROM V$RETENTION_JOB WHERE TABLE_NAME = 'CH7_RETENTION';
SELECT event_id FROM ch7_retention ORDER BY event_id;
```
DURATION은 남길 기간, INTERVAL은 삭제를 실행하는 주기입니다.
연결 직후에는 두 행이 보일 수 있습니다. 한 주기와 작업 처리 시간이 지난 뒤 아래 쿼리를
다시 실행해 2번만 남는지 확인하세요. LAST_DELETED_TIME은 삭제 기준 시각이지
벽시계 기준의 작업 완료 시각이 아닙니다.
```sql
SELECT TABLE_NAME, STATE, LAST_DELETED_TIME
FROM V$RETENTION_JOB WHERE TABLE_NAME = 'CH7_RETENTION';
SELECT event_id FROM ch7_retention ORDER BY event_id;
```
실습을 마쳤으면 연결부터 해제한 뒤 정책과 테이블을 삭제합니다.
```sql
ALTER TABLE ch7_retention DROP RETENTION;
SELECT TABLE_NAME FROM V$RETENTION_JOB WHERE TABLE_NAME = 'CH7_RETENTION';
DROP RETENTION ch7_policy;
DROP TABLE ch7_retention;
```
해제 후 작업 조회는 0건입니다. 이미 삭제된 행이 복원되는 것은 아닙니다.
한 테이블에는 하나의 정책을 연결하며, 사용 중인 정책은 먼저 해제해야 삭제할 수 있습니다.
전체 운영 기준은 [데이터 보존 정책](/dbms/operations-configuration-recovery/policy-data-retention/)을
참고하세요.
## 삭제 전 백업 검증
백업 파일이 있다는 사실만으로 복구 준비가 끝나지는 않습니다.
삭제할 기간을 Mount 또는 격리된 Restore 환경에서 실제로 조회해 보세요.
원본, 백업, 별도 집계 데이터의 보관 기간도 각각 정해야 합니다.
환경별 명령은 [백업·복원·마운트](/dbms/operations-configuration-recovery/backup-restore-mount/)를
따릅니다.
## 데이터와 디스크 공간
행이 조회에서 사라지는 것과 운영체제에서 파일 공간이 회수되는 시점은 같지 않을 수
있습니다. 입력량, 남은 행의 가장 오래된 시각, 인덱스·저장소 정리 상태, 디스크 사용량을
함께 확인하세요. 삭제 직후 디스크가 줄지 않는다는 이유로 더 넓은 기간을 지우지 마세요.
삭제 결과가 예상과 다르면 경계 시각과 적용된 정책부터 확인해 보세요.
보관 의무가 있는 데이터라면 추가 삭제를 멈추고 담당자와 범위를 먼저 맞추는 것이 좋습니다.
---
title: "7.8 제약, 오류, 문제 해결"
url: https://docs.machbase.com/kr/dbms/log-table-usage/constraints-errors-troubleshooting/
language: kr
kind: page
---
# 7.8 제약, 오류, 문제 해결
오류가 났을 때 같은 SQL을 반복 실행하면 원인은 그대로이고 상태만 더 복잡해질 수 있습니다.
먼저 지원하지 않는 작업인지, 입력값이나 객체 상태의 문제인지 구분해 보세요.
이 절에서는 확인할 대상을 증상별로 좁혀 봅니다.
## 기능 지원 범위
| 요청 | LOG 지원 여부 | 대안 |
|---|---|---|
| 일반 UPDATE | 미지원 | 정정 이벤트 설계 또는 변경 가능한 테이블 선택 |
| 임의 조건 DELETE WHERE | 미지원 | BEFORE·OLDEST·EXCEPT 또는 모델 변경 |
| PRIMARY KEY·UNIQUE 제약 | 미지원 | 수집 단계의 중복 처리 또는 다른 테이블 선택 |
| 값·범위 인덱스 | LSM 지원 | 타입과 조건에 맞춰 선택 |
| 단어 검색 | KEYWORD 지원 | VARCHAR·TEXT에 생성 후 SEARCH·ESEARCH 사용 |
| BITMAP 분석 인덱스 | 지원 조건 내 사용 | 타입·인코딩·값 분포 확인 |
| TEXT 자체의 ORDER BY·GROUP BY | 미지원 | 별도 코드·등급·시각 컬럼 사용 |
## 증상별 진단
| 증상 | 먼저 확인할 것 | 조치 |
|---|---|---|
| 명시한 도착 시각과 저장 시각이 다름 | 직전 시각과 TIME_INVERSION_MODE | 보정 여부 확인, 발생 시각은 별도 보존 |
| SEARCH에서 인덱스 관련 오류 | 해당 컬럼의 KEYWORD 인덱스 | 타입·테이블·인덱스 이름 확인 |
| 단어가 있는데 검색되지 않음 | SEARCH 토큰과 LIKE 부분 문자열의 차이 | 원문 한 행으로 두 결과 비교 |
| 인덱스를 만들었는데 조회가 느림 | 실행 계획·빌드 상태·시간 범위 | 반영 상태 확인 후 대표 부하 측정 |
| 컬럼 길이 변경 실패 | 기존 타입·새 길이 | VARCHAR 확장만 사용, 최대 길이 확인 |
| MINMAX 변경 실패 | 가변 길이 타입 여부 | LOG의 지원 고정 길이 컬럼만 대상 |
| NOT NULL 변경 실패 | 기존 NULL 행 | 기존 데이터 검사와 NOCHECK 의미 구분 |
| 같은 이벤트가 여러 번 보임 | 원본 ID·재시도·파일 재처리 | 재전송 정책 점검, 임의 행 삭제로 해결하지 않음 |
| DDL 실행 중 리소스 사용 오류 | 입력·조회 작업과 DDL 충돌 | 작업 시점 조정 후 재확인 |
서버 설정과 인덱스 목록은 다음처럼 확인할 수 있습니다.
```sql
SELECT NAME, VALUE FROM V$PROPERTY
WHERE NAME IN ('DISK_COLUMNAR_TABLE_TIME_INVERSION_MODE', 'TABLE_SCAN_DIRECTION');
SHOW INDEXES;
```
설정값을 확인하는 것과 설정을 바꾸는 것은 별도 작업입니다.
원인 확인 없이 운영 서버의 전역 설정부터 바꾸지 마세요.
## 오류 재현과 해결
```sql
CREATE LOG TABLE ch7_error (event_id INTEGER, message TEXT);
INSERT INTO ch7_error VALUES (1, 'connection timeout');
SELECT event_id, message FROM ch7_error;
```
한 행이 조회됩니다. 아래는 각각 의도적으로 실패하는 SQL입니다.
정상 실습에 묶어 실행하지 말고 확인하려는 문장만 따로 실행하세요.
```sql
-- KEYWORD 인덱스가 없는 상태입니다.
SELECT event_id FROM ch7_error WHERE message SEARCH 'timeout';
-- TEXT 자체의 정렬·그룹화는 지원하지 않습니다.
SELECT message FROM ch7_error ORDER BY message;
SELECT message, COUNT(*) FROM ch7_error GROUP BY message;
-- LOG는 일반 UPDATE·조건 DELETE를 지원하지 않습니다.
UPDATE ch7_error SET message = 'fixed' WHERE event_id = 1;
DELETE FROM ch7_error WHERE event_id = 1;
-- TEXT를 VARCHAR로 변환하는 명령이 아닙니다.
ALTER TABLE ch7_error MODIFY COLUMN (message VARCHAR(4096));
```
이제 인덱스를 만들고 지원되는 검색 경로로 확인합니다.
```sql
CREATE INDEX ch7_error_msg ON ch7_error(message) INDEX_TYPE KEYWORD;
EXEC TABLE_FLUSH(ch7_error);
EXEC INDEX_FLUSH(ch7_error);
SELECT event_id, message FROM ch7_error
WHERE message SEARCH 'timeout'
ORDER BY event_id;
DROP TABLE ch7_error;
```
1번 행이 조회되어야 합니다. 인덱스를 추가했다고 TEXT 정렬이나 LOG UPDATE까지
가능해지는 것은 아닙니다.
## 보존 정책 진단
기간이 지난 행이 남아 있다면 적용 정책, 실행 주기, LAST_DELETED_TIME과 실제
`_arrival_time`을 함께 확인하세요. `event_time`만 보고 삭제 실패로 판단하지 마세요.
관련 실습은 [운영과 데이터 생명주기](../operations-lifecycle/)에 있습니다.
문제가 계속되면 서버 버전·Edition, 테이블 DDL, 실패 SQL, 오류 전체와 대표 입력값을
준비해 주세요. 비밀번호와 민감한 로그는 가린 뒤 공유하면 됩니다.
큰 데이터 전체를 보내기보다 같은 증상을 보이는 작은 예제가 원인 파악에 더 도움이 됩니다.
---
title: "7.9 활용 패턴과 시나리오"
url: https://docs.machbase.com/kr/dbms/log-table-usage/patterns-scenarios/
language: kr
kind: page
---
# 7.9 활용 패턴과 시나리오
실제 분석에서는 시간, 호스트, 오류 등급, 메시지를 함께 봅니다.
각 기능을 따로 실행해 보았다면 이제 “어느 서버에서 어떤 오류가 늘었는가?”라는 질문으로
연결해 보겠습니다. 이 실습은 앞 절의 테이블 없이도 실행할 수 있습니다.
## 원본 이벤트와 현재 상태
LOG에는 사건이 발생할 때마다 새 행을 추가합니다.
장비의 현재 이름·위치처럼 변경 가능한 기준 정보는 LOOKUP에 분리할 수 있지만,
과거 상태까지 필요하다면 당시 값을 이벤트에 남기거나 이력을 별도로 설계해야 합니다.
보안 이벤트나 작업 추적에도 같은 접근을 사용할 수 있습니다.
센서 이름별 계측 집계가 중심이라면 TAG를, 업무 행의 수정·트랜잭션이 중심이라면
TRANSACTION을 먼저 검토하세요.
## 로그와 인덱스 생성
시간 조건을 실행 날짜와 무관하게 비교하기 위해 도착 시각을 명시합니다.
빈 테이블에 오름차순으로 입력하는 실습이며, 일반 수집에서는 원본 발생 시각을
별도 DATETIME 컬럼에 보관하는 편이 좋습니다.
```sql
CREATE LOG TABLE ch7_app (
event_id INTEGER,
host VARCHAR(32),
level VARCHAR(16),
message TEXT
);
CREATE INDEX ch7_app_message ON ch7_app(message) INDEX_TYPE KEYWORD;
INSERT INTO ch7_app(_arrival_time, event_id, host, level, message)
VALUES (TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'),
1, 'web-01', 'INFO', 'service started');
INSERT INTO ch7_app(_arrival_time, event_id, host, level, message)
VALUES (TO_DATE('2026-01-01 10:10:00', 'YYYY-MM-DD HH24:MI:SS'),
2, 'web-01', 'WARN', 'slow response');
INSERT INTO ch7_app(_arrival_time, event_id, host, level, message)
VALUES (TO_DATE('2026-01-01 10:20:00', 'YYYY-MM-DD HH24:MI:SS'),
3, 'web-02', 'ERROR', 'database timeout');
INSERT INTO ch7_app(_arrival_time, event_id, host, level, message)
VALUES (TO_DATE('2026-01-01 10:30:00', 'YYYY-MM-DD HH24:MI:SS'),
4, 'web-02', 'ERROR', 'connection refused');
INSERT INTO ch7_app(_arrival_time, event_id, host, level, message)
VALUES (TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS'),
5, 'web-01', 'INFO', 'normal service');
EXEC TABLE_FLUSH(ch7_app);
EXEC INDEX_FLUSH(ch7_app);
SELECT COUNT(*) AS received_rows FROM ch7_app;
```
입력 건수는 5입니다. 실습을 반복하기 전에 마지막 DROP까지 실행했는지 확인하세요.
단순 재전송은 중복 행이 될 수 있습니다.
## 오류와 시간대별 조회
```sql
SELECT event_id, host, message FROM ch7_app
WHERE _arrival_time >= TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND _arrival_time < TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND message SEARCH 'timeout'
ORDER BY event_id;
SELECT event_id, host, level, message FROM ch7_app
WHERE _arrival_time >= TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND _arrival_time < TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND level = 'ERROR'
ORDER BY event_id;
```
첫 쿼리는 web-02의 3번, 두 번째는 3·4번을 선택합니다.
특정 단어로 시작하되 분석할 때는 같은 호스트·시간대의 다른 오류도 함께 확인하는
흐름입니다. 검색 범위를 넓힐 때는 시간 조건을 통째로 제거하기보다 필요한 구간만 늘리세요.
## 시간별·등급별 집계
```sql
SELECT TO_CHAR(_arrival_time, 'YYYY-MM-DD HH24') AS event_hour,
level, COUNT(*) AS event_count
FROM ch7_app
WHERE _arrival_time >= TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS')
AND _arrival_time < TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS')
GROUP BY TO_CHAR(_arrival_time, 'YYYY-MM-DD HH24'), level
ORDER BY event_hour, level;
```
| event_hour | level | event_count |
|---|---|---|
| 2026-01-01 10 | ERROR | 2 |
| 2026-01-01 10 | INFO | 1 |
| 2026-01-01 10 | WARN | 1 |
| 2026-01-01 11 | INFO | 1 |
이 쿼리는 원본 LOG를 읽어 수행하는 집계입니다. TAG의 ROLLUP을 자동으로 사용하는
쿼리가 아닙니다. 데이터가 커지면 조회 구간과 실행 계획을 확인하고, 사전 집계가 필요한지
별도로 판단하세요.
실수하기 쉬운 부분은 고정 시각 데이터에 `DURATION 1 HOUR`를 적용하는 것입니다.
그 조건은 현재 시각 기준이므로 실행 날짜가 바뀌면 표본이 선택되지 않을 수 있습니다.
운영의 최근 로그 조회와 재현용 고정 시각 조회를 구분하세요.
## 수집과 보존 관리
지속 수집은 [Append 입력](../data-input-mutation/)에서 이어서 구성할 수 있습니다.
장기 운영에서는 [보존 정책](../operations-lifecycle/)을 함께 정하세요.
원본 로그, 백업, 별도로 만드는 집계 데이터는 필요한 보관 기간이 다를 수 있습니다.
```sql
DROP TABLE ch7_app;
```
여기까지 결과가 맞으면 실제 로그 몇 건으로 필드와 메시지를 바꾸어 보세요.
한 번에 전체 수집을 옮기기보다 작은 표본으로 같은 질문에 답할 수 있는지 확인하는 편이
문제를 찾기 쉽습니다.
---
title: "7.10 _arrival_time 시간 모델"
url: https://docs.machbase.com/kr/dbms/log-table-usage/arrival-time-model/
language: kr
kind: page
---
# 7.10 _arrival_time 시간 모델
원본 로그에는 어제 시각이 적혀 있는데 조회에서는 오늘 수집한 데이터로 보일 수 있습니다.
발생 시각과 수집 시각을 섞어서 사용하면 정상 입력도 누락처럼 보입니다.
LOG에서는 두 시간을 구분하는 것이 조회와 보존 정책의 출발점입니다.
## 발생 시각과 도착 시각
`event_time` 같은 사용자 DATETIME 컬럼은 “사건이 언제 발생했는가?”에 답합니다.
자동 생성되는 `_arrival_time`은 LOG의 시간 범위 접근과 보존형 삭제 기준입니다.
값을 생략하면 서버 시각을 사용하지만, 명시 입력과 역전 보정도 있으므로
항상 실제 네트워크 수신 시각이라고 단정해서는 안 됩니다.
DATETIME은 나노초 단위 값을 표현합니다. 이는 서버 시계가 매번 나노초 정밀도로 시간을
측정한다는 뜻이 아닙니다. 저장 후 LOG의 UPDATE로 시각을 고칠 수도 없습니다.
## 지연 이벤트 조회
아래는 빈 실습 테이블에 도착 시각을 오름차순으로 지정한 예제입니다.
```sql
CREATE LOG TABLE ch7_time (
event_id INTEGER,
event_time DATETIME,
message VARCHAR(64)
);
INSERT INTO ch7_time(_arrival_time, event_id, event_time, message)
VALUES (TO_DATE('2026-01-02 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 1,
TO_DATE('2026-01-02 09:59:00', 'YYYY-MM-DD HH24:MI:SS'), 'normal arrival');
INSERT INTO ch7_time(_arrival_time, event_id, event_time, message)
VALUES (TO_DATE('2026-01-02 10:01:00', 'YYYY-MM-DD HH24:MI:SS'), 2,
TO_DATE('2026-01-01 23:00:00', 'YYYY-MM-DD HH24:MI:SS'), 'delayed arrival');
SELECT event_id
FROM ch7_time
WHERE event_time >= TO_DATE('2026-01-01', 'YYYY-MM-DD')
AND event_time < TO_DATE('2026-01-02', 'YYYY-MM-DD')
ORDER BY event_id;
SELECT event_id
FROM ch7_time
DURATION FROM TO_DATE('2026-01-02 10:00:00', 'YYYY-MM-DD HH24:MI:SS')
TO TO_DATE('2026-01-02 10:01:00', 'YYYY-MM-DD HH24:MI:SS');
```
발생 시각 조회는 2번만, 도착 시각 조회는 1·2번 모두를 선택합니다.
늦게 들어온 이벤트의 발생 시각을 억지로 바꿀 필요는 없습니다.
## 시각 역전과 보정
`DISK_COLUMNAR_TABLE_TIME_INVERSION_MODE`는 직전 도착 시각보다 작은 값이 들어올 때의
처리를 정합니다. 현재 Standard 코드 기준 동작은 다음과 같습니다.
| 값 | 역전 입력 처리 |
|---|---|
| 1, 기본값 | 직전 저장 `_arrival_time`보다 1ns 큰 값으로 보정 |
| 0 | 시각 역전 오류로 입력 거부 |
같은 시각은 역전이 아니므로 이 규칙만으로 행마다 고유한 시간이 보장되지는 않습니다.
시간이 같을 때도 순서를 고정해야 하면 이벤트 번호 등 추가 기준을 ORDER BY에 넣으세요.
다음은 앞의 실습 테이블을 그대로 사용하는 선택 실습입니다.
먼저 설정을 조회하고, 값이 1인 검증 환경에서만 실행하세요.
이 예제를 위해 운영 서버 설정을 변경하지는 마세요.
```sql
SELECT NAME, VALUE FROM V$PROPERTY
WHERE NAME = 'DISK_COLUMNAR_TABLE_TIME_INVERSION_MODE';
```
```sql
-- 설정값 1에서 실행합니다. 값이 0이면 입력 오류가 예상됩니다.
INSERT INTO ch7_time(_arrival_time, event_id, event_time, message)
VALUES (TO_DATE('2026-01-02 09:00:00', 'YYYY-MM-DD HH24:MI:SS'), 3,
TO_DATE('2026-01-02 08:59:00', 'YYYY-MM-DD HH24:MI:SS'), 'inverted arrival');
SELECT event_id,
TO_CHAR(_arrival_time, 'YYYY-MM-DD HH24:MI:SS mmm:uuu:nnn') AS stored_time
FROM ch7_time
ORDER BY event_id;
```
설정값 1에서는 3번의 저장 시각이 입력한 09:00이 아니라
`2026-01-02 10:01:00 000:000:001`이 됩니다. 발생 시각은 입력한 값 그대로입니다.
따라서 시각 역전 “허용”을 과거 시각의 무조건 보존으로 해석하면 안 됩니다.
## 데이터 이관 주의사항
원본 `_arrival_time`을 보존하려면 빈 대상에 오름차순으로 입력하는 것이 기본입니다.
정렬했더라도 대상에 더 최신 시각의 행이 있거나 다른 입력이 끼어들면 보정·오류가
발생할 수 있습니다. 이관과 일반 실시간 수집을 같은 테이블에서 섞지 마세요.
`DURATION`은 `event_time`이 아닌 `_arrival_time`을 사용합니다.
시간대와 날짜 형식까지 맞췄는데 범위가 다르다면 어떤 시간 컬럼을 조회하고 있는지
다시 확인해 보세요. 경계와 출력 방향은 [조회 실습](../query-analysis/)에서 이어집니다.
```sql
DROP TABLE ch7_time;
```
시간 문제를 문의할 때는 원본 시각, 저장된 시각, 해당 설정값을 함께 준비하면 좋습니다.
세 값을 나란히 놓으면 변환과 보정 중 어느 단계인지 구분하기 쉬워집니다.
---
title: "7.11 텍스트 검색과 KEYWORD 인덱스"
url: https://docs.machbase.com/kr/dbms/log-table-usage/text-search-keyword-index/
language: kr
kind: page
---
# 7.11 텍스트 검색과 KEYWORD 인덱스
메시지에 같은 글자가 들어 있어도 SEARCH와 LIKE의 결과는 다를 수 있습니다.
SEARCH는 색인된 단어를 찾고, LIKE는 원문 문자열에 패턴을 적용하기 때문입니다.
성능을 비교하기 전에 “어떤 행을 찾으려는가”를 먼저 맞춰 보겠습니다.
## 검색 데이터 준비
```sql
CREATE LOG TABLE ch7_search (
event_id INTEGER,
message TEXT
);
CREATE INDEX ch7_search_msg ON ch7_search(message) INDEX_TYPE KEYWORD;
INSERT INTO ch7_search VALUES (1, 'ERROR connection timeout');
INSERT INTO ch7_search VALUES (2, 'connection slowly refused');
INSERT INTO ch7_search VALUES (3, 'pretimeout marker');
INSERT INTO ch7_search VALUES (4, 'normal service');
INSERT INTO ch7_search VALUES (5, NULL);
INSERT INTO ch7_search VALUES (6, '대한민국 연결 오류');
INSERT INTO ch7_search VALUES (7, 'ERR-1001 network');
INSERT INTO ch7_search VALUES (8, 'timeout refused connection');
EXEC TABLE_FLUSH(ch7_search);
EXEC INDEX_FLUSH(ch7_search);
```
표본의 `event_id`로 결과를 비교합니다. 메시지는 TEXT이므로 정렬 기준으로 사용하지
않습니다. LOG의 VARCHAR에도 같은 KEYWORD 검색을 사용할 수 있습니다.
## SEARCH와 NOT SEARCH
```sql
SELECT event_id FROM ch7_search WHERE message SEARCH 'timeout' ORDER BY event_id;
SELECT event_id FROM ch7_search WHERE message SEARCH 'connection refused' ORDER BY event_id;
SELECT event_id FROM ch7_search
WHERE message SEARCH 'connection' AND message SEARCH 'refused'
ORDER BY event_id;
SELECT event_id FROM ch7_search WHERE message NOT SEARCH 'timeout' ORDER BY event_id;
```
| 조건 | 선택되는 event_id | 이유 |
|---|---|---|
| SEARCH 'timeout' | 1, 8 | pretimeout은 별도 단어 |
| SEARCH 'connection refused' | 2, 8 | 두 단어가 모두 존재 |
| SEARCH 두 조건을 AND로 결합 | 2, 8 | 같은 메시지에서 두 단어 확인 |
| NOT SEARCH 'timeout' | 2, 3, 4, 6, 7 | 해당 단어가 없으며 NULL 행은 제외 |
다중 단어 SEARCH는 단어의 어순이나 인접성을 보장하는 구문 검색이 아닙니다.
2번에는 중간 단어가 있고 8번에는 순서가 뒤집혀 있지만 둘 다 선택됩니다.
다른 컬럼까지 SEARCH하려면 그 컬럼에도 해당 인덱스가 필요합니다.
기본 토큰화에서 일반 ASCII 단어는 소문자로 정규화됩니다.
예를 들어 1번의 `ERROR`는 `SEARCH 'error'`로도 검색됩니다.
이를 모든 Unicode 문자의 언어별 대소문자 처리로 확대해서 해석하지 마세요.
### 한글 토큰 분리
```sql
SELECT event_id FROM ch7_search WHERE message SEARCH '대한' ORDER BY event_id;
SELECT event_id FROM ch7_search WHERE message SEARCH '연결' ORDER BY event_id;
```
둘 다 6번이 선택됩니다. 기본 모드에서 `대한민국`은 `대한`, `한민`, `민국`처럼
겹치는 2-gram으로 색인됩니다. 형태소나 문장 의미를 이해하는 검색은 아닙니다.
공백·구두점·한 글자·영문과 한글이 섞인 값은 토큰 경계가 달라질 수 있으므로 실제 표본으로
확인하세요. MODE 같은 인덱스 옵션을 바꾸면 토큰화도 달라질 수 있습니다.
## ESEARCH 확장 검색
```sql
SELECT event_id FROM ch7_search WHERE message ESEARCH 'time%' ORDER BY event_id;
SELECT event_id FROM ch7_search WHERE message ESEARCH '%time%' ORDER BY event_id;
SELECT event_id FROM ch7_search WHERE message ESEARCH 'err%' ORDER BY event_id;
```
| 패턴 | 선택되는 event_id | 의미 |
|---|---|---|
| time% | 1, 8 | time으로 시작하는 단어 |
| %time% | 1, 3, 8 | time을 포함하는 단어 |
| err% | 1, 7 | error 또는 err처럼 err로 시작하는 단어 |
`time%`가 단어 중간의 time까지 찾는 것은 아닙니다.
또한 ESEARCH는 원문 전체에 LIKE를 적용하는 것과 같지 않습니다.
공백·구두점을 가로지르는 원문 패턴을 그대로 ESEARCH에 옮기지 마세요.
복잡한 다중 조건은 별도 SEARCH·ESEARCH 조건을 AND·OR로 결합해 의미를 명시하세요.
현재 비교 경로에서 ESEARCH는 ASCII 대소문자를 구분하지 않습니다.
대상 키워드가 넓게 매칭될수록 검색 비용도 커지므로 항상 LIKE보다 빠르다고 단정할 수는
없습니다. 이 예제는 ASCII 키워드 패턴을 기준으로 합니다.
`NOT ESEARCH` 구문은 지원하지 않습니다. `NOT SEARCH`나 `NOT LIKE`로 바꾸면
검색 의미도 바뀝니다. 어떤 행을 제외할지 다시 정의하고 NULL 처리까지 확인하세요.
## LIKE와 NOT LIKE
```sql
SELECT event_id FROM ch7_search WHERE message LIKE '%TIMEOUT%' ORDER BY event_id;
SELECT event_id FROM ch7_search WHERE message LIKE 'ERR-____' ORDER BY event_id;
SELECT event_id FROM ch7_search WHERE message NOT LIKE '%timeout%' ORDER BY event_id;
```
첫 쿼리는 1·3·8번, 두 번째는 0건, 세 번째는 2·4·6·7번입니다.
7번 메시지는 `ERR-1001` 뒤에도 문자열이 있으므로 `ERR-____` 전체 패턴과 맞지 않습니다.
`%`는 0개 이상의 문자, `_`는 한 문자를 나타냅니다.
리터럴 `%`·`_`·역슬래시를 찾을 때는 역슬래시 이스케이프 규칙도 확인하세요.
LIKE도 현재 ASCII 비교에서는 대소문자를 구분하지 않습니다.
KEYWORD 인덱스는 사용하지 않으며, WHERE의 다른 조건이나 시간 범위로 검사할 행을
줄일 수 있습니다. 따라서 LIKE가 있다는 이유만으로 항상 테이블 전체를 읽는다고
설명하는 것도 정확하지 않습니다.
## REGEXP와 REGEXP_LIKE
```sql
SELECT event_id FROM ch7_search
WHERE message REGEXP '^ERR-[0-9]+'
ORDER BY event_id;
SELECT event_id FROM ch7_search
WHERE message NOT REGEXP 'timeout'
ORDER BY event_id;
```
첫 쿼리는 7번, 두 번째는 2·4·6·7번입니다.
REGEXP는 정규식 패턴에 맞는 부분이 있는지 검사합니다.
문자열 시작이나 끝을 제한하려면 `^`·`$`를 명시하세요.
함수로 사용할 때는 `REGEXP_LIKE`를 사용합니다.
현재 이 함수의 입력은 VARCHAR여야 하고, 패턴과 옵션도 상수 VARCHAR여야 합니다.
앞의 TEXT 컬럼을 그대로 전달하면 타입 오류가 나므로 별도 표본을 준비합니다.
```sql
CREATE LOG TABLE ch7_regexp_fn (event_id INTEGER, message VARCHAR(200));
INSERT INTO ch7_regexp_fn VALUES (1, 'ERROR connection timeout');
INSERT INTO ch7_regexp_fn VALUES (5, NULL);
SELECT event_id,
REGEXP_LIKE(message, 'error') AS case_sensitive,
REGEXP_LIKE(message, 'error', 'i') AS case_insensitive
FROM ch7_regexp_fn
WHERE event_id IN (1, 5)
ORDER BY event_id;
```
1번은 각각 0·1, NULL 메시지인 5번은 두 결과 모두 NULL입니다.
기본 정규식 비교는 대소문자를 구분하며, `i` 옵션은 비구분 비교입니다.
명시적인 구분 비교에는 `c`를 사용할 수 있습니다.
정규식은 KEYWORD 인덱스로 직접 처리되지 않습니다.
시간 조건이나 SEARCH로 대상을 줄일 수 있지만, 선행 조건이 원하는 행을 놓치면
뒤의 정규식이 그 행을 되살려 주지는 못합니다.
## TEXT 제약과 검색 성능
TEXT는 최대 64MiB 원문을 담을 수 있지만, TEXT 자체의 ORDER BY·GROUP BY는 지원하지
않습니다. 정렬·집계할 장치·오류 코드·등급은 별도 컬럼에 두세요.
같은 데이터에서 인덱스 존재 여부, 빌드 상태, 조회 범위를 확인한 뒤 성능을 비교합니다.
```sql
DROP TABLE ch7_regexp_fn;
DROP TABLE ch7_search;
```
검색 결과가 다르면 원문 한 행과 사용한 패턴을 함께 확인해 보세요.
“단어를 찾는지, 원문 일부를 찾는지”를 구분하는 것만으로 해결되는 경우가 많습니다.
---
title: "7.12 네트워크 타입 조회"
url: https://docs.machbase.com/kr/dbms/log-table-usage/regex-network-query/
language: kr
kind: page
---
# 7.12 네트워크 타입 조회
IP 주소를 문자열로 보관하면 눈으로 읽기는 편하지만, 문자열 정렬과 주소 범위의 순서가
같다고 가정하기 쉽습니다. 주소 비교가 목적이라면 IPV4·IPV6 타입을 사용하고,
원문 표기까지 필요할 때만 별도 문자열을 함께 보관하세요.
## 네트워크 데이터 준비
```sql
CREATE LOG TABLE ch7_network (
event_id INTEGER,
src_ip IPV4,
dst_ip IPV6,
dst_port INTEGER
);
INSERT INTO ch7_network VALUES (1, '192.0.2.1', '2001:db8::1', 80);
INSERT INTO ch7_network VALUES (2, '192.0.2.255', '2001:db8::2', 65535);
INSERT INTO ch7_network VALUES (3, '198.51.100.1', '2001:db8::3', 443);
INSERT INTO ch7_network VALUES (4, NULL, NULL, NULL);
SELECT event_id, src_ip, dst_ip, dst_port FROM ch7_network ORDER BY event_id;
```
네 행이 조회됩니다. 포트는 INTEGER를 사용합니다.
USHORT의 최댓값 65535는 NULL 예약값이므로 포트 전체 범위를 그대로 표현하기에 맞지 않습니다.
## 동등 비교와 범위 조회
```sql
SELECT event_id FROM ch7_network
WHERE src_ip BETWEEN '192.0.2.1' AND '192.0.2.255'
ORDER BY event_id;
SELECT event_id FROM ch7_network
WHERE src_ip IN ('192.0.2.1', '198.51.100.1')
ORDER BY event_id;
SELECT event_id FROM ch7_network
WHERE dst_ip = '2001:db8::2'
ORDER BY event_id;
SELECT event_id FROM ch7_network
WHERE dst_ip BETWEEN '2001:db8::1' AND '2001:db8::2'
ORDER BY event_id;
SELECT event_id FROM ch7_network
WHERE src_ip IS NULL
ORDER BY event_id;
```
| 조건 | 선택되는 event_id |
|---|---|
| IPv4 BETWEEN | 1, 2 |
| IPv4 IN | 1, 3 |
| IPv6 동등 비교 | 2 |
| IPv6 BETWEEN | 1, 2 |
| IPv4 IS NULL | 4 |
BETWEEN은 양 끝 주소를 포함합니다. 이 예제의 IPv4 범위는 CIDR 대역의 의미를
자동 적용한 것이 아니라, 직접 지정한 두 주소 사이의 범위입니다.
대역 자체로 판정하려면 아래 `CONTAINED`를 사용하세요.
NULL은 `= NULL`이 아니라 `IS NULL`로 검사하세요.
## CIDR 대역 판정
주소가 특정 네트워크에 속하는지 검사할 때는 `CONTAINED`를 사용합니다. 대역은 반드시
`주소/prefix` 형태로 지정하며 IPv4와 IPv6 모두 사용할 수 있습니다.
```sql
SELECT event_id FROM ch7_network
WHERE src_ip CONTAINED '192.0.2.0/24'
ORDER BY event_id;
SELECT event_id FROM ch7_network
WHERE dst_ip CONTAINED '2001:db8::/32'
ORDER BY event_id;
SELECT event_id FROM ch7_network
WHERE src_ip NOT CONTAINED '192.0.2.0/24'
ORDER BY event_id;
```
| 조건 | 선택되는 event_id |
|---|---|
| IPv4 `CONTAINED '192.0.2.0/24'` | 1, 2 |
| IPv6 `CONTAINED '2001:db8::/32'` | 1, 2, 3 |
| IPv4 `NOT CONTAINED '192.0.2.0/24'` | 3 |
방향을 뒤집은 `'192.0.2.0/24' CONTAINS src_ip`도 같은 의미입니다. 좌변에 네트워크,
우변에 주소가 오는 형태가 `CONTAINS`이고 그 반대가 `CONTAINED`입니다.
prefix 표기를 빼고 `CONTAINED '192.0.2.0'`처럼 쓰면 네트워크로 해석하지 못해 오류입니다.
NULL 주소인 4번은 어느 조건에도 선택되지 않으므로, 미수집 주소를 함께 세야 하면
`IS NULL` 조건을 따로 두십시오.
## 주소 형식과 원문 보존
IPv4만 들어오면 IPV4로, IPv6가 들어오면 IPV6로 스키마를 정합니다.
두 종류를 함께 다루는 경우에는 입력 변환·컬럼 분리 정책을 먼저 정하고 실제 표본으로
검증하세요. 암시적 변환이 원본 주소를 항상 의도대로 정규화해 줄 것이라고 가정하지 마세요.
실수하기 쉬운 부분은 출력 문자열입니다. 같은 IPv6 주소라도 원문 압축 표기와 조회 결과의
표기가 다를 수 있습니다. 원문을 증빙으로 남겨야 한다면 주소 타입 컬럼과 원문 컬럼을
분리하는 편이 명확합니다.
대량 데이터에서는 주소 조건과 함께 `_arrival_time` 범위를 제한하고 실행 계획을
확인하세요. 메시지의 정규식 검색은
[텍스트 검색](../text-search-keyword-index/#regex)과
[SQL 함수 사전](/dbms/reference/sql/functions/)에서 이어서 살펴볼 수 있습니다.
```sql
DROP TABLE ch7_network;
```
주소 조회가 예상과 다르면 원본 문자열, 입력 타입, 범위의 양 끝부터 나란히 비교해 보세요.
문자열 표현의 차이인지 실제 주소 범위의 차이인지 구분하기 쉬워집니다.
---
title: "8. TRANSACTION 테이블 활용"
url: https://docs.machbase.com/kr/dbms/rdb-table-usage/
language: kr
kind: section
---
# 8. TRANSACTION 테이블 활용
원본 로그를 쌓는 일과 주문 상태·재고·장비 정보를 바꾸는 일은 성격이 다릅니다.
상태를 변경하는 업무에서는 “몇 행이 바뀌었는가?”, “중간에 실패하면 어디까지 되돌리는가?”,
“다른 연결이 동시에 수정하면 어떻게 되는가?”까지 확인해야 합니다.
TRANSACTION 테이블은 이런 관계형 조회와 변경을 위한 Standard Edition 전용 테이블입니다.
이 장에서는 작은 표본으로 결과를 확인하면서 스키마, 갱신, 트랜잭션, 동시 접근과 복구를
연결해 봅니다. 내부에 SQLite 저장소를 사용하지만 공개 문법과 지원 범위는 Machbase SQL을
기준으로 해야 합니다. SQLite나 다른 RDBMS의 기능을 모두 그대로 사용할 수 있는 것은 아닙니다.
## 이 장의 구성
| 절 | 확인할 내용 |
|---|---|
| [8.1 개요와 사용 기준](./overview-use-criteria/) | LOG·TAG·LOOKUP과의 역할 구분 |
| [8.2 테이블 구조와 스키마](./table-structure-schema/) | 식별자·업무 키·타입·제약 설계 |
| [8.3 생성, 변경, 삭제](./create-alter-drop/) | DDL 실행과 기존 데이터 확인 |
| [8.4 데이터 입력과 변경](./data-input-mutation/) | 조건부 갱신·삭제·복사·Append |
| [8.5 조회와 분석](./query-analysis/) | 필터·정렬·집계·JSON 조회 |
| [8.6 인덱스와 성능](./index-performance/) | PK·UNIQUE·복합·JSON path 인덱스 |
| [8.7 운영과 데이터 생명주기](./operations-lifecycle/) | 배치 정리와 운영 확인 기준 |
| [8.8 제약, 오류, 문제 해결](./constraints-errors-troubleshooting/) | 증상별 확인과 재시도 판단 |
| [8.9 트랜잭션](./transaction/) | 문장 실패·ROLLBACK·커밋 보장 범위 |
| [8.10 잠금, 충돌, busy timeout](./locking-conflict-timeout/) | 두 연결의 충돌과 스냅샷 재시도 |
| [8.11 JOIN과 관계형 조회 설계](./join-relational-query/) | 조인으로 늘거나 빠지는 행 확인 |
| [8.12 백업, 복원, 마운트](./backup-restore-mount/) | 백업 시점의 데이터를 실제로 검증 |
| [8.13 INSERT ON DUPLICATE KEY UPDATE](./insert-on-duplicate-key-update/) | 삽입·갱신 분기와 중복 처리 |
자동 번호의 공통 문법은
[AUTO_INCREMENT](/dbms/reference/sql/syntax/auto-increment-syntax/)에 있습니다.
## 실습 환경과 실행 단위
SQL 실습은 DBMS 8.7 Standard Edition의 검증 환경을 기준으로 합니다.
각 절에서 `ch8_` 접두사의 객체를 준비하고 정리하므로 다른 절의 실행 결과에 의존하지
않습니다. 테이블·인덱스 생성 권한이 필요하며, 백업 실습에는 별도의 권한과 서버 경로가
필요합니다.
BEGIN부터 COMMIT·ROLLBACK까지는 같은 연결에서 실행하세요.
두 세션 실습은 지정한 A·B 순서를 지켜야 합니다. 의도적으로 실패하는 SQL은 정상 흐름과
분리해 두었습니다. 실습을 다시 실행하기 전에는 정리 SQL까지 끝냈는지 확인하세요.
주의: 테이블 이름에 TRANSACTION이 들어간다고 모든 작업과 모든 장애가 한 번에
되돌려지는 것은 아닙니다. DDL, 다른 타입의 쓰기, 여러 테이블의 장애 시 커밋 경계는
[8.9 트랜잭션](./transaction/)에서 먼저 확인하세요.
---
title: "8.1 개요와 사용 기준"
url: https://docs.machbase.com/kr/dbms/rdb-table-usage/overview-use-criteria/
language: kr
kind: page
---
# 8.1 개요와 사용 기준
장비의 측정값은 계속 쌓이지만, 점검 상태나 재고 수량은 기존 값을 바꾸어야 합니다.
둘을 같은 모델로 처리하려 하면 원본 보관과 상태 변경의 요구가 섞이기 쉽습니다.
TRANSACTION은 변경 가능한 업무 데이터를 맡기고, 원본 시계열은 TAG·LOG에 남기는 구성을
먼저 생각해 보세요.
## TRANSACTION 테이블의 특성
TRANSACTION은 SELECT·INSERT·UPDATE·DELETE, PRIMARY KEY, UNIQUE INDEX와 보조 인덱스를
지원합니다. Standard Edition 전용이며 다음 세 문법은 같은 테이블을 만듭니다.
| 문법 | 의미 |
|---|---|
| CREATE TABLE | 타입을 생략한 기본 TRANSACTION 생성 |
| CREATE TRANSACTION TABLE | 타입을 명시한 생성 |
| CREATE TXN TABLE | 축약형 생성 |
공개 문서와 운영 스크립트에서는 타입이 드러나는 CREATE TRANSACTION TABLE을 권장합니다.
CREATE RDB TABLE·CREATE TRX TABLE은 지원하지 않습니다.
Cluster에서는 위 세 생성 문법을 모두 사용할 수 없으며 LOG는 CREATE LOG TABLE로 명시합니다.
## 상태 변경과 롤백
```sql
CREATE TRANSACTION TABLE ch8_overview (
item_id LONG PRIMARY KEY,
qty INTEGER NOT NULL
);
INSERT INTO ch8_overview VALUES (42, 10);
BEGIN;
UPDATE ch8_overview SET qty = qty - 3 WHERE item_id = 42 AND qty >= 3;
SELECT item_id, qty FROM ch8_overview;
ROLLBACK;
SELECT item_id, qty FROM ch8_overview;
DROP TABLE ch8_overview;
```
같은 연결에서 트랜잭션 안의 조회는 수량 7, ROLLBACK 뒤 조회는 10입니다.
`qty >= 3`은 재고가 부족하면 변경하지 않도록 하는 업무 조건입니다.
실수하기 쉬운 부분은 UPDATE가 오류 없이 끝나면 업무도 성공했다고 판단하는 것입니다.
조건에 맞는 행이 없으면 변경 건수는 0일 수 있습니다. 애플리케이션에서는 영향 행 수가
기대한 1인지 확인하고 다음 작업이나 롤백을 결정해야 합니다.
SQL 예제에 등장하는 숫자만 그대로 바꾸는 것보다 이 확인 절차가 더 중요합니다.
## 다른 테이블과의 비교
| 주된 요구 | 먼저 검토할 테이블 |
|---|---|
| 센서 이름별 측정값과 ROLLUP | TAG |
| 수정하지 않는 로그·이벤트 원본 | LOG |
| 작은 현재 기준 정보 | LOOKUP |
| 관계형 DML과 명시적 트랜잭션 | TRANSACTION |
| 재시작 후 사라져도 되는 메모리 상태 | VOLATILE |
TRANSACTION에는 장비 점검 상태, 업무 이력, 별도 요약 결과 등을 둘 수 있습니다.
대량 원본 수집은 TAG·LOG와 처리량·입력 경로를 비교하세요.
TRANSACTION도 Append를 지원하지만 TAG·LOG와 같은 처리량이나 배치 경계를 가정해서는
안 됩니다. [입력 방식](../data-input-mutation/)에서 구체적으로 구분합니다.
LOOKUP은 TRANSACTION의 모든 기능을 대신하는 테이블이 아닙니다.
Cluster에서 관계형 트랜잭션이 꼭 필요하다면 별도 RDBMS를 포함한 구성을 검토해야 합니다.
## 설계 기준
한 행을 식별할 키와 중복을 막을 업무 키를 구분하세요.
내부 번호에는 PRIMARY KEY, 외부 시스템 코드 같은 별도 고유값에는 UNIQUE INDEX가
필요할 수 있습니다. 자동 번호를 사용해도 업무 키의 중복이 저절로 없어지지는 않습니다.
이어서 자주 실행할 WHERE 조건과 업무의 성공 기준을 정합니다.
트랜잭션을 어디서 끝내고 오류가 나면 무엇을 확인할지까지 정해 두면,
동시 요청이나 연결 장애가 생겨도 처리 방향이 흔들리지 않습니다.
[스키마](../table-structure-schema/)와 [트랜잭션](../transaction/)을 함께 읽어 보세요.
---
title: "8.2 테이블 구조와 스키마"
url: https://docs.machbase.com/kr/dbms/rdb-table-usage/table-structure-schema/
language: kr
kind: page
---
# 8.2 테이블 구조와 스키마
자동 번호가 있으니 중복 걱정은 끝났다고 생각하기 쉽습니다.
하지만 같은 외부 장비가 서로 다른 번호로 두 번 들어오는 일은 여전히 가능합니다.
행을 식별하는 키와 업무상 중복을 막는 키를 구분하는 것이 스키마 설계의 출발점입니다.
## 내부 식별자와 업무 키
다음 예제는 내부 번호와 외부 장비 코드를 따로 관리합니다.
```sql
CREATE TRANSACTION TABLE ch8_schema (
id LONG PRIMARY KEY AUTO_INCREMENT,
external_code VARCHAR(64) NOT NULL,
device_name VARCHAR(128) NOT NULL,
price DECIMAL(18,2),
state JSON,
updated_at DATETIME
);
CREATE UNIQUE INDEX ch8_schema_code ON ch8_schema(external_code);
INSERT INTO ch8_schema(external_code, device_name, price, state, updated_at)
VALUES ('ERP-01', 'Pump A', 19900.25, '{"status":"NORMAL"}',
TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'));
SELECT external_code, device_name, price, state->'$.status' AS status
FROM ch8_schema;
```
ERP-01 한 행, 가격 19900.25, 상태 NORMAL이 조회됩니다.
id는 서버가 부여합니다. 연속 번호나 빈 번호 없는 발급을 업무 조건으로 삼지는 마세요.
발급 번호를 받는 방법은 사용하는 SDK와
[AUTO_INCREMENT](/dbms/reference/sql/syntax/auto-increment-syntax/)를 확인하세요.
## PRIMARY KEY와 UNIQUE
TRANSACTION의 PRIMARY KEY는 테이블당 하나이며 단일 컬럼입니다.
컬럼 뒤에 PRIMARY KEY를 지정하거나, 기존 테이블에 CREATE PRIMARY KEY INDEX로 추가할 수
있습니다. NULL과 중복이 있는 데이터에는 만들 수 없습니다.
여러 컬럼의 조합을 고유하게 만들려면 복합 UNIQUE INDEX를 사용합니다.
CREATE TABLE 내부의 UNIQUE·FOREIGN KEY·테이블 수준 PRIMARY KEY 문법을 다른 DBMS에서
그대로 가져오지 마세요. 고유성은 테이블 생성 후 CREATE UNIQUE INDEX로 지정합니다.
UNIQUE INDEX의 키에 NULL이 포함되면 NULL 포함 키끼리는 중복으로 보지 않습니다.
“코드가 반드시 있고 고유해야 한다”면 예제처럼 NOT NULL도 함께 선언해야 합니다.
실수하기 쉬운 또 다른 값은 빈 문자열입니다. Machbase의 빈 문자열과 NULL 처리도 실제
입력 경로에서 확인하고, 필수 코드는 수집 단계에서 검증하세요.
다음 SQL은 UNIQUE 위반을 확인하는 선택 실습입니다. 정상 입력과 분리해서 실행하세요.
```sql
-- 의도적으로 실패: external_code 중복
INSERT INTO ch8_schema(external_code, device_name)
VALUES ('ERP-01', 'Duplicate Pump');
```
실패 후 ERP-01은 여전히 한 행이어야 합니다.
중복 시 갱신하려면 [UPSERT](../insert-on-duplicate-key-update/)의 별도 규칙을 사용합니다.
## 데이터 타입 선택
| 값 | 타입 선택 | 확인할 사항 |
|---|---|---|
| 식별자·수량 | SHORT·INTEGER·LONG 및 지원 unsigned 타입 | 범위와 NULL 예약값 |
| 측정값·근삿값 | FLOAT·DOUBLE | 부동소수점 반올림 |
| 금액·정확한 소수 | DECIMAL(M,D)와 NUMERIC 등 별칭 | precision·scale·입력 변환 |
| 코드·이름 | VARCHAR(n) | 문자 수가 아닌 바이트 길이 |
| 긴 문자열·바이너리 | TEXT/CLOB·BINARY/BLOB | 저장 지원과 정렬·함수·인덱스 지원을 구분 |
| 발생·변경 시각 | DATETIME | 원본 시간대와 변환 형식 |
| 네트워크 주소 | IPV4·IPV6 | 주소 형식과 비교 의미 |
| 부가 속성 | JSON | 자주 검색할 경로와 타입 |
| 고정 길이 수치 묶음 | 숫자 ARRAY | 요소 타입·길이·whole NULL과 요소 NULL |
전체 범위는 [데이터 타입 사전](/dbms/reference/sql/types/)을,
금액은 [DECIMAL](/dbms/reference/sql/types/decimal-numeric-fixed-point/)을
기준으로 확인하세요. 예제의 DOUBLE을 모든 금액 컬럼에 관성적으로 사용하지 않는 것이 좋습니다.
## 제약과 입력 검증
TRANSACTION에는 최소 하나의 사용자 컬럼이 필요합니다.
LOG의 자동 도착 시각이나 TAG의 METADATA·BASETIME·BASEDISTANCE를 사용할 수 없습니다.
외래 키가 자동으로 참조 무결성을 검사한다고 가정하지 말고, 필요한 관계 검증을
애플리케이션과 데이터 점검 절차에 포함하세요.
예제의 UNIQUE INDEX가 있다고 장비 이름·가격의 업무 유효성까지 검사되는 것은 아닙니다.
필수값, 허용 상태, 수량 범위는 별도로 정의해야 합니다.
```sql
SELECT COUNT(*) AS device_count FROM ch8_schema;
DROP TABLE ch8_schema;
```
정리 전 건수는 1입니다.
스키마 변경은 [생성·변경·삭제](../create-alter-drop/), 조회 경로는
[인덱스 설계](../index-performance/)에서 이어서 확인하세요.
---
title: "8.3 생성, 변경, 삭제"
url: https://docs.machbase.com/kr/dbms/rdb-table-usage/create-alter-drop/
language: kr
kind: page
---
# 8.3 생성, 변경, 삭제
스키마 변경에서는 명령의 성공 여부만큼 기존 데이터와 입력 프로그램의 상태가 중요합니다.
이름을 바꾼 뒤 예전 SQL을 계속 실행하거나, 인덱스가 참조하는 컬럼부터 삭제하면 오류를
만나게 됩니다. 이 절에서는 표본을 넣은 상태에서 변경 전후를 확인합니다.
## 테이블 생성
```sql
CREATE TRANSACTION TABLE ch8_ddl (
id LONG,
code VARCHAR(32),
qty INTEGER
);
INSERT INTO ch8_ddl VALUES (1, 'P-01', 10);
```
## 키와 인덱스 생성
```sql
CREATE PRIMARY KEY INDEX ch8_ddl_pk ON ch8_ddl(id);
CREATE UNIQUE INDEX ch8_ddl_code ON ch8_ddl(code);
CREATE INDEX ch8_ddl_qty ON ch8_ddl(qty);
SHOW INDEX ch8_ddl_code;
```
id는 단일 PRIMARY KEY, code는 별도 업무 키입니다.
기존 데이터에 중복이나 PRIMARY KEY의 NULL이 있으면 생성이 실패할 수 있습니다.
복합 고유성은 CREATE UNIQUE INDEX로 만들며, CREATE TABLE 내부의 UNIQUE 제약 문법은
지원하지 않습니다.
자동 번호가 필요한 경우에는 생성 시 `LONG PRIMARY KEY AUTO_INCREMENT`를 지정합니다.
이 절의 id는 직접 입력한 값이며 자동 번호 컬럼이 아닙니다.
두 생성 방식을 같은 객체에 중복 실행하지 마세요.
[AUTO_INCREMENT](/dbms/reference/sql/syntax/auto-increment-syntax/)에 별도 실습이 있습니다.
## 컬럼 추가와 기본값
```sql
ALTER TABLE ch8_ddl ADD COLUMN (label VARCHAR(64));
ALTER TABLE ch8_ddl ADD COLUMN (status VARCHAR(16) DEFAULT 'NEW');
ALTER TABLE ch8_ddl ADD COLUMN (limits DECIMAL(12)[2] DEFAULT [10, 20]);
SELECT id, label, status, limits FROM ch8_ddl ORDER BY id;
```
1번 행에서 label은 NULL, status는 NEW, limits는 [10, 20]입니다.
DEFAULT가 없는 ARRAY 컬럼을 추가하면 기존 행은 배열 전체가 NULL입니다.
`ADD COLUMN`의 `DEFAULT`와 `CREATE TABLE`의 `DEFAULT`는 허용 범위가 다릅니다.
위처럼 `ADD COLUMN`에는 타입에 맞는 값을 지정할 수 있지만, `CREATE TABLE`의 컬럼 정의에서는
`DATETIME` 컬럼의 `DEFAULT SYSDATE`만 사용할 수 있습니다. 다른 타입이나 다른 값을 지정하면
각각 `ERR-02346`, `ERR-02347`로 거부됩니다. 생성 시점에 기본값이 필요하면 컬럼을 먼저
만들고 `ADD COLUMN`으로 추가하거나, 입력 구문에서 값을 지정하십시오.
배열 안의 일부 요소가 NULL인 경우와 구분하세요.
컬럼 정의는 괄호로 묶습니다. 이 문법은 다른 DBMS의 ALTER TABLE 형식과 혼동하기 쉽습니다.
TRANSACTION은 MODIFY COLUMN으로 길이·타입을 변경하는 기능을 지원하지 않습니다.
필요하면 새 스키마로 이관하는 절차를 별도로 준비하세요.
## 컬럼 변경과 의존 객체
```sql
DROP INDEX ch8_ddl_qty;
ALTER TABLE ch8_ddl DROP COLUMN (qty);
ALTER TABLE ch8_ddl DROP COLUMN (label);
ALTER TABLE ch8_ddl DROP COLUMN (limits);
ALTER TABLE ch8_ddl RENAME COLUMN code TO product_code;
SELECT id, product_code, status FROM ch8_ddl;
SHOW INDEX ch8_ddl_code;
```
기존 1번 행의 P-01·NEW 값과 업무 키 인덱스가 유지됩니다.
PRIMARY KEY·UNIQUE·일반·JSON path 인덱스가 참조하는 컬럼은 해당 인덱스부터 확인해야
합니다. 마지막 사용자 컬럼은 삭제할 수 없습니다.
VIEW가 테이블이나 컬럼을 참조하면 관련 이름 변경·삭제가 거부될 수 있습니다.
VIEW뿐 아니라 애플리케이션 SQL과 prepared statement도 변경 영향을 받습니다.
DDL 이후에는 기존 prepared statement를 무조건 재사용하지 말고 다시 준비할 필요를
확인하세요.
```sql
ALTER TABLE ch8_ddl RENAME TO ch8_product;
SELECT id, product_code, status FROM ch8_product;
```
테이블 이름을 바꾼 뒤에는 ch8_product로 조회합니다.
## 전체 삭제와 테이블 삭제
```sql
BEGIN;
TRUNCATE TABLE ch8_product;
SELECT COUNT(*) AS during_delete FROM ch8_product;
ROLLBACK;
SELECT COUNT(*) AS after_rollback FROM ch8_product;
DROP TABLE ch8_product;
```
건수는 각각 0과 1입니다. 현재 TRANSACTION의 TRUNCATE는 전체 행 삭제로 처리되어
명시적 트랜잭션 안에서 롤백할 수 있습니다. 이 동작을 LOG·TAG의 TRUNCATE에 확대해서
적용하지 마세요. 마지막 DROP은 데이터·정의·관련 인덱스를 삭제합니다.
## DDL 운영 주의사항
TRUNCATE의 위 동작과 CREATE·ALTER·DROP 같은 스키마 작업을 구분해야 합니다.
스키마 변경을 BEGIN 안에 넣어 나중에 ROLLBACK할 수 있다고 가정하지 마세요.
같은 테이블의 활성 트랜잭션이나 열린 커서는 DDL을 차단할 수 있으므로 먼저 결과 집합과
업무 트랜잭션을 정리합니다.
ADD·DROP COLUMN은 카탈로그와 별도 저장 파일을 함께 변경합니다.
작업 중 서버가 중단되었다면 재시작 복구가 끝나기 전에 같은 DDL을 반복하지 마세요.
복구 후 DESC, 대표 SELECT·INSERT, 인덱스·VIEW를 확인하고 서버 로그도 점검합니다.
내부 저장 파일을 직접 이동·수정·삭제하는 방식으로 복구하려 하지 마세요.
예상과 다른 스키마가 보이면 변경 전 DDL, 실행 순서, 첫 오류를 함께 살펴보세요.
마지막 오류만 보는 것보다 원인을 찾기 쉽습니다.
---
title: "8.4 데이터 입력과 변경"
url: https://docs.machbase.com/kr/dbms/rdb-table-usage/data-input-mutation/
language: kr
kind: page
---
# 8.4 데이터 입력과 변경
UPDATE가 성공했다는 응답과 주문 한 건이 원하는 상태로 바뀌었다는 사실은 다릅니다.
조건에 맞는 행이 없으면 오류 없이 0행이 처리될 수 있기 때문입니다.
수정 전 대상, 영향 행 수, 수정 후 값을 함께 확인하는 습관이 필요합니다.
## 조건부 UPDATE와 DELETE
```sql
CREATE TRANSACTION TABLE ch8_mutation (
order_id LONG PRIMARY KEY,
amount DECIMAL(18,2),
status VARCHAR(16),
ordered DATETIME
);
INSERT INTO ch8_mutation VALUES (
1001, 19900.25, 'PENDING', TO_DATE('2026-01-01', 'YYYY-MM-DD'));
INSERT INTO ch8_mutation VALUES (
1002, 29900.50, 'CANCELLED', TO_DATE('2026-01-02', 'YYYY-MM-DD'));
BEGIN;
UPDATE ch8_mutation SET status = 'SHIPPED'
WHERE order_id = 1001 AND status = 'PENDING';
SELECT order_id, status FROM ch8_mutation ORDER BY order_id;
COMMIT;
```
1001은 SHIPPED, 1002는 CANCELLED입니다.
같은 UPDATE를 다시 실행하면 PENDING이 아니므로 영향 행 수가 0입니다.
애플리케이션은 SDK의 영향 행 수를 확인해서 작업 성공·이미 처리됨·대상 없음 등을
업무 규칙에 맞게 구분해야 합니다.
아래 삭제도 대상 건수를 확인한 뒤 트랜잭션 안에서 실행합니다.
```sql
BEGIN;
SELECT COUNT(*) AS delete_candidates FROM ch8_mutation WHERE status = 'CANCELLED';
DELETE FROM ch8_mutation WHERE status = 'CANCELLED';
SELECT order_id, amount, status FROM ch8_mutation ORDER BY order_id;
COMMIT;
```
대상은 1행이고 삭제 후 1001 한 행만 남습니다.
WHERE가 없는 UPDATE·DELETE는 전체 행 대상입니다.
운영에서 사전 SELECT와 실제 변경 사이에 다른 세션이 데이터를 바꿀 수 있으므로
사전 건수만을 성공의 근거로 삼지 마세요.
## INSERT SELECT와 자기 참조
```sql
CREATE TRANSACTION TABLE ch8_archive (
order_id LONG PRIMARY KEY,
amount DECIMAL(18,2),
status VARCHAR(16),
ordered DATETIME
);
INSERT INTO ch8_archive(order_id, amount, status, ordered)
SELECT order_id, amount, status, ordered FROM ch8_mutation
WHERE ordered < TO_DATE('2026-02-01', 'YYYY-MM-DD');
INSERT INTO ch8_mutation(order_id, amount, status, ordered)
SELECT order_id + 10000, amount, status, ordered FROM ch8_mutation
WHERE order_id = 1001;
SELECT order_id FROM ch8_archive ORDER BY order_id;
SELECT order_id FROM ch8_mutation ORDER BY order_id;
```
아카이브는 1001, 원본은 1001·11001입니다.
자기 테이블에서 읽은 결과를 다시 삽입해도 이 예제가 새 행을 끝없이 재입력하지는 않습니다.
다만 키를 바꾸지 않고 복사하면 고유성 위반이 날 수 있습니다.
대상 컬럼 수와 타입을 맞추고 복사 범위를 재실행 가능하게 나누세요.
일반 제약 오류와 연결 장애를 같은 실패로 취급하지 마세요.
문장 실패가 전체 BEGIN을 자동 롤백하지는 않으며, 커밋 응답을 잃었을 때는 반영 여부를
다시 조회해야 합니다. [트랜잭션](../transaction/)에서 처리 경계를 설명합니다.
```sql
DROP TABLE ch8_archive;
DROP TABLE ch8_mutation;
```
## 대량 입력과 배치 경계
TRANSACTION도 Append API를 지원합니다.
예전 파일명이나 앵커에 reject·unsupported가 남아 있다는 이유로 현재 지원하지 않는다고
판단하지 마세요. 언어별 공개 API는
[SDK 기능 지원 범위](/dbms/development-tools-integration/sdk-support-scope/#support-scope-sdk-append)를
기준으로 선택합니다.
| 입력 방식 | 확인할 기준 |
|---|---|
| SQL INSERT·prepared 실행 | 문장별 오류와 명시적 트랜잭션 경계 |
| 드라이버 batch | 실제 전송 단위, 부분 성공, 자동 커밋 |
| Append | SDK 버퍼·서버 배치 경계, 오류 콜백·반환값 |
| machloader | 매핑과 실패 행, 처리 구간 기록 |
현재 SQLCLI SQLAppendBatch 경로는 별도의 활성 트랜잭션이 없으면 서버 배치를
트랜잭션으로 처리합니다. 이 경로의 제약 오류 회귀에서는 실패한 배치 전체가 롤백됩니다.
이를 모든 SDK의 논리 배치, 여러 번의 flush, Appender 전체 수명에 대한 하나의
원자적 작업으로 확대해석해서는 안 됩니다.
AUTO_INCREMENT와 DECIMAL 입력은
[SQLCLI와 ODBC](/dbms/development-tools-integration/cli-odbc/)의 전용 규칙도 확인하세요.
Append 프로토콜의 도착 시각 필드가 TRANSACTION에 LOG의 자동 시간 컬럼을 만드는 것은
아닙니다.
실제 도입 전에는 정상 행에 중복 키·NULL 오류 한 행을 섞어 성공·실패 건수와 저장 결과를
확인하세요. 네트워크 오류 뒤 재전송은 이미 커밋된 데이터의 중복 처리까지 고려해야 합니다.
---
title: "8.5 조회와 분석"
url: https://docs.machbase.com/kr/dbms/rdb-table-usage/query-analysis/
language: kr
kind: page
---
# 8.5 조회와 분석
조회 SQL이 문법상 맞아도 입력한 상태값과 조건이 다르면 결과는 0건입니다.
정렬 기준이 부족하면 같은 시간의 행이 조회할 때마다 다르게 보일 수도 있습니다.
다양한 상태와 경계 시각을 가진 표본으로 필터·정렬·집계를 함께 확인하겠습니다.
## 실습 데이터 준비
```sql
CREATE TRANSACTION TABLE ch8_query (
order_id LONG PRIMARY KEY,
customer VARCHAR(32),
item_id LONG,
amount DECIMAL(18,2),
status VARCHAR(16),
ordered DATETIME
);
CREATE TRANSACTION TABLE ch8_query_product (id LONG PRIMARY KEY, name VARCHAR(64));
INSERT INTO ch8_query_product VALUES (42, 'Pump');
INSERT INTO ch8_query_product VALUES (43, 'Valve');
INSERT INTO ch8_query VALUES (
1001, 'C-01', 42, 10.25, 'PENDING', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO ch8_query VALUES (
1002, 'C-01', 43, 20.50, 'PENDING', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO ch8_query VALUES (
1003, 'C-02', 42, 30.75, 'SHIPPED', TO_DATE('2026-01-02', 'YYYY-MM-DD'));
SELECT order_id, amount, status FROM ch8_query WHERE order_id = 1001;
```
1001·10.25·PENDING이 조회됩니다.
## 시간 조건과 정렬
```sql
SELECT order_id, amount FROM ch8_query
WHERE ordered >= TO_DATE('2026-01-01', 'YYYY-MM-DD')
AND ordered < TO_DATE('2026-01-02', 'YYYY-MM-DD')
AND status = 'PENDING'
ORDER BY ordered DESC, order_id DESC
LIMIT 1;
```
1002 한 행이 선택됩니다. ordered가 같아도 order_id로 순서를 고정했습니다.
LIMIT만 지정해 원하는 순서를 기대하지 마세요.
연속된 일별 집계는 시작 포함·끝 제외 조건으로 경계의 중복을 피할 수 있습니다.
LOG 전용 DURATION이나 자동 _arrival_time은 TRANSACTION에 적용하지 않습니다.
## 기준 정보 조인
```sql
SELECT o.order_id, p.name, o.amount
FROM ch8_query o JOIN ch8_query_product p ON o.item_id = p.id
WHERE o.customer = 'C-01'
ORDER BY o.order_id;
```
결과는 (1001, Pump, 10.25), (1002, Valve, 20.50)입니다.
이 INNER JOIN에서는 기준 정보가 없는 주문이 빠집니다.
조인 상대에 같은 키가 여러 행이면 결과가 늘어나므로 키의 고유성도 확인해야 합니다.
다른 타입과의 조인은 [JOIN 실습](../join-relational-query/)에서 다룹니다.
## 집계 조회
```sql
SELECT status, COUNT(*) AS cnt, SUM(amount) AS total_amount
FROM ch8_query GROUP BY status ORDER BY status;
```
PENDING은 2건·30.75, SHIPPED는 1건·30.75입니다.
인덱스가 있다고 모든 집계가 자동으로 빨라지는 것은 아닙니다.
범위·그룹 수·반환량을 확인하고 반복 업무라면 별도 요약을 검토하세요.
## JSON 경로 조회
```sql
CREATE TRANSACTION TABLE ch8_query_json (id INTEGER PRIMARY KEY, state JSON);
INSERT INTO ch8_query_json VALUES (1, '{"status":"ALARM","score":90}');
INSERT INTO ch8_query_json VALUES (2, '{"status":"NORMAL","score":10}');
INSERT INTO ch8_query_json VALUES (3, '{"score":20}');
SELECT id, state->'$.status' AS status FROM ch8_query_json
WHERE state->'$.status' = 'ALARM'
ORDER BY id;
```
1번만 선택됩니다. 없는 경로와 조건값이 다른 행을 구분해서 표본을 준비하세요.
화살표 경로의 문자열 비교와 숫자 추출 함수의 비교를 혼동하지 마세요.
자주 쓰는 경로는 [JSON path 인덱스](../index-performance/#index-strategy-rdb-json-path)를
검토할 수 있습니다.
## 인덱스와 실행 계획
```sql
CREATE INDEX ch8_query_status_time ON ch8_query(status, ordered);
EXPLAIN SELECT order_id FROM ch8_query
WHERE status = 'PENDING'
AND ordered >= TO_DATE('2026-01-01', 'YYYY-MM-DD');
```
복합 인덱스의 선두 컬럼과 조건을 맞추되, 실제 사용 여부는 EXPLAIN으로 확인하세요.
Machbase가 관계형 쿼리 전체를 내부 SQLite에 그대로 맡긴다고 가정하면
인덱스 선택과 함수 조건을 잘못 해석할 수 있습니다.
```sql
DROP TABLE ch8_query_json;
DROP TABLE ch8_query_product;
DROP TABLE ch8_query;
```
결과가 다르면 집계나 JOIN부터 복잡하게 분석하기보다 원본 건수, WHERE 조건,
정렬 기준을 차례로 확인해 보세요.
---
title: "8.6 인덱스와 성능"
url: https://docs.machbase.com/kr/dbms/rdb-table-usage/index-performance/
language: kr
kind: page
---
# 8.6 인덱스와 성능
인덱스는 조회 속도뿐 아니라 데이터의 고유성을 결정하기도 합니다.
업무 키를 보장할 인덱스와 읽기를 줄일 인덱스를 구분해야,
성능 정리 과정에서 필요한 제약을 실수로 없애지 않습니다.
## 인덱스 유형
| 종류 | 용도 | 복합 컬럼 | NULL |
|---|---|---|---|
| PRIMARY KEY | 행 식별, 테이블당 하나 | 미지원 | 불허 |
| UNIQUE INDEX | 업무 키의 고유성 | 지원 | NULL 포함 키끼리는 중복 아님 |
| 일반 인덱스 | 조건 조회의 접근 경로 | 지원 | 고유성 검사 없음 |
TRANSACTION 인덱스는 BTREE로 표시됩니다.
컬럼의 PRIMARY KEY 또는 사후 CREATE PRIMARY KEY INDEX를 사용할 수 있습니다.
LOG의 LSM·KEYWORD 인덱스 구문을 TRANSACTION에 그대로 적용하지 마세요.
## UNIQUE INDEX와 NULL
```sql
CREATE TRANSACTION TABLE ch8_index_account (
id LONG PRIMARY KEY,
email VARCHAR(120),
tenant INTEGER NOT NULL,
login VARCHAR(64)
);
INSERT INTO ch8_index_account VALUES (1, 'a@example.com', 1, 'alpha');
INSERT INTO ch8_index_account VALUES (2, 'b@example.com', 1, 'beta');
INSERT INTO ch8_index_account VALUES (3, NULL, 2, NULL);
INSERT INTO ch8_index_account VALUES (4, NULL, 2, NULL);
CREATE UNIQUE INDEX ch8_index_email ON ch8_index_account(email);
CREATE UNIQUE INDEX ch8_index_login ON ch8_index_account(tenant, login);
SELECT id FROM ch8_index_account WHERE email IS NULL ORDER BY id;
SHOW INDEX ch8_index_email;
```
3·4번이 모두 존재하고 인덱스도 생성됩니다.
반드시 값이 있어야 하는 업무 키라면 UNIQUE뿐 아니라 각 컬럼의 NOT NULL도 필요합니다.
CREATE TABLE 내부에 UNIQUE를 붙이는 대신 별도 CREATE UNIQUE INDEX를 사용하세요.
다음 두 문장은 각각 고유성 위반을 확인하는 선택 실습입니다.
```sql
-- 의도적으로 실패: email 중복
INSERT INTO ch8_index_account VALUES (5, 'a@example.com', 1, 'gamma');
UPDATE ch8_index_account SET email = 'a@example.com' WHERE id = 2;
```
실패 후에는 네 행과 2번의 b@example.com이 유지되어야 합니다.
현재 일반 UNIQUE 위반은 ERR-01418로 보고됩니다.
오류 메시지만 보고 어떤 업무 키가 중복되었는지 단정하지 말고 인덱스와 입력값을 함께
확인하세요.
## UNIQUE INDEX 삭제
```sql
DROP INDEX ch8_index_email;
INSERT INTO ch8_index_account VALUES (5, 'a@example.com', 1, 'gamma');
SELECT id, email FROM ch8_index_account WHERE email = 'a@example.com' ORDER BY id;
```
이제 1·5번이 모두 저장됩니다.
중복이 있는 상태에서 인덱스를 재생성하면 실패합니다.
```sql
-- 의도적으로 실패: 기존 데이터에 중복이 있습니다.
CREATE UNIQUE INDEX ch8_index_email ON ch8_index_account(email);
```
실습 중 추가한 5번을 제거한 뒤 다시 생성합니다.
```sql
DELETE FROM ch8_index_account WHERE id = 5;
CREATE UNIQUE INDEX ch8_index_email ON ch8_index_account(email);
SELECT COUNT(*) AS remaining_rows FROM ch8_index_account;
```
건수는 4입니다.
운영에서는 인덱스를 없애기 전에 이것이 성능용인지 고유성 보장용인지부터 확인해야 합니다.
## 일반·복합 인덱스
```sql
CREATE TRANSACTION TABLE ch8_index_event (
id LONG PRIMARY KEY,
status VARCHAR(16),
created DATETIME,
state JSON
);
INSERT INTO ch8_index_event VALUES (
1, 'OPEN', TO_DATE('2026-01-01', 'YYYY-MM-DD'), '{"status":"ALARM","code":500}');
INSERT INTO ch8_index_event VALUES (
2, 'CLOSED', TO_DATE('2026-01-02', 'YYYY-MM-DD'), '{"status":"NORMAL","code":200}');
INSERT INTO ch8_index_event VALUES (
3, 'OPEN', TO_DATE('2026-01-03', 'YYYY-MM-DD'), '{"code":500}');
EXPLAIN SELECT id FROM ch8_index_event
WHERE status = 'OPEN' AND created >= TO_DATE('2026-01-01', 'YYYY-MM-DD');
CREATE INDEX ch8_index_status_time ON ch8_index_event(status, created);
EXPLAIN SELECT id FROM ch8_index_event
WHERE status = 'OPEN' AND created >= TO_DATE('2026-01-01', 'YYYY-MM-DD');
SELECT id FROM ch8_index_event
WHERE status = 'OPEN' AND created >= TO_DATE('2026-01-01', 'YYYY-MM-DD')
ORDER BY id;
```
결과는 생성 전후 의미가 같아야 하며 마지막 조회는 1·3번입니다.
선두 컬럼을 조건에 맞추되 모든 복합 조건이 반드시 해당 인덱스를 사용한다고
단정하지 마세요. Machbase의 계획 선택과 지원되는 조건 모양을 EXPLAIN으로 확인합니다.
세 행의 실행 시간은 성능 벤치마크가 아닙니다.
## JSON path 인덱스
```sql
CREATE INDEX ch8_index_json_status ON ch8_index_event(state->'$.status');
CREATE INDEX ch8_index_json_code ON ch8_index_event(state->'$.code');
EXPLAIN SELECT id FROM ch8_index_event WHERE state->'$.status' = 'ALARM';
SELECT id FROM ch8_index_event WHERE state->'$.status' = 'ALARM' ORDER BY id;
SELECT id FROM ch8_index_event WHERE state->'$.code' = '500' ORDER BY id;
```
상태 조회는 1번, code 조회는 1·3번입니다.
인덱스를 만들었다고 모든 JSON 함수 표현식이 이 경로를 사용하는 것은 아닙니다.
화살표 경로와 숫자 추출 함수는 결과 타입과 비교 의미를 구분하세요.
복잡한 조건이 자주 반복되면 일반 컬럼으로 분리한 설계도 비교할 수 있습니다.
JSON path UNIQUE INDEX를 만들 수 있는 경우에도 TRANSACTION UPSERT의 충돌 선택 키로는
사용하지 않습니다. [UPSERT 제약](../insert-on-duplicate-key-update/)을 확인하세요.
## 읽기·쓰기 비용
인덱스 생성 전후에 같은 데이터량·조건값·동시 입력률을 사용하세요.
조회 시간뿐 아니라 INSERT·UPDATE·DELETE 처리량과 인덱스 공간도 함께 비교합니다.
LIMIT은 반환량을 줄이지만 어떤 행을 반환할지 고정하려면 ORDER BY가 필요합니다.
```sql
DROP TABLE ch8_index_event;
DROP TABLE ch8_index_account;
```
성능 때문에 UNIQUE INDEX를 일반 인덱스로 바꾸면 고유성 보장은 사라집니다.
튜닝 전후에 결과와 제약이 같은지 먼저 확인하는 것이 좋은 출발점입니다.
---
title: "8.7 운영과 데이터 생명주기"
url: https://docs.machbase.com/kr/dbms/rdb-table-usage/operations-lifecycle/
language: kr
kind: page
---
# 8.7 운영과 데이터 생명주기
오래된 업무 데이터를 지울 때는 날짜만 보는 것으로 충분하지 않습니다.
취소된 주문과 아직 처리 중인 주문은 보관 기준이 다르고, 정리 작업 자체도 다른 쓰기와
충돌할 수 있습니다. 업무 조건과 작업 단위를 먼저 정한 뒤 삭제를 실행하세요.
## 데이터 보관 기준
TAG·LOG에는 원본 시계열을, TRANSACTION에는 변경 가능한 상태나 요약 결과를 둘 수 있습니다.
두 종류의 보관 기간이 반드시 같을 필요는 없습니다.
TRANSACTION에는 TAG·LOG의 Retention Policy를 그대로 적용하지 않습니다.
업무 조건에 맞는 DELETE와 외부 작업 스케줄을 설계하세요.
## 삭제와 롤백
```sql
CREATE TRANSACTION TABLE ch8_cleanup (
id LONG PRIMARY KEY,
status VARCHAR(16),
created DATETIME
);
INSERT INTO ch8_cleanup VALUES (1, 'CANCELLED', TO_DATE('2025-12-01', 'YYYY-MM-DD'));
INSERT INTO ch8_cleanup VALUES (2, 'PENDING', TO_DATE('2025-12-01', 'YYYY-MM-DD'));
INSERT INTO ch8_cleanup VALUES (3, 'CANCELLED', TO_DATE('2026-02-01', 'YYYY-MM-DD'));
BEGIN;
SELECT COUNT(*) AS delete_candidates FROM ch8_cleanup
WHERE status = 'CANCELLED' AND created < TO_DATE('2026-01-01', 'YYYY-MM-DD');
DELETE FROM ch8_cleanup
WHERE status = 'CANCELLED' AND created < TO_DATE('2026-01-01', 'YYYY-MM-DD');
SELECT id, status FROM ch8_cleanup ORDER BY id;
ROLLBACK;
SELECT COUNT(*) AS after_rollback FROM ch8_cleanup;
```
대상은 1건, 삭제 중에는 2·3번, 롤백 후에는 3건입니다.
확인용 실습에서는 롤백했지만 운영에서 확정할 때는 업무 승인과 결과 확인 후 COMMIT을
선택합니다. 결과 커서는 종료 전에 닫아야 합니다.
실수하기 쉬운 부분은 사전 조회 건수와 실제 영향 행 수를 같은 것으로 보는 것입니다.
동시 변경과 스냅샷 충돌을 고려하고, 실행 시 받은 영향 행 수와 최종 상태까지 기록하세요.
[잠금과 재시도](../locking-conflict-timeout/)에서 관련 상황을 확인할 수 있습니다.
## 배치 처리와 재개
전체 삭제를 하나의 긴 트랜잭션으로 처리하기보다 날짜 구간이나 고유 키 범위로 나눕니다.
각 구간의 경계, 처리 건수와 커밋 결과를 기록하세요.
중간 장애 뒤 어디부터 다시 처리해야 하는지 알 수 있어야 합니다.
기준 정보 이동을 위해 다른 테이블에 복사한 뒤 원본을 삭제할 때도 주의가 필요합니다.
현재 여러 TRANSACTION 테이블의 장애 시 원자적 커밋을 보장한다고 가정하지 마세요.
[트랜잭션의 보장 범위](../transaction/)에 맞춰 복사 결과 확인과 재개 절차를 설계합니다.
외부 API 호출과 긴 파일 작업을 BEGIN 안에서 기다리지 않는 것도 중요합니다.
## 백업 검증
백업 명령의 성공만 확인하지 말고, 마운트한 백업에서 업무 키·행 수·합계·인덱스를 점검하세요.
마운트 조회에는 `마운트명.소유자.테이블명`의 세 부분 이름을 사용합니다.
두 부분 이름을 사용해 운영 데이터와 혼동하지 마세요.
TRANSACTION은 증분 백업에서도 해당 백업 시점의 전체 테이블 저장소 스냅샷이 포함됩니다.
변경한 행 수만큼만 공간이 늘어날 것이라고 계산하지 마세요.
구체적인 실습은 [백업·복원·마운트](../backup-restore-mount/)에 있습니다.
## 정리 후 운영 점검
행이 줄었다고 운영체제의 파일 크기가 곧바로 같은 비율로 줄어드는 것은 아닙니다.
업무 데이터 건수, 실제 파일 사용량, 백업 보관량을 각각 살펴보세요.
파일 크기를 줄이려고 내부 SQLite 파일에 직접 접속하거나 임의로 파일을 삭제하지 마세요.
```sql
DROP TABLE ch8_cleanup;
```
정리 작업이 실패하면 추가 삭제부터 시도하지 말고 마지막 성공 구간과 커밋 결과부터
확인하세요. 이런 기록이 있어야 데이터 누락과 중복 처리를 줄일 수 있습니다.
---
title: "8.8 제약, 오류, 문제 해결"
url: https://docs.machbase.com/kr/dbms/rdb-table-usage/constraints-errors-troubleshooting/
language: kr
kind: page
---
# 8.8 제약, 오류, 문제 해결
다른 RDBMS에서 쓰던 SQL을 옮길 때는 이름이 비슷하다는 이유만으로 같은 기능을 기대하기
쉽습니다. 먼저 Edition과 공개 문법을 확인하고, 그다음 데이터 제약과 동시 접근 문제를
나누어 살펴보세요.
## 기능 지원 범위
TRANSACTION은 Standard Edition 전용입니다.
Cluster에서는 CREATE TABLE·CREATE TRANSACTION TABLE·CREATE TXN TABLE이 모두 거부됩니다.
LOG는 CREATE LOG TABLE로 명시해야 합니다.
| 요구 | 지원·대안 |
|---|---|
| 일반 SELECT·INSERT·UPDATE·DELETE | 지원, WHERE 없는 변경은 전체 행 대상 |
| 단일 PRIMARY KEY | 지원, 테이블당 하나 |
| 단일·복합 UNIQUE INDEX | 지원, 테이블 생성 후 별도 생성 |
| 컬럼 뒤 UNIQUE·테이블 수준 PRIMARY KEY | 해당 생성 문법 미지원 |
| FOREIGN KEY·Trigger·Stored Procedure | 미지원 |
| BEGIN·COMMIT·ROLLBACK | 지원, 중첩 BEGIN·SAVEPOINT 미지원 |
| ADD·DROP·RENAME COLUMN, RENAME TO | 지원 조건 확인 |
| MODIFY COLUMN | 미지원 |
| Append | SDK별 공개 경로와 배치 경계 확인 |
| TAG의 METADATA·BASETIME·BASEDISTANCE | TRANSACTION에는 적용하지 않음 |
Cluster에서 작은 기준 정보 변경은 LOOKUP을 검토할 수 있지만,
명시적 관계형 트랜잭션의 완전한 대체는 아닙니다.
원본 이벤트는 LOG·TAG, 관계형 트랜잭션은 별도 RDBMS 등 요구에 맞게 나누어야 합니다.
## 오류별 진단
| 증상 | 확인할 부분 | 다음 조치 |
|---|---|---|
| ERR-01418 고유성 위반 | PK·UNIQUE 키와 기존 데이터 | 입력 수정 또는 UPSERT 규칙 검토 |
| NOT NULL 위반 | 생략·NULL·빈 문자열·DEFAULT | 입력값과 제약 확인 |
| UPDATE가 0행 처리 | 키와 현재 상태 조건 | 대상 없음·이미 처리됨 등 업무 상태 확인 |
| Resource busy | 다른 쓰기, 오래된 읽기 스냅샷, 열린 커서 | 원인에 따라 대기·새 트랜잭션·커서 종료 |
| COMMIT·ROLLBACK이 busy | 같은 연결의 열린 결과 집합 | 결과 집합 정리 후 종료 재시도 |
| 오류 뒤 후속 SQL도 거부 | 롤백 전용 상태 여부 | ROLLBACK 후 새 작업 시작 |
| DDL 실패 | 참조 인덱스·VIEW·활성 트랜잭션 | 의존 관계와 변경 시점 조정 |
| 마운트에서 값이 예상과 다름 | 마운트명·소유자·테이블명 | 운영 데이터와 백업 데이터 구분 |
같은 Resource busy라도 재시도 방법은 다릅니다.
자세한 두 연결 실습은 [잠금과 busy timeout](../locking-conflict-timeout/)에서 확인하세요.
오류 문자열에 TRANSACTION이 있다는 이유만으로 반복 실행하지 마세요.
## 제약 오류와 데이터 보존
```sql
CREATE TRANSACTION TABLE ch8_error (
id LONG PRIMARY KEY,
code VARCHAR(32) NOT NULL,
value INTEGER
);
CREATE UNIQUE INDEX ch8_error_code ON ch8_error(code);
INSERT INTO ch8_error VALUES (1, 'A', 10);
```
아래는 각각 의도적으로 실패하는 선택 실습입니다.
정상 SQL과 묶지 말고 확인할 문장만 실행하세요.
```sql
-- PRIMARY KEY 중복
INSERT INTO ch8_error VALUES (1, 'B', 20);
-- UNIQUE 중복
INSERT INTO ch8_error VALUES (2, 'A', 20);
-- 필수값 위반
INSERT INTO ch8_error VALUES (3, NULL, 30);
-- 지원하지 않는 스키마 변경
ALTER TABLE ch8_error MODIFY COLUMN (code VARCHAR(64));
```
```sql
SELECT id, code, value FROM ch8_error ORDER BY id;
DROP TABLE ch8_error;
```
마지막 조회에는 (1, A, 10)만 남습니다.
단일 문장 실패가 상태를 보존한다는 점과, BEGIN 안의 앞선 성공 문장까지
자동으로 취소되는 것은 아니라는 점을 구분해야 합니다.
[트랜잭션](../transaction/)에 해당 비교 예제가 있습니다.
## 진단 정보 수집
서버 버전·Edition, DDL과 인덱스, 실행 SQL, 오류 코드와 전체 메시지, 실제 영향 행 수를
함께 준비하면 좋습니다. 연결 장애라면 COMMIT 요청·응답 시각과 업무 키도 필요합니다.
비밀번호·개인정보·민감한 업무 값은 가린 뒤 재현에 필요한 최소 표본만 공유하세요.
복구를 위해 내부 저장 파일을 직접 수정하거나 운영 테이블을 재생성하지 마세요.
원인과 반영 상태를 먼저 확인하는 것이 데이터 손실을 줄이는 길입니다.
---
title: "8.9 트랜잭션"
url: https://docs.machbase.com/kr/dbms/rdb-table-usage/transaction/
language: kr
kind: page
---
# 8.9 트랜잭션
여러 SQL을 실행하다가 중간 문장이 실패하면 앞선 변경도 사라졌다고 생각하기 쉽습니다.
하지만 일반 제약 오류는 실패한 문장과 전체 트랜잭션을 구분해서 처리합니다.
이 절에서는 같은 테이블 안의 변경으로 COMMIT·ROLLBACK 경계를 먼저 확인합니다.
## 트랜잭션 실행
```sql
CREATE TRANSACTION TABLE ch8_tx (
item_id LONG PRIMARY KEY,
qty INTEGER NOT NULL
);
INSERT INTO ch8_tx VALUES (1, 10);
INSERT INTO ch8_tx VALUES (2, 20);
BEGIN;
UPDATE ch8_tx SET qty = qty - 3 WHERE item_id = 1 AND qty >= 3;
UPDATE ch8_tx SET qty = qty + 3 WHERE item_id = 2;
SELECT item_id, qty FROM ch8_tx ORDER BY item_id;
ROLLBACK;
SELECT item_id, qty FROM ch8_tx ORDER BY item_id;
```
트랜잭션 안에서는 7·23, ROLLBACK 뒤에는 10·20이 조회됩니다.
애플리케이션은 두 UPDATE가 각각 기대한 1행을 처리했는지 확인해야 합니다.
조건 미일치로 0행이 갱신된 것은 SQL 오류가 아니므로 DB가 업무 실패를 자동 판단하지는
않습니다.
다음은 같은 변경을 확정하는 정상 실습입니다.
```sql
BEGIN;
UPDATE ch8_tx SET qty = qty - 3 WHERE item_id = 1 AND qty >= 3;
UPDATE ch8_tx SET qty = qty + 3 WHERE item_id = 2;
COMMIT;
SELECT item_id, qty FROM ch8_tx ORDER BY item_id;
```
확정 후 값은 7·23입니다.
공개 문법은 BEGIN이며 BEGIN TRANSACTION, 중첩 BEGIN, SAVEPOINT는 지원하지 않습니다.
명시적 트랜잭션이 없으면 TRANSACTION DML은 문장 단위로 처리됩니다.
드라이버의 자동 커밋·트랜잭션 API는 별도로 확인하세요.
## 문장 오류와 롤백
아래는 오류 처리 학습용 흐름입니다. 중복 INSERT는 의도적으로 실패합니다.
SQL 실행 도구가 오류에서 중단되면 같은 연결에서 반드시 ROLLBACK까지 실행하세요.
```sql
BEGIN;
UPDATE ch8_tx SET qty = 100 WHERE item_id = 1;
```
```sql
-- 의도적으로 실패: item_id 중복
INSERT INTO ch8_tx VALUES (1, 999);
```
```sql
SELECT item_id, qty FROM ch8_tx ORDER BY item_id;
ROLLBACK;
SELECT item_id, qty FROM ch8_tx ORDER BY item_id;
```
첫 조회는 100·23, ROLLBACK 뒤는 7·23입니다.
일반 제약 오류가 실패한 문장을 되돌려도 앞선 성공 문장은 트랜잭션 안에 남습니다.
업무 전체를 취소하려면 애플리케이션이 ROLLBACK을 선택해야 합니다.
모든 오류 뒤 계속 실행할 수 있는 것은 아닙니다. 복구 과정에서 롤백 전용 상태가 되었다면
ROLLBACK으로 종료해야 합니다.
## TRUNCATE와 롤백
```sql
BEGIN;
TRUNCATE TABLE ch8_tx;
SELECT COUNT(*) AS during_truncate FROM ch8_tx;
ROLLBACK;
SELECT COUNT(*) AS after_rollback FROM ch8_tx;
```
결과는 0과 2입니다.
현재 TRANSACTION의 TRUNCATE는 전체 행 삭제로 명시적 트랜잭션에 포함됩니다.
LOG·TAG의 데이터 정리나 CREATE·ALTER·DROP 같은 스키마 변경과 혼동하지 마세요.
스키마 작업은 업무 트랜잭션 밖에서 수행하는 것이 운영 기준입니다.
## 테이블 타입별 트랜잭션 범위
활성 TRANSACTION 트랜잭션에서도 LOG·TAG·LOOKUP·VOLATILE 조회와 혼합 조인은 허용됩니다.
반면 이 타입들의 쓰기를 같은 트랜잭션에 묶을 수는 없습니다.
허용된 조회라고 해서 해당 타입이 TRANSACTION과 동일한 스냅샷·롤백 보장을 얻는 것도
아닙니다. 원본 수집과 업무 상태 변경 사이의 일관성은 별도로 설계하세요.
TRANSACTION 테이블의 읽기는 다른 세션의 미커밋 변경을 보지 않는 스냅샷을 사용합니다.
BEGIN 호출 시 모든 테이블에 공통인 읽기 시점이 한꺼번에 고정된다고 가정하지 마세요.
읽은 뒤 쓰기로 전환할 때의 충돌은 [잠금과 재시도](../locking-conflict-timeout/)에서 다룹니다.
## 다중 테이블 커밋과 장애
여러 TRANSACTION 테이블의 DML을 BEGIN으로 묶고 정상적으로 COMMIT·ROLLBACK할 수 있습니다.
다만 현재 저장소는 테이블별 핸들을 사용하고 COMMIT도 핸들별로 순차 처리합니다.
따라서 커밋 도중 장애가 나도 여러 테이블 전체가 반드시 함께 확정되거나 함께 취소되는
원자적 커밋을 보장한다고 해석하면 안 됩니다.
여러 테이블의 불가분 처리가 필수인 업무는 이 제한을 먼저 검토하세요.
커밋 실패나 응답 유실 후 ROLLBACK을 보냈다는 이유만으로 모든 테이블이 원상 복구되었다고
판단하지 말고, 업무 키로 반영 상태를 다시 확인해야 합니다.
이 절의 기본 실습을 한 테이블의 두 행으로 구성한 이유이기도 합니다.
## 커서와 트랜잭션 종료
열린 TRANSACTION 커서가 있으면 COMMIT·ROLLBACK이 Resource busy로 실패할 수 있습니다.
SDK의 결과 집합·statement를 정리한 뒤 종료 문을 다시 실행하세요.
연결 종료 시 미커밋 변경은 롤백되지만, 연결을 잃기 전에 서버에서 이미 커밋했는지는
클라이언트가 별도로 확인해야 합니다.
```sql
DROP TABLE ch8_tx;
```
BEGIN 안에서 외부 API 호출이나 긴 계산을 기다리지 마세요.
트랜잭션을 짧게 유지하고, 실패할 때 “다시 실행할 것인가, 결과부터 확인할 것인가”를
구분해 두면 운영 중 판단이 훨씬 명확해집니다.
---
title: "8.10 잠금, 충돌, busy timeout"
url: https://docs.machbase.com/kr/dbms/rdb-table-usage/locking-conflict-timeout/
language: kr
kind: page
---
# 8.10 잠금, 충돌, busy timeout
서로 다른 행을 바꾸는데도 Resource busy가 발생하면 행 잠금만 떠올려서는 원인을 찾기
어렵습니다. TRANSACTION의 쓰기 충돌은 같은 테이블의 다른 행 사이에서도 발생할 수
있습니다. 기다리면 풀리는 충돌과 트랜잭션을 새로 시작해야 하는 충돌을 구분해 보겠습니다.
## 실습 환경
이 실습은 기본 WAL 설정인 검증용 Standard 환경을 기준으로 합니다.
A·B는 같은 계정·데이터베이스에 접속한 서로 다른 연결입니다.
각 코드 블록을 표시된 순서대로 실행하고, SELECT 결과는 끝까지 읽어 커서를 닫으세요.
스냅샷 실습을 위해 운영 서버의 저널 모드를 바꾸지는 마세요.
A에서 준비합니다.
```sql
SELECT NAME, VALUE FROM V$PROPERTY WHERE NAME = 'TRANSACTION_JOURNAL_MODE';
CREATE TRANSACTION TABLE ch8_lock (id INTEGER PRIMARY KEY, val INTEGER);
INSERT INTO ch8_lock VALUES (1, 10);
INSERT INTO ch8_lock VALUES (2, 20);
```
TRANSACTION_JOURNAL_MODE=4가 WAL입니다.
다른 값이면 아래 WAL 스냅샷 실습의 결과를 그대로 기대하지 말고 환경부터 확인하세요.
## 동시 쓰기 충돌
A에서 트랜잭션을 열고 종료하지 않은 채 기다립니다.
```sql
BEGIN;
UPDATE ch8_lock SET val = 11 WHERE id = 1;
```
이어서 B에서 조회합니다. 실습용 B 연결만 대기 시간을 0으로 바꿉니다.
```sql
ALTER SESSION SET TRANSACTION_BUSY_TIMEOUT_MS = 0;
SELECT id, val FROM ch8_lock ORDER BY id;
```
B는 아직 커밋되지 않은 A의 11이 아니라 기존 값 10·20을 봅니다.
다음 B의 UPDATE는 의도적으로 Resource busy 오류가 나는 단계입니다.
```sql
-- B: A와 다른 행이지만 같은 테이블 쓰기이므로 실패가 예상됩니다.
UPDATE ch8_lock SET val = val + 1 WHERE id = 2;
```
A에서 COMMIT한 다음 B의 UPDATE를 다시 실행합니다.
```sql
-- A
COMMIT;
```
```sql
-- B
UPDATE ch8_lock SET val = val + 1 WHERE id = 2;
SELECT id, val FROM ch8_lock ORDER BY id;
```
이제 값은 11·21입니다.
같은 테이블의 쓰기 충돌을 일반적인 행 단위 잠금으로 해석하면 안 된다는 것을 보여 줍니다.
## WAL 스냅샷 충돌
이전 단계가 모두 끝난 뒤 A에서 읽기 트랜잭션을 엽니다.
```sql
-- A
BEGIN;
SELECT val FROM ch8_lock WHERE id = 1;
```
A가 11을 읽은 뒤 B에서 값을 변경합니다. B는 명시적 트랜잭션이 없는 상태입니다.
```sql
-- B
UPDATE ch8_lock SET val = val + 10 WHERE id = 1;
```
이어서 A에서 쓰기로 전환하면 스냅샷 충돌이 예상됩니다.
```sql
-- A: 의도적으로 실패하는 단계
UPDATE ch8_lock SET val = val + 1 WHERE id = 1;
```
다른 연결이 이미 커밋했으므로 A의 오래된 읽기 스냅샷을 그대로 쓰기로 전환할 수 없습니다.
현재 이 충돌은 busy timeout을 늘리거나 -1로 설정해도 기다려서 해결되지 않습니다.
같은 UPDATE만 반복하지 말고 A의 트랜잭션을 종료한 뒤 새 상태에서 다시 판단합니다.
```sql
-- A
ROLLBACK;
BEGIN;
UPDATE ch8_lock SET val = val + 1 WHERE id = 1;
COMMIT;
SELECT id, val FROM ch8_lock ORDER BY id;
```
최종 값은 22·21입니다.
업무에서 읽은 값으로 다음 변경을 계산했다면 새로운 트랜잭션에서 읽기와 판단부터
다시 해야 합니다.
## busy timeout
서버 기본 TRANSACTION_BUSY_TIMEOUT_MS는 30000ms이고 새 세션에 복사됩니다.
현재 세션은 ALTER SESSION으로 바꿀 수 있습니다.
| 값 | 일시적인 잠금 충돌 처리 |
|---|---|
| -1 | 취소·연결 종료 또는 잠금 해제까지 대기 |
| 0 | 대기 없이 busy 반환 |
| 양수 | 해당 밀리초 범위 내 대기 후 처리 또는 busy 반환 |
스냅샷 전환 충돌처럼 재시도로 풀리지 않는 경우는 이 정책의 예외입니다.
-1을 모든 충돌에 대한 무한 재시도라고 해석하지 마세요.
DDL_LOCK_TIMEOUT은 별도의 DDL 잠금 대기 설정이며 이를 바꾼다고 스냅샷 충돌이
해결되지는 않습니다.
## 오류와 재시도
메시지에 TRANSACTION이 있다는 이유만으로 재시도하면 타입·제약·권한 오류까지 반복하게
됩니다. 드라이버의 오류 코드와 전체 진단, 작업 종류를 함께 보고 재시도 가능한 잠금
충돌인지 구분하세요.
명시적 트랜잭션에서 재시도하려면 열린 결과 집합을 닫고 ROLLBACK한 뒤,
제한된 횟수·전체 요청 시간 안에서 새 트랜잭션으로 재실행합니다.
연결 유실이나 COMMIT 응답 유실은 별도 경우입니다. 이미 처리되었는지 업무 키로 확인하지
않고 카운터 증가나 주문 처리를 다시 실행하면 중복 반영될 수 있습니다.
## 충돌 진단
```sql
SELECT id, user_name, user_ip, transaction_busy_timeout_ms
FROM V$SESSION WHERE closed = 0 ORDER BY id;
SELECT id, sess_id, state, query FROM V$STMT
WHERE state LIKE 'Execute in progress%'
OR state LIKE 'Fetch in progress%';
```
이 조회가 잠금 소유자를 직접 매핑해 주는 것은 아닙니다.
V$MUTEX도 서버 내부 뮤텍스 통계이지 업무 행 잠금 목록은 아닙니다.
접속 정보와 애플리케이션의 BEGIN·종료 기록을 함께 확인하세요.
세션 강제 종료는 미커밋 업무를 취소할 수 있으므로 첫 조치로 사용하지 마세요.
두 연결에 열린 트랜잭션이 없는지 확인한 뒤 A에서 정리합니다.
```sql
DROP TABLE ch8_lock;
```
실습용 연결 A·B를 종료하면 B에 지정한 세션별 timeout도 더 이상 영향을 주지 않습니다.
운영에서는 외부 API 호출·긴 계산을 BEGIN 밖으로 옮기는 것만으로도 대기 원인을 줄일 수
있습니다.
---
title: "8.11 JOIN과 관계형 조회 설계"
url: https://docs.machbase.com/kr/dbms/rdb-table-usage/join-relational-query/
language: kr
kind: page
---
# 8.11 JOIN과 관계형 조회 설계
JOIN을 붙였는데 주문 건수가 줄거나 늘어났다면 먼저 관계의 모양을 확인해야 합니다.
INNER JOIN은 상대가 없는 행을 제외하고, 한 건에 여러 상대가 맞으면 결과를 늘립니다.
SQL이 오류 없이 실행되는 것과 업무 건수를 올바르게 집계하는 것은 다른 문제입니다.
## TRANSACTION–LOOKUP 조인
```sql
CREATE TRANSACTION TABLE ch8_join_order (
order_id LONG PRIMARY KEY,
item_id LONG,
qty INTEGER
);
CREATE LOOKUP TABLE ch8_join_product (id LONG PRIMARY KEY, name VARCHAR(64));
CREATE TRANSACTION TABLE ch8_join_payment (order_id LONG PRIMARY KEY, status VARCHAR(16));
INSERT INTO ch8_join_order VALUES (1, 42, 2);
INSERT INTO ch8_join_order VALUES (2, 99, 1);
INSERT INTO ch8_join_product VALUES (42, 'Pump');
INSERT INTO ch8_join_payment VALUES (1, 'PAID');
SELECT o.order_id, p.name, o.qty
FROM ch8_join_order o JOIN ch8_join_product p ON o.item_id = p.id
ORDER BY o.order_id;
SELECT o.order_id, p.name, o.qty
FROM ch8_join_order o LEFT JOIN ch8_join_product p ON o.item_id = p.id
ORDER BY o.order_id;
```
INNER JOIN은 1번 주문만, LEFT JOIN은 1·2번 주문을 반환합니다.
2번의 제품 이름은 NULL입니다. 제품 99가 없어도 주문 입력 자체는 외래 키로 차단되지
않으므로, 필요한 참조 검증은 별도로 설계해야 합니다.
실수하기 쉬운 부분은 LEFT JOIN 뒤 오른쪽 테이블 조건을 WHERE에 넣는 것입니다.
예를 들어 `WHERE p.name = 'Pump'`를 추가하면 NULL 행이 제외됩니다.
상대를 찾는 조건인지 최종 결과를 거르는 조건인지 구분하세요.
## TRANSACTION 간 조인
```sql
SELECT o.order_id, o.qty, p.status
FROM ch8_join_order o
JOIN ch8_join_payment p ON o.order_id = p.order_id
ORDER BY o.order_id;
```
(1, 2, PAID) 한 행이 조회됩니다.
실제 결제 이력이 주문당 여러 행이면 이 결과도 여러 행이 됩니다.
주문 금액을 조인 후 합산할 때 중복 합계가 생기지 않도록 관계와 집계 단위를 확인하세요.
## TAG 근접 시간 조인
다음은 알람 전후 5초에 있는 모든 측정값을 연결하는 예제입니다.
```sql
CREATE TRANSACTION TABLE ch8_join_alarm (
alarm_id LONG PRIMARY KEY,
sensor VARCHAR(32),
occurred DATETIME
);
CREATE TAG TABLE ch8_join_sensor (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE SUMMARIZED
);
INSERT INTO ch8_join_alarm VALUES (
1, 'TEMP-01', TO_DATE('2026-01-01 10:00:05', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO ch8_join_sensor VALUES (
'TEMP-01', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10);
INSERT INTO ch8_join_sensor VALUES (
'TEMP-01', TO_DATE('2026-01-01 10:00:10', 'YYYY-MM-DD HH24:MI:SS'), 20);
INSERT INTO ch8_join_sensor VALUES (
'TEMP-01', TO_DATE('2026-01-01 10:00:11', 'YYYY-MM-DD HH24:MI:SS'), 30);
SELECT a.alarm_id, s.time, s.value
FROM ch8_join_alarm a JOIN ch8_join_sensor s ON a.sensor = s.name
WHERE s.time >= a.occurred - 5s
AND s.time <= a.occurred + 5s
ORDER BY a.alarm_id, s.time;
```
알람 1번에 값 10·20의 두 행이 연결됩니다. 양쪽 경계를 포함하며 값 30은 제외됩니다.
이 쿼리는 가장 가까운 측정값 하나나 정확히 같은 시각의 값을 고르는 기능이 아닙니다.
한 값만 필요하면 최근 이전 값·최단 거리 등 선택 기준과 동률 처리 규칙을 별도로 정하세요.
## 조인 설계 기준
조인 키의 타입과 값 형식을 맞추고 시간 범위를 제한한 뒤 실행 계획을 확인합니다.
필요한 컬럼만 반환하고, 함수·형 변환을 조인 키에 무심코 추가해 접근 경로가 달라지지
않는지 비교하세요. 조인 순서나 알고리즘이 다른 RDBMS와 같다고 가정하지 마세요.
혼합 조인이 허용되어도 다른 테이블 타입이 TRANSACTION과 동일한 트랜잭션 스냅샷을
공유하는 것은 아닙니다. 또한 현재 LOOKUP 설명을 붙인 결과가 과거 시점의 설명까지
재현하지는 않습니다.
```sql
DROP TABLE ch8_join_sensor;
DROP TABLE ch8_join_alarm;
DROP TABLE ch8_join_payment;
DROP TABLE ch8_join_product;
DROP TABLE ch8_join_order;
```
결과 건수가 맞지 않으면 조인 전 건수와 키별 상대 행 수를 먼저 비교해 보세요.
이 두 가지가 확인되면 쿼리를 어떻게 고쳐야 할지도 명확해집니다.
---
title: "8.12 TRANSACTION 백업, 복원, 마운트"
url: https://docs.machbase.com/kr/dbms/rdb-table-usage/backup-restore-mount/
language: kr
kind: page
---
# 8.12 TRANSACTION 백업, 복원, 마운트
백업 명령이 성공했다고 복구 준비가 끝난 것은 아닙니다.
특히 운영 테이블과 백업 테이블의 이름이 같으면 잘못된 쪽을 조회하고도 검증이 끝났다고
생각하기 쉽습니다. 백업 이후 운영 값을 바꾸어 두 결과가 다른지 확인하는 방법으로
검증해 보겠습니다.
## 백업·복원 지원 범위
| 작업 | TRANSACTION 관련 기준 |
|---|---|
| BACKUP DATABASE | 대상 범위의 영속 TRANSACTION 데이터 포함 |
| BACKUP TABLE | 지정한 테이블과 필요한 메타데이터 백업 |
| 증분 백업 | TRANSACTION 저장소는 그 백업 시점의 전체 스냅샷으로 포함 |
| MOUNT DATABASE | 백업을 읽기 전용으로 조회 |
| 오프라인 인스턴스 복원 | 서버를 중단하고 machadmin -r 사용 |
| 온라인 논리 데이터베이스 복원 | 지원되는 논리 백업을 RESTORE DATABASE로 복원 |
증분 백업의 TRANSACTION 데이터를 변경 행만의 델타라고 계산하지 마세요.
내부 파일을 직접 복사하는 대신 지원되는 백업 명령을 사용해야 합니다.
TRANSACTION이 포함된 백업을 Cluster에서 사용하기 위한 우회 경로로 삼을 수도 없습니다.
## 백업과 마운트 검증
이 실습은 검증용 Standard 환경의 SYS 계정을 기준으로 합니다.
백업·마운트 권한과 서버 파일 접근 권한이 필요합니다.
경로는 서버 기준 예시이며 실행할 때마다 존재하지 않는 새 경로로 바꾸세요.
상위 디렉터리와 여유 공간을 확인하고, 경로를 재사용하려고 기존 백업을 삭제하지 마세요.
```sql
CREATE TRANSACTION TABLE ch8_backup (
id LONG PRIMARY KEY,
code VARCHAR(32) NOT NULL,
amount DECIMAL(18,2)
);
CREATE UNIQUE INDEX ch8_backup_code ON ch8_backup(code);
INSERT INTO ch8_backup VALUES (1, 'A', 10.25);
INSERT INTO ch8_backup VALUES (2, 'B', 20.50);
BACKUP TABLE ch8_backup INTO DISK = '/backup/ch8_table_20260907_a';
UPDATE ch8_backup SET amount = 99.00 WHERE id = 1;
MOUNT DATABASE '/backup/ch8_table_20260907_a' TO ch8_bak;
SELECT id, code, amount FROM ch8_bak.SYS.ch8_backup ORDER BY id;
SELECT id, code, amount FROM ch8_backup ORDER BY id;
```
백업 쪽은 10.25·20.50, 운영 쪽은 99.00·20.50입니다.
마운트 조회는 `마운트명.소유자.테이블명`의 세 부분 이름을 사용합니다.
다른 소유 계정으로 실습했다면 SYS 부분도 실제 소유자에 맞춰야 합니다.
실수하기 쉬운 부분은 `SELECT ... FROM ch8_backup`만 실행하는 것입니다.
이것은 마운트 백업 검증이 아니라 현재 연결의 운영 테이블 조회입니다.
백업에 포함하지 않은 다른 테이블이 당연히 있어야 한다고 기대하지도 마세요.
마운트는 읽기 전용이며 UPDATE·DDL을 수행하는 복구 환경이 아닙니다.
검증을 마치고 열린 커서를 정리한 뒤 마운트를 해제합니다.
```sql
UMOUNT DATABASE ch8_bak;
DROP TABLE ch8_backup;
```
이 정리는 실습 테이블과 마운트만 제거합니다.
백업 디렉터리는 남겨 두었으므로 이후 보관 정책에 따라 별도로 관리하세요.
## 복원 검증 항목
격리된 복원 환경에서는 소유자, 행 수, 업무 키, 금액 합계와 대표 JSON 값을 확인하세요.
PRIMARY KEY·UNIQUE INDEX가 남아 있는지, 필요한 권한과 애플리케이션의
COMMIT·ROLLBACK 흐름이 정상인지도 점검합니다.
마운트 조회만으로 실제 쓰기 복구 검증까지 완료한 것은 아닙니다.
온라인 RESTORE DATABASE는 논리 백업과 대상 데이터베이스 조건을 따릅니다.
여러 데이터베이스를 포함한 전체 인스턴스 이미지와 혼동하지 마세요.
기존 인스턴스를 지우는 오프라인 복원이나 REPLACE는 이 실습에 포함하지 않습니다.
[복원 문법](/dbms/reference/sql/syntax/backup-restore-mount-syntax/)과
[운영 절차](/dbms/operations-configuration-recovery/backup-restore-mount/)에서
권한·중단·대상 교체 조건을 확인한 뒤 별도로 실행하세요.
여러 테이블의 업무 일관성까지 검증해야 한다면 백업 명령의 성공만으로 판단하지 마세요.
쓰기 중단·업무 기준 시점과 교차 테이블 검증 기준을 함께 정하는 것이 좋습니다.
---
title: "8.13 TRANSACTION INSERT ON DUPLICATE KEY UPDATE"
url: https://docs.machbase.com/kr/dbms/rdb-table-usage/insert-on-duplicate-key-update/
language: kr
kind: page
---
# 8.13 TRANSACTION INSERT ON DUPLICATE KEY UPDATE
“없으면 추가하고 있으면 수정”하는 작업은 간단해 보이지만, 무엇을 중복으로 보는지에
따라 다른 행이 바뀔 수 있습니다. 특히 카운터 증가를 재전송하면 같은 이벤트가 두 번
반영될 수 있습니다. 키와 갱신 대상, 재시도 정책을 함께 확인하세요.
TRANSACTION의 `INSERT ... ON DUPLICATE KEY UPDATE`는 PRIMARY KEY 또는 일반 UNIQUE INDEX
충돌 시 기존 행을 갱신합니다. 아래 실습은 페이지 안에서 필요한 객체를 직접 준비합니다.
오류 확인 SQL은 정상 흐름과 분리하고, 마지막 정리까지 실행한 뒤 재실행하세요.
## 지원 문법
TRANSACTION 테이블에서는 `INSERT ... VALUES ...` 형태의 upsert를 지원합니다.
```text
INSERT INTO table_name VALUES (...)
ON DUPLICATE KEY UPDATE;
INSERT INTO table_name VALUES (...)
ON DUPLICATE KEY UPDATE SET column_name = expression [, ...];
INSERT INTO table_name(column_name, ...)
VALUES (...)
ON DUPLICATE KEY UPDATE;
INSERT INTO table_name(column_name, ...)
VALUES (...)
ON DUPLICATE KEY UPDATE SET column_name = expression [, ...];
```
충돌 판정 키로 인정되는 대상은 다음과 같습니다.
- TRANSACTION PRIMARY KEY
- TRANSACTION UNIQUE INDEX
- TRANSACTION 복합 UNIQUE INDEX
## 기본 동작
중복이 없으면 일반 INSERT와 같이 새 행을 추가합니다.
```sql
CREATE TRANSACTION TABLE ch8_up_device_state (
device_id INTEGER PRIMARY KEY,
status VARCHAR(16),
alarm_count INTEGER,
updated_at DATETIME
);
INSERT INTO ch8_up_device_state
VALUES (1, 'NORMAL', 0, TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS'))
ON DUPLICATE KEY UPDATE SET
status = 'NORMAL',
updated_at = TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS');
```
PRIMARY KEY가 중복되면 기존 행을 UPDATE합니다.
```sql
INSERT INTO ch8_up_device_state
VALUES (1, 'ALARM', 1, TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS'))
ON DUPLICATE KEY UPDATE SET
status = 'ALARM',
alarm_count = alarm_count + 1,
updated_at = TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS');
```
`SET` 절에서는 PRIMARY KEY를 변경할 수 없습니다. 오른쪽 식은 충돌한 기존 행을
기준으로 평가됩니다. 따라서 `alarm_count = alarm_count + 1`은 INSERT하려던 값이
아니라 기존 행의 alarm_count에 1을 더합니다.
```sql
SELECT device_id, status, alarm_count, updated_at
FROM ch8_up_device_state
WHERE device_id = 1;
```
예상 결과 형태는 다음과 같습니다.
```text
DEVICE_ID STATUS ALARM_COUNT UPDATED_AT
--------- ------ ----------- -----------------------------
1 ALARM 1 2026-07-10 09:05:00 000:000:000
```
`ON DUPLICATE KEY UPDATE` 뒤에 `SET` 절을 쓰지 않을 수 있습니다.
```sql
CREATE TRANSACTION TABLE ch8_up_asset_cache (
asset_id INTEGER PRIMARY KEY,
asset_name VARCHAR(80),
location VARCHAR(80),
keep_value INTEGER
);
INSERT INTO ch8_up_asset_cache VALUES (1, 'compressor-a', 'plant-1', 100);
INSERT INTO ch8_up_asset_cache(asset_id, asset_name, location)
VALUES (1, 'compressor-a-renamed', 'plant-2')
ON DUPLICATE KEY UPDATE;
```
`SET` 절이 없으면 INSERT 대상 컬럼 중 PRIMARY KEY가 아닌 컬럼만 기존 행에 반영됩니다. 위 예에서는 `asset_name`, `location`이 갱신되고, 컬럼 목록에 없던 `keep_value`는 기존 값을 유지합니다.
```sql
SELECT asset_id, asset_name, location, keep_value
FROM ch8_up_asset_cache
ORDER BY asset_id;
```
예상 결과 형태는 다음과 같습니다.
```text
ASSET_ID ASSET_NAME LOCATION KEEP_VALUE
-------- -------------------- -------- ----------
1 compressor-a-renamed plant-2 100
```
PRIMARY KEY 컬럼만 있는 테이블에서 중복 행에 `SET` 없는 upsert를 실행하면 갱신 대상 컬럼이 없으므로 행은 변경되지 않습니다.
## UNIQUE INDEX 중복 처리
PRIMARY KEY뿐 아니라 UNIQUE INDEX 충돌도 갱신 경로를 실행합니다.
```sql
CREATE TRANSACTION TABLE ch8_up_account_profile (
id INTEGER PRIMARY KEY,
email VARCHAR(120),
display_name VARCHAR(80),
login_count INTEGER
);
CREATE UNIQUE INDEX ch8_up_uidx_account_profile_email
ON ch8_up_account_profile(email);
INSERT INTO ch8_up_account_profile
VALUES (1, 'ops@example.com', 'ops-user', 1);
INSERT INTO ch8_up_account_profile
VALUES (2, 'ops@example.com', 'ops-renamed', 1)
ON DUPLICATE KEY UPDATE SET
display_name = 'ops-renamed',
login_count = login_count + 1;
SELECT id, email, display_name, login_count
FROM ch8_up_account_profile
ORDER BY id;
```
`email` UNIQUE INDEX가 중복되므로 `id = 1` 행이 UPDATE됩니다. INSERT하려던 `id = 2` 행은 새로 추가되지 않습니다.
복합 UNIQUE INDEX는 키 조합 전체가 같은 경우 중복으로 처리됩니다.
```sql
CREATE TRANSACTION TABLE ch8_up_daily_device_summary (
id INTEGER PRIMARY KEY,
device_id INTEGER,
summary_day VARCHAR(10),
event_count INTEGER,
last_status VARCHAR(16)
);
CREATE UNIQUE INDEX ch8_up_uidx_daily_device_summary
ON ch8_up_daily_device_summary(device_id, summary_day);
INSERT INTO ch8_up_daily_device_summary
VALUES (1, 101, '2026-07-10', 3, 'NORMAL');
INSERT INTO ch8_up_daily_device_summary
VALUES (2, 101, '2026-07-10', 1, 'ALARM')
ON DUPLICATE KEY UPDATE SET
event_count = event_count + 1,
last_status = 'ALARM';
INSERT INTO ch8_up_daily_device_summary
VALUES (3, 101, '2026-07-11', 1, 'NORMAL')
ON DUPLICATE KEY UPDATE SET
event_count = event_count + 1;
```
첫 번째 upsert는 `(device_id, summary_day) = (101, '2026-07-10')` 행을 UPDATE합니다. 두 번째 upsert는 날짜가 다르므로 새 행을 INSERT합니다.
UNIQUE KEY에 NULL이 포함된 행끼리는 중복으로 처리하지 않습니다.
다음 두 입력은 서로 덮어쓰지 않고 각각 새 행이 됩니다.
```sql
INSERT INTO ch8_up_daily_device_summary VALUES (4, NULL, '2026-07-10', 1, 'NORMAL')
ON DUPLICATE KEY UPDATE;
INSERT INTO ch8_up_daily_device_summary VALUES (5, NULL, '2026-07-10', 2, 'ALARM')
ON DUPLICATE KEY UPDATE;
SELECT id, device_id, summary_day, event_count
FROM ch8_up_daily_device_summary ORDER BY id;
```
결과 id는 1·3·4·5이며 event_count는 각각 4·1·1·2입니다.
필수 업무 키라면 UNIQUE INDEX뿐 아니라 구성 컬럼의 NOT NULL도 필요합니다.
## 활용 예제
TAG 테이블에는 시간순 측정값이 계속 쌓이고, TRANSACTION 테이블에는 장비별 최신 상태만 유지할 수 있습니다.
```sql
CREATE TRANSACTION TABLE ch8_up_latest_device_status (
device_name VARCHAR(80) PRIMARY KEY,
last_value DOUBLE,
last_state VARCHAR(16),
event_count LONG,
updated_at DATETIME
);
INSERT INTO ch8_up_latest_device_status
VALUES ('compressor-a', 72.5, 'NORMAL', 1, TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS'))
ON DUPLICATE KEY UPDATE SET
last_value = 72.5,
last_state = 'NORMAL',
event_count = event_count + 1,
updated_at = TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS');
INSERT INTO ch8_up_latest_device_status
VALUES ('compressor-a', 91.2, 'ALARM', 1, TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS'))
ON DUPLICATE KEY UPDATE SET
last_value = 91.2,
last_state = 'ALARM',
event_count = event_count + 1,
updated_at = TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS');
```
이 패턴은 대시보드가 최신 상태만 빠르게 조회해야 할 때 사용할 수 있습니다.
외부 시스템에서 같은 업무 키로 기준 데이터를 반복 전송할 때 UNIQUE INDEX를 기준으로 upsert할 수 있습니다.
```sql
CREATE TRANSACTION TABLE ch8_up_customer_device (
id LONG PRIMARY KEY AUTO_INCREMENT,
external_device_id VARCHAR(64),
device_name VARCHAR(80),
owner_name VARCHAR(80),
enabled INTEGER
);
CREATE UNIQUE INDEX ch8_up_uidx_customer_device_external_id
ON ch8_up_customer_device(external_device_id);
INSERT INTO ch8_up_customer_device(external_device_id, device_name, owner_name, enabled)
VALUES ('ERP-DEV-10001', 'compressor-a', 'line-1', 1)
ON DUPLICATE KEY UPDATE SET
device_name = 'compressor-a',
owner_name = 'line-1',
enabled = 1;
INSERT INTO ch8_up_customer_device(external_device_id, device_name, owner_name, enabled)
VALUES ('ERP-DEV-10001', 'compressor-a-renamed', 'line-2', 1)
ON DUPLICATE KEY UPDATE SET
device_name = 'compressor-a-renamed',
owner_name = 'line-2',
enabled = 1;
```
첫 INSERT는 새 행을 생성하고, 두 번째 INSERT는 `external_device_id` UNIQUE INDEX 충돌로 기존 행을 UPDATE합니다. 내부 `id`는 유지됩니다.
소스 데이터의 컬럼 값을 그대로 최신 캐시에 반영하고 싶으면 `SET` 절 없는 upsert를 사용할 수 있습니다.
```sql
CREATE TRANSACTION TABLE ch8_up_tag_alias_cache (
alias_name VARCHAR(80) PRIMARY KEY,
tag_name VARCHAR(80),
unit VARCHAR(16),
description VARCHAR(160),
manually_checked INTEGER
);
INSERT INTO ch8_up_tag_alias_cache
VALUES ('compressor-a-temp', 'comp_a.temp', 'celsius', 'main compressor temp', 1);
INSERT INTO ch8_up_tag_alias_cache(alias_name, tag_name, unit, description)
VALUES ('compressor-a-temp', 'comp_a.temperature', 'celsius', 'renamed tag')
ON DUPLICATE KEY UPDATE;
```
위 구문은 `tag_name`, `unit`, `description`만 갱신합니다. 컬럼 목록에 없는 `manually_checked`는 기존 값을 유지합니다.
집계 테이블에서 키별 발생 횟수를 누적할 수 있습니다.
```sql
CREATE TRANSACTION TABLE ch8_up_alarm_counter (
alarm_code VARCHAR(32) PRIMARY KEY,
first_seen DATETIME,
last_seen DATETIME,
hit_count LONG
);
INSERT INTO ch8_up_alarm_counter
VALUES (
'OVER_TEMP',
TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS'),
TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS'),
1
)
ON DUPLICATE KEY UPDATE SET
last_seen = TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS'),
hit_count = hit_count + 1;
INSERT INTO ch8_up_alarm_counter
VALUES (
'OVER_TEMP',
TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS'),
TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS'),
1
)
ON DUPLICATE KEY UPDATE SET
last_seen = TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS'),
hit_count = hit_count + 1;
```
`hit_count = hit_count + 1`은 기존 행 기준으로 계산되므로 누적 카운터 구현에 사용할 수 있습니다.
JSON 컬럼도 update 대상 컬럼으로 사용할 수 있습니다.
```sql
CREATE TRANSACTION TABLE ch8_up_device_json_state (
device_id INTEGER PRIMARY KEY,
state JSON,
updated_at DATETIME
);
INSERT INTO ch8_up_device_json_state
VALUES (
1,
'{"status":"NORMAL","score":10}',
TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS')
)
ON DUPLICATE KEY UPDATE SET
state = '{"status":"NORMAL","score":10}',
updated_at = TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS');
INSERT INTO ch8_up_device_json_state
VALUES (
1,
'{"status":"ALARM","score":90}',
TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS')
)
ON DUPLICATE KEY UPDATE SET
state = '{"status":"ALARM","score":90}',
updated_at = TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS');
SELECT JSON_EXTRACT_STRING(state, '$.status') AS status,
JSON_EXTRACT_INTEGER(state, '$.score') AS score
FROM ch8_up_device_json_state
WHERE device_id = 1;
```
제한: JSON path UNIQUE INDEX는 충돌 판정 키 후보에서 제외됩니다. JSON path UNIQUE INDEX
충돌은 upsert 갱신 경로로 전환되지 않고 고유성 제약 오류로 처리됩니다.
## 트랜잭션과 권한
TRANSACTION upsert는 일반 INSERT/UPDATE와 같이 트랜잭션 안에서 COMMIT 또는 ROLLBACK됩니다.
```sql
CREATE TRANSACTION TABLE ch8_up_tx_device_state (
id INTEGER PRIMARY KEY,
status VARCHAR(16),
count_value INTEGER
);
INSERT INTO ch8_up_tx_device_state VALUES (1, 'NORMAL', 10);
BEGIN;
INSERT INTO ch8_up_tx_device_state VALUES (2, 'NORMAL', 1)
ON DUPLICATE KEY UPDATE SET count_value = count_value + 1;
INSERT INTO ch8_up_tx_device_state VALUES (1, 'ALARM', 1)
ON DUPLICATE KEY UPDATE SET
status = 'ALARM',
count_value = count_value + 1;
ROLLBACK;
SELECT id, status, count_value
FROM ch8_up_tx_device_state
ORDER BY id;
```
위 예에서는 삽입 경로와 갱신 경로가 모두 ROLLBACK됩니다.
일반 제약 위반으로 중복 갱신 문장이 실패해도 명시적 트랜잭션의 앞선 성공 변경은
남을 수 있습니다. 응용 프로그램은 오류를 확인한 뒤 후속 처리를 결정하거나
ROLLBACK으로 업무 변경을 취소해야 합니다.
TRANSACTION upsert 문장에는 `INSERT` 권한과 `UPDATE` 권한이 모두 필요합니다. 실제 실행 결과가
삽입 경로가 되더라도 문장에 갱신 경로가 포함되므로 두 권한을 모두 부여해야 합니다.
아래는 권한 형식만 보여 주는 예입니다. 실제 소유자·테이블·기존 애플리케이션 계정에
맞춰 별도의 관리 작업으로 적용하세요.
```text
GRANT INSERT ON owner.table_name TO app_user;
GRANT UPDATE ON owner.table_name TO app_user;
```
`SELECT` 권한은 TRANSACTION upsert 문장 실행 자체에는 필요하지 않습니다. 단, 응용 프로그램이 결과 확인을 위해 `SELECT`를 실행한다면 별도로 `SELECT` 권한이 필요합니다.
## 지원 타입과 제약
`SET` 절에서 갱신할 수 있는 컬럼 타입은 일반 TRANSACTION `UPDATE`와 같은 공개 타입
지원 범위를 따릅니다.
| 분류 | 타입 |
| --- | --- |
| 정수 | `SHORT`, `INT16`, `USHORT`, `UINT16`, `INT`, `INTEGER`, `INT32`, `UINTEGER`, `UINT32`, `LONG`, `INT64`, `ULONG`, `UINT64` |
| 실수 | `FLOAT`, `DOUBLE` |
| 고정소수 | `DECIMAL`, `NUMERIC`, `DEC`, `FIXED`, `NUMBER` |
| 문자열/LOB | `VARCHAR`, `TEXT`, `CLOB`, `BINARY`, `BLOB` |
| 기타 | `DATETIME`, `IPV4`, `IPV6`, `JSON` |
위 표는 TRANSACTION에서 사용하는 스칼라 타입을 정리한 것입니다. 숫자 ARRAY의 지원 범위는
[데이터 타입 사전](/dbms/reference/sql/types/)을 참고하십시오.
충돌 판정 키가 되는 키·인덱스 타입은 TRANSACTION PRIMARY KEY 및 UNIQUE INDEX의 타입 정책을
따릅니다. 이 기능은 key 타입의 지원 범위를 확장하지 않습니다.
다음 구문은 지원하지 않습니다.
```text
-- 지원하지 않는 문법을 보여 주는 설명용 구역입니다.
-- INSERT SELECT와 ON DUPLICATE KEY UPDATE 결합은 지원하지 않습니다.
INSERT INTO ch8_up_device_state(device_id, status, alarm_count, updated_at)
SELECT device_id, status, alarm_count, updated_at
FROM staging_device_state
ON DUPLICATE KEY UPDATE SET status = 'UPDATED';
-- MySQL VALUES(col) 함수는 지원하지 않습니다.
INSERT INTO ch8_up_device_state VALUES (1, 'ALARM', 1, TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS'))
ON DUPLICATE KEY UPDATE SET status = VALUES(status);
-- EXCLUDED alias는 지원하지 않습니다.
INSERT INTO ch8_up_device_state VALUES (1, 'ALARM', 1, TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS'))
ON DUPLICATE KEY UPDATE SET status = EXCLUDED.status;
-- conflict target syntax는 지원하지 않습니다.
INSERT INTO ch8_up_device_state VALUES (1, 'ALARM', 1, TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS'))
ON CONFLICT (device_id) DO UPDATE SET status = 'ALARM';
```
대상 테이블 제한은 다음과 같습니다.
- 이 페이지는 TRANSACTION의 PRIMARY KEY·UNIQUE 충돌 동작만 다룹니다. 같은 SQL 형태는
LOOKUP과 VOLATILE의 PRIMARY KEY 충돌에도 지원되며 공통 문법은
[DML 사전](/dbms/reference/sql/syntax/dml-syntax/#on-duplicate-key-update)을
정본으로 사용합니다.
- LOG와 TAG DATA 행에는 지원하지 않습니다. TAG METADATA의 태그 이름 충돌 처리는
[DML 사전](/dbms/reference/sql/syntax/dml-syntax/#on-duplicate-key-update)을
참고합니다.
- TRANSACTION 테이블이라도 PRIMARY KEY 또는 UNIQUE INDEX가 없으면 사용할 수 없습니다.
- JSON path UNIQUE INDEX는 충돌 판정 키로 사용하지 않습니다.
## 충돌과 오류 처리
여러 UNIQUE INDEX가 같은 기존 행을 가리키면 해당 행을 한 번 UPDATE합니다.
반면 서로 다른 기존 행과 충돌하면 어느 행을 갱신해야 할지 결정할 수 없어 문장이
실패합니다.
다음은 두 업무 키가 서로 다른 행과 충돌하는 표본입니다.
```sql
CREATE TRANSACTION TABLE ch8_up_user_contact (
id INTEGER PRIMARY KEY,
email VARCHAR(120),
phone VARCHAR(40),
note VARCHAR(80)
);
CREATE UNIQUE INDEX ch8_up_uidx_user_contact_email ON ch8_up_user_contact(email);
CREATE UNIQUE INDEX ch8_up_uidx_user_contact_phone ON ch8_up_user_contact(phone);
INSERT INTO ch8_up_user_contact VALUES (1, 'a@example.com', '010-0000-0001', 'user-a');
INSERT INTO ch8_up_user_contact VALUES (2, 'b@example.com', '010-0000-0002', 'user-b');
```
다음 INSERT만 의도적으로 실패하는 선택 실습입니다.
```sql
-- email은 id=1과 충돌하고 phone은 id=2와 충돌합니다.
-- 서로 다른 row와 충돌하므로 갱신 경로를 선택하지 않고 실패합니다.
INSERT INTO ch8_up_user_contact VALUES (3, 'a@example.com', '010-0000-0002', 'ambiguous')
ON DUPLICATE KEY UPDATE SET note = 'updated';
```
`SET` 결과가 다른 UNIQUE 제약 또는 `NOT NULL` constraint를 위반해도 문장은 실패하며 기존 행은 보존됩니다.
## 재전송과 최신 상태
카운터 증가 UPSERT는 자동 중복 제거가 아닙니다.
같은 이벤트를 다시 실행하면 기존 카운터가 다시 증가합니다.
COMMIT 응답을 잃었다면 업무 키·이벤트 처리 기록으로 결과부터 확인하세요.
또한 “최신 상태”는 입력 순서와 이벤트 발생 순서가 같을 때만 단순 덮어쓰기로 유지됩니다.
늦게 도착한 과거 이벤트가 최신 값을 덮어쓰지 않도록 원본 시각 비교와 수집 정책을
별도로 정하세요. 여러 테이블을 함께 변경하는 작업은
[트랜잭션의 장애 시 커밋 범위](../transaction/)도 확인해야 합니다.
## 운영 권장 사항
- 업무 key가 명확하면 PRIMARY KEY 또는 UNIQUE INDEX를 먼저 정의합니다.
- 카운터 누적에는 `SET count_col = count_col + 1` 형태를 사용합니다.
- 소스 행의 값을 그대로 반영하려면 `SET` 없는 upsert를 사용할 수 있습니다. 이때 컬럼 목록에서 생략한 컬럼은 보존됩니다.
- 여러 UNIQUE INDEX가 있는 테이블에서는 서로 다른 행과 동시에 충돌할 수 있는 입력을 사전에 정리합니다.
- MySQL 호환 SQL을 이식할 때 `VALUES(col)`, `EXCLUDED`, `ON CONFLICT` 구문은 Machbase 지원 구문으로 바꿉니다.
- JSON path UNIQUE INDEX를 upsert key로 사용하는 설계는 피하고, 필요한 경우 별도 일반 컬럼에 key 값을 저장한 뒤 UNIQUE INDEX를 생성합니다.
## 결과 확인과 정리
정상 실습이 끝났으면 대표 값과 건수를 다시 확인합니다.
아래 예상값은 의도적인 오류 외에 모든 정상 SQL을 한 번씩 실행한 기준입니다.
```sql
SELECT id, email, display_name, login_count
FROM ch8_up_account_profile ORDER BY id;
SELECT device_name, last_state, event_count
FROM ch8_up_latest_device_status;
SELECT external_device_id, device_name, owner_name
FROM ch8_up_customer_device;
SELECT alias_name, tag_name, manually_checked FROM ch8_up_tag_alias_cache;
SELECT alarm_code, hit_count FROM ch8_up_alarm_counter;
SELECT id, status, count_value FROM ch8_up_tx_device_state ORDER BY id;
SELECT id, note FROM ch8_up_user_contact ORDER BY id;
```
| 표본 | 확인할 결과 |
|---|---|
| account_profile | id=1 유지, display_name=ops-renamed, login_count=2 |
| latest_device_status | ALARM, event_count=2 |
| customer_device | 외부 키 한 행, 이름 compressor-a-renamed, 소유 line-2 |
| tag_alias_cache | tag_name=comp_a.temperature, manually_checked=1 유지 |
| alarm_counter | OVER_TEMP의 hit_count=2 |
| tx_device_state | ROLLBACK 후 기존 id=1, NORMAL, count_value=10만 존재 |
| user_contact | id=1·2의 user-a·user-b가 그대로 유지 |
```sql
DROP TABLE ch8_up_user_contact;
DROP TABLE ch8_up_tx_device_state;
DROP TABLE ch8_up_device_json_state;
DROP TABLE ch8_up_alarm_counter;
DROP TABLE ch8_up_tag_alias_cache;
DROP TABLE ch8_up_customer_device;
DROP TABLE ch8_up_latest_device_status;
DROP TABLE ch8_up_daily_device_summary;
DROP TABLE ch8_up_account_profile;
DROP TABLE ch8_up_asset_cache;
DROP TABLE ch8_up_device_state;
```
DROP은 이 페이지의 실습 객체만 대상으로 합니다.
키가 복잡할수록 새 입력값과 기존 충돌 행을 나란히 비교해 보세요.
어느 행을 갱신하려 했는지 분명해지면 오류의 원인도 찾기 쉬워집니다.
---
title: "9. LOOKUP 테이블 활용"
url: https://docs.machbase.com/kr/dbms/lookup-table-usage/
language: kr
kind: section
---
# 9. LOOKUP 테이블 활용
LOOKUP 테이블은 영속 저장된 기준 정보와 마스터 데이터를 서버 기동 시 메모리에 적재하고,
PRIMARY KEY를 key로 사용해 빠르게 참조하는 테이블입니다. 메모리 상주 구조, JSON/SEQUENCE,
JOIN과 일반 조건식 기반 DML을 다룹니다.
## 이 장의 구성
| 절 | 내용 |
|----|------|
| [개요와 사용 기준](./overview-use-criteria/) | LOOKUP 테이블의 용도와 적합한 사용 조건 |
| [테이블 구조와 스키마](./table-structure-schema/) | 컬럼 구성, PK 구조, 스키마 설계 |
| [생성, 변경, 삭제](./create-alter-drop/) | DDL: CREATE, ALTER, DROP |
| [데이터 입력과 변경](./data-input-mutation/) | INSERT, Append, 리로드, 삭제 |
| [조회와 분석](./query-analysis/) | SELECT, JOIN, 조건 조회 |
| [인덱스와 성능](./index-performance/) | Red-Black 인덱스, 보조 인덱스, 튜닝 |
| [운영과 데이터 생명주기](./operations-lifecycle/) | 백업·복구, 데이터 영속성 |
| [제약, 오류, 문제 해결](./constraints-errors-troubleshooting/) | 제한 사항, 오류 원인과 대응 |
| [활용 패턴과 시나리오](./patterns-scenarios/) | 코드 테이블, 기준 정보, 임계값 관리 |
| [PRIMARY KEY 정책](./primary-key-policy/) | 자연키 vs 대리키, PK 불변 원칙 |
| [SEQUENCE 컬럼](./sequence-column/) | 자동 증가 번호 설정과 NEXTVAL 사용법 |
| [JSON 컬럼과 JSON 조회](./json-column-query/) | JSON 컬럼 지원 범위, path 조건 조회, primary key 제약 |
| [일반 predicate UPDATE/DELETE](./predicate-update-delete/) | non-PK, 범위, 문자열, 날짜, JSON path 조건 기반 변경 |
---
title: "9.1 개요와 사용 기준"
url: https://docs.machbase.com/kr/dbms/lookup-table-usage/overview-use-criteria/
language: kr
kind: page
---
# 9.1 개요와 사용 기준
LOOKUP 테이블은 기준 코드, 장비 마스터, 임계값, 설정값처럼 비교적 작고 자주 참조되는 데이터를
저장하는 테이블입니다. 데이터는 영속 저장되지만 SQL 조회에 사용하는 전체 행은 메모리에
상주합니다. 따라서 반복적인 키 기반 조회와 갱신에 적합합니다.
## LOOKUP 테이블의 특성
LOOKUP 테이블은 `CREATE LOOKUP TABLE` 문으로 생성하며, `PRIMARY KEY`가 필수입니다.
```sql
CREATE LOOKUP TABLE ch9_overview (
sensor_id VARCHAR(64) PRIMARY KEY,
site VARCHAR(32),
unit VARCHAR(16),
status VARCHAR(16)
);
```
LOOKUP 테이블의 주요 특성은 다음과 같습니다.
| 항목 | 내용 |
|------|------|
| 주요 용도 | 코드 테이블, 장비 마스터, 임계값, 참조 데이터 |
| 필수 조건 | `PRIMARY KEY` 필요 |
| 저장 방식 | 영속 저장 후 서버 기동 시 전체 행을 메모리에 적재 |
| 조회 패턴 | PK 조회에 최적화, 일반 조건 조회와 다른 테이블 JOIN 지원 |
| 변경 패턴 | INSERT, UPDATE, DELETE |
| 부가 기능 | SEQUENCE 컬럼, Append 중복 키 정책 |
## 사용 기준
다음 조건에 해당하면 LOOKUP 테이블을 사용합니다.
- 데이터 건수가 상대적으로 작고 전체 데이터가 자주 참조됩니다.
- 전체 행과 필요한 보조 인덱스를 서버 메모리에 유지할 수 있습니다.
- 코드, 이름, 위치, 단위, 상태 같은 기준 정보를 관리합니다.
- TAG 또는 LOG 테이블의 원본 데이터에 설명 정보를 JOIN해야 합니다.
- 임계값이나 설정값처럼 운영 중 변경될 수 있는 참조값을 저장합니다.
- `PRIMARY KEY`로 행을 명확하게 식별할 수 있습니다.
TAG 또는 LOG 데이터에 위치와 단위 같은 설명을 붙이는 JOIN 예제는
[조회와 분석](/dbms/lookup-table-usage/query-analysis/)에서 다룹니다.
## 다른 테이블을 검토할 경우
다음 요구사항에는 다른 테이블 타입을 검토합니다.
| 요구사항 | 권장 테이블 |
|----------|-------------|
| 대량 시계열 계측 데이터 저장 | TAG |
| append 중심 원본 이벤트 저장 | LOG |
| 메모리에 전체 행을 적재하기 어려운 대규모 관계형 데이터 | TRANSACTION |
| 트랜잭션과 관계형 업무 처리가 필요한 데이터 | TRANSACTION |
| 서버 메모리에서만 유지할 최신 상태 캐시 | VOLATILE |
LOOKUP 테이블은 참조 데이터에 적합하지만, 데이터가 영속 저장된다는 이유로 디스크 중심의
대용량 테이블처럼 사용해서는 안 됩니다. 원본 데이터는 LOG 또는 TAG 테이블에 저장하고,
LOOKUP 테이블에는 메모리에 상주시켜 반복 조회할 기준 정보를 저장합니다. 관계형 데이터가
메모리 용량보다 커지거나 복합적인 업무 처리가 필요하면 TRANSACTION 테이블을 사용합니다.
이 절의 실습 테이블은 다음과 같이 정리합니다.
```sql
DROP TABLE ch9_overview;
```
## 설계 순서
LOOKUP 테이블을 설계할 때는 다음 순서로 결정합니다.
1. 행을 식별할 `PRIMARY KEY`를 정합니다.
2. 자연키를 사용할지, SEQUENCE 또는 AUTO_INCREMENT 기반 대리키를 사용할지 결정합니다.
3. 자주 조회하거나 JOIN하는 컬럼에 인덱스를 추가합니다.
4. 예상 행 크기와 행 수, 보조 인덱스를 포함한 메모리 사용량을 검증합니다.
5. 운영 중 갱신되는 컬럼과 불변 컬럼을 구분합니다.
6. 대량 변경 전에는 대상 범위를 확인하는 쿼리를 준비합니다.
스키마와 키 설계는 [테이블 구조와 스키마](/dbms/lookup-table-usage/table-structure-schema/)와 [PRIMARY KEY 정책](/dbms/lookup-table-usage/primary-key-policy/)에서 다룹니다.
---
title: "9.2 테이블 구조와 스키마"
url: https://docs.machbase.com/kr/dbms/lookup-table-usage/table-structure-schema/
language: kr
kind: page
---
# 9.2 테이블 구조와 스키마
LOOKUP 테이블의 구조와 스키마 설계를 다룹니다.
## LOOKUP 테이블 설계
LOOKUP 테이블은 코드 테이블과 기준 정보를 저장하는 타입입니다. PRIMARY KEY로 각 행을
식별하고 Primary key 또는 일반 조건식의 UPDATE/DELETE를 지원하며 디스크에 영속 저장됩니다.
### 영속 저장과 메모리 조회 구조
LOOKUP 테이블은 영속성과 메모리 조회 성능을 함께 제공하는 2계층 구조입니다.
1. 변경된 행은 재시작 후에도 유지할 수 있도록 영속 저장소에 기록됩니다.
2. 서버가 기동되면 영속 저장소의 LOOKUP 행을 모두 읽어 각 컬럼 값을 포함한 메모리 행으로
복원합니다.
3. 각 메모리 행은 필수 `PRIMARY KEY`의 Red-Black 인덱스에 등록됩니다.
4. SQL 조회는 복원된 메모리 행과 인덱스를 사용합니다.
개념적으로 한 행은 다음과 같은 key-value 항목으로 볼 수 있습니다.
```
PRIMARY KEY 나머지 컬럼 값
sensor_id = 'TEMP-01' ───────► { site, unit, status, ... }
key value
```
이는 LOOKUP이 SQL 테이블 인터페이스와 일반 조건 조회, JOIN, 보조 인덱스를 지원하면서도
`PRIMARY KEY` 조회에 특히 적합한 이유입니다. 영속 저장소가 있다고 해서 조회 시 필요한 행만
디스크에서 가져오는 구조는 아닙니다. 전체 행과 생성한 Red-Black 보조 인덱스가 메모리를
사용하므로 스키마를 설계할 때 행 수뿐 아니라 가변 길이 컬럼, JSON 값, 보조 인덱스 크기도
함께 고려합니다.
- **[활용 사례](/dbms/lookup-table-usage/patterns-scenarios/#use-cases-lookup)**
- **[PRIMARY KEY 설계](/dbms/lookup-table-usage/primary-key-policy/#design-primary-key)**
- **[컬럼 및 시퀀스 설계](/dbms/lookup-table-usage/sequence-column/#design-column-lookup-sequence)**
- **[JSON 컬럼과 조회](/dbms/lookup-table-usage/json-column-query/#condition-query-lookup-json)**
- **[참조 설계 패턴](/dbms/lookup-table-usage/patterns-scenarios/#patterns-reference-design)**
- **[인덱스 전략](/dbms/lookup-table-usage/index-performance/#index-strategy-lookup)**
- **[PRIMARY KEY 정책](/dbms/lookup-table-usage/primary-key-policy/#policy-lookup-primary-key)**
- **[일반 조건식 기반 UPDATE·DELETE](/dbms/lookup-table-usage/predicate-update-delete/)**
- **[백업·복구 지원 범위](/dbms/lookup-table-usage/operations-lifecycle/#recovery-support-scope-backup-lookup)**
- **[제약 및 주의사항](/dbms/lookup-table-usage/constraints-errors-troubleshooting/#limitations-lookup)**
---
title: "9.3 생성, 변경, 삭제"
url: https://docs.machbase.com/kr/dbms/lookup-table-usage/create-alter-drop/
language: kr
kind: page
---
# 9.3 생성, 변경, 삭제
LOOKUP 테이블의 생성, 변경, 삭제 방법을 다룹니다.
## Lookup 테이블 생성 및 관리
참조 테이블을 생성하는 방법은 다음과 같습니다. LOOKUP 테이블은 반드시 `PRIMARY KEY`를 지정해야 합니다.
## LOOKUP 테이블 생성
```sql
CREATE LOOKUP TABLE ch9_ddl (id INTEGER PRIMARY KEY, name VARCHAR(20));
```
운영에서 사용하는 기준 정보는 의미 있는 컬럼명을 사용해 정의합니다.
```sql
CREATE LOOKUP TABLE ch9_ddl_equip (
equip_id LONG PRIMARY KEY,
equip_name VARCHAR(128),
location VARCHAR(64),
status VARCHAR(16),
updated_at DATETIME
);
```
`PRIMARY KEY`는 행을 고유하게 식별하고 UPDATE, DELETE, JOIN의 기준이 됩니다. 여러 컬럼 조합이 비즈니스 키라면 조합 문자열을 별도 키 컬럼으로 만들거나 SEQUENCE 기반 대리키를 사용합니다.
```sql
CREATE LOOKUP TABLE ch9_ddl_price (
price_key VARCHAR(64) PRIMARY KEY,
product_id VARCHAR(32),
region VARCHAR(16),
price DOUBLE
);
```
## AUTO_INCREMENT PRIMARY KEY
서버가 숫자 PRIMARY KEY를 생성해야 하면 단일 `LONG` 또는 `INT64` 컬럼에
`AUTO_INCREMENT`를 지정합니다.
```sql
CREATE LOOKUP TABLE ch9_ddl_registry (
equip_id LONG PRIMARY KEY AUTO_INCREMENT,
equip_name VARCHAR(128),
location VARCHAR(64)
);
INSERT INTO ch9_ddl_registry(equip_name, location)
VALUES ('compressor-01', 'SEOUL-A');
```
PK 컬럼을 생략하거나 NULL로 지정하면 서버가 값을 생성합니다. 단일
`INSERT ... VALUES`에서 `0..INT64_MAX` 범위의 PK 값을 직접 지정할 수도 있습니다. 지정값이
현재 다음 자동값 이상이면 다음 자동값은 `지정값 + 1`로 진행하며, 작은 값을 지정해도
되감기지 않습니다. 데이터와 다음 자동값은 정상 재시작 후 유지됩니다.
AUTO_INCREMENT를 사용하는 LOOKUP 테이블에서는 `INSERT ... SELECT`와
`ON DUPLICATE KEY UPDATE`를 사용할 수 없습니다. SDK에서 INSERT 결과 ID를 받는 방법은
[ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를 참고하십시오.
## SEQUENCE 컬럼 사용
자동 증가 번호가 필요하면 `LONG PROPERTY(SEQUENCE=1)` 컬럼을 사용합니다. 입력 시에는 `NEXTVAL()` 함수로 다음 값을 가져옵니다.
```sql
CREATE LOOKUP TABLE ch9_ddl_alarm (
seq LONG PROPERTY(SEQUENCE=1) PRIMARY KEY,
sensor_id VARCHAR(64),
alarm_type VARCHAR(32),
occurred_at DATETIME,
message VARCHAR(256)
);
INSERT INTO ch9_ddl_alarm
VALUES (NEXTVAL(seq), 'TEMP-01', 'HIGH', NOW, '온도 초과');
```
SEQUENCE 컬럼의 세부 정책은 [SEQUENCE 컬럼](/dbms/lookup-table-usage/sequence-column/)에서 다룹니다.
`PROPERTY(SEQUENCE)`와 `AUTO_INCREMENT`는 별개의 기능이며 같은 컬럼에 함께 지정하지
않습니다.
## 컬럼 추가와 삭제
Standard Edition에서는 LOOKUP 테이블에 고정 길이 숫자 ARRAY 컬럼을 추가하고 삭제할 수
있습니다.
```sql
ALTER TABLE ch9_ddl_equip
ADD COLUMN (limits DECIMAL(12,4)[2] DEFAULT [0.0000, NULL]);
ALTER TABLE ch9_ddl_equip
DROP COLUMN (limits);
```
DEFAULT가 없으면 기존 row의 새 ARRAY 컬럼은 whole NULL입니다. DEFAULT를 지정하면 기존
row에도 해당 값을 적용합니다. ARRAY DEFAULT의 요소 수는 선언 cardinality와 정확히
같아야 합니다. ARRAY 컬럼은 PRIMARY KEY나 index key로 사용할 수 없습니다.
지원 타입과 제약은
[숫자 ARRAY 타입](/dbms/reference/sql/types/array/)을 참고하십시오.
## 인덱스 추가
자주 조회하거나 JOIN 조건에 사용하는 컬럼에는 인덱스를 추가합니다.
```sql
CREATE INDEX ch9_ddl_loc_idx ON ch9_ddl_equip(location);
CREATE INDEX ch9_ddl_status_idx ON ch9_ddl_equip(status);
```
PRIMARY KEY 컬럼에는 기본 인덱스가 생성되므로, 같은 컬럼에 별도 인덱스를 중복 생성하지 않습니다. 인덱스가 많으면 입력과 갱신 비용이 증가하므로 조회 조건이 명확한 컬럼에만 추가합니다.
## 데이터 삭제와 테이블 삭제
행을 삭제하려면 `DELETE` 문을 사용합니다. 단건 삭제는 PK 조건을 사용하는 것이 가장 명확합니다.
```sql
DELETE FROM ch9_ddl_equip
WHERE equip_id = 1001;
```
일괄 삭제는 일반 조건식을 사용할 수 있습니다. 운영 데이터에서는 먼저 같은 조건으로 대상 건수를 확인합니다.
```sql
SELECT COUNT(*)
FROM ch9_ddl_equip
WHERE status = 'RETIRED';
DELETE FROM ch9_ddl_equip
WHERE status = 'RETIRED';
```
테이블 자체를 삭제하려면 `DROP TABLE`을 사용합니다.
```sql
DROP TABLE ch9_ddl_alarm;
DROP TABLE ch9_ddl_registry;
DROP TABLE ch9_ddl_price;
DROP TABLE ch9_ddl_equip;
DROP TABLE ch9_ddl;
```
`DROP TABLE`은 테이블 정의와 데이터를 모두 삭제합니다. 필요한 경우 삭제 전에 백업 또는 내보내기 절차를 수행합니다.
## 주의사항
- LOOKUP 테이블은 `PRIMARY KEY`가 필수입니다.
- `PRIMARY KEY` 컬럼은 하나만 지정합니다.
- `PRIMARY KEY` 값을 변경해야 하면 기존 행을 삭제한 뒤 새 키로 삽입합니다.
- 기준 데이터가 커지고 조회·갱신 패턴이 복잡해지면 TRANSACTION 테이블을 검토합니다.
---
title: "9.4 데이터 입력과 변경"
url: https://docs.machbase.com/kr/dbms/lookup-table-usage/data-input-mutation/
language: kr
kind: page
---
# 9.4 데이터 입력과 변경
LOOKUP 테이블의 데이터 입력, 갱신, 삭제 방법을 하나의 실행 가능한 예제로 설명합니다.
## 예제 테이블 준비
다음 예제는 마지막의 정리 구문까지 순서대로 실행할 수 있습니다.
```sql
CREATE LOOKUP TABLE ch9_mutation (
code VARCHAR(32) PRIMARY KEY,
label VARCHAR(64),
status VARCHAR(16),
updated_at DATETIME
);
INSERT INTO ch9_mutation VALUES ('TEMP', 'Temperature', 'ACTIVE', NOW);
INSERT INTO ch9_mutation VALUES ('PRESS', 'Pressure', 'ACTIVE', NOW);
```
SEQUENCE 키가 필요하면 [SEQUENCE](/dbms/lookup-table-usage/sequence-column/)를 참고합니다.
## UPDATE
단건 변경은 PRIMARY KEY 조건으로 처리합니다.
```sql
UPDATE ch9_mutation
SET status = 'INACTIVE',
updated_at = NOW
WHERE code = 'TEMP';
```
여러 행을 변경할 때는 먼저 같은 `WHERE` 절로 대상을 조회합니다. 단건 변경은 대상이 명확하고
인덱스를 사용할 수 있는 `PRIMARY KEY` 조건을 권장합니다.
PRIMARY KEY 컬럼 자체는 UPDATE할 수 없습니다. 키를 바꿔야 하면 기존 행을 삭제하고 새 키로
다시 입력합니다. 두 문장을 하나의 TRANSACTION 트랜잭션으로 묶을 수 없으므로 중간 실패와
참조 데이터의 변경 절차도 함께 설계합니다.
## 중복 키 처리
SQL INSERT에서 키 중복 시 갱신이 필요하면 `ON DUPLICATE KEY UPDATE`를 사용합니다.
```sql
INSERT INTO ch9_mutation
VALUES ('TEMP', 'Temperature sensor', 'ACTIVE', NOW)
ON DUPLICATE KEY UPDATE SET label = 'Temperature sensor', status = 'ACTIVE', updated_at = NOW;
```
Append로 데이터를 삽입할 때 primary key가 중복되면 `LOOKUP_APPEND_UPDATE_ON_DUPKEY` 설정에 따라 해당 행을 업데이트할 수 있습니다. 이 설정은 LOOKUP 테이블 append 경로의 중복 키 처리 정책이므로, 운영 환경에서는 현재 설정값을 확인한 뒤 사용합니다.
```sql
SELECT name, value
FROM v$property
WHERE name = 'LOOKUP_APPEND_UPDATE_ON_DUPKEY';
```
## TABLE_REFRESH
영속 저장된 LOOKUP 내용을 실행 중인 메모리 테이블에 다시 반영해야 할 때는
`TABLE_REFRESH`를 실행합니다.
```sql
EXEC TABLE_REFRESH(ch9_mutation);
```
일반 SQL DML 직후마다 실행하는 명령은 아닙니다. 대상은 현재 database의 LOOKUP table이며,
READ ONLY database에서는 실행할 수 없습니다. 이름 범위·권한·오류 계약은
[EXEC procedure 정본](/dbms/reference/sql/syntax/execute-procedure-syntax/#table-refresh)을
참고하고, 클러스터 운영 절차는 [운영과 수명주기](/dbms/lookup-table-usage/operations-lifecycle/)를
참고합니다.
## Lookup 데이터 삭제
단건 삭제는 PRIMARY KEY 조건을 사용합니다.
```sql
DELETE FROM ch9_mutation
WHERE code = 'PRESS';
```
일반 조건식을 사용하면 조건에 맞는 모든 행을 삭제합니다. 모든 행을 삭제하려면 WHERE 절을
생략합니다.
```sql
DELETE FROM ch9_mutation;
DROP TABLE ch9_mutation;
```
## 변경 작업 체크리스트
- 단건 변경은 PRIMARY KEY 조건을 사용합니다.
- 일반 조건식으로 일괄 변경하기 전에 같은 조건으로 대상 범위를 조회합니다.
- 모든 행을 삭제하기 전에는 백업 또는 재입력 원본을 확인합니다.
- PRIMARY KEY 값 변경은 DELETE 후 INSERT로 처리합니다.
- Append 중복 키 처리는 `LOOKUP_APPEND_UPDATE_ON_DUPKEY` 설정을 확인합니다.
- 영속 LOOKUP을 runtime memory에 다시 반영해야 할 때만 `EXEC TABLE_REFRESH(table_name)`을
사용합니다.
---
title: "9.5 조회와 분석"
url: https://docs.machbase.com/kr/dbms/lookup-table-usage/query-analysis/
language: kr
kind: page
---
# 9.5 조회와 분석
LOOKUP 테이블의 키 조회, 일반 조건 조회, TAG 데이터와의 JOIN을 실행 가능한 예제로 설명합니다.
## 예제 데이터 준비
다음 LOOKUP 테이블과 TAG 테이블을 준비합니다.
```sql
CREATE LOOKUP TABLE ch9_query_master (
sensor_id VARCHAR(32) PRIMARY KEY,
site VARCHAR(32),
unit VARCHAR(16),
status VARCHAR(16)
);
INSERT INTO ch9_query_master VALUES ('TEMP-01', 'SEOUL', 'C', 'ACTIVE');
INSERT INTO ch9_query_master VALUES ('TEMP-02', 'BUSAN', 'C', 'INACTIVE');
CREATE TAG TABLE ch9_query_data (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE SUMMARIZED
);
INSERT INTO ch9_query_data VALUES ('TEMP-01', TO_DATE('2026-01-01 00:00:00'), 23.5);
INSERT INTO ch9_query_data VALUES ('TEMP-02', TO_DATE('2026-01-01 00:00:00'), 19.0);
```
## PRIMARY KEY 조회
단건 조회는 `PRIMARY KEY` 조건을 사용합니다.
```sql
SELECT sensor_id, site, unit, status
FROM ch9_query_master
WHERE sensor_id = 'TEMP-01';
```
## 일반 조건 조회
LOOKUP 테이블은 일반 컬럼 조건으로도 조회할 수 있습니다. 자주 사용하는 조건 컬럼에는 인덱스를 추가합니다.
```sql
SELECT sensor_id, site, unit
FROM ch9_query_master
WHERE site = 'SEOUL'
AND status = 'ACTIVE';
```
자주 사용하는 일반 조건 컬럼에는 인덱스를 추가할 수 있습니다. 인덱스 설계와 생성 방법은
[인덱스](/dbms/lookup-table-usage/index-performance/)를 참고합니다.
## TAG·LOG 테이블과 JOIN
LOOKUP 테이블은 TAG 또는 LOG 테이블의 원본 데이터에 설명 정보를 붙이는 용도로 자주 사용합니다.
```sql
SELECT d.name, m.site, m.unit, d.time, d.value
FROM ch9_query_data d
JOIN ch9_query_master m ON d.name = m.sensor_id
WHERE m.status = 'ACTIVE';
```
## 분석 패턴
LOOKUP 테이블은 원본 데이터를 저장하기보다 분석 기준을 제공합니다. 다음과 같은 패턴에 적합합니다.
| 패턴 | 설명 |
|------|------|
| 코드 변환 | 상태 코드, 알람 코드, 장비 타입을 라벨로 변환 |
| 기준값 비교 | 센서값을 임계값 테이블과 JOIN해 초과 여부 판단 |
| 그룹 기준 제공 | 위치, 부서, 라인 등 집계 기준 제공 |
| 최신 설정 반영 | 운영 중 변경되는 설정값을 조회 시점에 반영 |
같은 방식으로 임계값 LOOKUP 테이블과 TAG 원본을 JOIN해 기준값 초과 여부를 판정할 수 있습니다.
시간 범위가 큰 TAG 또는 LOG 테이블은 JOIN 전에 시간 조건으로 조회 범위를 제한합니다.
```sql
DROP TABLE ch9_query_data CASCADE;
DROP TABLE ch9_query_master;
```
## 조회 성능 기준
- 단건 조회와 JOIN 기준 컬럼은 `PRIMARY KEY` 또는 인덱스 컬럼을 사용합니다.
- 조건에 자주 쓰는 일반 컬럼은 별도 인덱스를 검토합니다.
- 대량 원본 데이터와 JOIN할 때는 원본 테이블의 시간 범위를 먼저 좁힙니다.
- LOOKUP 테이블에는 기준 정보를 저장하고, 장기 원본 데이터는 TAG 또는 LOG 테이블에 저장합니다.
---
title: "9.6 인덱스와 성능"
url: https://docs.machbase.com/kr/dbms/lookup-table-usage/index-performance/
language: kr
kind: page
---
# 9.6 인덱스와 성능
LOOKUP 테이블의 인덱스 구조와 성능 튜닝을 다룹니다.
## LOOKUP 인덱스 튜닝
LOOKUP의 PRIMARY KEY에는 Red-Black 트리 인덱스가 자동 생성됩니다. 전체 행과 인덱스는
SQL 조회 중 메모리에 상주합니다. 필요하면 non-PK 컬럼에도 Red-Black 보조 인덱스를
추가할 수 있습니다.
### LOOKUP 테이블 인덱스
#### PK 자동 Red-Black 트리 인덱스
LOOKUP 테이블을 생성하면 PRIMARY KEY 컬럼에 Red-Black 트리 인덱스가 자동으로 생성됩니다.
인덱스 항목은 해당 key의 전체 컬럼 값이 들어 있는 메모리 행을 가리킵니다. 따라서 영속
테이블이지만 조회 실행 경로는 메모리 기반이며, 소규모 기준 정보를 key로 반복 조회하는
패턴에 최적화되어 있습니다.
```sql
CREATE LOOKUP TABLE ch9_index_device (
device_id VARCHAR(64) PRIMARY KEY, -- Red-Black 트리 자동 생성
device_name VARCHAR(128),
location VARCHAR(256),
category VARCHAR(32)
);
INSERT INTO ch9_index_device VALUES ('DEV-01', 'Boiler', 'Seoul', 'temperature');
INSERT INTO ch9_index_device VALUES ('DEV-02', 'Pump', 'Busan', 'pressure');
```
```sql
-- PK 조회: Red-Black 트리 인덱스 사용
SELECT device_id, device_name, location FROM ch9_index_device
WHERE device_id = 'DEV-01';
```
한 행이 조회됩니다. 이벤트 로그와 결합할 때도 LOOKUP 쪽은 PK로 찾습니다.
```sql
CREATE LOG TABLE ch9_index_event (device_id VARCHAR(64), level SHORT);
INSERT INTO ch9_index_event VALUES ('DEV-01', 3);
INSERT INTO ch9_index_event VALUES ('DEV-02', 1);
EXEC TABLE_FLUSH(ch9_index_event);
SELECT e.device_id, e.level, d.location
FROM ch9_index_event e, ch9_index_device d
WHERE e.device_id = d.device_id
ORDER BY e.device_id;
```
두 행이 조회됩니다. LOG 쪽에 시간 범위를 함께 걸면 읽을 원본을 더 줄일 수 있습니다.
#### non-PK 컬럼 보조 인덱스
non-PK 컬럼에도 Red-Black 보조 인덱스를 생성할 수 있습니다. 보조 인덱스가 없는 컬럼으로 필터링하면 테이블 전체를 순차 스캔합니다.
```sql
-- 자주 필터링하는 non-PK 컬럼에 보조 인덱스 생성
CREATE INDEX ch9_index_device_location ON ch9_index_device(location);
SELECT device_id FROM ch9_index_device WHERE location = 'Seoul';
-- 인덱스가 없는 컬럼은 전체 스캔 가능
SELECT device_id FROM ch9_index_device WHERE category = 'temperature';
```
두 조회 모두 DEV-01을 반환합니다. 결과가 같아도 접근 경로가 다르므로,
실제 비교는 운영 규모의 데이터에서 실행 계획과 함께 확인합니다.
보조 인덱스는 조회를 빠르게 하지만 갱신 비용과 메모리 사용량이 늘어납니다. 자주 사용하는 조건 컬럼에만 생성합니다.
#### LOOKUP 테이블 사용 가이드라인
Primary key와 Red-Black 보조 인덱스의 조회 비용은 트리 크기에 따라 증가하고, 인덱스가 없는
조건은 메모리에 적재된 전체 행을 스캔합니다. 행 수만으로 사용 한계를 정하지 말고 전체 행의
실제 크기, 가변 길이 값, 인덱스 수, 조회와 갱신 비율을 같은 워크로드로 측정합니다. 서버
기동 시 영속 데이터를 모두 읽어 메모리 행과 인덱스를 구성하므로 기동 시간도 함께 확인합니다.
| 사용 패턴 | 적합성 |
|----------|-------|
| PK로 장치 정보 조회 | 적합 (PK 사용) |
| non-PK 컬럼으로 목록 검색 | 보조 인덱스 생성 시 적합 |
| 소수의 기준 코드 테이블 | 적합 |
| 전체 행이 서버 메모리에 들어가지 않는 대규모 기준 정보 | TRANSACTION 테이블 검토 |
| 관계형 트랜잭션이 필요한 기준 정보 | TRANSACTION 테이블 검토 |
### VOLATILE과의 구분
VOLATILE은 재시작 때 데이터가 사라지는 별도 테이블 타입입니다. VOLATILE 인덱스 설계는
[VOLATILE 인덱스와 성능](/dbms/volatile-table-usage/index-performance/)을 참고하십시오.
### 대용량 기준 정보가 필요한 경우
LOOKUP 테이블의 크기와 보조 인덱스 갱신 비용 때문에 다음 시나리오에서는 대안을 고려합니다.
**시나리오**: 장치 기준 정보를 여러 컬럼으로 필터링하고 변경 이력도 함께 보존해야 하는 경우
```sql
-- 대안: LOG 테이블 + LSM/BITMAP 인덱스
CREATE LOG TABLE ch9_index_device_hist (
device_id VARCHAR(64),
device_name VARCHAR(128),
location VARCHAR(256),
category VARCHAR(32),
updated_at DATETIME
);
-- non-PK 컬럼에 인덱스 생성 가능
CREATE INDEX ch9_index_hist_location ON ch9_index_device_hist (location);
CREATE INDEX ch9_index_hist_category ON ch9_index_device_hist (category) INDEX_TYPE BITMAP;
```
단, LOG 테이블은 Append 전용이므로 기준 정보 갱신 패턴에 맞게 설계해야 합니다.
실습에 사용한 객체는 다음과 같이 정리합니다.
```sql
DROP TABLE ch9_index_device_hist;
DROP INDEX ch9_index_device_location;
DROP TABLE ch9_index_event;
DROP TABLE ch9_index_device;
```
### 핵심 정리
- PRIMARY KEY 인덱스는 자동 생성됩니다.
- 반복하는 non-PK 조건에만 보조 인덱스를 만듭니다.
- 행·가변 길이 값·인덱스의 메모리, 기동 시간과 갱신 부하를 함께 측정합니다.
---
title: "9.7 운영과 데이터 생명주기"
url: https://docs.machbase.com/kr/dbms/lookup-table-usage/operations-lifecycle/
language: kr
kind: page
---
# 9.7 운영과 데이터 생명주기
LOOKUP 테이블의 백업·복구와 데이터 영속성을 다룹니다.
LOOKUP 테이블은 디스크에 영속 저장되는 참조 데이터 테이블입니다. 운영 중 값이 바뀔 수 있으므로 변경 절차, 백업, 복구, 조회 반영 시점을 함께 관리합니다.
영속 저장과 조회 시 데이터 위치는 구분해야 합니다. 서버가 기동되면 영속 저장본의 모든
LOOKUP 행을 메모리 테이블에 복원하고 `PRIMARY KEY`와 보조 인덱스를 구성합니다. 서비스 중
SQL 조회는 이 메모리 구조를 사용합니다.
## 데이터 생명주기
LOOKUP 데이터는 생성, 입력, 갱신, 참조, 백업, 복구의 흐름으로 관리합니다.
```
테이블 생성
└── 기준 데이터 입력
└── TAG/LOG/TRANSACTION 조회에서 JOIN 또는 참조
└── 운영 중 UPDATE/DELETE
└── 백업 / 복구 / 마운트
```
기준 데이터는 원본 이벤트보다 작지만, 조회 결과 해석에 직접 영향을 줍니다. 따라서 변경 전후의 값과 적용 시점을 기록하는 운영 절차를 둡니다.
## 기준 데이터 변경 절차
운영 중 LOOKUP 데이터를 변경할 때는 다음 순서로 진행합니다.
1. 변경 대상 행을 조회합니다.
2. 영향 범위를 확인합니다.
3. UPDATE 또는 DELETE를 실행합니다.
4. 필요한 경우 `EXEC TABLE_REFRESH(table_name)`을 실행합니다.
5. 대표 조회 쿼리로 반영 여부를 확인합니다.
`TABLE_REFRESH`는 일반 SQL DML 직후마다 실행하는 명령이 아닙니다. 영속 LOOKUP 내용을 runtime
memory table에 다시 반영해야 할 때 사용하며, 이름 범위·권한·오류 계약은
[EXEC procedure 정본](/dbms/reference/sql/syntax/execute-procedure-syntax/#table-refresh)을
참고합니다.
다음은 이 절차를 그대로 따라가는 실습입니다.
```sql
CREATE LOOKUP TABLE ch9_ops_sensor (
sensor_id VARCHAR(32) PRIMARY KEY,
site VARCHAR(16),
status VARCHAR(16),
updated_at DATETIME
);
INSERT INTO ch9_ops_sensor VALUES ('TEMP-01', 'SEOUL', 'READY', NOW);
INSERT INTO ch9_ops_sensor VALUES ('TEMP-02', 'SEOUL', 'READY', NOW);
INSERT INTO ch9_ops_sensor VALUES ('TEMP-03', 'BUSAN', 'READY', NOW);
-- 변경 대상을 조회합니다.
SELECT sensor_id, site, status FROM ch9_ops_sensor WHERE sensor_id = 'TEMP-01';
-- 변경합니다.
UPDATE ch9_ops_sensor
SET status = 'INACTIVE', updated_at = NOW
WHERE sensor_id = 'TEMP-01';
-- 필요하면 memory table에 다시 반영합니다.
EXEC TABLE_REFRESH(ch9_ops_sensor);
-- 대표 조회로 확인합니다.
SELECT sensor_id, status FROM ch9_ops_sensor ORDER BY sensor_id;
```
TEMP-01만 `INACTIVE`가 되고 나머지 두 행은 `READY`로 남습니다.
일괄 변경은 반드시 대상 건수를 먼저 확인합니다.
```sql
SELECT COUNT(*) FROM ch9_ops_sensor
WHERE site = 'SEOUL' AND status = 'READY';
```
TEMP-01이 이미 바뀌었으므로 COUNT는 1입니다. 변경 전에 세지 않으면 대상이 달라집니다.
```sql
DROP TABLE ch9_ops_sensor;
```
## 백업·복구 지원 범위
LOOKUP은 disk에 영속 저장되고 database backup에 포함되며, restore 뒤 row를 memory table과
index로 다시 구성합니다.
공통 BACKUP·RESTORE·MOUNT 명령과 Edition 범위는
[백업·복원·마운트](/dbms/operations-configuration-recovery/backup-restore-mount/)를 정본으로
사용합니다. 복구 뒤 대표 key와 JOIN 결과를 검증합니다.
## 운영 점검 항목
- 기준 데이터 변경 이력을 별도 로그나 운영 절차로 남깁니다.
- 대량 UPDATE/DELETE 전에는 대상 건수를 확인합니다.
- 자주 JOIN하는 컬럼에는 인덱스를 검토합니다.
- 서버 기동 시간과 LOOKUP 행·인덱스의 메모리 사용량을 실제 데이터 규모로 점검합니다.
- Append 중복 키 처리를 사용하는 경우 `LOOKUP_APPEND_UPDATE_ON_DUPKEY` 설정을 확인합니다.
- 백업 복구 후 대표 JOIN 쿼리로 참조 결과를 확인합니다.
---
title: "9.8 제약, 오류, 문제 해결"
url: https://docs.machbase.com/kr/dbms/lookup-table-usage/constraints-errors-troubleshooting/
language: kr
kind: page
---
# 9.8 제약, 오류, 문제 해결
LOOKUP 테이블의 제약 사항, 발생 가능한 오류, 문제 해결 방법을 다룹니다.
## 제약 요약
| 항목 | 제약 | 대표 오류 |
|---|---|---|
| PRIMARY KEY | 필수이며 하나만 지정 | `ERR-02322`, `ERR-02171` |
| PRIMARY KEY 컬럼 | `SET` 대상으로 지정 불가 | `ERR-02176` |
| 컬럼 타입 | `TEXT`·`CLOB`·`BLOB`·`BINARY` 사용 불가 | `ERR-02173` |
| JSON 컬럼 | 일반 컬럼으로는 가능, PRIMARY KEY로는 불가 | — |
| 메모리 | VOLATILE과 하나의 한도를 공유 | `ERR-01344` |
| UPDATE | `WHERE` 필수. 생략 시 실행되지 않음 | — |
## PRIMARY KEY 오류
LOOKUP은 PRIMARY KEY 없이 만들 수 없고, 두 개 이상 지정할 수도 없습니다.
다음 두 문장은 각각 실패합니다.
```sql
-- 실패: PRIMARY KEY가 없습니다. (ERR-02322)
CREATE LOOKUP TABLE ch9_err_nopk (code VARCHAR(16), label VARCHAR(64));
-- 실패: PRIMARY KEY가 두 개입니다. (ERR-02171)
CREATE LOOKUP TABLE ch9_err_twopk (
code VARCHAR(16) PRIMARY KEY,
name VARCHAR(32) PRIMARY KEY
);
```
두 문장 모두 테이블을 만들지 않으므로 정리할 객체가 없습니다.
복합 키가 필요하면 구분자로 조합한 단일 키 컬럼을 두고,
[PRIMARY KEY 정책](../primary-key-policy/)의 설계 기준을 따릅니다.
PRIMARY KEY 컬럼은 값을 바꿀 수 없습니다.
```sql
CREATE LOOKUP TABLE ch9_err_pk (code VARCHAR(16) PRIMARY KEY, label VARCHAR(64));
INSERT INTO ch9_err_pk VALUES ('KR', '대한민국');
-- 실패: PRIMARY KEY 컬럼은 SET 대상이 아닙니다. (ERR-02176)
UPDATE ch9_err_pk SET code = 'KO' WHERE code = 'KR';
```
키를 바꿔야 하면 기존 행을 삭제하고 새 키로 다시 입력합니다.
## 지원하지 않는 컬럼 타입
`TEXT`, `CLOB`, `BLOB`, `BINARY`는 LOOKUP 컬럼으로 사용할 수 없습니다.
긴 문자열은 `VARCHAR`로 선언하고, 원문 보관이 필요하면 LOG 테이블에 분리합니다.
```sql
-- 실패: 지원하지 않는 컬럼 타입입니다. (ERR-02173)
ALTER TABLE ch9_err_pk ADD COLUMN (memo TEXT);
```
JSON은 일반 컬럼으로 사용할 수 있습니다. 사용 범위는
[JSON 컬럼과 조회](../json-column-query/)를 참고합니다.
```sql
DROP TABLE ch9_err_pk;
```
## 메모리 한도
LOOKUP은 디스크에 영속 저장되지만 조회 실행 경로는 메모리입니다. 서버가 기동하면 전체 행과
인덱스를 메모리에 올리므로 사용량이 한도를 넘으면 `ERR-01344`가 발생합니다.
이 한도는 **VOLATILE 테이블과 공유합니다.** 설정 이름이 `VOLATILE_`로 시작하지만 LOOKUP도
같은 한도에 포함되므로, 두 타입을 함께 쓰는 환경에서는 합계로 판단해야 합니다.
```sql
SELECT NAME, VALUE FROM V$PROPERTY
WHERE NAME = 'VOLATILE_TABLESPACE_MEMORY_MAX_SIZE';
SELECT * FROM V$STORAGE_DC_VOLATILE_TABLE;
```
한도에 가까워지면 보존 범위를 줄이거나, 사용하지 않는 보조 인덱스를 제거하거나,
대규모 기준 정보는 [인덱스와 성능](../index-performance/)의 기준에 따라 다른 테이블 타입을
검토합니다.
## 다중 row 변경 범위
일반 predicate UPDATE/DELETE는 여러 row에 적용될 수 있습니다. 실행 전 같은 predicate로
대상 수를 확인하고 [일반 predicate UPDATE/DELETE](../predicate-update-delete/)의 계약을
따릅니다.
## LOOKUP JSON primary key 오류
JSON column은 일반 column으로 사용할 수 있지만 PRIMARY KEY로 선언할 수 없습니다. 식별자를
별도 scalar column으로 두고 [JSON 컬럼과 조회](../json-column-query/)의 type·path 규칙을
따릅니다.
## 제약 및 주의사항
- PRIMARY KEY 정책은 [PRIMARY KEY 정책](../primary-key-policy/)을 참고합니다.
- memory 규모와 index 비용은 [인덱스와 성능](../index-performance/)에서 측정합니다.
- 시계열 원본은 TAG, 재시작 후 사라져도 되는 cache는 VOLATILE을 선택합니다.
- Append gate는 [SDK Append matrix](/dbms/development-tools-integration/sdk-support-scope/#append-table-type-matrix)를 따릅니다.
---
title: "9.9 활용 패턴과 시나리오"
url: https://docs.machbase.com/kr/dbms/lookup-table-usage/patterns-scenarios/
language: kr
kind: page
---
# 9.9 활용 패턴과 시나리오
LOOKUP 테이블의 활용 패턴과 시나리오를 다룹니다.
## 활용 사례
LOOKUP 테이블은 다음과 같은 데이터를 저장하는 데 적합합니다.
## 적합한 데이터 유형
| 유형 | 예시 |
|------|------|
| 코드 테이블 | 국가 코드, 언어 코드, 상태 코드 |
| 기준 정보 | 설비 목록, 제품 분류, 부서 정보 |
| 실시간 갱신 참조 | 환율 테이블, 임계값 설정 |
| 태그 메타데이터 대체 | 센서 정보 (소규모) |
## 코드 테이블
```sql
CREATE LOOKUP TABLE ch9_pattern_country (
code VARCHAR(4) PRIMARY KEY,
name VARCHAR(64),
region VARCHAR(32)
);
INSERT INTO ch9_pattern_country VALUES ('KR', '대한민국', 'Asia');
INSERT INTO ch9_pattern_country VALUES ('US', '미국', 'America');
UPDATE ch9_pattern_country SET name = 'United States' WHERE code = 'US';
SELECT code, name FROM ch9_pattern_country ORDER BY code;
```
두 행이 조회되며, US 행의 name만 `United States`로 바뀌어 있습니다.
상태 코드나 알람 코드도 같은 방식으로 관리하며, 이벤트 원본과 JOIN해 사용합니다.
```sql
CREATE LOOKUP TABLE ch9_pattern_status (
code VARCHAR(16) PRIMARY KEY,
label VARCHAR(64),
color VARCHAR(16)
);
INSERT INTO ch9_pattern_status VALUES ('RUN', '가동', 'green');
INSERT INTO ch9_pattern_status VALUES ('STOP', '정지', 'red');
CREATE LOG TABLE ch9_pattern_event (
event_time DATETIME,
device_id VARCHAR(64),
status VARCHAR(16)
);
INSERT INTO ch9_pattern_event
VALUES (TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 'DEV-01', 'RUN');
INSERT INTO ch9_pattern_event
VALUES (TO_DATE('2026-01-01 10:05:00', 'YYYY-MM-DD HH24:MI:SS'), 'DEV-01', 'STOP');
EXEC TABLE_FLUSH(ch9_pattern_event);
SELECT e.event_time, e.device_id, c.label
FROM ch9_pattern_event e
JOIN ch9_pattern_status c ON e.status = c.code
ORDER BY e.event_time;
```
두 행이 코드 대신 `가동`·`정지` 라벨로 조회됩니다. 코드에 없는 상태가 이벤트에 들어오면
INNER JOIN에서 그 행이 빠지므로, 코드 테이블의 누락도 함께 점검합니다.
## 설비 마스터
```sql
CREATE LOOKUP TABLE ch9_pattern_equip (
equip_id VARCHAR(32) PRIMARY KEY,
equip_name VARCHAR(128),
location VARCHAR(64),
dept VARCHAR(64),
install_dt DATETIME
);
INSERT INTO ch9_pattern_equip
VALUES ('TEMP-01', 'Boiler', 'Seoul', 'Production',
TO_DATE('2025-01-01', 'YYYY-MM-DD'));
INSERT INTO ch9_pattern_equip
VALUES ('TEMP-02', 'Chiller', 'Busan', 'Facility',
TO_DATE('2025-01-01', 'YYYY-MM-DD'));
```
TAG 테이블의 센서 데이터와 JOIN하면 위치, 부서 같은 기준 정보를 함께 조회할 수 있습니다.
```sql
CREATE TAG TABLE ch9_pattern_sensor (
name VARCHAR(32) PRIMARY KEY,
time DATETIME BASETIME,
value DOUBLE SUMMARIZED
);
INSERT INTO ch9_pattern_sensor
VALUES ('TEMP-01', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 90.0);
INSERT INTO ch9_pattern_sensor
VALUES ('TEMP-02', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 20.0);
EXEC TABLE_FLUSH(ch9_pattern_sensor);
SELECT d.name, m.location, m.dept, d.value
FROM ch9_pattern_sensor d
JOIN ch9_pattern_equip m ON d.name = m.equip_id
WHERE m.dept = 'Production'
ORDER BY d.name;
```
TEMP-01 한 행만 조회됩니다. 부서 조건은 LOOKUP 쪽 컬럼이므로, 조건을 바꾸면 읽는 TAG
원본은 그대로여도 결과 집합이 달라집니다.
## 임계값 설정
```sql
CREATE LOOKUP TABLE ch9_pattern_threshold (
sensor_name VARCHAR(64) PRIMARY KEY,
low_limit DOUBLE,
high_limit DOUBLE,
alert_level SHORT
);
INSERT INTO ch9_pattern_threshold VALUES ('TEMP-01', 0.0, 100.0, 1);
INSERT INTO ch9_pattern_threshold VALUES ('TEMP-02', 0.0, 100.0, 1);
-- 실시간 임계값 변경
UPDATE ch9_pattern_threshold SET high_limit = 85.0 WHERE sensor_name = 'TEMP-01';
```
임계값 테이블은 TAG 데이터와 결합해 알람 조건을 판단하는 데 사용합니다.
```sql
SELECT s.name, s.value, t.high_limit
FROM ch9_pattern_sensor s
JOIN ch9_pattern_threshold t ON s.name = t.sensor_name
WHERE s.value > t.high_limit
ORDER BY s.name;
```
TEMP-01만 초과로 판정됩니다. 값 90은 변경 전 한계 100에서는 정상이었으므로, 같은 원본이라도
임계값을 언제 바꿨는지에 따라 판정이 달라진다는 점을 함께 기록합니다.
## SEQUENCE 기반 이력 번호
작은 규모의 관리 이력이나 운영 이벤트에 순번이 필요하면 SEQUENCE 컬럼을 사용할 수 있습니다.
```sql
CREATE LOOKUP TABLE ch9_pattern_note (
seq LONG PROPERTY(SEQUENCE=1) PRIMARY KEY,
target_id VARCHAR(64),
note VARCHAR(512),
created_at DATETIME
);
INSERT INTO ch9_pattern_note
VALUES (NEXTVAL(seq), 'TEMP-01', 'threshold changed', NOW);
INSERT INTO ch9_pattern_note
VALUES (NEXTVAL(seq), 'TEMP-02', 'inspection done', NOW);
SELECT seq, target_id FROM ch9_pattern_note ORDER BY seq;
```
seq는 1과 2입니다. 대량 원본 이력은 LOOKUP보다 LOG 테이블에 저장합니다.
이 페이지의 실습 객체는 다음과 같이 정리합니다.
```sql
DROP TABLE ch9_pattern_note;
DROP TABLE ch9_pattern_threshold;
DROP TABLE ch9_pattern_sensor;
DROP TABLE ch9_pattern_equip;
DROP TABLE ch9_pattern_event;
DROP TABLE ch9_pattern_status;
DROP TABLE ch9_pattern_country;
```
## 부적합한 경우
- 명시적 트랜잭션과 일반 관계형 DML이 필요한 데이터 → TRANSACTION 테이블 권장
- UPDATE 불필요한 추가 전용 이력 → LOG 테이블 권장
- 센서 계측값 → TAG 테이블 권장
- 재시작 후 사라져도 되는 최신 상태 캐시 → VOLATILE 테이블 권장
---
title: "9.10 PRIMARY KEY 정책"
url: https://docs.machbase.com/kr/dbms/lookup-table-usage/primary-key-policy/
language: kr
kind: page
---
# 9.10 PRIMARY KEY 정책
LOOKUP 테이블의 PRIMARY KEY 설계 원칙과 정책을 다룹니다. SDK가 SELECT 결과에서 PK 여부를
확인하는 방법은 [PRIMARY KEY 메타데이터 지원 범위](/dbms/development-tools-integration/sdk-support-scope/#support-scope-sdk-primary-key-metadata)를
참고합니다.
## PRIMARY KEY 설계
LOOKUP 테이블은 `PRIMARY KEY`가 필수입니다. PRIMARY KEY는 행을 고유하게 식별하고 중복 입력을
제어합니다. UPDATE와 DELETE의 `WHERE` 절에는 일반 조건식을 사용할 수 있지만, Primary key
컬럼 자체는 UPDATE할 수 없습니다.
### 기본 문법
```text
CREATE LOOKUP TABLE table_name (
pk_col type PRIMARY KEY,
col2 type,
...
);
```
### 단일 컬럼 PRIMARY KEY
```sql
CREATE LOOKUP TABLE ch9_pk_country_code (
code VARCHAR(4) PRIMARY KEY,
name VARCHAR(64),
region VARCHAR(32)
);
```
### 복합 키가 필요한 경우
```sql
CREATE LOOKUP TABLE ch9_pk_price (
price_key VARCHAR(64) PRIMARY KEY,
product_id VARCHAR(32),
region VARCHAR(16),
price DOUBLE
);
-- 삽입
INSERT INTO ch9_pk_price VALUES ('PROD-01:KR', 'PROD-01', 'KR', 99.0);
INSERT INTO ch9_pk_price VALUES ('PROD-01:US', 'PROD-01', 'US', 79.0);
-- 조합 키 기반 UPDATE
UPDATE ch9_pk_price SET price = 89.0
WHERE price_key = 'PROD-01:KR';
```
LOOKUP 테이블은 PRIMARY KEY 컬럼을 하나만 지정할 수 있습니다. 여러 컬럼의 조합이
비즈니스 키라면 조합 문자열 또는 대리키를 별도 PRIMARY KEY 컬럼으로 둡니다.
### PRIMARY KEY 타입 선택
| 타입 | 장점 | 단점 |
|------|------|------|
| `VARCHAR(n)` | 가독성, 의미 있는 키 | 문자열 비교 비용 |
| `INTEGER` / `LONG` | 비교 빠름, 저장 효율 | 의미 없음, 별도 매핑 필요 |
### 주의사항
- PRIMARY KEY 값은 중복될 수 없습니다.
- PRIMARY KEY 값은 변경할 수 없습니다 (변경 시 DELETE + INSERT).
- PRIMARY KEY 컬럼에는 인덱스가 자동 생성됩니다.
- PRIMARY KEY 컬럼은 하나만 지정합니다.
## PRIMARY KEY 정책
PRIMARY KEY 설계 시 고려할 정책과 모범 사례입니다.
### 자연키 vs 대리키
#### 자연키 (Natural Key)
비즈니스 의미를 갖는 값을 그대로 PRIMARY KEY로 사용합니다.
```sql
-- 국가 코드: 표준화된 자연키
CREATE LOOKUP TABLE ch9_pk_country (
iso_code VARCHAR(4) PRIMARY KEY, -- ISO 3166-1 alpha-2
name VARCHAR(64)
);
```
**장점**: 의미 파악 쉬움, 별도 조회 불필요
**단점**: 키 변경 시 참조 무결성 문제
#### 대리키 (Surrogate Key)
SEQUENCE 컬럼이나 UUID처럼 의미 없는 값을 PRIMARY KEY로 사용합니다.
```sql
-- 설비 마스터: 대리키
CREATE LOOKUP TABLE ch9_pk_equip (
equip_id LONG PROPERTY(SEQUENCE=1) PRIMARY KEY,
code VARCHAR(32), -- 비즈니스 키
name VARCHAR(128)
);
CREATE INDEX ch9_pk_equip_idx ON ch9_pk_equip(code);
```
**장점**: 불변, 조인 효율적
**단점**: 코드-ID 변환 필요
### PRIMARY KEY 불변 원칙
PRIMARY KEY 값은 UPDATE할 수 없습니다. 변경이 필요하면 DELETE + INSERT를 사용합니다.
```sql
-- 잘못된 패턴 (PK 변경은 DELETE + INSERT로)
-- UPDATE는 PK 변경 불가
-- 올바른 패턴
DELETE FROM ch9_pk_country WHERE iso_code = 'OLD';
INSERT INTO ch9_pk_country VALUES ('NEW', '새 국가명');
```
LOOKUP 테이블 DML은 개별 문장 단위로 실행합니다. `BEGIN`/`COMMIT`으로 묶는 TRANSACTION
트랜잭션에는 LOOKUP DML을 포함할 수 없습니다.
따라서 두 문장 사이의 조회와 삽입 실패에 대비해야 합니다. 기존 값을 보관하고 참조하는
키의 전환 순서를 정한 뒤 변경하며, 전체 변경의 원자성이 필요하면 TRANSACTION 테이블을
검토합니다.
이 페이지의 실습 테이블은 다음과 같이 정리합니다.
```sql
DROP INDEX ch9_pk_equip_idx;
DROP TABLE ch9_pk_equip;
DROP TABLE ch9_pk_country;
DROP TABLE ch9_pk_price;
DROP TABLE ch9_pk_country_code;
```
---
title: "9.11 SEQUENCE 컬럼"
url: https://docs.machbase.com/kr/dbms/lookup-table-usage/sequence-column/
language: kr
kind: page
---
# 9.11 SEQUENCE 컬럼
LOOKUP 테이블의 SEQUENCE 컬럼 설정과 활용을 다룹니다.
## LOOKUP SEQUENCE 컬럼 정의
SEQUENCE 컬럼은 `NEXTVAL`로 입력 번호를 생성할 때 사용합니다. LOOKUP 테이블은
[PRIMARY KEY가 필수](../primary-key-policy/)이므로, SEQUENCE 컬럼 자체를 PRIMARY KEY로
지정할지 다른 컬럼을 PRIMARY KEY로 둘지 함께 정합니다. 이 번호를 이벤트 발생 시각의
순서와 동일하게 해석하지 않습니다.
## SEQUENCE 컬럼이 필요한 이유
같은 시각에 발생한 알람이나 관리 이력을 서로 다른 행으로 식별해야 할 때 사용할 수
있습니다. LOOKUP은 전체 행을 메모리에 유지하므로 이 예제는 소규모 관리 이력에 해당합니다.
장기간 누적하는 대량 이벤트는 LOG 테이블을 검토합니다.
## SEQUENCE 컬럼 선언
SEQUENCE는 `LONG` 또는 `INT64` 타입 컬럼에 지정할 수 있습니다. PROPERTY 절의 `SEQUENCE`
파라미터로 시작값을 설정하며, 이 속성은 LOOKUP 테이블에서만 사용할 수 있습니다.
```sql
CREATE LOOKUP TABLE ch9_sequence (
seq LONG PROPERTY(SEQUENCE=1) PRIMARY KEY,
sensor_id VARCHAR(40),
alarm_type VARCHAR(20),
occurred_at DATETIME,
message VARCHAR(200)
);
```
- `SEQUENCE=1`: seq 컬럼이 1부터 자동 증가
- 시작값은 1 이상 4,294,967,295 미만의 정수만 지정할 수 있습니다.
- 위 예제처럼 SEQUENCE 컬럼을 PRIMARY KEY로 지정하면 번호의 고유성까지 함께 보장됩니다.
## SEQUENCE 값 삽입: NEXTVAL()
서버는 SEQUENCE 컬럼마다 다음에 사용할 번호를 카운터로 보관하며, `NEXTVAL()`은 그 값을
가져와 삽입합니다. 매번 테이블의 최댓값을 다시 계산하지 않습니다.
```sql
-- NEXTVAL()로 자동 증가값 입력
INSERT INTO ch9_sequence (seq, sensor_id, alarm_type, occurred_at, message)
VALUES (NEXTVAL(seq), 'TEMP-01', 'HIGH', NOW, '온도 초과');
INSERT INTO ch9_sequence (seq, sensor_id, alarm_type, occurred_at, message)
VALUES (NEXTVAL(seq), 'PRESS-02', 'LOW', NOW, '압력 저하');
-- 조회
SELECT * FROM ch9_sequence ORDER BY seq;
-- seq=1, seq=2 순으로 정렬됨
```
## 일반 컬럼처럼 직접 값 지정
SEQUENCE 컬럼에 직접 값을 입력하는 것도 허용됩니다. 입력값이 현재 카운터보다 크면
카운터가 `입력값 + 1`로 올라갑니다. 현재 카운터보다 작은 값을 입력해도 카운터는
내려가지 않습니다.
```sql
-- 직접 값 지정 (nextval을 쓰지 않아도 됨)
INSERT INTO ch9_sequence (seq, sensor_id, alarm_type, occurred_at, message)
VALUES (100, 'FLOW-03', 'NORMAL', NOW, '정상 복구');
-- 이후 NEXTVAL() 호출 시 101이 됩니다
INSERT INTO ch9_sequence (seq, sensor_id, alarm_type, occurred_at, message)
VALUES (NEXTVAL(seq), 'TEMP-01', 'NORMAL', NOW, '온도 정상');
-- seq = 101
```
## 활용 패턴
```sql
-- 최신 알람 N건 조회
SELECT * FROM ch9_sequence ORDER BY seq DESC LIMIT 10;
-- 특정 seq 이후 알람 조회
SELECT * FROM ch9_sequence WHERE seq > 500 ORDER BY seq;
-- 알람 확인 처리 (PK 기준 UPDATE)
UPDATE ch9_sequence SET alarm_type = 'ACKNOWLEDGED'
WHERE seq = 101;
```
실습 테이블은 다음과 같이 정리합니다.
```sql
DROP TABLE ch9_sequence;
```
## 주의 사항
- SEQUENCE 컬럼은 `LONG`, `INT64` 타입을 지원합니다.
- 시작값은 양수(`SEQUENCE=1` 이상)만 허용되며 4,294,967,295 미만이어야 합니다.
- 카운터는 서버에 저장되고 한 방향으로만 올라갑니다. 가장 큰 seq 행을 DELETE해도 그 번호는
다시 사용되지 않으므로, 번호가 비어 있는 구간이 생길 수 있습니다.
- SEQUENCE 컬럼을 PRIMARY KEY로 지정하지 않으면 `NEXTVAL()`을 쓰지 않고 중복 값을 직접
입력할 수 있습니다. 번호의 고유성이 필요하면 이 컬럼을 PRIMARY KEY로 지정합니다.
---
title: "9.12 JSON 컬럼과 JSON 조회"
url: https://docs.machbase.com/kr/dbms/lookup-table-usage/json-column-query/
language: kr
kind: page
---
# 9.12 JSON 컬럼과 JSON 조회
LOOKUP 테이블의 JSON 컬럼 지원 범위와 JSON 조건 조회를 다룹니다.
## LOOKUP JSON 조건 조회
LOOKUP 테이블은 `JSON` 컬럼을 일반 컬럼으로 지원합니다. JSON 컬럼은 유동적인 속성 값을 참조 데이터와 함께 저장할 때 사용할 수 있습니다.
```sql
CREATE LOOKUP TABLE ch9_json (
sensor_id VARCHAR(80) PRIMARY KEY,
location VARCHAR(200),
config JSON
);
INSERT INTO ch9_json VALUES (
'TEMP-01',
'factory1',
'{"unit":"celsius","level":3,"threshold":{"high":90.0}}'
);
SELECT sensor_id, config
FROM ch9_json
WHERE config->'$.unit' = 'celsius';
```
## 타입별 JSON 조건
숫자 값을 숫자로 비교할 때는 타입별 JSON 추출 함수를 사용합니다.
```sql
SELECT sensor_id
FROM ch9_json
WHERE JSON_EXTRACT_INTEGER(config, '$.level') >= 3
AND JSON_EXTRACT_DOUBLE(config, '$.threshold.high') > 80.0;
```
JSON 구조 자체를 확인할 수도 있습니다.
```sql
SELECT sensor_id
FROM ch9_json
WHERE JSON_IS_VALID(config) = 1
AND JSON_TYPEOF(config, '$.threshold') = 'Object';
```
## PRIMARY KEY 제약
LOOKUP 테이블은 JSON 컬럼을 저장할 수 있지만, JSON 컬럼을 `PRIMARY KEY`로 사용할 수 없습니다. 행 식별자는 `INTEGER`, `LONG`, `VARCHAR` 등 안정적인 일반 타입으로 둡니다.
```sql
-- 실패: JSON 컬럼은 primary key로 사용하지 않습니다.
CREATE LOOKUP TABLE ch9_json_bad (
config JSON PRIMARY KEY,
note VARCHAR(80)
);
```
```sql
-- 권장: 별도 식별자를 primary key로 사용합니다.
CREATE LOOKUP TABLE ch9_json_ok (
sensor_id VARCHAR(80) PRIMARY KEY,
config JSON,
note VARCHAR(80)
);
```
## 설계 기준
| 상황 | 권장 접근 |
|------|----------|
| 조인/검색에 자주 쓰는 값 | 별도 컬럼 |
| 장비별로 다른 유동 속성 | JSON 컬럼 |
| 숫자 조건 검색 | 일반 숫자 컬럼으로 분리 |
| primary key | 안정적인 식별자 컬럼 사용 |
| 고빈도 path 검색 | 별도 컬럼으로 추출 |
JSON path별 전용 인덱스는 지원하지 않습니다. 고빈도 검색 조건은 별도 컬럼으로 분리하고, 해당 컬럼에 인덱스를 적용하는 설계를 우선 검토합니다.
```sql
CREATE LOOKUP TABLE ch9_json_fast (
sensor_id VARCHAR(80) PRIMARY KEY,
unit VARCHAR(16),
level INTEGER,
config JSON
);
CREATE INDEX ch9_json_unit_idx ON ch9_json_fast(unit);
```
## UPDATE·DELETE 조건
```sql
UPDATE ch9_json
SET location = 'factory2'
WHERE config->'$.unit' = 'celsius';
DELETE FROM ch9_json
WHERE JSON_EXTRACT_INTEGER(config, '$.level') < 2;
```
대상 범위가 넓을 수 있으므로 UPDATE/DELETE 전에는 같은 조건으로 건수를 확인합니다.
이 페이지의 실습 객체는 다음과 같이 정리합니다. `ch9_json_bad`는 생성에 실패하는
예제이므로 정리 대상이 아닙니다.
```sql
DROP TABLE ch9_json_fast;
DROP TABLE ch9_json_ok;
DROP TABLE ch9_json;
```
## 주의사항
- LOOKUP 테이블은 JSON 컬럼을 일반 컬럼으로 지원합니다.
- JSON 컬럼은 primary key로 사용할 수 없습니다.
- JSON path별 전용 인덱스는 지원하지 않습니다.
- 자주 검색하는 값은 LOOKUP 일반 컬럼으로 분리합니다.
- JSON path 인덱스가 필요하면 TRANSACTION 또는 TAG 테이블을 검토합니다.
---
title: "9.13 일반 predicate UPDATE/DELETE"
url: https://docs.machbase.com/kr/dbms/lookup-table-usage/predicate-update-delete/
language: kr
kind: page
---
# 9.13 일반 predicate UPDATE/DELETE
LOOKUP 테이블은 기본 키뿐 아니라 일반 조건식으로 여러 행을 수정하거나 삭제할 수 있습니다.
변경 전 같은 조건으로 대상 행 수를 확인하십시오.
이 페이지의 실습은 다음 테이블 하나로 진행하며 마지막에 정리합니다.
```sql
CREATE LOOKUP TABLE ch9_predicate (
equip_id VARCHAR(32) PRIMARY KEY,
site VARCHAR(16),
status VARCHAR(16),
score INTEGER
);
INSERT INTO ch9_predicate VALUES ('EQ-01', 'SEOUL', 'READY', 10);
INSERT INTO ch9_predicate VALUES ('EQ-02', 'SEOUL', 'READY', 20);
INSERT INTO ch9_predicate VALUES ('EQ-03', 'SEOUL', 'RETIRED', 30);
INSERT INTO ch9_predicate VALUES ('EQ-04', 'BUSAN', 'READY', 40);
```
## UPDATE
조건에 맞는 모든 행을 갱신합니다. `SET` 표현식은 현재 행 값을 참조할 수 있지만 기본 키
컬럼 자체는 변경할 수 없습니다.
LOOKUP UPDATE에는 `WHERE` 조건이 필요합니다. 전체 행을 갱신하려는 경우에도 지원되는
조건식을 명시합니다. `WHERE` 없이 전체 삭제가 가능한 DELETE와 구분하십시오.
```sql
-- 변경 전 영향 범위를 먼저 셉니다.
SELECT COUNT(*) FROM ch9_predicate
WHERE site = 'SEOUL' AND status = 'READY';
UPDATE ch9_predicate
SET status = 'ACTIVE', score = score + 10
WHERE site = 'SEOUL' AND status = 'READY';
SELECT equip_id, site, status, score FROM ch9_predicate ORDER BY equip_id;
```
COUNT는 2이고, EQ-01과 EQ-02만 `ACTIVE`로 바뀌면서 score가 20·30이 됩니다.
같은 SEOUL이라도 status가 다른 EQ-03과, 같은 READY라도 site가 다른 EQ-04는 그대로입니다.
## DELETE
조건에 맞는 모든 행을 삭제합니다. `WHERE`를 생략하면 테이블의 모든 행이 삭제됩니다.
```sql
SELECT COUNT(*) FROM ch9_predicate WHERE status = 'RETIRED';
DELETE FROM ch9_predicate WHERE status = 'RETIRED';
SELECT equip_id, status FROM ch9_predicate ORDER BY equip_id;
```
COUNT는 1이고 EQ-03이 삭제되어 세 행이 남습니다.
## 조건 설계 지침
1. 단건 변경에는 기본 키 조건을 사용합니다.
2. 일괄 변경 전 동일한 조건의 `SELECT COUNT(*)`로 영향 범위를 확인합니다.
3. 자주 필터링하는 JSON 값은 일반 컬럼으로 분리해 인덱스를 적용하는 방안을 검토합니다.
4. 기본 키를 바꿔야 한다면 기존 행을 삭제하고 새 키로 삽입합니다.
지원 연산자와 JSON 조건의 정확한 범위는 SQL 레퍼런스를 기준으로 확인하십시오.
- [LOOKUP predicate UPDATE](/dbms/reference/sql/syntax/dml-syntax/lookup-predicate-update-syntax/)
- [LOOKUP predicate DELETE](/dbms/reference/sql/syntax/dml-syntax/lookup-predicate-delete-syntax/)
## 권한과 성능
UPDATE와 DELETE에는 각각 대상 테이블의 `UPDATE`, `DELETE` 권한이 필요합니다. 애플리케이션이
변경 전후 값을 직접 조회할 때만 `SELECT`도 부여합니다. 권한 SQL은
[권한 관리](/dbms/security-access-control/privileges/)를 정본으로 사용합니다.
기본 키 equality는 단건 대상을 직접 찾고, 일반 조건식은 조건을 평가해 변경 대상을 수집합니다.
반복 단건 변경에는 prepared statement와 bind를 사용하고, 대량 변경은 같은 조건의 행 수와
실행 시간을 검증 환경에서 측정하십시오.
```sql
DROP TABLE ch9_predicate;
```
---
title: "10. VOLATILE 테이블 활용"
url: https://docs.machbase.com/kr/dbms/volatile-table-usage/
language: kr
kind: section
---
# 10. VOLATILE 테이블 활용
VOLATILE 테이블은 서버 프로세스 범위에서 공유되는 메모리 테이블입니다. 재시작 시 데이터
소실, UPSERT와 재생성 가능한 캐시 패턴을 다룹니다.
## 이 장의 구성
| 절 | 내용 |
|----|------|
| [개요와 사용 기준](./overview-use-criteria/) | VOLATILE 테이블의 특성과 적용 판단 기준 |
| [테이블 구조와 스키마](./table-structure-schema/) | PRIMARY KEY 설계, 컬럼 타입, 스키마 구성 |
| [생성, 변경, 삭제](./create-alter-drop/) | CREATE VOLATILE TABLE, DROP, 영속성 차이 |
| [데이터 입력과 변경](./data-input-mutation/) | INSERT, ON DUPLICATE KEY UPDATE, DELETE |
| [조회와 분석](./query-analysis/) | SELECT, 조건 조회, LIKE |
| [인덱스와 성능](./index-performance/) | Red-Black 트리 인덱스, PK 인덱스 |
| [운영과 데이터 생명주기](./operations-lifecycle/) | 운영 절차와 데이터 관리 |
| [제약, 오류, 문제 해결](./constraints-errors-troubleshooting/) | 기능 제약, 오류 대응 |
---
title: "10.1 개요와 사용 기준"
url: https://docs.machbase.com/kr/dbms/volatile-table-usage/overview-use-criteria/
language: kr
kind: page
---
# 10.1 개요와 사용 기준
VOLATILE 테이블은 데이터를 메모리에 저장하는 임시 테이블입니다. 서버가 재시작되면 데이터가 소멸하므로, 재생성 가능한 최신 상태, 임시 집계, 세션 간 공유 캐시 용도로 사용합니다.
## VOLATILE 테이블의 특성
VOLATILE 테이블은 `CREATE VOLATILE TABLE` 문으로 생성합니다.
```sql
CREATE VOLATILE TABLE ch10_overview (
sensor_id VARCHAR(64) PRIMARY KEY,
value DOUBLE,
updated_at DATETIME
);
```
VOLATILE 테이블의 주요 특성은 다음과 같습니다.
| 항목 | 내용 |
|------|------|
| 주요 용도 | 최신 상태 캐시, 임시 집계, 중간 결과 |
| 저장 위치 | 메모리 |
| 재시작 후 데이터 | 소멸 |
| 공유 범위 | 서버 수준 공유 |
| 키 | PRIMARY KEY 선택 |
| 주요 기능 | UPDATE, DELETE, `ON DUPLICATE KEY UPDATE`, Red-Black 트리 인덱스 |
| 백업 | 지원하지 않음 |
## 사용 기준
다음 조건에 해당하면 VOLATILE 테이블을 사용합니다.
- 서버 재시작 후 데이터가 없어져도 됩니다.
- 원본 데이터에서 언제든 다시 계산하거나 재구성할 수 있습니다.
- 최신 상태, 최근 집계, 임시 작업 결과를 빠르게 조회해야 합니다.
- 여러 세션에서 같은 임시 상태를 공유해야 합니다.
- 디스크 영속성보다 메모리 기반 응답 시간이 중요합니다.
최신 센서 상태를 유지하는 예시는 다음과 같습니다.
```sql
INSERT INTO ch10_overview VALUES ('TEMP-01', 23.5, NOW)
ON DUPLICATE KEY UPDATE SET value = 23.5, updated_at = NOW;
SELECT *
FROM ch10_overview
WHERE sensor_id = 'TEMP-01';
-- 다음 절에서 같은 이름을 다시 사용하므로 정리합니다.
DROP TABLE ch10_overview;
```
### 활용 패턴
| 패턴 | key | 재생성 원본 | 권장 만료 방식 |
|------|-----|-------------|----------------|
| 장비 최신 상태 | 장비 ID | TAG 또는 LOG | 같은 key 갱신 |
| 짧은 주기 집계 | 대상과 시간 bucket | TAG 또는 LOG | bucket 교체 또는 재구성 |
| 작업 진행 상태 | 작업 ID | 작업 시스템 | 완료 후 key 삭제 |
| 임시 조회 cache | 요청 또는 객체 ID | 영속 table | 전체 재구성 |
최신 상태 갱신은 [데이터 입력과 변경](../data-input-mutation/)을, 임시 집계는
[조회와 분석](../query-analysis/)을 참고합니다.
## 다른 테이블을 검토할 경우
다음 요구사항에는 다른 테이블 타입을 사용합니다.
| 요구사항 | 권장 테이블 |
|----------|-------------|
| 재시작 후에도 반드시 보존해야 하는 원본 데이터 | TAG 또는 LOG |
| 기준 코드나 장비 마스터처럼 영속 참조 데이터 | LOOKUP |
| 트랜잭션과 관계형 갱신이 필요한 업무 데이터 | TRANSACTION |
| 장기 분석 대상 시계열 데이터 | TAG |
VOLATILE 테이블에만 저장된 데이터는 서버 종료 시 복구할 수 없습니다. 중요한 데이터는 TAG, LOG, LOOKUP, TRANSACTION 중 적합한 영속 테이블에 저장하고, VOLATILE 테이블은 캐시나 중간 결과로 사용합니다.
## 설계 순서
VOLATILE 테이블 설계 시 다음 순서로 결정합니다.
1. 데이터가 재생성 가능한지 확인합니다.
2. PRIMARY KEY가 필요한지 결정합니다.
3. 예상 행 수와 메모리 사용량을 산정합니다.
4. 재시작 후 초기 적재 절차를 준비합니다.
5. 보존해야 하는 결과는 애플리케이션의 명시적인 쓰기 작업으로 영속 테이블에 저장합니다.
VOLATILE을 영속화하는 전용 flush 명령은 없습니다.
스키마와 PRIMARY KEY 설계는 [테이블 구조와 스키마](/dbms/volatile-table-usage/table-structure-schema/)에서, 재시작 대응은 [재시작과 데이터 소실](/dbms/volatile-table-usage/operations-lifecycle/)에서 다룹니다.
---
title: "10.2 테이블 구조와 스키마"
url: https://docs.machbase.com/kr/dbms/volatile-table-usage/table-structure-schema/
language: kr
kind: page
---
# 10.2 테이블 구조와 스키마
VOLATILE 테이블의 PRIMARY KEY 설계와 스키마 구성 방법을 다룹니다.
## PRIMARY KEY 설계
VOLATILE 테이블은 PRIMARY KEY 없이도 생성할 수 있습니다. 단, PK 기반 조회나 `ON DUPLICATE KEY UPDATE`를 사용하려면 PRIMARY KEY가 필요합니다.
### 단일 PRIMARY KEY
```sql
CREATE VOLATILE TABLE ch10_schema_state (
device_id VARCHAR(32) PRIMARY KEY,
state VARCHAR(16),
updated_at DATETIME
);
```
### 복합 키가 필요한 경우
여러 컬럼 조합으로 행을 고유하게 식별해야 하면 조합 키를 별도 PRIMARY KEY 컬럼으로 둡니다.
```sql
CREATE VOLATILE TABLE ch10_schema_hourly (
key_id VARCHAR(96) PRIMARY KEY,
sensor_id VARCHAR(64),
hour_ts DATETIME,
avg_value DOUBLE,
sample_cnt INTEGER
);
-- 조합 키 삽입
INSERT INTO ch10_schema_hourly
VALUES ('TEMP-01:2026010110', 'TEMP-01', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 23.5, 60);
INSERT INTO ch10_schema_hourly
VALUES ('TEMP-01:2026010111', 'TEMP-01', TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS'), 24.1, 60);
INSERT INTO ch10_schema_hourly
VALUES ('TEMP-02:2026010110', 'TEMP-02', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 21.0, 60);
-- 조합 키 조회
SELECT sensor_id, avg_value FROM ch10_schema_hourly
WHERE key_id = 'TEMP-01:2026010110';
```
### ON DUPLICATE KEY UPDATE와 함께 사용
```sql
-- PRIMARY KEY 중복 시 UPDATE 실행
INSERT INTO ch10_schema_state VALUES ('DEV-01', 'ONLINE', NOW)
ON DUPLICATE KEY UPDATE SET state = 'ONLINE', updated_at = NOW;
```
### 주의사항
- PRIMARY KEY 없이 생성할 수 있지만, PK 기반 동작(UPSERT, PK 조회 등)은 사용할 수 없습니다.
- PRIMARY KEY가 같은 행을 둘 이상 저장할 수 없습니다. `ON DUPLICATE KEY UPDATE`는 중복 행을
추가하는 대신 기존 행을 갱신합니다.
- PRIMARY KEY 컬럼은 하나만 지정합니다.
## VOLATILE 테이블 설계
VOLATILE 테이블은 데이터를 메모리에만 두고, 서버 재시작 시 데이터가 소멸합니다.
테이블 정의는 남으므로 설계에서 정할 것은 "무엇을 잃어도 되는가"와 "어떻게 다시 채우는가"
두 가지입니다. 여러 세션이 공유하되 재시작 후 복구할 필요가 없는 상태나 캐시에 사용합니다.
스키마를 정할 때는 다음 항목을 함께 검토합니다.
- [활용 사례](../overview-use-criteria/#use-cases-volatile)
- [영속성 차이와 DDL](../create-alter-drop/#differences-persistence-ddl)
- [메모리 생명주기](../operations-lifecycle/#lifecycle-memory)
- [Red-Black 트리 인덱스](../index-performance/#index-strategy-red-black)
- [ON DUPLICATE KEY UPDATE](../data-input-mutation/#on-duplicate-key-update)
- [재시작과 데이터 재구성](../operations-lifecycle/#data-loss)
이 페이지의 실습 테이블은 다음과 같이 정리합니다.
```sql
DROP TABLE ch10_schema_hourly;
DROP TABLE ch10_schema_state;
```
---
title: "10.3 생성, 변경, 삭제"
url: https://docs.machbase.com/kr/dbms/volatile-table-usage/create-alter-drop/
language: kr
kind: page
---
# 10.3 생성, 변경, 삭제
VOLATILE 테이블의 생성·삭제 방법과 다른 테이블 타입과의 영속성 차이를 다룹니다.
## Volatile 테이블 생성 및 관리
volatile 테이블의 생성 및 삭제 방법은 다음과 같습니다.
### 생성
```sql
create volatile table vtable (id1 integer, name varchar(20));
```
### 삭제
```sql
drop table vtable;
```
## 영속성 차이·DDL
VOLATILE 테이블은 다른 테이블 타입과 달리 메모리에만 존재합니다.
### 영속성 비교
| 항목 | VOLATILE | LOOKUP | TRANSACTION | TAG | LOG |
|------|----------|--------|-----|-----|-----|
| 저장 위치 | 메모리 | 디스크 | 디스크 | 디스크 | 디스크 |
| 서버 재시작 후 데이터 유지 | X | O | O | O | O |
| 테이블 구조(DDL) 유지 | O | O | O | O | O |
### DDL 특성
VOLATILE에서 사라지는 것은 데이터이고, 테이블 정의는 다른 테이블 타입과 마찬가지로
영속 저장됩니다. 재시작 후 필요한 작업은 재생성이 아니라 초기 적재입니다.
```sql
-- 테이블 정의는 한 번만 만들면 됩니다.
CREATE VOLATILE TABLE ch10_ddl (
sensor_id VARCHAR(64) PRIMARY KEY,
value DOUBLE,
updated_at DATETIME
);
```
### 생성 문법
기본 구문은 `CREATE VOLATILE TABLE 테이블명 (컬럼 정의, ...)`입니다. 키 기반 갱신이나 삭제가
필요할 때 한 컬럼에 `PRIMARY KEY`를 지정합니다.
- `PRIMARY KEY`는 선택 사항입니다.
- PRIMARY KEY 컬럼은 하나만 지정합니다.
### AUTO_INCREMENT PRIMARY KEY
서버가 숫자 PRIMARY KEY를 생성해야 하면 단일 `LONG` 또는 `INT64` 컬럼에
`AUTO_INCREMENT`를 지정합니다.
```sql
CREATE VOLATILE TABLE ch10_ddl_seq (
request_id LONG PRIMARY KEY AUTO_INCREMENT,
payload VARCHAR(256)
);
INSERT INTO ch10_ddl_seq(payload) VALUES('refresh');
```
PK 컬럼을 생략하거나 NULL로 지정하면 서버가 값을 생성합니다. 단일
`INSERT ... VALUES`에서 `0..INT64_MAX` 범위의 PK 값을 직접 지정할 수도 있습니다. 지정값이
현재 다음 자동값 이상이면 다음 자동값은 `지정값 + 1`로 진행하며, 작은 값을 지정해도
되감기지 않습니다.
AUTO_INCREMENT를 사용하는 VOLATILE 테이블에서는 `INSERT ... SELECT`와
`ON DUPLICATE KEY UPDATE`를 사용할 수 없습니다.
VOLATILE 테이블은 서버 재시작 시 데이터가 사라지므로 다음 자동값도 1부터 다시
시작합니다. 테이블 정의는 유지되므로 다시 만들 필요는 없습니다. SDK에서 INSERT 결과 ID를 받는 방법은
[ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를 참고하십시오.
### 컬럼 추가와 삭제
Standard Edition에서는 VOLATILE 테이블에 고정 길이 숫자 ARRAY 컬럼을 추가하고 삭제할
수 있습니다.
```sql
ALTER TABLE ch10_ddl
ADD COLUMN (thresholds DOUBLE[2] DEFAULT [10.0, 20.0]);
ALTER TABLE ch10_ddl
DROP COLUMN (thresholds);
```
VOLATILE은 기존 scalar `ADD COLUMN`과 마찬가지로 ALTER 전에 존재한 row를 DEFAULT로 다시
쓰지 않습니다. ARRAY DEFAULT를 지정해도 기존 row의 새 컬럼은 whole NULL입니다. 이
동작은 LOG, LOOKUP, TRANSACTION과 TAG METADATA의 backfill 규칙과 다릅니다.
ARRAY의 지원 요소 타입, cardinality와 DEFAULT 규칙은
[숫자 ARRAY 타입](/dbms/reference/sql/types/array/)을 참고하십시오.
### 삭제
```sql
DROP TABLE ch10_ddl;
DROP TABLE ch10_ddl_seq;
```
### 주의사항
- VOLATILE 테이블의 DDL(구조 정의)은 데이터베이스에 저장되며 서버 재시작 후에도 남습니다.
- 데이터는 재시작 후 자동 복원되지 않으므로, 초기 적재 스크립트(예: 시작 시 machsql 실행)를 구성해야 합니다.
---
title: "10.4 데이터 입력과 변경"
url: https://docs.machbase.com/kr/dbms/volatile-table-usage/data-input-mutation/
language: kr
kind: page
---
# 10.4 데이터 입력과 변경
VOLATILE 테이블의 `INSERT`, 중복 키 갱신, `DELETE`를 실행 가능한 예제로 설명합니다.
## 데이터 입력과 갱신
다음 예제는 마지막 정리 구문까지 순서대로 실행할 수 있습니다. 최신 상태처럼 같은 키의 값을
계속 바꿔야 한다면 `PRIMARY KEY`를 정의하고 `ON DUPLICATE KEY UPDATE`를 사용합니다.
```sql
CREATE VOLATILE TABLE ch10_mutation (
id INTEGER PRIMARY KEY,
direction VARCHAR(10),
refcnt INTEGER
);
INSERT INTO ch10_mutation VALUES (1, 'west', 0);
INSERT INTO ch10_mutation VALUES (2, 'east', 0);
INSERT INTO ch10_mutation VALUES (1, 'south', 0)
ON DUPLICATE KEY UPDATE;
INSERT INTO ch10_mutation VALUES (1, 'south', 0)
ON DUPLICATE KEY UPDATE SET refcnt = 1;
SELECT * FROM ch10_mutation ORDER BY id;
```
중복 키가 없으면 새 행이 입력됩니다. 중복 키가 있으면 `SET` 절이 없는 구문은 입력값으로
행 전체를 갱신하고, `SET` 절이 있는 구문은 지정한 컬럼만 갱신합니다. `PRIMARY KEY` 자체는
갱신 대상으로 지정할 수 없습니다.
대량 입력 API는 언어와 드라이버에 따라 초기화·바인딩·오류 처리가 다릅니다. 불완전한 코드
조각을 복사하지 말고 [SDK 및 통합](/dbms/development-tools-integration/)의 해당 드라이버 예제를
사용합니다.
## 조건부 갱신
이미 있는 행의 일부 컬럼만 바꿀 때는 `UPDATE`를 사용합니다. `INSERT ... ON DUPLICATE KEY
UPDATE`가 "없으면 넣고 있으면 바꾼다"인 것과 달리, `UPDATE`는 대상 행이 있을 때만 값을
바꿉니다.
VOLATILE 테이블의 `UPDATE`에는 `WHERE`가 필요하며, 조건은 삭제와 마찬가지로
`PRIMARY KEY = 값` 형태만 지원합니다. 다른 컬럼 조건이나 복합 조건은 사용할 수 없습니다.
`WHERE`를 생략한 전체 갱신도 지원하지 않습니다.
```sql
UPDATE ch10_mutation SET refcnt = refcnt + 1 WHERE id = 2;
SELECT * FROM ch10_mutation ORDER BY id;
```
`SET` 절에는 `PRIMARY KEY` 컬럼을 지정할 수 없습니다. 키를 바꿔야 하면 기존 행을 삭제하고
새 키로 다시 입력합니다.
## 데이터 삭제
조건부 삭제는 `PRIMARY KEY = 값` 형태만 지원합니다. 다른 컬럼 조건이나 복합 조건은 사용할 수
없습니다.
```sql
DELETE FROM ch10_mutation WHERE id = 2;
SELECT * FROM ch10_mutation ORDER BY id;
DROP TABLE ch10_mutation;
```
테이블 정의를 유지한 채 모든 행을 비우려면 `DELETE FROM 테이블명`처럼 WHERE를 생략합니다.
스키마까지 초기화해야 하면 DROP 후 다시 생성합니다. 서버를 재시작하면 데이터만 사라지고
테이블 정의는 남으므로, 재생성이 아니라 재적재 스크립트를 별도로 관리합니다.
---
title: "10.5 조회와 분석"
url: https://docs.machbase.com/kr/dbms/volatile-table-usage/query-analysis/
language: kr
kind: page
---
# 10.5 조회와 분석
VOLATILE 테이블의 키 조회, 일반 조건 조회, 임시 집계를 실행 가능한 예제로 설명합니다.
## 예제 데이터 준비
VOLATILE 테이블은 다른 테이블 타입과 동일하게 `SELECT` 문으로 조회합니다. 다음 예제는
마지막 정리 구문까지 순서대로 실행할 수 있습니다.
```sql
CREATE VOLATILE TABLE ch10_query (
device_id VARCHAR(64) PRIMARY KEY,
status VARCHAR(16),
value DOUBLE,
updated_at DATETIME
);
INSERT INTO ch10_query VALUES ('DEV-01', 'RUNNING', 42.5, NOW);
INSERT INTO ch10_query VALUES ('DEV-02', 'STOPPED', 0, NOW);
```
## PRIMARY KEY 조회
키 조건은 최신 상태 캐시의 단건 조회에 적합합니다.
```sql
SELECT device_id, status, value, updated_at
FROM ch10_query
WHERE device_id = 'DEV-01';
```
## 일반 조건 조회
`PRIMARY KEY`가 아닌 컬럼으로 반복 조회한다면 보조 인덱스를 검토합니다.
```sql
CREATE INDEX ch10_query_status_idx ON ch10_query(status);
SELECT device_id, value, updated_at
FROM ch10_query
WHERE status = 'RUNNING';
```
인덱스도 메모리를 사용하므로 실제 조회에 필요한 컬럼에만 생성합니다.
## 임시 집계 조회
짧은 주기의 집계 결과를 저장하면 대시보드나 알람 판단에서 반복 계산을 줄일 수 있습니다.
```sql
CREATE VOLATILE TABLE ch10_query_summary (
summary_key VARCHAR(96) PRIMARY KEY,
sensor_id VARCHAR(64),
bucket_time DATETIME,
avg_value DOUBLE,
max_value DOUBLE,
sample_cnt LONG
);
INSERT INTO ch10_query_summary
VALUES ('TEMP-01:2026-01-01T00:00', 'TEMP-01', TO_DATE('2026-01-01 00:00:00'),
21.5, 23.0, 60);
SELECT sensor_id, bucket_time, avg_value, max_value
FROM ch10_query_summary
WHERE sensor_id = 'TEMP-01'
ORDER BY bucket_time DESC
LIMIT 10;
DROP TABLE ch10_query_summary;
DROP TABLE ch10_query;
```
집계 결과를 장기 보관해야 하면 LOG 또는 TRANSACTION 테이블로 주기적으로 복사합니다.
## 조회 시 주의사항
- 서버 재시작 후에는 데이터가 비어 있으므로 초기 적재 여부를 먼저 확인합니다.
- 키 조회가 중심이면 `PRIMARY KEY`를 지정합니다.
- 범위 조회나 정렬에 자주 쓰는 컬럼은 보조 인덱스를 검토합니다.
- 중요한 원본 데이터는 영속 테이블에 저장하고 VOLATILE은 캐시로 사용합니다.
---
title: "10.6 인덱스와 성능"
url: https://docs.machbase.com/kr/dbms/volatile-table-usage/index-performance/
language: kr
kind: page
---
# 10.6 인덱스와 성능
VOLATILE 테이블의 인덱스 생성과 선택 기준을 설명합니다.
## 지원 인덱스
`PRIMARY KEY`를 선언하면 키 조회용 인덱스가 생성됩니다. 일반 컬럼에는 `REDBLACK` 인덱스를
추가할 수 있습니다. `BITMAP`과 `KEYWORD` 인덱스는 VOLATILE 테이블에서 지원하지 않습니다.
다음 예제는 생성부터 정리까지 순서대로 실행할 수 있습니다.
```sql
CREATE VOLATILE TABLE ch10_index (
id INTEGER PRIMARY KEY,
name VARCHAR(20),
status VARCHAR(16)
);
CREATE INDEX ch10_index_name_idx
ON ch10_index(name) INDEX_TYPE REDBLACK;
INSERT INTO ch10_index VALUES (1, 'west device', 'ACTIVE');
INSERT INTO ch10_index VALUES (2, 'east device', 'INACTIVE');
SELECT id, name
FROM ch10_index
WHERE name = 'west device';
DROP INDEX ch10_index_name_idx;
DROP TABLE ch10_index;
```
## 설계 기준
- 키 기반 단건 조회와 갱신에는 `PRIMARY KEY`를 사용합니다.
- 일반 컬럼의 동등·범위 조건이 반복될 때만 보조 인덱스를 추가합니다.
- 데이터뿐 아니라 인덱스도 메모리를 사용하므로 불필요한 인덱스를 제거합니다.
- 실제 쿼리의 조건과 행 수를 기준으로 생성 전후 응답 시간과 메모리를 비교합니다.
구문 세부사항은 [인덱스 구문](/dbms/reference/sql/syntax/index-syntax/)을
참고합니다.
---
title: "10.7 운영과 데이터 생명주기"
url: https://docs.machbase.com/kr/dbms/volatile-table-usage/operations-lifecycle/
language: kr
kind: page
---
# 10.7 운영과 데이터 생명주기
VOLATILE 테이블의 생성·적재·사용·소멸·재구성 절차를 정리합니다.
## 데이터 생명주기
1. 서버 시작 후 테이블 생성 SQL을 실행합니다.
2. 필요하면 영속 원본에서 초기 데이터를 적재합니다.
3. 애플리케이션이 조회와 갱신을 시작합니다.
4. 보존해야 할 결과는 영속 테이블에 기록합니다.
5. 서버가 종료되면 데이터가 소멸합니다. 테이블 정의는 남습니다.
## 세션 공유
VOLATILE 테이블은 서버 수준에서 공유됩니다. 한 세션이 입력한 행을 다른 세션이 조회할 수
있으며, 연결 종료만으로 데이터가 사라지지 않습니다.
## 영속 데이터와의 경계
VOLATILE에는 원본에서 재생성 가능한 최신 상태나 중간 결과만 둡니다. 감사 기록, 원본 이벤트,
복구할 수 없는 결과는 TAG, LOG, LOOKUP 또는 TRANSACTION 테이블에 저장합니다. 복사 SQL은
원본과 대상의 컬럼, 중복 처리, 실행 주기를 포함해 별도의 작업으로 관리합니다.
## 재시작 절차
- 테이블이 있는지 확인합니다. 재시작만으로는 정의가 사라지지 않으므로 보통 새로 만들지 않습니다.
- 영속 원본이 있으면 정의된 기준 시점의 데이터만 적재합니다.
- 예상 행 수와 최신 시각을 확인합니다.
- 검증이 끝난 뒤 수집기와 애플리케이션 쓰기를 재개합니다.
- 재구성이 실패하면 빈 캐시 상태에서 서비스가 안전하게 동작하는지 확인합니다.
## 운영 체크리스트
- 초기 적재 SQL을 형상 관리합니다. 최초 구축용 생성 SQL도 함께 보관합니다.
- 스크립트를 운영 계정과 실제 접속 정보로 사전 검증합니다.
- 행 수와 메모리 한도를 관찰합니다.
- 보존이 필요한 데이터가 VOLATILE에만 남지 않도록 점검합니다.
- 재시작 훈련에서 적재와 검증 순서를 확인합니다.
## 메모리 확인과 캐시 재구성
```sql
SELECT * FROM V$STORAGE_DC_VOLATILE_TABLE;
SELECT * FROM V$SYSMEM;
SELECT * FROM V$SESMEM;
SELECT NAME, VALUE
FROM V$PROPERTY
WHERE NAME = 'VOLATILE_TABLESPACE_MEMORY_MAX_SIZE';
```
특정 내부 컬럼명에 의존하지 말고 배포 버전의 view 정의를 확인합니다. 캐시를 다시 만들 때는
행 수와 표본 값을 기록하고, 사용 흐름을 전환한 뒤 테이블 생성·초기 적재·검증 순서로
진행합니다. 실패 시 빈 캐시로 안전하게 동작할 수 있어야 합니다.
---
title: "10.8 제약, 오류, 문제 해결"
url: https://docs.machbase.com/kr/dbms/volatile-table-usage/constraints-errors-troubleshooting/
language: kr
kind: page
---
# 10.8 제약, 오류, 문제 해결
VOLATILE 테이블의 제약 사항, 발생 가능한 오류, 문제 해결 방법을 다룹니다. 대부분의 문제는 메모리 한도, PRIMARY KEY 설계, 지원하지 않는 컬럼 타입, 재시작 후 데이터 소실에서 발생합니다.
## 제약 사항
VOLATILE 테이블 사용 시 다음 제약을 고려합니다.
| 항목 | 제약 |
|------|------|
| 저장 위치 | 메모리 |
| 재시작 후 데이터 | 소멸 |
| 백업·마운트 | 지원하지 않음 |
| JSON 컬럼 | 지원하지 않음 |
| PRIMARY KEY | 선택 사항, 하나만 지정 |
| UPDATE/DELETE | `PRIMARY KEY = 값` 조건만 지원 |
| 메모리 한도 | Volatile/Lookup 테이블 전체 메모리 한도 영향 |
```sql
-- 실패 예: VOLATILE 테이블에는 JSON 컬럼을 사용하지 않습니다.
CREATE VOLATILE TABLE ch10_err_json (
session_id VARCHAR(64) PRIMARY KEY,
payload JSON
);
```
유동 속성이 필요하면 자주 조회하는 값을 일반 컬럼으로 분리하거나, 영속 JSON 컬럼이 필요한 경우 TRANSACTION 또는 TAG 테이블을 검토합니다.
## 메모리 부족
VOLATILE 테이블의 데이터와 인덱스는 메모리를 사용합니다. 행 수가 증가하거나 인덱스가 많으면 메모리 한도에 도달할 수 있습니다.
진단은 다음 순서로 수행합니다. 아래 실습 테이블은 이 페이지 끝에서 정리합니다.
```sql
-- 진단 예제용 실습 테이블입니다.
CREATE VOLATILE TABLE ch10_diag (
device_id VARCHAR(64) PRIMARY KEY,
value DOUBLE
);
INSERT INTO ch10_diag VALUES ('DEV-01', 10.0);
-- 1. 대상 테이블의 행 수를 확인합니다.
SELECT COUNT(*) FROM ch10_diag;
```
```sql
-- 2. VOLATILE 테이블 전체의 메모리 사용을 확인합니다.
SELECT *
FROM V$STORAGE_DC_VOLATILE_TABLE;
```
필요하면 설정의 `VOLATILE_TABLESPACE_MEMORY_MAX_SIZE` 값을 확인합니다.
```sql
SELECT NAME, VALUE
FROM V$PROPERTY
WHERE NAME = 'VOLATILE_TABLESPACE_MEMORY_MAX_SIZE';
```
해결 방법은 다음과 같습니다.
- 불필요한 행을 삭제합니다.
- 캐시 보관 범위를 줄입니다.
- 사용하지 않는 인덱스를 제거합니다.
- 중요한 데이터는 영속 테이블로 옮긴 뒤 VOLATILE 테이블을 재구성합니다.
- 운영 정책에 맞게 메모리 한도 조정을 검토합니다.
## PRIMARY KEY 관련 오류
`ON DUPLICATE KEY UPDATE`, [PK 기반 UPDATE](../data-input-mutation/#volatile-primary-key-update), PK 기반 DELETE를 사용하려면 PRIMARY KEY가 필요합니다.
```sql
CREATE VOLATILE TABLE ch10_err_device (
device_id VARCHAR(64) PRIMARY KEY,
status VARCHAR(16),
updated_at DATETIME
);
```
PRIMARY KEY 값은 중복될 수 없습니다. 중복 입력을 갱신으로 처리하려면 `ON DUPLICATE KEY UPDATE`를 사용합니다.
```sql
INSERT INTO ch10_err_device VALUES ('DEV-01', 'ONLINE', NOW)
ON DUPLICATE KEY UPDATE SET status = 'ONLINE', updated_at = NOW;
```
PRIMARY KEY 컬럼 자체는 UPDATE할 수 없습니다. 키를 변경해야 하는 경우 기존 행을 삭제하고
새 키로 삽입합니다.
## 재시작 후 데이터 소실
서버가 정상 종료, 비정상 종료, 재시작되면 VOLATILE 테이블 데이터는 소멸합니다. 이는 오류가 아니라 테이블 타입의 특성입니다.
문제가 발생했을 때는 다음을 확인합니다.
1. 서버 재시작 이력이 있는지 확인합니다.
2. VOLATILE 테이블 생성 스크립트가 실행되었는지 확인합니다.
3. 초기 적재 쿼리가 정상 실행되었는지 확인합니다.
4. 원본 TAG/LOG/TRANSACTION 테이블에서 캐시를 재구성합니다.
```sql
SELECT COUNT(*) FROM ch10_diag;
```
결과가 0이면 초기 적재 절차를 다시 실행합니다.
이 페이지의 실습 테이블은 다음과 같이 정리합니다.
```sql
DROP TABLE ch10_diag;
DROP TABLE ch10_err_device;
```
## 문제 해결 체크리스트
- 테이블에 저장한 데이터가 재생성 가능한지 확인합니다.
- PRIMARY KEY가 필요한 작업인지 확인합니다.
- `COUNT(*)`와 `V$STORAGE_DC_VOLATILE_TABLE`로 규모와 메모리 사용을 확인합니다.
- 재시작 후에는 초기 적재 SQL을 다시 실행합니다. 테이블은 다시 만들지 않아도 됩니다.
- 영속 보존이 필요하면 VOLATILE이 아니라 TAG, LOG, LOOKUP, TRANSACTION 테이블을 사용합니다.
---
title: "11. 개발 및 애플리케이션 연동"
url: https://docs.machbase.com/kr/dbms/development-tools-integration/
language: kr
kind: section
---
# 11. 개발 및 애플리케이션 연동
애플리케이션 요구사항에 맞는 연동 방식을 선택하고, SDK별 설치·API와 공통 운영 원칙을
확인합니다. 언어별 구현은 SDK 페이지를, 여러 SDK에 공통인 판단 기준은 선택·개념·지원 범위
페이지를 참고합니다.
## 읽는 순서
1. [연동 방식 선택](selection-integration-method/)에서 언어와 입력 방식에 맞는 인터페이스를
고릅니다.
2. [공통 연동 개념](concepts-common/)에서 인증, 시간값, 바인딩, 트랜잭션과 재시도를
확인합니다.
3. [SDK 기능 지원 범위](sdk-support-scope/)에서 필요한 기능의 실제 지원 여부를 비교합니다.
4. 해당 언어의 SDK 페이지에서 설치, 연결과 실행 코드를 확인합니다.
5. [데이터 입력과 반출](data-input-load-export/)에서 작업별 절차를 적용합니다. SQL의 ROWID
의미는 [ROWID](../reference/sql/rowid/)를 참고하십시오. Machbase DBMS 8.7.0의 ARRAY
부분 입력은 [Sparse ARRAY와 선택 컬럼 Append API](data-input-load-export/array-append/)를
참고하십시오.
## SDK별 레퍼런스
| 환경 | 문서 |
|---|---|
| C/C++ 네이티브 또는 ODBC | [Machbase SQLCLI와 ODBC](cli-odbc/) |
| Java·Spring | [JDBC](jdbc/) |
| Python | [Python](python/) |
| Node.js·TypeScript | [Node.js / TypeScript](node-js-typescript/) |
| C#·VB.NET | [.NET Connector](net-connector/) |
| Go 네이티브·`database/sql` | [Go](go/) |
## 공통 연결 정보
| 항목 | 기본값 | 설명 |
|---|---|---|
| HOST | `127.0.0.1` | Machbase 서버 호스트명 또는 IP |
| PORT | `5656` | Machbase 서버 포트 (`machbase.conf`의 `PORT_NO`) |
| USER | `SYS` | 사용자 ID |
| PASSWORD | `MANAGER` | 사용자 비밀번호 |
운영 환경에서는 별도 사용자와 필요한 최소 권한을 사용합니다. 계정과 인증 설정은
[계정, 권한, 접속 제어](../security-access-control/)를 참고하십시오.
## 기존 지원 범위 링크
NULL·PRIMARY KEY 메타데이터, ROWID, Append와 AUTH KEY의 SDK별 지원 여부는
[SDK 기능 지원 범위](sdk-support-scope/)에서 확인하십시오.
---
title: "11.1 연동 방식 선택"
url: https://docs.machbase.com/kr/dbms/development-tools-integration/selection-integration-method/
language: kr
kind: section
---
# 11.1 연동 방식 선택
프로젝트의 언어, 입력 특성, 배포 환경에 맞는 연동 방식을 선택합니다.
## 선택 기준
| 요구사항 | 우선 검토할 방식 |
|----------|------------------|
| C/C++ 네이티브 수집기 | SQLCLI |
| ODBC 관리자·DSN 기반 애플리케이션 | ODBC |
| Java·Spring | JDBC |
| Python 분석·자동화 | `machbaseapi` |
| C#·VB.NET | .NET Connector |
| Go 수집기·서비스 | 네이티브 `machgo` 또는 `database/sql` |
| Node.js·TypeScript 백엔드 | `@machbase/ts-client` |
| R 분석 환경 | Machbase ODBC 드라이버와 RODBC |
| 큰 파일의 일괄 입력·반출 | machloader, csvimport, csvexport |
| 지속적인 TAG·LOG 대량 입력 | 선택한 SDK의 Append API |
브라우저에서 5656 포트로 직접 연결하지 않습니다. 백엔드에서 쿼리를 실행하고 필요한
결과만 전달합니다.
## 결정 순서
1. 애플리케이션 언어에서 유지보수 가능한 공식 드라이버를 고릅니다.
2. 5656 포트 연결, 운영체제와 실행 시점 호환성을 확인합니다.
3. SQL, 준비된 문장, Append, 트랜잭션 중 필요한 기능을 정합니다.
4. [SDK 기능 지원표](../sdk-support-scope/)에서 실제 지원 여부를
확인합니다.
5. 표본 데이터로 타임스탬프, NULL, 숫자, 문자열을 왕복 검증합니다.
6. 목표 행 크기·동시 연결·배치 크기로 부하 테스트합니다.
Append 지원만으로 드라이버를 결정하지 않습니다. flush 지연, 오류 서버 처리 응답, 재연결, 실패 행
처리까지 실제 SDK에서 확인합니다. TRANSACTION DML에는 명시적 트랜잭션 지원 범위를
확인하고, TAG·LOG Append를 같은 롤백 단위로 가정하지 않습니다.
## 주제별 상세 문서
| 내용 | 상세 문서 |
|------|------|
| SDK 설치·연결·API·완전한 코드 | 이 장의 SDK별 페이지 |
| 공통 인증·바인딩·트랜잭션·재시도 | [공통 연동 개념](../concepts-common/) |
| SDK별 기능 지원 여부 | [SDK 기능 지원 범위](../sdk-support-scope/) |
| SQL·설정·명령줄 상세 | [16장 레퍼런스](/dbms/reference/) |
| 입력·반출 방식 선택 | [데이터 입력과 반출](../data-input-load-export/) |
연동 방식을 선택한 뒤에는 해당 SDK 페이지에서 설치와 연결 예제를 실행합니다.
기능 지원 여부는 문서에 표시된 버전과 실제 배포 산출물을 함께 확인합니다.
---
title: "11.2 공통 연동 개념"
url: https://docs.machbase.com/kr/dbms/development-tools-integration/concepts-common/
language: kr
kind: section
---
# 11.2 공통 연동 개념
드라이버나 언어에 관계없이 공통으로 적용되는 연결, 바인딩, 트랜잭션, 대량 입력, 오류 처리
원칙을 설명합니다. SDK별 함수명과 완전한 코드는 이 장의 SDK별 페이지를 참고합니다.
## 연결 문자열과 인증
연결에는 호스트, 네이티브 포트, 사용자, 인증 정보를 사용합니다. 기본 포트는 `5656`이지만
배포 환경의 `machbase.conf` 설정을 확인합니다.
| 항목 | 확인 사항 |
|------|-----------|
| 호스트·포트 | 애플리케이션 실행 위치에서 TCP 연결 가능 여부 |
| 사용자 | 대상 데이터베이스와 테이블에 필요한 최소 권한 |
| 비밀번호 | 환경 변수나 비밀 관리 시스템으로 주입 |
| 데이터베이스 | SDK가 초기 데이터베이스 선택을 지원하는지 확인 |
| 시간 초과 | 연결·명령·읽기 제한을 워크로드에 맞게 설정 |
| 시간대 | SDK와 서버가 지원하는 옵션명과 적용 범위 확인 |
예제의 `SYS`/`MANAGER`는 로컬 검증용입니다. 운영 애플리케이션에는 전용 계정을 만들고
소스, 명령 이력, 로그에 비밀번호를 기록하지 않습니다.
AUTH KEY는 비밀번호 대신 개인키로 challenge에 서명하는 방식입니다. 키 형식, 파일 권한,
SDK별 옵션은 [AUTH KEY 인증](/dbms/security-access-control/authentication-auth-key/)과
해당 드라이버 문서를 함께 확인합니다.
연결 풀을 사용하면 반환된 연결의 현재 데이터베이스, 세션 설정, 열린 문장이 다음
요청에 영향을 주지 않도록 초기화 동작을 검증합니다.
## 타임존과 시간값
Machbase `DATETIME`은 나노초 정밀도를 지원합니다. 애플리케이션에서는 시간값의 의미와
표현을 분리해 관리합니다.
- 수집 시각의 기준대(UTC 또는 업무 지역)를 명시합니다.
- 문자열을 바인딩할 때 형식과 시간대를 함께 고정합니다.
- epoch 값을 전달할 때 SDK가 요구하는 단위가 초, 밀리초, 마이크로초, 나노초 중 무엇인지
확인합니다.
- 조회 문자열의 시간대는 연결 옵션이나 `TO_CHAR()` 등 실제 사용 경로에서
왕복 테스트합니다.
- `NOW`와 `SYSDATE`를 업무 규칙에 혼용하지 말고 필요한 의미를 SQL 레퍼런스에서 확인합니다.
문자열 왕복 검증은 동일한 연결에서 입력값, 조회값, 시간대 변경 후 조회값을
비교합니다. 상세 함수는 [SQL 함수](/dbms/reference/sql/functions/functions-full/)를
참고합니다.
## Prepared statement
준비된 문장(prepared statement)은 SQL 구조와 값을 분리하고 같은 SQL을 반복 실행할 때
사용합니다.
```text
INSERT INTO sensor_data VALUES (?, ?, ?)
SELECT value FROM sensor_data WHERE name = ? AND time >= ?
```
문장을 준비한 연결과 현재 데이터베이스가 바뀌지 않았는지 확인하고, 사용 후 닫습니다.
문장 캐시를 제공하는 SDK는 캐시 범위와 항목 제거 정책, 데이터베이스 전환 시 초기화
동작을 확인합니다.
CTE, LIMIT 등 문법 위치에 따라 매개변수 자리표시자가 허용되지 않을 수 있습니다. 문법 오류가
나면 값을 문자열로 합치기 전에 [Named Bind Parameter](/dbms/reference/sql/syntax/named-bind-parameter-syntax/)와
해당 SDK의 자리표시자 지원 범위를 확인합니다.
## Parameter binding
| 값 종류 | 권장 방식 |
|---------|-----------|
| 정수·실수 | 언어의 고정 폭 타입과 SQL 타입 범위를 맞춤 |
| 문자열 | 인코딩과 최대 길이를 확인 |
| DATETIME | SDK의 시간 객체 또는 명시된 epoch 단위 사용 |
| NULL | 언어별 NULL 표현과 SQL 타입을 함께 지정 |
| DECIMAL | 문자열 변환보다 SDK의 정확한 고정소수 타입을 우선 |
| binary·IP | SDK가 요구하는 바이트 배열 또는 전용 타입 사용 |
위치 기반 자리표시자 `?`는 등장 순서대로 값을 바인딩합니다. 이름 기반 자리표시자 `:name`은 지원하는
서버와 SDK에서만 사용하며, 같은 이름의 반복 처리 규칙을 확인합니다. 식별자나 SQL keyword는
값 매개변수로 바인딩할 수 없으므로 허용 목록으로 검증한 뒤 SQL을 구성합니다.
## DML 영향 행 수
`INSERT`, `UPDATE`, `DELETE` 뒤에는 SDK가 반환한 영향 행 수를 확인합니다. 성공 응답만으로
업무 대상이 실제 변경됐다고 가정하지 않습니다.
- 단건 변경은 기대값이 1인지 확인합니다.
- 0건이면 조건에 맞는 행이 없는지, 이미 같은 변경이 반영되었는지 확인합니다.
- 권한 부족이나 지원하지 않는 DML은 영향 행 수와 별도로 오류 코드와 예외를 확인합니다.
- 일괄 변경은 실행 전 같은 조건의 `COUNT(*)`로 범위를 확인합니다.
- Append는 SQL 영향 행 수 대신 close·서버 처리 응답의 성공 건수와 실패 건수를 확인합니다.
## 트랜잭션
명시적 `BEGIN`, `COMMIT`, `ROLLBACK`은 TRANSACTION 테이블의 관계형 DML에 사용합니다.
LOG·TAG Append를 같은 롤백 단위로 가정하지 않습니다.
1. 실제 SDK가 트랜잭션 API를 제공하는지 확인합니다.
2. 제공하지 않으면 지원되는 SQL 제어 구문을 같은 연결에서 실행합니다.
3. 오류 경로에서 롤백하고 연결을 재사용할 수 있는지 확인합니다.
4. 연결 풀 반환 전에 미완료 트랜잭션이 남지 않도록 합니다.
5. 여러 테이블 타입을 섞은 작업은 각 문장의 커밋 범위를 사전 검증합니다.
완전한 SQL 예제는
[TRANSACTION 테이블의 트랜잭션](/dbms/rdb-table-usage/transaction/)을 참고합니다.
## Append API와 batch
입력 방식 선택과 결과 확인 항목은 [데이터 입력과 반출](../data-input-load-export/)에서,
클라이언트·테이블 타입별 Append gate는
[SDK Append 지원표](../sdk-support-scope/#append-table-type-matrix)에서 다룹니다.
Append 연결은 일반 쿼리 연결과 분리하고 flush·close와
실패 행을 확인합니다.
## 오류 처리와 재시도
오류는 연결, 인증·권한, SQL·스키마, 데이터, 자원 부족으로 분류합니다.
- 연결 단절과 일시적 시간 초과만 제한된 횟수와 재시도 대기 시간으로 재시도합니다.
- 인증 실패, 권한 부족, 문법 오류, 타입 오류는 수정 전 자동 재시도하지 않습니다.
- 재시도 전에 문장·커서·Append 핸들과 연결을 정리합니다.
- INSERT 재시도는 업무 키나 중복 처리 정책으로 멱등성을 확보합니다.
- 서버 오류 코드와 메시지는 기록하되 자격 증명과 원문 민감 데이터는 제거합니다.
- 연결 풀에서 오류가 난 연결은 유효성 검사 후 반환하거나 폐기합니다.
운영 오류 분류와 진단 순서는 [문제 해결](/dbms/troubleshooting/)을 참고합니다.
---
title: "11.3 SDK 기능 지원 범위"
url: https://docs.machbase.com/kr/dbms/development-tools-integration/sdk-support-scope/
language: kr
kind: section
---
# 11.3 SDK 기능 지원 범위
애플리케이션 요구사항에 맞는 SDK를 선택할 수 있도록 기능별 지원 여부와 API 진입점을
비교합니다. 설치, 연결, 함수와 실행 코드는 각 SDK 페이지를 참고합니다.
SDK를 처음 고를 때는 [연동 방식 선택](../selection-integration-method/)을 먼저 읽고, 이
페이지에서는 필요한 기능과 정확한 API 경로를 대조합니다.
## Nullable 메타데이터
Machbase는 SELECT 결과 컬럼의 NULL 가능성을 `NO_NULLS`, `NULLABLE`, `UNKNOWN`으로
구분합니다. 애플리케이션은 `NULLABLE`과 `UNKNOWN`을 모두 NULL 처리 대상으로 가정합니다.
| SDK | 조회 방법 |
|---|---|
| JDBC | `ResultSetMetaData.isNullable()` |
| Python | `cursor.description[i][6]` |
| Go 네이티브 | `api.Column.Nullability` |
| Go `database/sql` | `Rows.ColumnTypeNullable()` |
| Node.js | `ColumnMeta.nullable` |
| .NET | `GetSchemaTable()`의 `AllowDBNull` |
| SQLCLI·ODBC | 디스크립터의 nullable 속성 |
표현식, 집계, VIEW와 JOIN 결과는 `UNKNOWN`이 될 수 있습니다. 직접 컬럼의 스키마 제약과
조회 결과 메타데이터를 같은 것으로 가정하지 마십시오.
## PRIMARY KEY 메타데이터
| SDK | 결과 컬럼 | 테이블 카탈로그 |
|---|:---:|:---:|
| JDBC | O | `DatabaseMetaData.getPrimaryKeys()` |
| Python | O | 카탈로그 SQL |
| Go 네이티브 | O | 카탈로그 SQL |
| Go `database/sql` | 표준 API 없음 | 카탈로그 SQL |
| Node.js | O | 카탈로그 SQL |
| .NET | O | 카탈로그 SQL |
| ODBC | 별도 결과 API 없음 | `SQLPrimaryKeys()` |
표현식·집계·외부 JOIN의 NULL 공급 측은 원본 컬럼의 PK 속성을 그대로 전달하지 않을 수
있습니다.
## INSERT 결과 ROWID
Machbase 8.7.0 Standard Edition에서 성공한 단일 `INSERT ... VALUES`는 지원 SDK에 ROWID를
제공할 수 있습니다.
| SDK·도구 | 확인 방법 | 값이 없을 때 |
|---|---|---|
| machsql | `SHOW LAST ROWID` | `NULL` |
| Machbase SQLCLI | `SQLGetGeneratedRowID()` | `SQL_NO_DATA` |
| 표준 ODBC | 전용 표준 API 없음 | - |
| JDBC | `Statement.getGeneratedKeys()` | 빈 `ResultSet` |
| Python | `cursor.lastrowid` | `None` |
| .NET | `MachCommand.RowId` | `null` |
| Go `database/sql` | `Result.LastInsertId()` | 오류 |
| Go 네이티브 | 미지원 | - |
| Node.js | 실행 결과 `rowId` | `undefined` |
배치, `executemany()`, Append, loader, `INSERT ... SELECT`와 UPSERT에서는 단일 ROWID를
반환하지 않습니다. SQL 의미와 테이블별 제약은 [ROWID](/dbms/reference/sql/rowid/)를
참고하십시오.
## Append API와 table type
| API 경로 | LOG | TAG | LOOKUP | VOLATILE | TRANSACTION | 기준 |
|---|:---:|:---:|:---:|:---:|:---:|---|
| SQLCLI `SQLAppend*` extension | O | O | O | O | O | TRANSACTION은 Standard |
| JDBC `MachStatement.executeAppend*` | O | O | O | O | O | NFX cce422d source/test |
| Python 2.4 `append*` | O | O | O | O | O | NFX cce422d source/test |
| .NET `MachAppendWriter` | O | O | O | O | O | NFX cce422d 프로바이더 |
| Go v1.8.4 네이티브 `Appender` | O | O | X | X | O | TRANSACTION은 Standard |
| Go `database/sql` 표준 API | X | X | X | X | X | `sql.Conn.Raw()` 확장은 네이티브 계약 |
| Node 소스 `appendBatch()` | O | △ | △ | △ | O | NFX cce422d 소스 빌드 |
| Node 소스 `appendOpen()` | O | O | △ | △ | △ | 범용 native/fallback, 테이블별 검증 필요 |
표준 쿼리 연결과 Append 핸들의 생명 주기를 구분하고 close·flush 결과를
확인합니다. `O`는 해당 source/test에서 확인된 범위이고 `△`는 범용 경로만 있어 테이블별
회귀 검증이 더 필요한 범위입니다. Append extension은 표준 ODBC나 `database/sql` 기능이
아닙니다.
### ARRAY와 선택 컬럼 Append
Machbase DBMS 8.7.0의 ARRAY 지원 범위는 다음과 같습니다.
| SDK | 밀집 ARRAY 조회·입력 | 희소 ARRAY | 선택 컬럼 Append |
|---|:---:|:---:|:---:|
| SQLCLI·C++ | O | O | O |
| Machbase ODBC extension | O | O | O |
| JDBC | O | O | O |
| Python | O | O | O |
| Node.js | O | O | O |
| .NET full/legacy 프로바이더 | O | O | O |
| Go | v2 main 소스 | v2 main 소스 | v2 main 소스 |
Machbase DBMS 8.7.0 서버와 ARRAY 기능이 포함된 SDK 빌드를 함께 사용합니다. ARRAY의 SQL
요소 위치와 Machbase 전용 SDK 위치는 0부터 시작하는 인덱스입니다. 기존 전체 행 scalar Append
API는 유지됩니다. Node.js prepared 대체 경로도 `SparseArray`를 지원합니다. Go는
[`neo-client` PR #17](https://github.com/machbase/neo-client/pull/17) 이후의 v2 main
소스를 사용해야 하며 공개 v2 릴리스가 지정되기 전에는
공개 모듈 버전만으로 지원을 가정하지 않습니다. 자세한 입력 방식과 API는
[Sparse ARRAY와 선택 컬럼 Append API](../data-input-load-export/array-append/)를
참고하십시오.
## AUTH KEY
| SDK·도구 | 지원 |
|---|:---:|
| machsql, Machbase SQLCLI, ODBC, JDBC | O |
| Go 네이티브, Go `database/sql` | O (neo-client v1.5.0+) |
| Python, Node.js, .NET | X |
키 생성·등록·교체는 [AUTH KEY 인증](/dbms/security-access-control/authentication-auth-key/)을,
연결 옵션은 지원 SDK 페이지를 참고합니다.
## Transaction, prepare와 bind
| SDK | Transaction API | Server prepared | Named bind API |
|---|:---:|:---:|:---:|
| JDBC | O | O | △ (Machbase extension) |
| Python | X | O | O |
| Go 네이티브 | △ | O | O |
| Go `database/sql` | O | O | O |
| .NET | X | X | △ |
| Node.js | X | O | O |
| SQLCLI | △ | O | O |
| ODBC | △ | O | 순번 bind |
준비된 문장(prepared statement)과 매개변수 바인딩은 트랜잭션 지원과 별개입니다. 자리표시자 문법은
[Named Bind Parameter](/dbms/reference/sql/syntax/named-bind-parameter-syntax/)를
참고하십시오.
Machbase 8.7.0 Standard Edition의 TAG 데이터 UPDATE는 각 SDK의 기존 positional/named API로
NAME과 BASETIME 조건 값을 바인딩할 수 있습니다. NFX #4127 회귀 테스트는 C/C++ SQLCLI,
Go `database/sql`, JDBC, Node.js, Python과 .NET 경로를 검증합니다. ODBC는 별도 SDK 회귀
매트릭스에 포함되지 않으며 `?` 또는 이름 기반 SQL의 자리표시자를 표준 `SQLBindParameter()` 순번으로
바인딩합니다. TAG UPDATE 조건 계약은
[TAG 데이터 UPDATE bind](/dbms/reference/sql/syntax/dml-syntax/tag-data-update-syntax/#tag-data-update-predicate-bind)를
참고합니다.
Transaction 열의 `△`는 전용 객체 대신 같은 연결에서 트랜잭션 SQL을 직접 실행하는
경로입니다. Named bind 열의 `△`는 표준 이름 기반 바인딩 API가 아니라 드라이버 확장
또는 클라이언트가 값을 SQL 리터럴로 변환하는 경로를 뜻합니다.
## 최소 확인 version과 provenance
| SDK | 이 문서의 확인 기준 |
|---|---|
| SQLCLI·JDBC·Python | NFX `cce422d2972`; Python 패키지 2.4 |
| Node.js | NFX `cce422d2972` 소스 빌드 (`package.json` 1.0.1) |
| .NET | Uni 8.0.55, limited 3.1.3, full 3.2.2 |
| Go | released neo-client v1.8.4; AUTH KEY는 v1.5.0+, 데이터베이스 선택은 v1.8.3+ |
NFX cce422d의 Node 기능 일부는 공개 npm 1.0.1 배포 이후 추가되었습니다. registry 패키지의
버전 문자열만으로 동일 기능을 가정하지 말고 배포 산출물의 커밋 출처를
확인하거나 NFX 소스 빌드를 사용합니다.
ARRAY와 선택 컬럼 Append의 확인 기준은 NFX
`655d1333870313c4951698b89c9a3c9ada11d630`과 병합 커밋
`f756986c4836982723e2aa7727ec05b7e05e9707`입니다. 서버는 Machbase DBMS 8.7.0을,
클라이언트는 해당 변경 이후의 검증된 SDK 산출물을 기준으로 판단합니다.
## SDK 레퍼런스
| SDK | 상세 문서 |
|---|---|
| Machbase SQLCLI·ODBC | [SQLCLI와 ODBC](../cli-odbc/) |
| JDBC | [JDBC](../jdbc/) |
| Python | [Python](../python/) |
| Node.js / TypeScript | [Node.js / TypeScript](../node-js-typescript/) |
| .NET Connector | [.NET Connector](../net-connector/) |
| Go | [Go](../go/) |
---
title: "11.4 Machbase SQLCLI와 ODBC"
url: https://docs.machbase.com/kr/dbms/development-tools-integration/cli-odbc/
language: kr
kind: section
---
# 11.4 Machbase SQLCLI와 ODBC
Machbase SQLCLI는 C/C++ 애플리케이션에서 사용하는 함수 호출 인터페이스(Call-Level
Interface)입니다. ODBC 드라이버는 표준 ODBC 애플리케이션에서 사용합니다. 두 인터페이스는
환경·연결·문장 핸들을 사용하는 실행 흐름을 공유하며, SQLCLI에는 고속 Append를
위한 확장 함수가 추가되어 있습니다.
## 선택 기준
| 요구사항 | 인터페이스 |
|----------|------------|
| Machbase 설치 패키지와 함께 C/C++ 애플리케이션 개발 | SQLCLI |
| 범용 ODBC 도구 또는 드라이버 관리자 사용 | ODBC |
| Append 확장 API로 대량 입력 | SQLCLI |
| 표준 SQL 실행과 결과 조회 | SQLCLI 또는 ODBC |
## 헤더와 라이브러리
설치 디렉터리에서 다음 파일을 확인합니다.
```bash
test -f "$MACHBASE_HOME/include/machbase_sqlcli.h"
test -f "$MACHBASE_HOME/lib/libmachbasecli_dll.so"
```
Linux 동적 링크 예시는 다음과 같습니다.
```bash
gcc cli_quickstart.c -I"$MACHBASE_HOME/include" -L"$MACHBASE_HOME/lib" -lmachbasecli_dll -o cli_quickstart
LD_LIBRARY_PATH="$MACHBASE_HOME/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" MACHBASE_PASSWORD='your-password' ./cli_quickstart
```
운영 빌드는 설치 패키지의 `install/machbase_env.mk`와 플랫폼별 링커 설정을 기준으로
구성합니다.
## 연결 문자열
SQLCLI에서 기본 연결 문자열은 다음 키를 사용합니다.
```text
SERVER=127.0.0.1;PORT_NO=5656;UID=APP_USER;PWD=secret;CONNTYPE=1
```
ODBC 데이터 소스를 사용한다면 DSN, 계정, 비밀번호를 지정합니다.
```text
DSN=MACHBASE;UID=APP_USER;PWD=secret
```
다중 데이터베이스 초기값을 지원하는 드라이버에서는 `DATABASE` 또는 `DBNAME`을 사용할 수
있습니다. 실제 배포 드라이버의 지원 여부를 확인하고, 연결 후
`SELECT CURRENT_DATABASE()`로 선택 결과를 검증합니다.
## 빠른 시작
다음 프로그램은 5656 포트에 연결해 시스템 테이블 조회를 실행한 뒤 모든 핸들을
해제합니다. 비밀번호는 환경 변수로 전달합니다.
```c
#include
#include
#include
int main(void)
{
SQLHENV env = SQL_NULL_HENV;
SQLHDBC dbc = SQL_NULL_HDBC;
SQLHSTMT stmt = SQL_NULL_HSTMT;
char conn[512];
const char *password = getenv("MACHBASE_PASSWORD");
if (password == NULL) {
fputs("MACHBASE_PASSWORD is required\n", stderr);
return 2;
}
snprintf(conn, sizeof(conn),
"SERVER=127.0.0.1;PORT_NO=5656;"
"UID=SYS;PWD=%s;CONNTYPE=1", password);
if (SQLAllocEnv(&env) != SQL_SUCCESS) {
return 3;
}
if (SQLAllocConnect(env, &dbc) != SQL_SUCCESS) {
SQLFreeEnv(env);
return 4;
}
if (SQLDriverConnect(
dbc, NULL, (SQLCHAR *)conn, SQL_NTS,
NULL, 0, NULL, SQL_DRIVER_NOPROMPT) != SQL_SUCCESS) {
SQLFreeConnect(dbc);
SQLFreeEnv(env);
return 5;
}
if (SQLAllocStmt(dbc, &stmt) != SQL_SUCCESS) {
SQLDisconnect(dbc);
SQLFreeConnect(dbc);
SQLFreeEnv(env);
return 6;
}
if (SQLExecDirect(
stmt, (SQLCHAR *)"SELECT COUNT(*) FROM V$TABLES",
SQL_NTS) != SQL_SUCCESS) {
SQLFreeStmt(stmt, SQL_DROP);
SQLDisconnect(dbc);
SQLFreeConnect(dbc);
SQLFreeEnv(env);
return 7;
}
puts("query succeeded");
SQLFreeStmt(stmt, SQL_DROP);
SQLDisconnect(dbc);
SQLFreeConnect(dbc);
SQLFreeEnv(env);
return 0;
}
```
실패 원인을 출력해야 하는 애플리케이션은 `SQLGetDiagRec()` 또는 기존 코드의
`SQLError()`로 SQLSTATE, 네이티브 오류 코드, 메시지를 읽습니다.
## 표준 실행 흐름
1. 환경과 연결 핸들을 할당합니다.
2. `SQLDriverConnect()` 또는 `SQLConnect()`로 연결합니다.
3. 문장 핸들을 할당합니다.
4. `SQLPrepare()`와 `SQLExecute()` 또는 `SQLExecDirect()`로 SQL을 실행합니다.
5. SELECT 결과는 `SQLBindCol()`과 `SQLFetch()`로 읽습니다.
6. 문장, 연결, 환경 순서로 자원을 해제합니다.
입력값은 문자열로 연결하지 말고 `SQLBindParameter()`로 바인딩합니다. nullable 여부는
`SQLDescribeCol()`의 마지막 인자 또는 `SQLColAttribute(..., SQL_DESC_NULLABLE, ...)`로
확인합니다.
## Named Bind Parameter
서버와 드라이버가 이름 기반 매개변수를 지원하면 `:name` 자리표시자를 사용하고
`SQLBindParameterByName()`으로 바인딩할 수 있습니다. 같은 이름이 여러 번 나오면 하나의
값이 모든 위치에 적용됩니다. 공통 제약과 예제는
[Named Bind Parameter](/dbms/reference/sql/syntax/named-bind-parameter-syntax/)를
참고합니다.
지원 여부를 확인하지 못한 환경에서는 표준 `?` 자리표시자와 `SQLBindParameter()`를 사용합니다.
## INSERT 결과 ROWID
Standard Edition에서 단일 `INSERT ... VALUES`가 성공한 뒤 생성된 ROWID가 필요하면 다음
방식을 사용합니다.
- SQLCLI 확장: `SQLGetGeneratedRowID()`
- 표준 ODBC: generated ROWID 전용 표준 API 없음
배치, Append, `INSERT ... SELECT`, UPSERT에서는 같은 반환을 가정하지 않습니다. 자세한
범위는 [ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를
참고합니다.
## Append 확장 API
고속 입력은 일반 문장과 분리된 Append 흐름을 사용합니다.
| 단계 | 주요 함수 |
|------|-----------|
| 열기 | `SQLAppendOpen()`, 선택 컬럼은 `SQLAppendOpenColumns()`/`W()` |
| 단건 입력 | `SQLAppendDataV2()` 또는 지원 버전의 Append 함수 |
| 배치 입력 | `SQLAppendBatch()` |
| 서버 반영 | `SQLAppendFlush()` |
| 오류 콜백 | `SQLAppendSetErrorCallback()` |
| 닫기 | `SQLAppendClose()` |
Append 행의 컬럼 순서와 타입은 대상 테이블 스키마와 정확히 일치해야 합니다. 문자열,
binary, IP, DATETIME, NULL 표현은 설치된 `machbase_sqlcli.h`의 `SQL_APPEND_PARAM` 정의를
기준으로 작성합니다. 오류 콜백에서는 실패한 행과 서버 오류를 기록하되 비밀번호나 원문
민감정보를 로그에 남기지 않습니다.
활성 Append 핸들이 있는 연결은 일반 쿼리와 공유하지 말고, close 결과의 성공·실패
건수를 확인합니다.
## 멀티스레드와 자원 관리
- 스레드마다 연결과 문장을 분리합니다.
- 하나의 문장 또는 Append 핸들을 여러 스레드가 동시에 사용하지 않습니다.
- 모든 오류 경로에서도 핸들이 역순으로 해제되도록 정리 함수를 둡니다.
- 재시도 전에 이전 연결과 Append 상태가 완전히 닫혔는지 확인합니다.
- 대량 입력은 성공 건수와 실패 건수를 모두 기록합니다.
## API 세부사항 확인
함수 원형, 상수, 구조체는 설치된
`$MACHBASE_HOME/include/machbase_sqlcli.h`가 해당 라이브러리와 일치하는 기준입니다.
샘플을 다른 버전의 헤더와 혼용하지 말고, 컴파일·링크·5656 연결 테스트를 배포 파이프라인에
포함합니다.
## DECIMAL Append
`SQLAppendDataV2()`와 `SQLAppendBatch()`로 `DECIMAL` 또는 `NUMERIC` 값을 입력할 때는
32바이트 opaque 타입 `SQL_APPEND_NUMERIC`과 공개 생성 함수를 사용합니다. 내부 바이트를
애플리케이션에서 직접 만들거나 수정하지 않습니다.
| 입력 | 함수 |
|---|---|
| UTF-8 숫자 문자열 | `SQLAppendNumericFromString()` |
| signed·unsigned 정수 | `SQLAppendNumericFromInt64()`, `SQLAppendNumericFromUInt64()` |
| `SQL_NUMERIC_STRUCT` | `SQLAppendNumericFromSQLNumeric()` |
| NULL | `SQLAppendNumericSetNull()` |
정확한 값을 보존하려면 문자열 또는 `SQL_NUMERIC_STRUCT`를 우선 사용합니다. 타입 배열에는
`SQL_APPEND_TYPE_NUMERIC` 또는 `SQL_APPEND_TYPE_DECIMAL`을 지정하고, 대상 컬럼의
전체 자릿수와 소수 자릿수를 기준으로 overflow와 반올림을 확인하십시오.
## ARRAY와 선택 컬럼 Append
Machbase DBMS 8.7.0은 `SQL_MACHBASE_ARRAY_DESC`를 사용한 typed ARRAY 조회·bind와
`SQL_MACHBASE_SPARSE_ARRAY_DESC`를 사용한 희소 입력을 지원합니다. 일반 Open에서도
ARRAY 컬럼에 희소 디스크립터를 전달할 수 있습니다.
```c
SQLAppendOpen(statement, (SQLCHAR *)"ARRAY_APPEND_FULL_EXAMPLE", 0);
row[0].mLong = 1;
row[1].mVar.mData = &sparse;
row[1].mVar.mLength = SQL_APPEND_SPARSE_ARRAY_DESC_LENGTH;
SQLAppendDataV3(statement, row, 2);
SQLAppendClose(statement, &success, &failure);
```
위 코드는 `ID LONG, A INT32[4]` 테이블의 입력 순서를 따릅니다. 연결·디스크립터·버퍼
준비와 오류 처리를 포함한 [일반 Open 전체 예제](../data-input-load-export/array-append/#c-full-open)를
먼저 확인하십시오. 구형 `SQLAppendData(void *[])`에 디스크립터를 넘기는 방식과는 다릅니다.
일부 컬럼이나 고정 ARRAY 요소만 선택할 때는 `SQLAppendOpenColumns()` 또는
`SQLAppendOpenColumnsW()`를 사용합니다.
```c
SQLCHAR *targets[] = {
(SQLCHAR *)"ID",
(SQLCHAR *)"CHANNELS[0]",
(SQLCHAR *)"CHANNELS[3]",
NULL
};
SQLAppendOpenColumns(statement, (SQLCHAR *)"SENSOR_ARRAY", targets, 0);
```
ARRAY 요소 대상과 희소 디스크립터의 위치는 0부터 시작하는 인덱스입니다. 컬럼명 목록은
마지막 원소가 `NULL`이어야 합니다. `SQLAppendBatch()`는 ARRAY를 지원하지
않습니다. 디스크립터 정의, 배열 전체 NULL와 요소 NULL 처리, direct ODBC 핸들 제약은
[Sparse ARRAY와 선택 컬럼 Append API](../data-input-load-export/array-append/)를
참고하십시오.
---
title: "11.5 JDBC"
url: https://docs.machbase.com/kr/dbms/development-tools-integration/jdbc/
language: kr
kind: section
---
# 11.5 JDBC
Machbase JDBC 드라이버는 Java 8을 기준으로 JDBC 4.2 핵심 API를 제공합니다. 표준 JDBC
API로 서버에 연결하고 PreparedStatement, 타입 지정 조회와 바인딩, 데이터베이스
메타데이터, 로컬 트랜잭션과 커넥션 풀을 사용할 수 있습니다.
| 항목 | 값 |
|------|----|
| Java 바이트코드 기준 | Java 8 |
| 드라이버가 보고하는 JDBC 버전 | 4.2 |
| 드라이버 버전 | 3.0.0 |
| JDBC URL | `jdbc:machbase:///[database]` |
| `Driver.jdbcCompliant()` | `false` |
`jdbcCompliant()`의 `false`는 JDBC 4.2 API 지원 여부가 아니라 SQL-92 Entry Level 전체
지원 여부를 나타냅니다. 애플리케이션에서는 필요한 선택 기능을
`DatabaseMetaData`의 기능 메서드로 확인합니다.
## 다중 데이터베이스
URL 경로 또는 `database` 연결 속성으로 초기 데이터베이스를 지정할 수 있습니다.
```java
String url = "jdbc:machbase://127.0.0.1:5656/factory_a";
Connection conn = DriverManager.getConnection(url, "APP_A", password);
System.out.println(conn.getCatalog());
conn.setCatalog("FACTORY_A");
```
`getCatalog()`와 `setCatalog()`는 서버 현재 데이터베이스와 동기화됩니다. URL 경로와
설정 속성을 동시에 지정하면 값이 같아야 하며, JDBC 메타데이터에서 카탈로그는 데이터베이스,
스키마는 소유자입니다. pooled 연결은 반환 시 초기 카탈로그로 복원되는지 확인하고,
준비된 문장과 append 핸들은 생성 시점 데이터베이스에 고정된다는 점을 고려합니다.
## 드라이버 설치
### JAR 파일 사용
Machbase 설치 디렉터리의 `machbase.jar`를 classpath에 추가합니다.
```bash
ls -l "$MACHBASE_HOME/lib/machbase.jar"
javac -classpath ".:$MACHBASE_HOME/lib/machbase.jar" MyApp.java
java -classpath ".:$MACHBASE_HOME/lib/machbase.jar" MyApp
```
JAR에는 `META-INF/services/java.sql.Driver`가 포함되어 있습니다. JDBC 4.0 이후 환경에서는
`Class.forName("com.machbase.jdbc.MachDriver")`를 호출하지 않아도 드라이버가 자동으로
등록됩니다. 기존 애플리케이션의 명시적 호출은 그대로 사용할 수 있습니다.
### Maven
```xml
com.machbasemachjdbc{{< jdbc_version >}}
```
### Gradle
```groovy
dependencies {
implementation 'com.machbase:machjdbc:{{< jdbc_version >}}'
}
```
배포 산출물 버전은 [Maven Central](https://mvnrepository.com/artifact/com.machbase/machjdbc)에서
확인합니다. 드라이버가 런타임 메타데이터로 반환하는 `3.0.0`과 배포 산출물 버전은 서로 다른
버전 체계입니다.
## 서버에 연결
사용자 이름과 비밀번호는 소스 코드에 기록하지 않고 환경 변수나 비밀 관리 시스템으로
전달합니다.
```java
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;
String url = "jdbc:machbase://127.0.0.1:5656/machbasedb";
Properties properties = new Properties();
properties.setProperty("user", "SYS");
properties.setProperty("password", System.getenv("MACHBASE_PASSWORD"));
try (Connection connection =
DriverManager.getConnection(url, properties)) {
// SQL을 실행합니다.
}
```
### 연결 옵션
연결 옵션은 `Properties` 또는 URL 쿼리 문자열로 지정합니다. `randomHost`는
`Properties`에서 지정하거나 다중 호스트 URL의 `^` 구분자를 사용합니다.
| 옵션 | 설명 |
|------|------|
| `user`, `password` | 비밀번호 인증 정보 |
| `TIMEZONE` | 세션 타임존. `+0900` 형식을 사용합니다. |
| `randomHost` | 호스트 목록에서 첫 연결 대상을 무작위로 선택합니다. |
| `maxStatements` | 풀링 연결의 최대 캐시 Statement 수 |
| `CONNECTION_TIMEOUT` | 소켓 연결 시간 초과(초). `0`은 제한 없음입니다. |
| `SOCKET_TIMEOUT` | 소켓 읽기 시간 초과(초). `0`은 제한 없음입니다. |
| `characterEncoding` | 클라이언트 문자 인코딩 |
| `AUTH_MODE` | `PASSWORD` 또는 `CHALLENGE` |
| `AUTH_SIG_SCHEME` | `ECDSA`, `RSA_PKCS1_V15`, `RSA_PSS` |
| `AUTH_KEY_FILE` | PEM 개인키 파일 경로 |
```java
String url =
"jdbc:machbase://127.0.0.1:5656/machbasedb?TIMEZONE=+0900";
```
### 다중 호스트 연결
Machbase 8.7.0 JDBC 드라이버는 하나의 URL에 여러 호스트를 지정할 수 있습니다.
| 선택 방식 | 지정 방법 | 동작 |
|-----------|-----------|------|
| 순차 선택 | 호스트를 `,`로 구분 | URL에 작성한 순서대로 연결을 시도합니다. |
| 무작위 시작 | 호스트를 `^`로 구분 | 호스트 목록에서 첫 연결 대상을 무작위로 선택합니다. |
| 무작위 시작 | `Properties`에 `randomHost=true` 지정 | `,`로 구분한 목록에서 첫 연결 대상을 무작위로 선택합니다. |
다음 URL은 `db1` 연결에 실패하면 `db2`에 연결을 시도합니다.
```java
String url =
"jdbc:machbase://db1.example.com:5656,db2.example.com:5656/" +
"machbasedb?CONNECTION_TIMEOUT=5";
```
`^` 구분자를 사용하면 첫 연결 대상을 무작위로 선택합니다.
```java
String url =
"jdbc:machbase://db1.example.com:5656^db2.example.com:5656/" +
"machbasedb?CONNECTION_TIMEOUT=5";
```
`randomHost` 설정 속성을 사용하려면 `,`로 호스트를 구분합니다.
```java
Properties properties = new Properties();
properties.setProperty("randomHost", "true");
String url =
"jdbc:machbase://db1.example.com:5656,db2.example.com:5656/" +
"machbasedb?CONNECTION_TIMEOUT=5";
```
- `,`와 `^` 구분자를 하나의 URL에서 함께 사용할 수 없습니다.
- 연결 refused, 연결 시간 초과, 소켓 오류 등 연결 단계의 I/O 오류가 발생하면
다음 호스트로 연결을 시도합니다. 모든 호스트가 실패하면
`DriverManager.getConnection()`이 `SQLException`을 반환합니다.
- `CONNECTION_TIMEOUT`은 호스트별 연결 시도에 적용됩니다. 따라서 전체 연결 대기 시간은
호스트 수와 각 호스트의 응답 시간에 따라 길어질 수 있습니다.
- `SOCKET_TIMEOUT`은 연결된 소켓의 읽기 대기 시간을 제한하며 호스트 선택 순서를 변경하지
않습니다.
다중 호스트 전환은 새 연결 또는 재연결 과정의 소켓 연결에 적용됩니다. 연결이 끊긴 뒤
자동 reconnect가 성공해도 이전 Statement, PreparedStatement와 ResultSet은 재사용하지
않습니다. 진행 중이던 SQL의 성공 여부나 안전한 재실행을 보장하지 않으므로, 활성
트랜잭션에서 연결 오류가 발생하면 연결을 폐기하고 업무의 멱등성 정책에 따라 전체
트랜잭션을 다시 실행합니다.
## AUTH KEY 인증
공개키 기반 challenge 인증에서는 비밀번호 대신 로컬 개인키로 서버 challenge에
서명합니다.
```java
Properties properties = new Properties();
properties.setProperty("user", "app_user");
properties.setProperty("AUTH_MODE", "CHALLENGE");
properties.setProperty("AUTH_SIG_SCHEME", "ECDSA");
properties.setProperty(
"AUTH_KEY_FILE", "/opt/machbase/keys/app_user_ecdsa.pem");
Connection connection = DriverManager.getConnection(
"jdbc:machbase://127.0.0.1:5656/machbasedb", properties);
```
- `AUTH_MODE=CHALLENGE`에서는 `password`를 인증에 사용하지 않습니다.
- `AUTH_KEY_FILE`은 필수입니다.
- `AUTH_SIG_SCHEME`을 생략하면 키 종류에 맞는 기본 서명 방식을 선택합니다.
- POSIX 환경에서는 개인키 파일 권한을 `600`으로 제한합니다.
## 빠른 시작
다음 예제는 LOG 테이블에 값을 입력하고 다시 조회합니다.
```java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
public class JdbcQuickStart {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
properties.setProperty("user", "SYS");
properties.setProperty(
"password", System.getenv("MACHBASE_PASSWORD"));
try (Connection connection = DriverManager.getConnection(
"jdbc:machbase://127.0.0.1:5656/machbasedb",
properties);
Statement statement = connection.createStatement()) {
statement.execute(
"CREATE LOG TABLE jdbc_sensor " +
"(ts DATETIME, name VARCHAR(40), value DOUBLE)");
try (PreparedStatement insert = connection.prepareStatement(
"INSERT INTO jdbc_sensor VALUES (?, ?, ?)")) {
insert.setLong(1, System.currentTimeMillis() * 1_000_000L);
insert.setString(2, "sensor-1");
insert.setDouble(3, 25.3);
insert.executeUpdate();
}
try (ResultSet result = statement.executeQuery(
"SELECT name, value FROM jdbc_sensor")) {
while (result.next()) {
System.out.printf("%s %.1f%n",
result.getString("NAME"),
result.getDouble("VALUE"));
}
}
}
}
}
```
DATETIME에 epoch 나노초 값을 전달할 때는 `long`을 사용합니다. 예제의 테이블이 이미
존재하면 `CREATE LOG TABLE`을 생략하거나 다른 이름을 사용합니다.
## INSERT 결과 ROWID
Standard Edition에서 단일 `INSERT ... VALUES`가 성공하면 JDBC 표준 generated keys API로
입력된 행의 ROWID를 확인할 수 있습니다.
```java
String sql = "INSERT INTO jdbc_sensor VALUES (?, ?, ?)";
try (PreparedStatement insert = connection.prepareStatement(
sql, Statement.RETURN_GENERATED_KEYS)) {
insert.setLong(1, System.currentTimeMillis() * 1_000_000L);
insert.setString(2, "sensor-2");
insert.setDouble(3, 26.1);
insert.executeUpdate();
try (ResultSet keys = insert.getGeneratedKeys()) {
if (keys.next()) {
java.sql.RowId rowId = keys.getRowId("ROWID");
}
}
}
```
결과는 `ROWID` 컬럼 하나와 최대 한 행으로 구성됩니다. 반환할 ROWID가 없으면 빈
`ResultSet`입니다. 지원 여부는 `DatabaseMetaData.supportsGetGeneratedKeys()`로 확인합니다.
배치, Append, `INSERT ... SELECT`, UPSERT의 차이는
[ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를 참고하십시오.
## 버전 확인
```java
import java.sql.DatabaseMetaData;
DatabaseMetaData metadata = connection.getMetaData();
System.out.println(metadata.getDriverName());
System.out.println(metadata.getDriverVersion());
System.out.println(metadata.getJDBCMajorVersion()); // 4
System.out.println(metadata.getJDBCMinorVersion()); // 2
```
## 관련 문서
| 문서 | 내용 |
|------|------|
| [PreparedStatement와 타입](./prepared-types/) | 매개변수 메타데이터, 이름 기반 bind, SQLType, NULL과 타입 변환 |
| [ResultSet, Statement와 LOB](./resultset-lob/) | typed 조회, 스트림, LOB, 시간 초과와 자원 관리 |
| [트랜잭션과 커넥션 풀](./transaction-pooling/) | Standard 로컬 트랜잭션, DataSource와 연결 풀 |
| [DatabaseMetaData](./database-metadata/) | 테이블, 컬럼, 키, 인덱스와 기능 조회 |
| [Append API](./append-api/) | `MachStatement` 기반 고속 입력 |
| [마이그레이션과 문제 해결](./migration-troubleshooting/) | 이전 드라이버 전환, 미지원 기능과 오류 처리 |
---
title: "11.5.1 PreparedStatement와 타입"
url: https://docs.machbase.com/kr/dbms/development-tools-integration/jdbc/prepared-types/
language: kr
kind: page
---
# 11.5.1 PreparedStatement와 타입
PreparedStatement는 SQL을 서버에서 prepare한 뒤 파라미터만 바꾸어 반복 실행합니다.
Machbase JDBC는 표준 위치 기반 매개변수와 이름 기반 확장 매개변수를 제공합니다.
## ParameterMetaData
`PreparedStatement.getParameterMetaData()`로 실행 전에 파라미터 개수와 타입을 확인할 수
있습니다. INSERT, UPDATE와 SELECT에서 동일하게 사용합니다.
```java
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
try (PreparedStatement statement = connection.prepareStatement(
"INSERT INTO sensor_tx " +
"(id, value, created_at) VALUES (?, ?, ?)")) {
ParameterMetaData metadata = statement.getParameterMetaData();
for (int index = 1; index <= metadata.getParameterCount(); index++) {
System.out.printf(
"%d: type=%s precision=%d scale=%d nullable=%d%n",
index,
metadata.getParameterTypeName(index),
metadata.getPrecision(index),
metadata.getScale(index),
metadata.isNullable(index));
}
}
```
파라미터 인덱스는 1부터 시작합니다. 0 또는 파라미터 개수를 초과하는 인덱스에는
`SQLException`이 발생합니다. precision은 타입에 따라 해석합니다. 숫자 타입에서는 저장
바이트 수가 아니라 JDBC의 10진수 자릿수를 나타냅니다. DATETIME은
`java.sql.Types.TIMESTAMP`로 매핑되지만 데이터베이스 타입 이름은
`DATETIME`입니다.
## Named Bind Parameter
`:name` 자리표시자와 `MachPreparedStatement.setObject(String, Object)`는 Machbase 확장
기능입니다. 같은 이름이 여러 번 나오면 해당 이름의 모든 위치에 값이 적용됩니다.
```java
import com.machbase.jdbc.MachPreparedStatement;
try (MachPreparedStatement statement =
(MachPreparedStatement) connection.prepareStatement(
"SELECT id FROM sensor_tx " +
"WHERE id = :id OR parent_id = :id")) {
statement.setObject("id", Integer.valueOf(10));
try (ResultSet result = statement.executeQuery()) {
while (result.next()) {
System.out.println(result.getInt("ID"));
}
}
}
```
- 이름은 선행 콜론을 포함하거나 생략할 수 있습니다.
- 이름은 대소문자를 구분합니다.
- 이름 기반 setter와 숫자 인덱스 setter를 한 문장에서 혼용하지 않습니다.
- SQL에 없는 이름 또는 named/positional 혼용에는 SQLState `07009`가 발생합니다.
- 이름 기반 bind를 지원하지 않는 이전 서버에는 SQLState `0A000`이 발생합니다.
이식성이 필요한 애플리케이션은 JDBC 표준인 `?`와 숫자 인덱스 setter를 사용합니다. 공통
이름 문법은 [Named Bind Parameter](/dbms/reference/sql/syntax/named-bind-parameter-syntax/)를
참고합니다.
## Prepared SELECT 재실행
같은 PreparedStatement로 SELECT를 반복 실행할 수 있습니다. 이전 ResultSet을 닫고 값을
다시 바인딩하면 다음 실행에서 새 ResultSet을 반환합니다.
```java
try (PreparedStatement statement = connection.prepareStatement(
"SELECT id, name FROM sensor_tx WHERE id = ?")) {
ResultSetMetaData metadata = statement.getMetaData();
System.out.println(metadata.getColumnCount());
for (int id = 1; id <= 2; id++) {
statement.setInt(1, id);
try (ResultSet result = statement.executeQuery()) {
while (result.next()) {
System.out.println(result.getString("NAME"));
}
}
}
}
```
Machbase JDBC는 한 Statement의 multiple open results를 지원하지 않습니다. 각 실행의
ResultSet을 소비하고 닫은 뒤 다음 실행을 시작합니다. prepare 단계의
`ResultSetMetaData`는 Statement가 열려 있는 동안 다시 조회할 수 있습니다.
## JDBC 4.2 SQLType 바인딩
Java 8의 `JDBCType`으로 파라미터의 SQL 타입을 지정할 수 있습니다.
```java
import java.math.BigDecimal;
import java.sql.JDBCType;
import java.sql.Timestamp;
try (PreparedStatement statement = connection.prepareStatement(
"INSERT INTO sensor_tx " +
"(id, name, value, created_at, payload) " +
"VALUES (?, ?, ?, ?, ?)")) {
statement.setObject(1, Integer.valueOf(1), JDBCType.INTEGER);
statement.setObject(2, "sensor-1", JDBCType.VARCHAR);
statement.setObject(
3, new BigDecimal("12.3400"), JDBCType.DECIMAL, 4);
statement.setObject(
4, Timestamp.valueOf("2026-07-26 10:00:00"),
JDBCType.TIMESTAMP);
statement.setObject(5, new byte[] {1, 2, 3}, JDBCType.BINARY);
statement.executeUpdate();
}
```
알 수 없는 vendor `SQLType`에는 `SQLFeatureNotSupportedException`이 발생합니다.
DECIMAL과 NUMERIC은 `BigDecimal`을 사용하며 지정한 소수 자릿수에 맞게 바인딩합니다.
### SQL NULL
`setNull()` 또는 `setObject(index, null, JDBCType)`을 사용하면 대상 타입에 맞는 SQL
NULL이 전달됩니다. 다음 타입도 typed NULL을 지원합니다.
- `REAL`, `BIT`, `TINYINT`, `BOOLEAN`
- `VARBINARY`, `LONGVARBINARY`, `BLOB`, `CLOB`
- `LONGVARCHAR`
unsigned 파라미터의 NULL은 ParameterMetaData를 기준으로 네이티브 NULL 값으로 변환됩니다.
unsigned 최대 데이터 값보다 하나 큰 wire NULL sentinel은 실제 데이터로 저장할 수 없으며,
해당 값을 전달하면 SQLState `22003`이 발생합니다.
| Machbase 타입 | Java 타입 | 데이터 범위 |
|---------------|-----------|-------------|
| `USHORT` | `Integer` | 0~65534 |
| `UINTEGER` | `Long` | 0~4294967294 |
| `ULONG` | `BigInteger` | 0~18446744073709551614 |
NULL을 입력할 때 sentinel 값을 직접 전달하지 말고 `setNull()`을 사용합니다.
## Boolean
`setBoolean()`과 `setObject(index, value, JDBCType.BOOLEAN)`은 `true`를 1, `false`를 0으로
전달합니다. 문자열은 대소문자와 관계없이 `true`와 `false`만 허용하며, 그 밖의 값에는
SQLState `22018`이 발생합니다.
## IPv4와 IPv6
IP 주소 컬럼에는 `MachPreparedStatement` 확장 setter를 사용할 수 있습니다.
```java
MachPreparedStatement statement =
(MachPreparedStatement) connection.prepareStatement(
"INSERT INTO net_log(ts, src_ip, dst_ip) VALUES (?, ?, ?)");
statement.setLong(1, System.currentTimeMillis() * 1_000_000L);
statement.setIpv4(2, "192.168.1.100");
statement.setIpv6(3, "::1");
statement.executeUpdate();
```
## Nullable 메타데이터
`ParameterMetaData.isNullable()`은 `parameterNoNulls`, `parameterNullable` 또는
`parameterNullableUnknown`을 반환합니다. `parameterNullableUnknown`을 NOT NULL로
해석하지 않습니다. SQL별 판정 규칙은
[Nullable 메타데이터 지원 범위](/dbms/development-tools-integration/sdk-support-scope/#support-scope-sdk-nullable-metadata)를
참고합니다.
---
title: "11.5.2 ResultSet, Statement와 LOB"
url: https://docs.machbase.com/kr/dbms/development-tools-integration/jdbc/resultset-lob/
language: kr
kind: page
---
# 11.5.2 ResultSet, Statement와 LOB
Machbase JDBC ResultSet은 forward-only, 읽기 전용 커서입니다. Connection, Statement와
ResultSet은 try-with-resources로 닫습니다.
```java
statement.getResultSetType(); // ResultSet.TYPE_FORWARD_ONLY
statement.getResultSetConcurrency(); // ResultSet.CONCUR_READ_ONLY
```
scrollable 또는 updateable ResultSet은 지원하지 않습니다. `next()` 전, 마지막 행 이후,
close 이후의 getter 호출과 범위를 벗어난 컬럼 인덱스에는 `SQLException`이 발생합니다.
## 타입 지정 조회
`getObject(index, Class)`와 `getObject(label, Class)`를 지원합니다.
```java
import java.math.BigDecimal;
import java.sql.Timestamp;
try (ResultSet result = statement.executeQuery(
"SELECT id, value, created_at FROM sensor_tx")) {
while (result.next()) {
Integer id = result.getObject("ID", Integer.class);
BigDecimal value =
result.getObject("VALUE", BigDecimal.class);
Timestamp createdAt =
result.getObject("CREATED_AT", Timestamp.class);
}
}
```
| Java 타입 | 일반적인 Machbase 타입 |
|-----------|-------------------------|
| `String` | CHAR, VARCHAR, TEXT, IPV4, IPV6, JSON |
| `Short`, `Integer`, `Long` | 정수 타입 |
| `Float`, `Double` | 실수 타입 |
| `BigDecimal` | DECIMAL, NUMERIC |
| `Boolean` | BOOLEAN |
| `Timestamp`, `Date`, `Time` | DATETIME |
| `byte[]` | BINARY |
| `Blob`, `Clob` | BLOB, CLOB |
SQL NULL은 객체 getter에서 Java `null`을 반환합니다. primitive getter는 0 또는 `false`를
반환하며, 바로 뒤의 `wasNull()`로 SQL NULL 여부를 확인합니다. 지원하지 않는 변환과 null
대상 클래스에는 `SQLException`이 발생합니다.
Machbase SQL에서 빈 문자열 리터럴 `''`은 SQL `NULL`입니다. 따라서 해당 결과 컬럼의
`ResultSetMetaData.isNullable()`은 `columnNullable`이고 `getObject()`는 `null`을
반환합니다. `''''`는 작은따옴표 한 글자이므로 NULL이 아닌 문자열입니다.
unsigned 타입의 기본 객체 매핑은 다음과 같습니다.
| Machbase 타입 | `getObject()` 반환 타입 |
|---------------|-------------------------|
| `USHORT` | `Integer` |
| `UINTEGER` | `Long` |
| `ULONG` | `BigInteger` |
`SHORT`는 `Integer`, 32비트 `FLOAT`는 `Float` 객체로 반환합니다. BOOLEAN 문자열은
`true`와 `false`만 허용합니다.
## 문자와 바이너리 stream
`setAsciiStream()`, `setBinaryStream()`과 `setCharacterStream()`은 `int` 길이, `long`
길이와 길이 없는 오버로드를 제공합니다. `setNCharacterStream()`과 `setNString()`은 별도
NCHAR 저장 타입이 아니라 VARCHAR 경로의 별칭입니다.
ResultSet에서는 다음 getter를 인덱스 또는 컬럼 이름으로 사용할 수 있습니다.
- `getAsciiStream()`, `getBinaryStream()`
- `getCharacterStream()`, `getNCharacterStream()`
- `getNString()`
길이를 지정한 입력이 선언한 길이보다 짧거나, 길이가 음수이거나,
`Integer.MAX_VALUE`를 초과하면 `SQLException`이 발생합니다. 현재 스트림은 클라이언트
메모리에 materialize하므로 일정한 메모리만 사용하는 대용량 스트리밍 용도로 사용하지
않습니다.
## BLOB과 CLOB
LOG 테이블의 BLOB/CLOB 컬럼은 표준 `Blob`과 `Clob` 객체로 조회하고 바인딩할 수 있습니다.
```java
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.sql.Blob;
import java.sql.Clob;
try (PreparedStatement insert = connection.prepareStatement(
"INSERT INTO event_log (payload, message) VALUES (?, ?)")) {
insert.setBlob(1, new ByteArrayInputStream(payload));
insert.setClob(2, new StringReader(message));
insert.executeUpdate();
}
try (ResultSet result = statement.executeQuery(
"SELECT payload, message FROM event_log")) {
while (result.next()) {
Blob payloadObject = result.getBlob("PAYLOAD");
Clob messageObject = result.getClob("MESSAGE");
byte[] payloadBytes = payloadObject.getBytes(
1, (int) payloadObject.length());
String messageText = messageObject.getSubString(
1, (int) messageObject.length());
payloadObject.free();
messageObject.free();
}
}
```
`Connection.createBlob()`과 `createClob()`으로 mutable 객체를 만들고 `setBytes()`,
`setString()`, `setBinaryStream()`, `setCharacterStream()`과 `truncate()`를 사용할 수
있습니다. 사용을 마치면 `free()`를 호출합니다.
LOB 위치는 JDBC 표준대로 1부터 시작합니다. 부분 스트림의 전체 요청 범위가 실제 값 안에
있어야 하며 끝을 넘어가면 짧게 잘라 반환하지 않고 SQLState `22003`이 발생합니다. 음수
길이 또는 Java 배열로 표현할 수 없는 길이에는 `HY090`이 발생합니다. `free()` 이후 객체를
다시 사용하면 `SQLException`이 발생합니다.
LOB은 전체 값을 클라이언트 메모리에 materialize합니다. 수백 MiB 이상의 값을 일정한
메모리로 처리하는 스트리밍 LOB 구현은 아닙니다.
## ResultSet 메타데이터
`ResultSetMetaData.isNullable()`로 SELECT 결과 컬럼의 NULL 가능 여부를 확인합니다.
```java
ResultSetMetaData metadata = result.getMetaData();
int nullable = metadata.isNullable(columnIndex);
```
`columnNullableUnknown`은 NOT NULL을 의미하지 않습니다. 테이블 컬럼의 제약은
`DatabaseMetaData.getColumns()`에서 확인하고 PRIMARY KEY는
`DatabaseMetaData.getPrimaryKeys()`로 별도 조회합니다.
## 행 수와 fetch 설정
JDBC 4.2 large update API는 update 건수를 `long`으로 반환합니다.
```java
long count = statement.executeLargeUpdate(
"DELETE FROM sensor_tx WHERE id < 100");
long[] counts = statement.executeLargeBatch();
```
`setLargeMaxRows()`와 `getLargeMaxRows()`도 사용할 수 있습니다. `setMaxRows()` 또는
`setLargeMaxRows()`는 ResultSet에서 가져올 최대 행 수를 제한하지만 Statement의
`fetchSize` 값을 변경하지 않습니다.
## Statement 수명주기
- `closeOnCompletion()`을 설정하면 마지막 ResultSet이 닫힐 때 Statement도 닫힙니다.
- 한 Statement의 이전 ResultSet은 재실행 전에 닫습니다.
- 커밋은 ResultSet을 닫지만 Statement와 PreparedStatement는 다시 사용할 수 있습니다.
- 한 ResultSet의 `next()`와 getter를 여러 스레드에서 동시에 호출하지 않습니다.
## 취소와 query timeout
`Statement.cancel()`은 현재 실행 중인 문장을 별도 세션으로 취소합니다. 실행 중인
문장이 없으면 아무 작업도 하지 않으며 PreparedStatement의 bind와 메타데이터는 유지됩니다.
`setQueryTimeout(seconds)`이 만료되면 `SQLTimeoutException`과 SQLState `HYT00`이
발생합니다. 같은 Statement는 예외 처리가 끝난 뒤 다음 쿼리에 재사용할 수 있습니다.
이전 실행의 시간 초과 작업은 다음 실행을 취소하지 않습니다.
독립 쿼리를 병렬 실행하려면 같은 Connection을 여러 worker가 공유하지 말고 커넥션
풀에서 worker별 논리 Connection을 대여합니다. 진행 중 fetch를 종료해야 할 때는 다른
스레드에서 `close()`, `cancel()` 또는 `Connection.abort()`를 호출할 수 있습니다.
---
title: "11.5.3 트랜잭션과 커넥션 풀"
url: https://docs.machbase.com/kr/dbms/development-tools-integration/jdbc/transaction-pooling/
language: kr
kind: page
---
# 11.5.3 트랜잭션과 커넥션 풀
Machbase JDBC는 Standard Edition의 TRANSACTION 테이블에서 표준 JDBC 로컬 트랜잭션을
지원합니다. Cluster Edition에는 TRANSACTION 테이블이 없으므로 이 페이지의 트랜잭션
기능을 적용하지 않습니다.
## TRANSACTION 테이블 생성
```sql
CREATE TRANSACTION TABLE sensor_tx
(
id INTEGER PRIMARY KEY,
parent_id INTEGER,
name VARCHAR(64),
value DECIMAL(20, 4),
created_at DATETIME,
payload BINARY
);
```
## commit과 rollback
`setAutoCommit(false)`, `commit()`과 `rollback()`을 사용합니다. 애플리케이션에서 SQL
`BEGIN`을 직접 전송할 필요가 없습니다.
```java
import java.sql.PreparedStatement;
import java.sql.SQLException;
connection.setAutoCommit(false);
try (PreparedStatement statement = connection.prepareStatement(
"INSERT INTO sensor_tx (id, value) VALUES (?, ?)")) {
statement.setInt(1, 1);
statement.setBigDecimal(2, new BigDecimal("10.5000"));
statement.executeUpdate();
connection.commit();
} catch (SQLException exception) {
try {
connection.rollback();
} catch (SQLException rollbackException) {
exception.addSuppressed(rollbackException);
}
throw exception;
}
```
`setAutoCommit(false)`는 즉시 `BEGIN`을 보내지 않습니다. 수동 모드의 첫 Statement를
실행할 때 트랜잭션을 시작합니다. 커밋 또는 롤백 뒤에도 auto-commit은 `false`로
유지되며 다음 Statement가 새 트랜잭션을 시작합니다.
- `setAutoCommit(true)`로 전환할 때 활성 트랜잭션이 있으면 먼저 커밋합니다.
- auto-commit이 `true`일 때 `commit()`이나 `rollback()`을 호출하면 SQLState `25000`이
발생합니다.
- Connection을 닫을 때 완료하지 않은 트랜잭션은 롤백됩니다.
- 커밋과 롤백은 열린 ResultSet을 닫지만 Statement는 재사용할 수 있습니다.
## 격리 수준과 cursor
지원하는 격리 수준은 `Connection.TRANSACTION_SERIALIZABLE`입니다. 다른 격리 수준을
요청하면 `SQLFeatureNotSupportedException`이 발생합니다.
지원하는 holdability는 `ResultSet.CLOSE_CURSORS_AT_COMMIT`입니다. 커밋 전에 필요한
ResultSet을 소비하거나 커밋 후 쿼리를 다시 실행합니다.
## 테이블 종류별 동작
| 작업 | 수동 트랜잭션 동작 |
|------|--------------------------|
| TRANSACTION 테이블 DML/SELECT | 트랜잭션에 참여합니다. |
| LOG/TAG 테이블 SELECT | 실행할 수 있습니다. |
| TRANSACTION 변경 전 첫 LOG DML | 호환 경로에서 auto-commit으로 재실행될 수 있습니다. |
| 독립 TAG DML | 트랜잭션에 참여하며 롤백할 수 있습니다. |
| TRANSACTION 변경 후 LOG/TAG DML 또는 DDL | 오류가 발생합니다. |
호환 경로로 auto-commit 재실행된 LOG DML은 이후 롤백 대상이 아닙니다. 롤백이
필요한 데이터는 TRANSACTION 테이블을 사용합니다. 여러 TRANSACTION 테이블에 대한 정상
커밋과 롤백은 지원하지만 백엔드 커밋 도중 장애가 발생했을 때 전역 원자성을
보장하지 않습니다. 중요한 원자 작업은 하나의 TRANSACTION 테이블 범위로 설계합니다.
## DataSource
`MachDataSource`는 애플리케이션 서버나 프레임워크에 연결 속성을 주입할 때 사용합니다.
```java
import com.machbase.jdbc.MachDataSource;
import java.sql.Connection;
MachDataSource dataSource = new MachDataSource();
dataSource.setUrl("jdbc:machbase://127.0.0.1:5656/machbasedb");
dataSource.setUser("SYS");
dataSource.setPassword(System.getenv("MACHBASE_PASSWORD"));
dataSource.setLoginTimeout(10);
try (Connection connection = dataSource.getConnection()) {
// SQL을 실행합니다.
}
```
DataSource는 URL, 사용자, password, login 시간 초과, 로그 writer와 JDBC `Wrapper` 계약을
지원합니다.
## ConnectionPoolDataSource
`MachConnectionPoolDataSource`는 물리 연결을 직접 노출하지 않고 논리 Connection을
반환합니다.
```java
import com.machbase.jdbc.MachConnectionPoolDataSource;
import java.sql.Connection;
import javax.sql.PooledConnection;
MachConnectionPoolDataSource source =
new MachConnectionPoolDataSource();
source.setUrl("jdbc:machbase://127.0.0.1:5656/machbasedb");
source.setUser("SYS");
source.setPassword(System.getenv("MACHBASE_PASSWORD"));
PooledConnection pooled = source.getPooledConnection();
try {
try (Connection logical = pooled.getConnection()) {
// logical connection을 사용합니다.
}
} finally {
pooled.close();
}
```
한 PooledConnection에서는 논리 핸들 하나만 활성화합니다. 논리 Connection을 닫으면
다음 상태를 초기화한 뒤 `connectionClosed` 이벤트가 한 번 발생합니다.
- 완료하지 않은 트랜잭션 롤백
- auto-commit 복원
- URL에서 결정한 초기 카탈로그 복원
- 네트워크 시간 초과 복원
close를 시작한 논리 핸들의 호출이 끝나기 전에 다음 연결 대여 기간을 대여하지 않습니다. 닫힌
Connection, Statement 또는 DatabaseMetaData는 다음 연결을 대여한 요청에서 재사용할 수 없으며
SQLState `08003`이 발생합니다. Statement pooling은 지원하지 않습니다.
SQLState 클래스 `08`의 치명적 연결 오류는 물리 연결을 폐기하고
`connectionErrorOccurred`를 발생시킵니다. duplicate 키와 같은 클래스 `23` 오류는 연결
손상이 아니므로 연결 오류 이벤트를 발생시키지 않습니다.
## HikariCP
```java
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:machbase://127.0.0.1:5656/machbasedb");
config.setUsername("SYS");
config.setPassword(System.getenv("MACHBASE_PASSWORD"));
config.setMaximumPoolSize(10);
config.setMinimumIdle(2);
config.setConnectionTimeout(30_000);
config.setIdleTimeout(600_000);
config.setMaxLifetime(1_800_000);
config.addDataSourceProperty("TIMEZONE", "+0900");
try (HikariDataSource dataSource = new HikariDataSource(config);
Connection connection = dataSource.getConnection()) {
// SQL을 실행합니다.
}
```
논리 Connection은 try-with-resources로 즉시 반환하고 반환한 핸들을 보관하지 않습니다.
## Network timeout
`setNetworkTimeout(executor, milliseconds)`는 소켓 읽기 시간 초과를 밀리초 단위로
설정하며 `0`은 제한 없음입니다. 음수 값, null executor 또는 작업을 거부하는 executor에는
`SQLException`이 발생합니다.
실제 네트워크 시간 초과가 만료되면 SQLState 클래스 `08`의 예외가 발생하고 물리 연결은
유효하지 않은 상태가 됩니다. 해당 연결에서 만든 Statement와 ResultSet을 재사용하지 말고
새 연결을 대여합니다. 활성 트랜잭션의 I/O 실패는 자동으로 재실행하지 않습니다.
---
title: "11.5.4 DatabaseMetaData"
url: https://docs.machbase.com/kr/dbms/development-tools-integration/jdbc/database-metadata/
language: kr
kind: page
---
# 11.5.4 DatabaseMetaData
`Connection.getMetaData()`는 드라이버, 서버, 스키마 객체와 JDBC 기능 정보를
반환합니다. 결과 컬럼은 JDBC 표준 이름과 순서를 사용하므로 숫자 위치보다 컬럼 이름으로
읽는 것을 권장합니다.
```java
import java.sql.DatabaseMetaData;
DatabaseMetaData metadata = connection.getMetaData();
System.out.println(metadata.getDriverName());
System.out.println(metadata.getDriverVersion());
System.out.println(metadata.getDatabaseProductName());
System.out.println(metadata.getDatabaseProductVersion());
```
## 테이블과 VIEW
```java
try (ResultSet tables = metadata.getTables(
null, null, "%", new String[] {"TABLE", "VIEW"})) {
while (tables.next()) {
System.out.printf("%s %s%n",
tables.getString("TABLE_NAME"),
tables.getString("TABLE_TYPE"));
}
}
```
`getTables()`는 TABLE과 VIEW를 구분합니다. Machbase 세부 테이블 종류는 `REMARKS`에서
확인합니다. 애플리케이션은 특정 순번에 의존하지 말고 표준 컬럼 이름을 사용합니다.
## 컬럼
```java
try (ResultSet columns = metadata.getColumns(
null, null, "SENSOR_TX", "%")) {
while (columns.next()) {
System.out.printf(
"%s %s size=%d nullable=%s%n",
columns.getString("COLUMN_NAME"),
columns.getString("TYPE_NAME"),
columns.getInt("COLUMN_SIZE"),
columns.getString("IS_NULLABLE"));
}
}
```
`NULLABLE`은 숫자 상수, `IS_NULLABLE`은 `YES`, `NO` 또는 빈 문자열로 반환됩니다.
LOOKUP과 VOLATILE 테이블의 PRIMARY KEY는 명시적인 NOT NULL 절이 없어도
`columnNoNulls`와 `NO`로 반환됩니다.
VARCHAR, DATETIME, BINARY, BLOB과 CLOB처럼 수치 속성이 적용되지 않는 컬럼의
`DECIMAL_DIGITS`와 `NUM_PREC_RADIX`는 SQL NULL입니다. `getInt()`의 0만 확인하지 말고
`getObject()` 또는 `wasNull()`로 NULL 여부를 확인합니다.
## PRIMARY KEY와 인덱스
```java
try (ResultSet keys = metadata.getPrimaryKeys(
null, null, "SENSOR_TX")) {
while (keys.next()) {
System.out.printf("%s position=%d%n",
keys.getString("COLUMN_NAME"),
keys.getShort("KEY_SEQ"));
}
}
try (ResultSet indexes = metadata.getIndexInfo(
null, null, "SENSOR_TX", false, false)) {
while (indexes.next()) {
System.out.printf("%s %s%n",
indexes.getString("INDEX_NAME"),
indexes.getString("COLUMN_NAME"));
}
}
```
PRIMARY KEY 여부를 `ResultSetMetaData.isNullable()` 값으로 추정하지 않습니다.
`getPrimaryKeys()`와 `getIndexInfo()`를 사용합니다.
SELECT 결과의 직접 컬럼에 대한 PK 여부는 Machbase JDBC 확장인
`MachResultSetMetaData.isPrimaryKey(column)`으로 확인할 수 있습니다.
```java
import com.machbase.jdbc.MachResultSetMetaData;
try (ResultSet result = statement.executeQuery(
"SELECT ID, ID + 1 AS ID_EXPR FROM SENSOR_TX")) {
MachResultSetMetaData resultMetadata =
(MachResultSetMetaData) result.getMetaData();
System.out.println(resultMetadata.isPrimaryKey(1)); // true 또는 false
System.out.println(resultMetadata.isPrimaryKey(2)); // expression: false
}
```
`getPrimaryKeys()`는 테이블 카탈로그의 PK를 조회하고, `isPrimaryKey()`는 현재 SELECT
결과의 컬럼 메타데이터를 조회합니다. 이전 버전 서버 또는 SDK와 연결한 경우에는 결과
컬럼의 PK 플래그가 `false`로 반환될 수 있습니다.
## 스키마와 타입 정보
다음 메서드는 JDBC 표준 형태의 ResultSet을 반환합니다.
- `getSchemas()`, `getCatalogs()`, `getTableTypes()`
- `getTypeInfo()`
- `getTables()`, `getColumns()`
- `getPrimaryKeys()`, `getIndexInfo()`
지원하지 않는 선택적 메타데이터 조회는 null이나 비표준 ResultSet 대신 표준 컬럼을 가진 빈
ResultSet을 반환할 수 있습니다. 기능을 사용하기 전에 기능 메서드를 확인합니다.
```java
if (metadata.supportsSavepoints()) {
// 지원되는 환경에서만 savepoint를 사용합니다.
}
```
## catalog
`Connection.getCatalog()`과 `setCatalog()`은 드라이버가 노출하는 현재 카탈로그 값을
관리합니다. 메타데이터 메서드의 카탈로그 인자는 이 값과 일치하는 요청을 필터링하는 데
사용합니다.
```java
String initialCatalog = connection.getCatalog();
connection.setCatalog(initialCatalog);
```
커넥션 풀에서 논리 Connection을 반환하면 카탈로그는 URL에서 결정한 초기 값으로
복원됩니다. 이전 연결 대여 기간에서 얻은 DatabaseMetaData 객체는 다음 연결 대여 기간에서 재사용하지
않습니다.
## 지원 범위 확인
Machbase JDBC는 실제 지원 범위를 기능에 반영합니다. 예를 들어 트랜잭션 격리 수준,
ResultSet 종류, savepoint, generated 키와 multiple open results 지원 여부를 다음과 같이
확인합니다.
```java
System.out.println(metadata.supportsTransactions());
System.out.println(metadata.supportsTransactionIsolationLevel(
Connection.TRANSACTION_SERIALIZABLE));
System.out.println(metadata.supportsResultSetType(
ResultSet.TYPE_FORWARD_ONLY));
System.out.println(metadata.supportsSavepoints());
System.out.println(metadata.supportsGetGeneratedKeys());
System.out.println(metadata.supportsMultipleOpenResults());
```
`Driver.jdbcCompliant()`이 `false`인 것과 개별 JDBC API의 지원 여부는 별개입니다.
애플리케이션은 필요한 기능을 직접 확인합니다.
Standard Edition에서 ROWID를 지원하는 서버와 연결하면 `supportsGetGeneratedKeys()`는
`true`, `getRowIdLifetime()`은 `ROWID_VALID_OTHER`를 반환합니다. 지원하지 않는 서버 또는
Cluster Edition에서는 각각 `false`와 `ROWID_UNSUPPORTED`를 반환합니다. 사용 예제는
[ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를 참고하십시오.
---
title: "11.5.5 Append API"
url: https://docs.machbase.com/kr/dbms/development-tools-integration/jdbc/append-api/
language: kr
kind: page
---
# 11.5.5 Append API
Machbase Append API는 여러 행을 연속 입력하는 대량 입력 API입니다. JDBC에서는
`MachStatement` 확장 메서드를 사용하며, 이 페이지는 LOG 입력 예제를 중심으로 설명합니다.
다른 테이블 타입의 지원 범위는 [SDK 지원표](../../sdk-support-scope/#append-table-type-matrix)를
함께 확인합니다.
## API
| 메서드 | 설명 |
|--------|------|
| `executeAppendOpen(tableName, errorCheckCount)` | Append 세션을 시작하고 컬럼 메타데이터를 반환합니다. |
| `executeAppendOpen(tableName, inputColumns, errorCheckCount)` | Machbase DBMS 8.7.0에서 선택 컬럼 또는 ARRAY 요소 대상으로 Append 세션을 시작합니다. |
| `executeAppendData(metadata, data)` | 한 행을 전송합니다. |
| `executeAppendDataByTime(metadata, time, data)` | 나노초 시간을 지정해 한 행을 전송합니다. |
| `executeAppendFlush()` | 대기 중인 응답을 동기화합니다. |
| `executeAppendClose()` | Append 세션을 종료합니다. |
| `executeSetAppendErrorCallback(callback)` | 행 오류 콜백을 등록합니다. |
| `getAppendSuccessCount()` | 성공한 행 수를 반환합니다. |
| `getAppendFailureCount()` | 실패한 행 수를 반환합니다. |
공개 `executeAppendData()`는 성공하면 `1`을 반환하고 유효하지 않은 내부 결과에는
`SQLException`을 던집니다. 최종 성공·실패 건수와 콜백도 함께 확인합니다.
## 입력 예제
```sql
CREATE LOG TABLE sensor_data (
time DATETIME,
name VARCHAR(40),
value DOUBLE
);
```
```java
import com.machbase.jdbc.MachStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
try (MachStatement statement =
(MachStatement) connection.createStatement()) {
ResultSet appendResult =
statement.executeAppendOpen("sensor_data", 100);
ResultSetMetaData metadata = appendResult.getMetaData();
statement.executeSetAppendErrorCallback(
(errorNumber, errorMessage, rowMessage) ->
System.err.printf(
"Append error [%05d]: %s%n%s%n",
errorNumber, errorMessage, rowMessage));
long baseTime = System.currentTimeMillis() * 1_000_000L;
for (int index = 0; index < 10_000; index++) {
ArrayList