TECH

[OpenMetadata] API Ingestion 트러블 슈팅

minoooo1119
2025년 12월 8일
28

⭐들어가며

OpenAPI 명세서가 없는 레거시 시스템을 연동하기 위해, 직접 JSON 스키마를 작성하고 GitHub Gist와 OpenMetadata REST 커넥터를 연결하는 과정까지 진행을 완료하였습니다.

이론적으로는 완벽한 준비를 마쳤고, Airflow를 통해 Ingestion DAG를 실행했습니다. 연결 테스트(Test Connection)도 통과했기에 무난한 성공을 예상하였습니다.

https://minoooo119.com/post-detail/openmetadata-api-%ec%84%9c%eb%b9%84%ec%8a%a4-%eb%93%b1%eb%a1%9d

⭐문제 발생

하지만 수집이 완료된 후 OpenMetadata UI에서 확인한 결과, 예상치 못한 세 가지 치명적인 문제가 발견되었습니다. 분명 OpenAPI Spec 3.0 표준에 맞춰 작성했음에도 불구하고, 정작 중요한 메타데이터들이 제대로 파싱되지 않는 현상이었습니다.

주요 오류 현상:

  1. Request/Response Schema 비어있음: 엔드포인트는 생성되었으나, 핵심인 요청/응답 본문의 스키마(Payload Structure)가 전혀 보이지 않음.
  2. 데이터 타입 인식 불가 (UNKNOWN): number, date 등으로 명시한 필드들이 OpenMetadata 상에서 모두 UNKNOWN 타입으로 표기됨.

원인 분석 및 해결

분명 문법 검사기(Swagger Editor)에서는 유효(Valid)하다고 나왔던 내 JSON 파일이 왜 OpenMetadata 수집기(Ingestion Framework)에서는 오작동을 일으켰을지 살펴보았습니다.

예시 상황을 통한 오류 상황 구현

