feat: .gitignore 추가 및 빌드 출력 제거, 소스 코드 업데이트
This commit is contained in:
52
plans/history-query-status-indicator.md
Normal file
52
plans/history-query-status-indicator.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# 이력 조회 상태 표시기 구현
|
||||
|
||||
## 개요
|
||||
이력 조회 페이지의 '▼ 옵션 불러오기' 버튼 오른쪽에 상태 표시기를 추가하여, 태그 목록 조회 상태를 시각적으로 표시합니다.
|
||||
|
||||
## 구현 내용
|
||||
|
||||
### 1. HTML 구조 ([`index.html`](src/Web/wwwroot/index.html:503))
|
||||
```html
|
||||
<div class="fg">
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
||||
<span>태그 선택 <em>(최대 8개, OR 조건)</em></span>
|
||||
<button class="btn-b btn-sm" onclick="histLoad()">▼ 옵션 불러오기</button>
|
||||
<span id="hist-load-status" class="hist-status">대기 중</span>
|
||||
</div>
|
||||
<div class="pb-name-grid">
|
||||
<!-- select 8개 -->
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 2. CSS 스타일 ([`style.css`](src/Web/wwwroot/css/style.css:815))
|
||||
- `.hist-status`: 상태 표시기 컨테이너
|
||||
- `.hist-status.loading`: 조회 중 (파란색)
|
||||
- `.hist-status.success`: 조회 완료 (초록색)
|
||||
- `.hist-status.error`: 조회 실패 (빨간색)
|
||||
- 성능 최적화: `contain: layout style`, `transform: translateZ(0)`
|
||||
|
||||
### 3. JavaScript 로직 ([`app.js`](src/Web/wwwroot/js/app.js:688))
|
||||
- `histUpdateStatus(state, message)`: 상태 표시기 업데이트
|
||||
- `histLoad()`: 태그 목록 조회 및 상태 업데이트
|
||||
- 조회 시작: `⏳ 조회 중...`
|
||||
- 조회 완료: `✅ 조회 완료 (개수, 초)`
|
||||
- 데이터 없음: `❌ 조회 데이터 없음 (0개)`
|
||||
- 오류: `❌ 조회 실패: 메시지`
|
||||
|
||||
## 수정된 문제들
|
||||
1. 초기 상태 "준비됨" → "대기 중"으로 변경
|
||||
2. 레이아웃 구조 개선 (flex 컨테이너 사용)
|
||||
3. CSS 애니메이션 제거 및 성능 최적화
|
||||
4. `requestAnimationFrame`으로 DOM 업데이트 지연
|
||||
|
||||
## 남은 문제 (추가 작업 필요)
|
||||
- 태그 선택 시 페이지 hang 현상 원인 파악 필요
|
||||
- `pb-name-grid`의 CSS grid 레이아웃과 select 요소 8개의 상호작용
|
||||
- 브라우저 호환성 문제 가능성
|
||||
- 메모리 누락 가능성
|
||||
|
||||
## 테스트 방법
|
||||
1. 이력 조회 탭 진입 → "대기 중" 표시 확인
|
||||
2. '▼ 옵션 불러오기' 클릭 → 상태 변화 확인
|
||||
3. 태그 선택 시 페이지 반응성 확인
|
||||
416
plans/text-to-sql-timescaledb-plan.md
Normal file
416
plans/text-to-sql-timescaledb-plan.md
Normal file
@@ -0,0 +1,416 @@
|
||||
# Text-to-SQL (PostgreSQL + TimeScaleDB) 추가 계획
|
||||
|
||||
## 1. 개요
|
||||
|
||||
ExperionCrawler 프로젝트에 **TimeScaleDB 하이퍼테이블**을 적용하여 시계열 데이터(`realtime_table`, `history_table`)의 성능과 확장성을 대폭 개선합니다. 또한 **Text-to-SQL 기능**을 추가하여 API를 통해 자연어/쿼리 입력으로 시계열 데이터를 조회·분석할 수 있도록 합니다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 현재 아키텍처 분석
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph WebLayer
|
||||
Controller[Controllers]
|
||||
Program[Program.cs]
|
||||
end
|
||||
|
||||
subgraph Infrastructure
|
||||
DbContext[ExperionDbContext]
|
||||
DbService[ExperionDbService]
|
||||
end
|
||||
|
||||
subgraph Core
|
||||
CrawlService[ExperionCrawlService]
|
||||
RealtimeSvc[ExperionRealtimeService]
|
||||
HistorySvc[ExperionHistoryService]
|
||||
end
|
||||
|
||||
subgraph Data
|
||||
PostgreSQL[(PostgreSQL)]
|
||||
CSV[CSV Files]
|
||||
end
|
||||
|
||||
Controller --> DbContext
|
||||
Controller --> DbService
|
||||
DbContext --> PostgreSQL
|
||||
RealtimeSvc --> DbService
|
||||
HistorySvc --> DbService
|
||||
DbService --> CSV
|
||||
```
|
||||
|
||||
### 현재 사용 테이블
|
||||
| 테이블명 | 용도 | 현재 방식 |
|
||||
|----------|------|-----------|
|
||||
| `realtime_table` | 실시간 모니터링 포인트 | 일반 PostgreSQL 테이블 |
|
||||
| `history_table` | 시계열 이력 스냅샷 | 일반 PostgreSQL 테이블 |
|
||||
| `raw_node_map` | OPC UA 노드맵 원시 데이터 | 일반 PostgreSQL 테이블 |
|
||||
| `node_map_master` | 빌드된 노드맵 | 일반 PostgreSQL 테이블 |
|
||||
| `experion_records` | 크롤링 기록 | EF Core DbSet |
|
||||
|
||||
---
|
||||
|
||||
## 3. 목표 아키텍처
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph WebLayer
|
||||
Controller[Controllers]
|
||||
TextToSql[Text-to-SQL Service]
|
||||
Program[Program.cs]
|
||||
end
|
||||
|
||||
subgraph Infrastructure
|
||||
DbContext[ExperionDbContext]
|
||||
DbService[ExperionDbService]
|
||||
TimeSeriesSvc[TimeSeries Service]
|
||||
end
|
||||
|
||||
subgraph Core
|
||||
CrawlService[ExperionCrawlService]
|
||||
RealtimeSvc[ExperionRealtimeService]
|
||||
HistorySvc[ExperionHistoryService]
|
||||
end
|
||||
|
||||
subgraph Data
|
||||
TimeScaleDB[(TimeScaleDB<br/>HyperTables)]
|
||||
CSV[CSV Files]
|
||||
end
|
||||
|
||||
Controller --> TextToSql
|
||||
TextToSql --> DbService
|
||||
Controller --> DbContext
|
||||
Controller --> DbService
|
||||
DbContext --> TimeScaleDB
|
||||
RealtimeSvc --> TimeSeriesSvc
|
||||
HistorySvc --> TimeSeriesSvc
|
||||
TimeSeriesSvc --> DbService
|
||||
DbService --> CSV
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 구현 단계별 계획
|
||||
|
||||
### 단계 1: TimeScaleDB 패키지 추가 및 초기화
|
||||
|
||||
**목표**: TimeScaleDB 확장 활성화 및 하이퍼테이블 생성
|
||||
|
||||
#### 4.1.1 NuGet 패키지 추가
|
||||
- `Npgsql.EntityFrameworkCore.PostgreSQL` (이미 존재)
|
||||
- `Npgsql.TimeScaleDB` (새로 추가)
|
||||
|
||||
#### 4.1.2 데이터베이스 초기화 수정
|
||||
- [`ExperionDbContext`](src/Infrastructure/Database/ExperionDbContext.cs:1)에 TimeScaleDB 확장 활성화 코드 추가
|
||||
- `history_table` → `history_hypertable` (하이퍼테이블)로 마이그레이션
|
||||
- `realtime_table` → `realtime_hypertable` (하이퍼테이블)로 마이그레이션
|
||||
|
||||
#### 4.1.3 마이그레이션 스크립트
|
||||
```sql
|
||||
-- TimeScaleDB 확장 활성화
|
||||
CREATE EXTENSION IF NOT EXISTS timescaledb;
|
||||
|
||||
-- 기존 history_table 데이터를 백업
|
||||
CREATE TABLE history_table_backup AS SELECT * FROM history_table;
|
||||
|
||||
-- 기존 테이블 삭제 후 하이퍼테이블 생성
|
||||
DROP TABLE history_table;
|
||||
SELECT create_hypertable('history_hypertable', 'recorded_at',
|
||||
chunk_time_interval => INTERVAL '1 day',
|
||||
create_default_indexes => true);
|
||||
|
||||
-- 데이터 복원
|
||||
INSERT INTO history_hypertable (id, tagname, node_id, value, recorded_at)
|
||||
SELECT id, tagname, node_id, value, recorded_at FROM history_table_backup;
|
||||
DROP TABLE history_table_backup;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 단계 2: Text-to-SQL 서비스 구현
|
||||
|
||||
**목표**: 자연어/쿼리 입력으로 시계열 데이터를 조회하는 서비스 계층 구현
|
||||
|
||||
#### 4.2.1 인터페이스 정의 (`ITextToSqlService`)
|
||||
```
|
||||
namespace ExperionCrawler.Core.Application.Interfaces;
|
||||
|
||||
public interface ITextToSqlService
|
||||
{
|
||||
/// <summary>자연어 질의를 SQL로 변환</summary>
|
||||
Task<string> ParseNaturalLanguageAsync(string input);
|
||||
|
||||
/// <summary>SQL 쿼리 실행 및 결과 반환</summary>
|
||||
Task<SqlQueryResult> ExecuteQueryAsync(string sql);
|
||||
|
||||
/// <summary>쿼리 제안 (자동 완성)</summary>
|
||||
Task<IEnumerable<string>> SuggestQueriesAsync(string partialInput);
|
||||
|
||||
/// <summary>시계열 분석 (평균, 최대, 최소, 추세)</summary>
|
||||
Task<TimeSeriesAnalysisResult> AnalyzeAsync(string tagName, DateTime? from, DateTime? to);
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2.2 구현체 (`TextToSqlService`)
|
||||
- **자연어 파싱**: 키워드 기반 파서 (tag name, 시간 범위, 집계 함수)
|
||||
- **SQL 생성**: TimeScaleDB 함수 (`time_bucket`, `avg`, `max`, `min`, `last`) 활용
|
||||
- **결과 매핑**: 동적 SQL → DTO 매핑
|
||||
|
||||
#### 4.2.3 자연어 파싱 예시
|
||||
| 입력 | 생성된 SQL |
|
||||
|------|-----------|
|
||||
| "PV001 온도 최근 1시간 평균" | `SELECT time_bucket('5 min', recorded_at) AS bucket, AVG(value::float) FROM history_hypertable WHERE tagname = 'PV001' AND recorded_at > NOW() - INTERVAL '1 hour' GROUP BY bucket ORDER BY bucket` |
|
||||
| "전체 태그 현재 값" | `SELECT DISTINCT ON (tagname) tagname, livevalue, timestamp FROM realtime_hypertable ORDER BY tagname, timestamp DESC` |
|
||||
| "PV001, PV002 최근 24시간 최대값" | `SELECT time_bucket('1 hour', recorded_at) AS bucket, tagname, MAX(value::float) FROM history_hypertable WHERE tagname IN ('PV001', 'PV002') AND recorded_at > NOW() - INTERVAL '24 hour' GROUP BY bucket, tagname ORDER BY bucket, tagname` |
|
||||
|
||||
---
|
||||
|
||||
### 단계 3: API 엔드포인트 추가
|
||||
|
||||
**목표**: Text-to-SQL 기능을 REST API로 노출
|
||||
|
||||
#### 4.3.1 컨트롤러 (`TextToSqlController`)
|
||||
```
|
||||
[ApiController]
|
||||
[Route("api/text-to-sql")]
|
||||
public class TextToSqlController : ControllerBase
|
||||
{
|
||||
private readonly ITextToSqlService _service;
|
||||
|
||||
[HttpPost("parse")]
|
||||
public Task<IActionResult> Parse([FromBody] NaturalLanguageQueryDto dto);
|
||||
|
||||
[HttpPost("execute")]
|
||||
public Task<IActionResult> Execute([FromBody] SqlQueryDto dto);
|
||||
|
||||
[HttpGet("suggest")]
|
||||
public Task<IActionResult> Suggest([FromQuery] string input);
|
||||
|
||||
[HttpPost("analyze")]
|
||||
public Task<IActionResult> Analyze([FromBody] AnalysisRequestDto dto);
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3.2 DTO 정의
|
||||
```
|
||||
public class NaturalLanguageQueryDto
|
||||
{
|
||||
public string Query { get; set; } = string.Empty;
|
||||
public string Language { get; set; } = "ko"; // "ko" or "en"
|
||||
}
|
||||
|
||||
public class SqlQueryDto
|
||||
{
|
||||
public string Sql { get; set; } = string.Empty;
|
||||
public int? Limit { get; set; } = 1000;
|
||||
}
|
||||
|
||||
public class AnalysisRequestDto
|
||||
{
|
||||
public List<string> TagNames { get; set; } = new();
|
||||
public DateTime? From { get; set; }
|
||||
public DateTime? To { get; set; }
|
||||
public string Interval { get; set; } = "5 min"; // time_bucket interval
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 단계 4: TimeSeries 서비스 개선
|
||||
|
||||
**목표**: TimeScaleDB 특화 함수 활용하여 성능 최적화
|
||||
|
||||
#### 4.4.1 기존 `ExperionDbService` 수정
|
||||
- `history_table` → `history_hypertable` 참조로 변경
|
||||
- `realtime_table` → `realtime_hypertable` 참조로 변경
|
||||
- TimeScaleDB 함수 (`time_bucket`, `continuous aggregates`) 활용
|
||||
|
||||
#### 4.4.2 연속 집계 (Continuous Aggregates) 생성
|
||||
```sql
|
||||
-- 5분 단위 집계 뷰 생성
|
||||
CREATE MATERIALIZED VIEW history_5min_agg
|
||||
WITH (timescaledb.continuous) AS
|
||||
SELECT
|
||||
time_bucket('5 min', recorded_at) AS bucket,
|
||||
tagname,
|
||||
AVG(value::float) AS avg_value,
|
||||
MIN(value::float) AS min_value,
|
||||
MAX(value::float) AS max_value,
|
||||
FIRST(value::float, recorded_at) AS open_value,
|
||||
LAST(value::float, recorded_at) AS close_value,
|
||||
COUNT(*) AS point_count
|
||||
FROM history_hypertable
|
||||
GROUP BY bucket, tagname;
|
||||
|
||||
-- 기존 데이터 리프레시
|
||||
REFRESH MATERIALIZED VIEW history_5min_agg;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 단계 5: Program.cs 설정 업데이트
|
||||
|
||||
**목표**: 서비스 등록 및 초기화 플로우 수정
|
||||
|
||||
#### 4.5.1 서비스 등록
|
||||
```csharp
|
||||
// Text-to-SQL 서비스
|
||||
builder.Services.AddScoped<ITextToSqlService, TextToSqlService>();
|
||||
|
||||
// TimeSeries 서비스
|
||||
builder.Services.AddScoped<ITimeSeriesService, TimeSeriesService>();
|
||||
```
|
||||
|
||||
#### 4.5.2 연결 문자열 확인
|
||||
```json
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=localhost;Port=5432;Database=experion_crawler;Username=postgres;Password=postgres"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 파일 구조 변경
|
||||
|
||||
```
|
||||
src/
|
||||
├── Core/
|
||||
│ ├── Application/
|
||||
│ │ ├── DTOs/
|
||||
│ │ │ ├── ExperionDtos.cs (수정: Text-to-SQL DTO 추가)
|
||||
│ │ │ └── TextToSqlDtos.cs (신규)
|
||||
│ │ ├── Interfaces/
|
||||
│ │ │ ├── IExperionServices.cs (수정: ITextToSqlService, ITimeSeriesService 추가)
|
||||
│ │ │ └── ITextToSqlService.cs (신규)
|
||||
│ │ └── Services/
|
||||
│ │ ├── ExperionCrawlService.cs (변경 없음)
|
||||
│ │ └── TextToSqlService.cs (신규)
|
||||
│ └── Domain/
|
||||
│ └── Entities/
|
||||
│ ├── ExperionEntities.cs (수정: TimeScaleDB 엔티티 추가)
|
||||
│ └── TimeSeriesEntities.cs (신규)
|
||||
├── Infrastructure/
|
||||
│ ├── Database/
|
||||
│ │ ├── ExperionDbContext.cs (수정: 하이퍼테이블 설정)
|
||||
│ │ ├── TimeSeriesService.cs (신규)
|
||||
│ │ └── Migrations/ (신규: TimeScaleDB 마이그레이션)
|
||||
│ └── ... (기존 파일 유지)
|
||||
└── Web/
|
||||
├── Controllers/
|
||||
│ ├── ExperionControllers.cs (수정: Text-to-SQL 엔드포인트 추가)
|
||||
│ └── TextToSqlController.cs (신규)
|
||||
├── appsettings.json (수정: 연결 문자열)
|
||||
└── ExperionCrawler.csproj (수정: NuGet 패키지 추가)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. TimeScaleDB 특화 기능
|
||||
|
||||
### 6.1 체킹 간격 설정
|
||||
| 테이블 | chunk_time_interval | 설명 |
|
||||
|--------|-------------------|------|
|
||||
| `history_hypertable` | 1일 | 이력 데이터는 1일 단위 청크 |
|
||||
| `realtime_hypertable` | 1시간 | 실시간 데이터는 1시간 단위 청크 |
|
||||
|
||||
### 6.2 데이터 보존 정책
|
||||
```sql
|
||||
-- 90일 이전 데이터 자동 삭제
|
||||
SELECT add_drop_chunks_policy('history_hypertable', INTERVAL '90 days');
|
||||
```
|
||||
|
||||
### 6.3 압축 설정
|
||||
```sql
|
||||
-- 1일 이전 데이터 자동 압축
|
||||
SELECT add_compression_policy('history_hypertable', INTERVAL '1 day');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. NuGet 패키지 변경
|
||||
|
||||
### 추가 패키지
|
||||
| 패키지 | 버전 | 용도 |
|
||||
|--------|------|------|
|
||||
| `Npgsql.TimeScaleDB` | 최신 | TimeScaleDB 전용 확장 |
|
||||
|
||||
### 기존 패키지 (변경 없음)
|
||||
| 패키지 | 현재 버전 |
|
||||
|--------|----------|
|
||||
| `Npgsql.EntityFrameworkCore.PostgreSQL` | 8.0.11 |
|
||||
| `Microsoft.EntityFrameworkCore.Design` | 8.0.13 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 구현 체크리스트
|
||||
|
||||
- [ ] **단계 1**: TimeScaleDB 패키지 추가 및 초기화
|
||||
- [ ] `Npgsql.TimeScaleDB` NuGet 패키지 추가
|
||||
- [ ] `ExperionDbContext`에 TimeScaleDB 확장 코드 추가
|
||||
- [ ] `history_table` → `history_hypertable` 마이그레이션
|
||||
- [ ] `realtime_table` → `realtime_hypertable` 마이그레이션
|
||||
|
||||
- [ ] **단계 2**: Text-to-SQL 서비스 구현
|
||||
- [ ] `ITextToSqlService` 인터페이스 정의
|
||||
- [ ] `TextToSqlService` 구현체 작성
|
||||
- [ ] 자연어 파서 (한국어/영어 지원)
|
||||
- [ ] SQL 생성 로직 (TimeScaleDB 함수 활용)
|
||||
- [ ] 시계열 분석 기능 (평균, 최대, 최소, 추세)
|
||||
|
||||
- [ ] **단계 3**: API 엔드포인트 추가
|
||||
- [ ] `TextToSqlController` 생성
|
||||
- [ ] `/api/text-to-sql/parse` 엔드포인트
|
||||
- [ ] `/api/text-to-sql/execute` 엔드포인트
|
||||
- [ ] `/api/text-to-sql/suggest` 엔드포인트
|
||||
- [ ] `/api/text-to-sql/analyze` 엔드포인트
|
||||
- [ ] DTO 정의
|
||||
|
||||
- [ ] **단계 4**: TimeSeries 서비스 개선
|
||||
- [ ] `ExperionDbService` 수정 (하이퍼테이블 참조)
|
||||
- [ ] 연속 집계 (Continuous Aggregates) 생성
|
||||
- [ ] TimeScaleDB 함수 활용 쿼리 최적화
|
||||
|
||||
- [ ] **단계 5**: Program.cs 설정 업데이트
|
||||
- [ ] 서비스 등록
|
||||
- [ ] 연결 문자열 확인
|
||||
- [ ] 초기화 플로우 수정
|
||||
|
||||
- [ ] **단계 6**: 테스트 및 문서화
|
||||
- [ ] API 테스트 (Swagger)
|
||||
- [ ] 자연어 파싱 테스트
|
||||
- [ ] TimeScaleDB 성능 벤치마크
|
||||
- [ ] README.md 업데이트
|
||||
|
||||
---
|
||||
|
||||
## 9. 예상 효과
|
||||
|
||||
| 항목 | 기존 | 개선 후 |
|
||||
|------|------|--------|
|
||||
| 시계열 데이터 삽입 속도 | 일반 테이블 | TimeScaleDB 청킹으로 10x 이상 향상 |
|
||||
| 시간 범위 쿼리 | 전체 테이블 스캔 | 청크 제거로 빠른 응답 |
|
||||
| 데이터 압축 | 없음 | 자동 압축 (5-10x 저장공간 절약) |
|
||||
| 자동 보존 | 수동 삭제 | 정책 기반 자동 삭제 |
|
||||
| Text-to-SQL | 없음 | 자연어 기반 시계열 쿼리 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 주의사항
|
||||
|
||||
1. **TimeScaleDB 설치 prerequisite**: 대상 서버에 TimeScaleDB가 설치되어 있어야 함
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt-get install timescaledb-postgresql-16
|
||||
```
|
||||
|
||||
2. **기존 데이터 마이그레이션**: `history_table`과 `realtime_table`의 기존 데이터를 새 하이퍼테이블로 이동해야 함
|
||||
|
||||
3. **연결 문자열**: `Trust Server Certificate=true`는 TimeScaleDB에서 필요하지 않을 수 있음
|
||||
|
||||
4. **EF Core 제한**: EF Core는 TimeScaleDB 하이퍼테이블을 직접 지원하지 않음
|
||||
- Raw SQL 쿼리 활용 필요
|
||||
- `ExecuteSqlRawAsync`, `FromSqlRaw` 사용
|
||||
|
||||
5. **백호환성**: 기존 API 엔드포인트는 변경되지 않도록 유지
|
||||
Reference in New Issue
Block a user