확장성 및 성능을 위한 테이블 최적화

Prev Next

본 기사 개요

  • 이 글에서는 성능이 뛰어나고, 안정적이며, 조합이 용이하고, 확장 가능한 튤립 테이블 모델을 구현하고 유지 관리하는 데 필요한 기본 사항을 다룹니다.
  • 이 기사의 목적은 다음을 제공하는 것입니다:
  • 일반적인 사용 사례의 출발점으로 Tulip Library 테이블 자산을 활용할 것을 권장합니다.
  • 추가 테이블 설계를 위한 모범 사례
  • 고성능 테이블 쿼리 구축
  • 테이블 레코드 아카이빙 전략의 계획 및 구현

대상: 앱 개발자, 솔루션 아키텍트, 데이터 아키텍트, 데이터 플랫폼 담당자


핵심 개념

  • 테이블은 모든 Tulip 솔루션의 기초 구성 요소이며, 조직 내 여러 데이터 소스 중 하나입니다.
  • 잘 설계된 테이블 모델을 통해 성능이 뛰어나고, 조합이 가능하며, 확장 가능한 솔루션을 구현할 수 있습니다.
  • Tulip 라이브러리 자산을 활용하면 공통 데이터 모델을 통해 신속하게 시작할 수 있습니다.
  • 테이블에는 설계상(예: 속도 제한) 및 관행상(예: 레코드 수 임계값)의 특정 제한 사항이 있습니다.
  • 고성능 쿼리를 설계하고, 레코드 수가 많은 테이블(예: 1,000만 레코드 이상)에서 오래된 테이블 레코드를 아카이빙하면 솔루션의 응답성을 유지하는 데 도움이 되며, 이는 운영자의 생산성과 사용자 경험을 직접적으로 지원합니다.

엔터프라이즈 데이터 흐름에서 Tulip의 일반적인 위치

단일 창(single pane of glass) 역할을 하도록 설계된 최전선 운영 플랫폼으로서, Tulip 솔루션은 중요한 제조 데이터를 호스팅하고 전사의 다른 데이터 소스와 통합됩니다. 아래 아키텍처 다이어그램을 참고하십시오. Tulip 솔루션은 커넥터 함수를 통해 기업 비즈니스 시스템과 상호 작용할 수 있으며, Machine Data Source로 구성된 MQTT 브로커 또는 OPC UA 서버를 통해 생산 현장 장비와도 상호 작용할 수 있습니다. 귀사의 iPaaS, ETL 솔루션 및 기업용 데이터 시각화 도구는 테이블 API를 통해 Tulip 솔루션과 상호 작용할 수 있습니다.

Tulip Position in Enterprise Data Flow.svg

공통 데이터 모델을 위한 Tulip 리소스

Tulip은 테이블을 기반으로 구축된 공통 데이터 모델을 통해 솔루션 개발을 가속화할 수 있는 여러 리소스를 제공합니다. 자세한 내용은 다음을 참조하십시오:

공통 데이터 모델에 관한 추가 문서

공통 데이터 모델에 관한 Tulip 라이브러리 자료

확장성을 위한 테이블 최적화

확장 가능한 테이블 설계를 위한 모범 사례

Tulip 라이브러리 공통 데이터 모델의 테이블은 아래에 설명된 모범 사례를 기반으로 구축되었습니다. 이러한 테이블과 그 기반이 되는 원칙들은 Tulip 로직 내에서 장기적으로 확장성과 사용성을 유지하는 테이블을 설계하기 위한 실용적인 지침이 됩니다.

테이블을 수평적으로 확장 가능하게 만드십시오(너비가 아닌 높이로 확장).

자재 명세서(BOM) 테이블을 예로 들어 보겠습니다. Tulip 앱 개발 초보자는 어셈블리의 각 구성 요소마다 별도의 ‘Number’ 열을 가진 ‘넓은’ 테이블을 만들려는 경향이 있을 수 있습니다. 단기적으로는 간단해 보일 수 있지만, 이는 쿼리 재사용을 빠르게 복잡하게 만들고, 트리거 로직에 부담을 주며, 다른 어셈블리에 대한 테이블의 적용 범위를 제한합니다.