test openAPI 명세
{
  "openapi": "3.0.0",
  "info": {
    "title": "HealthPlus 회원 관리 API (Inline Schema)",
    "description": "참조($ref)를 사용하지 않고 스키마를 직접 정의한 버전입니다. OpenMetadata 수집 테스트용입니다.",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.healthplus.internal"
    }
  ],
  "tags": [
    { "name": "Auth" },
    { "name": "User" }
  ],
  "paths": {
    "/api/v1/auth/login": {
      "post": {
        "tags": ["Auth"],
        "summary": "사용자 로그인",
        "operationId": "loginUser",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["username", "password"],
                "properties": {
                  "username": {
                    "type": "string",
                    "example": "health_user_01"
                  },
                  "password": {
                    "type": "string",
                    "format": "password",
                    "example": "P@ssw0rd123!"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "로그인 성공",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "access_token": {
                      "type": "string",
                      "example": "eyJhbGci..."
                    },
                    "expires_in": {
                      "type": "integer",
                      "example": 3600
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/auth/signup": {
      "post": {
        "tags": ["Auth"],
        "summary": "회원 가입",
        "operationId": "registerUser",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["username", "birth_date", "height", "weight"],
                "properties": {
                  "username": { "type": "string" },
                  "password": { "type": "string", "format": "password" },
                  "birth_date": {
                    "type": "string",
                    "format": "date",
                    "description": "생년월일"
                  },
                  "height": {
                    "type": "number",
                    "format": "float",
                    "description": "키 (cm)"
                  },
                  "weight": {
                    "type": "number",
                    "format": "float",
                    "description": "몸무게 (kg)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "가입 성공",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user_id": { "type": "string" },
                    "message": { "type": "string", "example": "Success" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/users/{userId}": {
      "get": {
        "tags": ["User"],
        "summary": "사용자 조회",
        "operationId": "getUser",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "성공",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "username": { "type": "string" },
                    "height": { "type": "number", "format": "float" },
                    "weight": { "type": "number", "format": "float" }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

등록 후에는 현재는 OpenMetadata UI의 Applications(메타데이터 수집 및 인덱싱 관리 메뉴)을 통해서 수집된 결과를 검증해 보았습니다.

제가 기대했던 이상적인 결과는 다음과 같았습니다.

  1. API 서비스 하위에 Auth, User라는 두 개의 API Collection이 깔끔하게 생성될 것.
  2. 각 Endpoint(login, signup, user-profile)가 빠짐없이 리스팅 될 것.
  3. 무엇보다 가장 중요한 Request와 Response의 Schema(필드 명세)가 우리가 작성한 JSON 그대로 시각화될 것.

하지만, 실제 화면에서 마주한 결과는 예상과 달랐습니다.

❌ 발생한 문제점

  • 스키마(Schema) 증발 현상: 엔드포인트 자체는 생성되었으나, 이를 클릭해서 상세 정보를 보려니 Request SchemaResponse Schema 탭이 텅 비어있거나(Empty) 표시되지 않는 문제가 발생했습니다.

/열

Swagger Editor에서는 문법 오류가 없다고 나왔는데, 도대체 왜 OpenMetadata 파서는 이를 읽지 못하는 걸까?” 이 의문을 풀기 위해 OpenMetadata의 소스 코드를 확인해보았습니다.

⭐Openmetadata Open Source Research(스키마 증발 문제)

문제의 원인을 찾기 위해, 현재 사내에 구축된 OpenMetadata v1.8.3 버전의 소스 코드를 직접 뜯어보기로 했습니다. GitHub Release 태그를 통해 해당 버전의 코드를 확인한 결과, 결정적인 로직 결함을 발견할 수 있었습니다.

https://github.com/open-metadata/OpenMetadata/tree/1.8.3-release

분석 대상 파일

  • Path: /ingestion/src/metadata/ingestion/source/api/rest/metadata.py
  • Target Method: _get_request_schema, _get_response_schema

문제의 코드 로직

해당 파일에서 요청/응답 스키마를 파싱하는 부분은 다음과 같이 구현되어 있었습니다.

무엇이 문제인가?

OpenAPI(Swagger) 스펙상, schema를 정의하는 방법은 크게 두 가지가 있습니다.

  1. 참조 방식 ($ref): #/components/schemas/LoginRequest 처럼 별도 정의된 모델을 가리키는 방식
  2. 인라인 방식 (Inline): schema 하위에 type: object, properties: {...}를 직접 적는 방식

하지만 위 코드를 보면, .get("$ref")를 통해 오직 참조($ref) 값만 찾도록 하드코딩 되어 있었습니다.

등록 JSON과의 불일치

제가 테스트를 위해 작성했던 JSON(Inline 방식)은 아래와 같은 구조였습니다.

JSON에는 $ref 키가 없었기 때문에, OpenMetadata의 수집 로직은 schema_refNone으로 판단했고, 결과적으로 **”스키마가 없다”**고 로그를 남기며 빈 껍데기만 리턴했던 것입니다.

결론

OpenMetadata v1.8.3의 REST 커넥터는 Inline Schema를 지원하지 않으며, 반드시 $ref를 사용해야만 스키마를 인식한다는 제약 사항이 있었습니다.

예시 JSON

최근 버전 확인 결과

최근 realease된 v1.11.0 버전에서는 아래와 같이 Inline Schema 지원을 하고 있어서 버전 업으로 해당 문제는 해결이 가능합니다.

스키마(Schema) 증발 현상 해결여부

$ref를 통한 Schema 처리 방식

OpenMetadata 소스 코드 분석 결과를 바탕으로, JSON 명세서의 모든 Inline 스키마를 $ref 참조 방식으로 리팩토링하여 다시 수집(Ingestion)을 시도했습니다. 결과는 절반의 성공이었습니다.

✅ 해결된 점: 스키마(Schema) 증발 현상 해결

예상대로 OpenMetadata의 파서가 $ref 키를 정상적으로 인식하기 시작했습니다.

  • Before: 엔드포인트를 클릭해도 Request Schema, Response Schema 탭이 비활성화되거나 비어있음.
  • After: 이제 각 탭에 LoginRequest, UserResponse 등의 모델명이 표시되고, 하위에 필드 목록이 정상적으로 렌더링 됩니다. 즉, 데이터의 ‘구조’ 자체는 성공적으로 가져왔습니다.

❌ 남은 문제: 특정 데이터 타입의 UNKNOWN 표기

하지만 세부 필드를 검토하던 중, 데이터의 ‘속성’을 정의하는 부분에서 여전히 문제가 발견되었습니다.

분명히 OpenAPI Spec에 맞춰 format: float나 format: date로 타입을 명시했음에도 불구하고, OpenMetadata UI 상에서는 해당 필드들의 타입이 UNKNOWN(혹은 그냥 STRING)으로 표기되는 현상이 발생했습니다.

  • height (Type: number, Format: float) ⇒ UNKNOWN
  • birth_date (Type: string, Format: date) ⇒ STRING

다시 소스 코드로 돌아가, 함수가 타입을 어떻게 변환하는지 분석해보았습니다.

버전업을 통한 방식

추후 진행해볼 예정…

⭐Openmetadata Open Source Research(타입 불일치 문제)

추후 작성 예정…

⭐타입 불일치 문제 해결 여부

추후 작성 예정…

'TECH' 카테고리의 다른 글

댓글

댓글 기능은 준비 중입니다.