13 KiB
13 KiB
Text-to-SQL (PostgreSQL + TimeScaleDB) 추가 계획
1. 개요
ExperionCrawler 프로젝트에 TimeScaleDB 하이퍼테이블을 적용하여 시계열 데이터(realtime_table, history_table)의 성능과 확장성을 대폭 개선합니다. 또한 Text-to-SQL 기능을 추가하여 API를 통해 자연어/쿼리 입력으로 시계열 데이터를 조회·분석할 수 있도록 합니다.
2. 현재 아키텍처 분석
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. 목표 아키텍처
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에 TimeScaleDB 확장 활성화 코드 추가history_table→history_hypertable(하이퍼테이블)로 마이그레이션realtime_table→realtime_hypertable(하이퍼테이블)로 마이그레이션
4.1.3 마이그레이션 스크립트
-- 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) 생성
-- 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 서비스 등록
// Text-to-SQL 서비스
builder.Services.AddScoped<ITextToSqlService, TextToSqlService>();
// TimeSeries 서비스
builder.Services.AddScoped<ITimeSeriesService, TimeSeriesService>();
4.5.2 연결 문자열 확인
{
"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 데이터 보존 정책
-- 90일 이전 데이터 자동 삭제
SELECT add_drop_chunks_policy('history_hypertable', INTERVAL '90 days');
6.3 압축 설정
-- 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.TimeScaleDBNuGet 패키지 추가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. 주의사항
-
TimeScaleDB 설치 prerequisite: 대상 서버에 TimeScaleDB가 설치되어 있어야 함
# Ubuntu/Debian sudo apt-get install timescaledb-postgresql-16 -
기존 데이터 마이그레이션:
history_table과realtime_table의 기존 데이터를 새 하이퍼테이블로 이동해야 함 -
연결 문자열:
Trust Server Certificate=true는 TimeScaleDB에서 필요하지 않을 수 있음 -
EF Core 제한: EF Core는 TimeScaleDB 하이퍼테이블을 직접 지원하지 않음
- Raw SQL 쿼리 활용 필요
ExecuteSqlRawAsync,FromSqlRaw사용
-
백호환성: 기존 API 엔드포인트는 변경되지 않도록 유지