아래의 자재 명세서(BOM) 테이블에서 열은 어셈블리의 상위 항목과 구성 요소를 나타냅니다. 어셈블리에 구성 요소가 5개 있다면, 테이블에는 5개의 레코드가 포함됩니다. 하위 어셈블리는 한 관계에서는 “상위 항목”으로, 다른 관계에서는 “구성 요소”로 작용할 수 있으므로, 이 구조는 n단계 계층 구조를 지원합니다.

또한 이 테이블 설계는 제품이나 산업 분야에 구애받지 않는다는 점에 유의하십시오. 식사부터 상업용 항공기에 이르기까지 무엇이든 BOM을 저장할 수 있습니다.

자재명세서(BOM) 테이블의 권장 구조| 레이블 | 필드 유형 | 설명 || --- | --- | --- || ID | 텍스트 | 필수: 고유 식별자 || 상위 자재 정의 ID | 텍스트 | 상위 조립체의 고유 식별자자재 정의의 고유 식별자 || 상위 자재 설명 | 텍스트 | 상위 자재에 대한 설명 || 구성품 자재 정의 ID | 텍스트 | 구성품 자재 정의의 고유 식별자 || 구성품 자재 설명 | 텍스트 | 조립되거나 소비될 자재에 대한 설명 || 구성품 수량 | 숫자 | 조립되거나 소비될 자재의 수량 || 구성품 단위 | 텍스트 | 구성품의 || 사용 지점 | 텍스트 | 자재가 조립되거나 소모될 위치, 작업 또는 단계 |

너무 넓은 테이블은 피하십시오

Tulip 플랫폼은 최대 200개의 열을 지원하지만, 열 수가 약 50개를 초과하는 테이블은 장기적인 확장성을 위해 기본 데이터 모델을 재검토해야 할 필요가 있음을 시사할 수 있습니다.

단일 테이블에 너무 많은 서로 다른 “사물”을 저장하지 마십시오

이 맥락에서 “항목”이란 특정 사용 사례에 해당하는 데이터 엔티티 유형을 의미합니다. 예를 들어, “장비 및 자산(Equipment & Assets)” 테이블에는 현장 장비와 교정 추적이 필요한 공구의 컨텍스트가 모두 저장될 수 있습니다. 서로 다른 유형의 엔티티를 동일한 테이블에 저장하는 것이 실용적일 수 있지만, 다음과 같은 경우에만 가장 적합합니다:

  • 엔티티 유형 전반에 걸쳐 레코드 ID 사용 방식이 일관될 때.
  • “현장 장비”와 “교정된 공구” 예시에서, 레코드 ID는 일반적으로 회사의 자산 태그입니다.
  • 서로 다른 엔티티 유형이 약 50% 이상의 동일한 열을 공유하는 경우.
  • 서로 다른 유형의 엔티티가 공유하는 열이 극히 적다면, 이를 별도의 테이블에 저장하는 것이 더 적합할 가능성이 높습니다.
  • 엔티티 유형 간에 일대다 관계가 없습니다.
  • 예를 들어, 배치(Batch)에는 여러 작업 지시서(Work Order)가 포함될 수 있습니다. 작업 지시서와 배치가 50% 이상의 동일한 열을 공유하고, ID 열이 작업 지시서 ID 또는 배치 ID 역할을 하는 경우에도 — 이 둘 간의 일대다 관계는 쿼리 설계와 트리거 로직을 복잡하게 만들어, 솔루션 구축자와 데이터 사용자 모두에게 직관적이지 않은 테이블 모델을 초래할 수 있습니다.
  • 이해 관계자들 간의 합의.
    • 솔루션 개발자와 데이터 사용자는 일반적으로 서로 다른 엔티티 유형이 근본적으로 유사하다는 데 동의합니다(예: '현장 장비'와 '교정된 도구').

