Files
ExperionCrawler/plans/text-to-sql-timescaledb-plan.md

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_tablehistory_hypertable (하이퍼테이블)로 마이그레이션
  • realtime_tablerealtime_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_tablehistory_hypertable 참조로 변경
  • realtime_tablerealtime_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.TimeScaleDB NuGet 패키지 추가
    • ExperionDbContext에 TimeScaleDB 확장 코드 추가
    • history_tablehistory_hypertable 마이그레이션
    • realtime_tablerealtime_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가 설치되어 있어야 함

    # Ubuntu/Debian
    sudo apt-get install timescaledb-postgresql-16
    
  2. 기존 데이터 마이그레이션: history_tablerealtime_table의 기존 데이터를 새 하이퍼테이블로 이동해야 함

  3. 연결 문자열: Trust Server Certificate=true는 TimeScaleDB에서 필요하지 않을 수 있음

  4. EF Core 제한: EF Core는 TimeScaleDB 하이퍼테이블을 직접 지원하지 않음

    • Raw SQL 쿼리 활용 필요
    • ExecuteSqlRawAsync, FromSqlRaw 사용
  5. 백호환성: 기존 API 엔드포인트는 변경되지 않도록 유지