테이블의 실질적인 한계

속도 제한

Tulip은 다음 사항과 관련하여 사용자에게 일관되고 안정적인 경험을 제공하기 위해 자산에 대한 속도 제한을 적용합니다:

  • 레코드 쓰기
  • 레코드 삭제

테이블 요청 제한에 대한 자세한 내용은 여기에서 확인하세요.

레코드 수 제한

Tulip 플랫폼은 테이블 내 레코드 수에 대해 엄격한 제한을 적용하지는 않지만, 테이블 사용 방식에 따라 실질적인 제한이 존재합니다. 레코드 수가 증가함에 따라 쿼리 및 집계 작업에서 성능 저하가 발생하는 경우가 흔하며, 이는 애플리케이션의 안정성, 운영자의 경험, 그리고 생산 현장의 생산성에 영향을 미칠 수 있습니다.

Tulip 고객의 프로덕션 앱 중 레코드 수가 1,000만 건을 초과하는 테이블에서 작동하면서도, 레코드 수 자체로 인한 성능 영향은 미미했던 사례가 문서화되어 있습니다.

가용성과 성능을 위한 테이블 최적화

연결 레코드 사용 최소화

이전 버전의 Tulip 플랫폼에서는 연결된 레코드 열이 여러 개 있고 레코드 수가 100만 개를 초과하는 테이블에서 심각한 성능 문제가 발생했습니다. 플랫폼 개선을 통해 이 문제가 상당 부분 해결되었지만, 다음과 같은 여러 가지 이유로 연결된 레코드의 사용을 최소화하거나 줄이는 것이 여전히 권장되는 모범 사례입니다:

  • 대규모 환경에서의 성능
  • API 또는 CSV 다운로드를 통한 테이블 레코드 추출
  • 알려진 기능 제한 사항(예: 연결된 테이블의 열을 기준으로 내장된 대화형 테이블을 필터링할 수 없음)

고성능 쿼리 작성

쿼리 성능은 필터 유형에 따라 달라집니다

필터 유형 대규모 환경에서의 성능 참고 사항
같음 양호 ~ 우수 텍스트 열에서 정확한 일치 검색 시 '우수'한 성능을 보입니다. 가능한 경우 다른 필터 유형보다 "같음" 필터를 사용하십시오.
~로 시작 양호
포함 보통 ~ 양호 항목 수가 적은 목록(< 10개)에서 가장 효과적입니다. 목록이 커질수록 성능이 저하됩니다.
null 보통에서 중간 대부분의 레코드에서 해당 열의 값이 null인 경우, 이 필터에만 의존하는 쿼리의 실행 속도가 느려질 수 있습니다.
NULL이 아님 보통에서 다소 느림 반대로, 대부분의 레코드가 null인 경우 이 쿼리는 결과를 매우 빠르게 반환할 수 있습니다.
로 끝남 보통 ~ 중간 대소문자를 구분하지 않습니다. 결과 제한이 크거나 레코드 수가 많을수록 성능이 저하됩니다.
~을 포함하는 보통에서 중간 대소문자를 구분하지 않습니다. 결과 제한 값이 크거나 레코드 수가 많을 경우 성능이 저하됩니다.
같지 않음 나쁨 ~ 보통
포함되지 않음 나쁨 ~ 보통
~로 시작하지 않음 '나쁨'에서 '보통'까지
로 끝나지 않음 'Poor to Fair'
다음이 포함되지 않음 '나쁨~보통'

쿼리 성능은 필터 수에 따라 달라집니다

쿼리를 설계할 때는 적절한 균형을 맞추도록 하십시오. 정확한 결과를 반환할 수 있을 만큼의 필터를 적용하되, 유사한 사용 사례에서도 쿼리를 재사용할 수 있도록 해야 합니다. 필터가 3~4개 이상 필요한 경우는 드뭅니다.

Queries with many filters

If you are building a query with many filters, it may indicate that your table model lacks scalability and would benefit from a partial redesign.

항상 적절한 쿼리 제한을 설정하십시오

모든 쿼리에 LIMIT을 설정하고, 사용 사례를 충족하는 최소값을 사용하십시오:

  • 일치하는 레코드가 하나라도 있는지 확인: 1개 레코드
  • 보고: 대신 Analytics 사용을 고려하십시오
  • 기타 사용 사례: 사용된 쿼리 필터를 기반으로 검색되거나 평가될 기록의 실질적인 최대 개수를 고려하고, 그 수치를 LIMIT으로 사용하십시오(1,000개 미만인 경우).

이것이 중요한 이유:

기본 LIMIT 값인 1,000을 사용하는 쿼리는, 실제로 필요한 레코드가 소수에 불과하더라도 최대 1,000개의 일치하는 레코드를 가져오려고 시도합니다. 더 작은 결과 집합으로도 충분한 사용 사례에서 불필요하게 큰 LIMIT 값은 시간과 리소스를 낭비합니다.

테이블 레코드 아카이빙 전략의 기본 원리

이 논의에서 “보관”이란 테이블에서 레코드를 추출하여 ETL 솔루션(예: iPaaS 또는 데이터 통합 플랫폼)을 통해 클라우드 데이터 플랫폼으로 로드한 후, 해당 테이블에서 해당 레코드를 삭제하는 과정을 의미합니다.

Tulip to ETL to Cloud Data to BI.svg

배경 및 권장 사항

  • Tulip은 테이블에 저장할 수 있는 레코드 수에 대해 엄격한 제한을 두지 않습니다.
  • Tulip은 레코드 수가 증가함에 따라 쿼리 성능이 저하될 수 있으므로, 개별 테이블의 레코드 수를 수백만 개 이하로 유지할 것을 권장합니다.
  • 연결된 레코드의 사용과 부적절하게 설계된 쿼리는 성능 문제를 더욱 악화시킵니다.

권장 사항:

오래된 레코드를 주기적으로 클라우드 데이터 플랫폼으로 이동시켜 테이블 레코드 수를 1,000만 개 미만으로 유지하십시오.

테이블 레코드 아카이빙 계획 및 구현

  1. 아카이빙 기준을 정의합니다. 즉, 각 테이블에서 아카이빙 대상 레코드를 식별하는 쿼리 필터를 결정합니다.
  2. 예: “역 상태 이력” 테이블의 경우, 전월 1일 이전 날짜의 모든 레코드를 아카이빙할 수 있습니다.
    Archiving Step 1.svg
  3. 대상 위치 식별 — 대상 클라우드 데이터 플랫폼(예: Redshift, Snowflake, Databricks 등)을 선택합니다.
    Archiving Step 2.svg
  4. ETL 솔루션 선택 — 레코드 이동을 수행할 데이터 통합 플랫폼, 타사 iPaaS 또는 사용자 지정 Python 스크립트를 선택합니다.
    Archiving Step 3.svg
  5. 초기 대량 마이그레이션 수행 — 1단계에서 정의한 기준을 충족하는 모든 레코드를 2단계에서 지정한 대상 위치로 일괄 처리하여 이동합니다.
    Archiving Step 4.svg
  6. 반복적인 증분 마이그레이션 예약 — 유사한 레코드 이동을 지속적으로 수행할 수 있도록 자동화된 프로세스를 설정합니다. 일반적인 모범 사례는 이러한 레코드 이동을 매우 빈번하게 수행하는 것입니다(예: 일반적인 근무일 동안 업데이트되는 테이블의 경우 15분마다).Archiving Step 5.svg

4번과 5번에 대한 더 자세한 지침은 아래의 ‘테이블 레코드 아카이빙 모범 사례’를 참조하십시오.

Tulip record archiving implementation fundamentals.svg

ETL 옵션에 대한 추가 자료:

테이블 레코드 아카이빙에 대한 모범 사례

시퀀스 번호를 커서로 사용

레코드를 페이지 단위로 처리할 때는 오프셋 기반 페이지 분할 대신 시퀀스 번호를 커서로 사용하십시오.

오프셋 기반 페이징이 문제가 되는 이유: 동기화가 실행되는 동안 레코드가 삽입되거나 업데이트되면, 오프셋 기반 페이징은 레코드를 건너뛰거나 중복할 수 있습니다. 시퀀스 번호 기반 커서는 안정적입니다. 즉, concurrent writes와 관계없이 sequenceNumber > last_seen_seq 조건은 항상 올바른 다음 페이지를 반환합니다.

테이블 동기화는 두 단계로 이루어집니다. 먼저 모든 과거 레코드를 로드하는 일회성 일괄 로드 단계가 있고, 그 다음에는 변경 사항을 반영하기 위한 반복적인 증분 마이그레이션 단계가 이어집니다. 두 단계 모두 시퀀스 번호를 커서로 사용해야 합니다.

초기 일괄 로드 후 증분 마이그레이션

증분 동기화를 실행하기 전에, 모든 과거 레코드를 한 번에 로드하는 일회성 대량 로드를 수행하십시오. 이를 통해 대상 시스템에 완전한 기준선을 마련할 수 있습니다.

패턴:

  1. last_seq = 0으로 시작합니다(중단된 일괄 로드를 재개하는 경우 마지막으로 저장된 체크포인트).
  2. 시퀀스 번호 오름차순으로 정렬된 페이지를 가져옵니다
  3. 각 페이지를 대상 시스템에 기록
  4. 약 500개 레코드마다 체크포인트를 저장합니다
  5. 페이지에서 레코드가 반환되지 않으면 중지

요율 제한을 준수하고 지수적 백오프를 사용합니다

Tulip Tables API는 모든 요청에 대해 요청 제한을 적용합니다. 이를 올바르게 처리하기 위해서는 두 가지 단계가 필요합니다:

사전 조절(Proactive throttling) — 요청이 거부될 때까지 기다리지 말고, 처음부터 제한 범위 내에 머물 수 있도록 요청 간격을 조절하십시오
사후 백오프 — 스로틀링에도 불구하고 429 오류가 발생했을 경우(예: 동일한 워크스페이스 API 할당량을 공유하는 다른 동시 프로세스 때문), GET 요청 빈도를 줄이고 재시도하십시오.

현재 제한 사항에 대해서는 Tulip API 속도 제한 가이드를 참조하십시오. ETL 솔루션에 제한 값을 하드코딩하지 마십시오. 제한이 변경될 경우 쉽게 업데이트할 수 있도록 이름이 지정된 상수를 사용하십시오.

필드 필터를 사용하여 연결된 레코드 필드 제외

테이블은 연결된 레코드 필드(tableLink 유형)를 지원합니다. 이는 한 테이블의 필드가 다른 테이블의 레코드를 참조하는 경우를 말합니다. 레코드 응답에 tableLink 필드가 포함되면, Tulip API는 서버 측에서 해당 참조를 해결해야 하므로 연쇄적인 데이터베이스 쿼리가 발생합니다. 연결된 필드가 많거나 레코드 수가 많은 테이블의 경우, 이로 인해 데이터베이스에 상당한 부하가 발생하고 워크스페이스의 모든 사용자에게 영향을 미칩니다.

해결 방법: fields 쿼리 매개변수를 사용하여 반환할 필드를 명시적으로 나열하고, tableLink 필드는 완전히 제외하십시오. 이를 위해서는 포함할 필드를 파악하기 위해 먼저 테이블 스키마를 가져와야 하므로 약간의 사전 작업이 필요하지만, 이는 올바른 접근 방식이며 더 빠르고 가벼운 쿼리를 생성합니다.

체크포인트 기반 오류 복구

네트워크 장애, 타임아웃, 예기치 않은 오류는 언제든 발생할 수 있습니다. 커넥터를 설계할 때는 처음부터 다시 시작하지 않고도 원활하게 복구될 수 있도록 해야 합니다.

패턴:

  • 약 500개의 레코드마다 마지막으로 성공적으로 처리된 시퀀스 번호를 내구성 있는 저장소에 영구 저장하십시오.
  • 재시작 시 체크포인트를 불러와 seq > checkpoint_seq 지점부터 재개합니다
  • 목적지에 레코드를 삽입(insert)이 아닌 업서트(upsert) 로직을 사용하여 기록함으로써, 체크포인트에서 레코드를 재처리하는 것이 안전하도록 하십시오

레코드 삭제 처리

Tulip 테이블 API는 삭제 이벤트 스트림을 제공하지 않습니다. Tulip에서 레코드가 영구 삭제되더라도 커넥터는 이를 감지할 방법이 없습니다. 즉, 알림 없이 레코드가 API에서 단순히 사라집니다. 이를 어떻게 처리할지는 외부 대상에서 수행하고자 하는 작업에 따라 달라집니다.

  • 시나리오 A: 대상 저장소를 Tulip과 동기화 유지(삭제 내용 미러링)
    • 목적지가 삭제 내역을 포함하여 Tulip의 현재 상태를 반영하도록 하는 것이목표라면 , Tulip에서 레코드를 영구 삭제하지 마십시오. 커넥터는 감지하지 못한 삭제 내역을 목적지로 전달할 수 없습니다.
    • 대신 테이블에 '보관됨(archived)' 또는 '삭제됨(deleted)'과 같은 부울(boolean) 열을 추가하고, 레코드를 비활성 상태로 처리해야 할 때 해당 값을 true로 설정하세요. 이는 필드 업데이트이므로, 다음 증분 동기화 시 이 변경 사항이 자동으로 감지되어 별도의 커넥터 로직 없이도 대상에 반영됩니다. 이후 대상에서 해당 레코드를 필터링하여 제외할 수 있습니다.
    • Tulip에서: 레코드를 삭제하는 대신, 레코드를 '비활성'으로 처리해야 할 때 앱, 함수 또는 자동화를 통해 '보관됨(archived)' 부울 열을 추가하고 이를 업데이트하십시오.
    • 대상 시스템에서: 동기화된 모든 레코드가 활성 상태라고 가정하지 말고, 쿼리 및 보고서에서 archived = false 조건으로 필터링하십시오.
  • 시나리오 B: 성능 향상을 위해 Tulip 정리, 대상 시스템에 레코드 유지
    • Tulip 테이블은 규모를 작게 유지할 때 성능이 더 우수합니다. 테이블이 매우 커진 경우, 오래되거나 완료된 레코드를 영구 삭제하는 것은 테이블 크기를 관리하는 유효한 방법입니다. 예를 들어, 보존 기간이 지난 완료된 작업 지시를 아카이빙하는 경우입니다.
    • 이 시나리오에서는 Tulip에서 영구 삭제를 수행해도 무방합니다. 커넥터는 삭제 이벤트를 수신하지 않으므로, Tulip에서 제거된 레코드는 대상 시스템에서 더 이상 업데이트되지 않고 있는 그대로 유지됩니다. Tulip은 높은 성능을 유지하는 동시에 대상 시스템은 장기 보관소로 기능하게 됩니다.
    • 이 패턴을 위해 커넥터를 변경할 필요는 없습니다. 단지 다운스트림 소비자가 대상 데이터베이스에 Tulip에는 더 이상 존재하지 않는 레코드가 포함될 수 있음을 이해하고, 이에 따라 쿼리를 설계하도록 하면 됩니다(예: 레코드가 Tulip에서 여전히 활성 상태라고 가정하지 말고, completed_at 타임스탬프를 기준으로 조인하는 방식).

추가 학습

{{snippet.원하는 정보를 찾으셨나요?}}