Azure Cosmos DB와 같은 스키마 없는 데이터베이스를 사용하면 구조화되지 않은 데이터와 반구조화된 데이터를 쉽게 저장하고 쿼리할 수 있지만, 성능, 확장성, 비용을 최적화하려면 데이터 모델에 대해 생각해 보세요.
데이터가 어떻게 저장되나요? 사용자의 애플리케이션은 어떻게 데이터를 검색하고 쿼리하나요? 애플리케이션 부하가 읽기 또는 쓰기 중 어디에 집중되어 있는가?
이 문서를 읽은 후, 다음 질문에 답할 수 있습니다.
- 데이터 모델링은 무엇이고 어떻게 사용해야 하는가?
- Azure Cosmos DB에서 데이터를 모델링하는 것은 관계형 데이터베이스와 어떻게 다른가요?
- 비관계형 데이터베이스에서 데이터 관계를 어떻게 표현하나요?
- 데이터를 포함해야 하는 경우 및 데이터에 링크하는 경우는 언제인가?
JSON의 숫자
Azure Cosmos DB는 문서를 JSON으로 저장하므로 JSON으로 저장하기 전에 숫자를 문자열로 변환할지 여부를 결정해야 합니다.
String에서 정의한 배정밀도 숫자의 경계를 초과할 수 있는 모든 숫자를 으로 변환합니다.
JSON 사양은 상호 운용성 문제로 인해 이 경계 외부의 숫자를 사용하는 것이 잘못된 사례인 이유를 설명합니다. 이러한 문제는 특히 파티션 키 열과 관련이 있습니다. 이 열은 변경할 수 없고 나중에 변경하려면 데이터 마이그레이션이 필요하기 때문입니다.
데이터 포함
Azure Cosmos DB에서 데이터를 모델링할 때 엔터티를 JSON 문서로 표현된 자체 포함 항목으로 처리합니다.
비교를 위해 먼저 관계형 데이터베이스에서 데이터를 모델링하는 방법을 살펴보겠습니다. 다음 예제에서는 관계형 데이터베이스에서 사용자가 저장되는 방법을 보여 줍니다.
관계형 데이터베이스로 작업할 때 전략은 모든 데이터를 정규화하는 것입니다. 데이터를 정규화하려면 일반적으로 사용자 등의 엔터티를 가져와 개별 구성 요소로 분석해야 합니다. 예에서는 사용자에게 여러 연락처 세부 정보 레코드와 여러 주소 레코드가 있을 수 있습니다. 형식 등의 일반적인 필드를 추출하여 연락처 세부 정보를 더욱 세부적으로 분석할 수 있습니다. 주소에도 동일한 방식이 적용됩니다. 각 레코드는 홈 또는 비즈니스로 분류될 수 있습니다.
데이터를 정규화할 때의 기본 전제는 각 레코드에 중복된 데이터를 저장하지 않고 대신 데이터를 참조하는 것입니다. 이 예제에서 모든 연락처 세부 정보와 주소를 포함하여 사용자를 읽으려면 JOINS를 사용하여 데이터를 런타임에 효율적으로 다시 작성(또는 비정규화)해야 합니다.
SELECT p.FirstName, p.LastName, a.City, cd.Detail
FROM Person p
JOIN ContactDetail cd ON cd.PersonId = p.Id
JOIN ContactDetailType cdt ON cdt.Id = cd.TypeId
JOIN Address a ON a.PersonId = p.Id
한 사람의 연락처 정보와 주소를 업데이트하려면 여러 개별 테이블에 대한 쓰기 작업이 필요합니다.
이제 Azure Cosmos DB에서 자체 포함 엔터티와 동일한 데이터를 모델링하는 방법을 살펴보겠습니다.
{
"id": "1",
"firstName": "Thomas",
"lastName": "Andersen",
"addresses": [
{
"line1": "100 Some Street",
"line2": "Unit 1",
"city": "Seattle",
"state": "WA",
"zip": 98012
}
],
"contactDetails": [
{"email": "thomas@andersen.com"},
{"phone": "+1 555 555-5555", "extension": 5555}
]
}
이 방식을 사용하면 연락처 정보와 주소 등 해당 개인과 관련된 모든 정보를 단일 JSON 문서로 포함하여 개인 정보 레코드를 비정규화했습니다. 또한 고정된 스키마로 제한되지 않기 때문에 다른 도형의 연락처 세부 정보를 완전히 포함하는 것과 같은 유연성이 있습니다.
이제 데이터베이스에서 전체 개인 정보 레코드를 검색하는 작업은 단일 컨테이너와 단일 항목에 대해 단일 읽기 작업을 수행하는 것과 같습니다. 개인 레코드의 연락처 세부 정보와 주소를 업데이트하는 작업도 단일 항목에 대해 단일 쓰기 작업을 수행하는 것과 같습니다.
데이터를 비정규화하면 애플리케이션에서 일반적인 작업을 완료하는 데 필요한 쿼리와 업데이트 수가 줄어들 수 있습니다.
포함해야 하는 경우
일반적으로 다음과 같은 경우에 포함된 데이터 모델을 사용합니다.
- 엔터티 간에 내포된 관계가 있습니다.
- 엔터티 사이에 일 대 몇(one-to-few) 의 관계가 있습니다.
- 데이터가 자주 변경되지 않습니다.
- 데이터가 무한히 증가하지 않습니다.
- 데이터가 자주 함께 쿼리됩니다.
참고
일반적으로 비정규화된 데이터 모델은 읽기 성능이 더 뛰어납니다.
포함하지 않는 경우
경험상 Azure Cosmos DB에서는 모든 것을 비정규화하고 모든 데이터를 단일 항목에 포함하는 것이지만, 이 방식을 사용하면 피해야 할 상황이 발생할 수 있습니다.
다음 JSON 코드 조각을 예로 들어 보겠습니다.
{
"id": "1",
"name": "What's new in the coolest Cloud",
"summary": "A blog post by someone real famous",
"comments": [
{"id": 1, "author": "anon", "comment": "something useful, I'm sure"},
{"id": 2, "author": "bob", "comment": "wisdom from the interwebs"},
…
{"id": 100001, "author": "jane", "comment": "and on we go ..."},
…
{"id": 1000000001, "author": "angry", "comment": "blah angry blah angry"},
…
{"id": ∞ + 1, "author": "bored", "comment": "oh man, will this ever end?"},
]
}
이 예는 일반적인 블로그나 CMS(콘텐츠 관리 시스템)를 모델링한다면 포함된 메모가 있는 게시물 엔터티가 어떻게 보일지 보여 줍니다. 이 예제의 문제는 주석 배열이 바인딩되지 않는 것입니다. 즉, 단일 게시물에 있을 수 있는 주석 수에 대한 제한(실질적인)이 없습니다. 이런 디자인은 물건의 크기가 무한히 커질 수 있기 때문에 문제를 일으킬 수 있으므로 방지합니다.
항목의 크기가 커짐에 따라, 대규모로 데이터를 전송하고, 읽고, 업데이트하는 일이 점점 더 어려워집니다.
이 경우에는 다음과 같은 데이터 모델을 고려하는 것이 더 효율적입니다.
Post item:
{
"id": "1",
"name": "What's new in the coolest Cloud",
"summary": "A blog post by someone real famous",
"recentComments": [
{"id": 1, "author": "anon", "comment": "something useful, I'm sure"},
{"id": 2, "author": "bob", "comment": "wisdom from the interwebs"},
{"id": 3, "author": "jane", "comment": "....."}
]
}
Comment items:
[
{"id": 4, "postId": "1", "author": "anon", "comment": "more goodness"},
{"id": 5, "postId": "1", "author": "bob", "comment": "tails from the field"},
...
{"id": 99, "postId": "1", "author": "angry", "comment": "blah angry blah angry"},
{"id": 100, "postId": "2", "author": "anon", "comment": "yet more"},
...
{"id": 199, "postId": "2", "author": "bored", "comment": "will this ever end?"}
]
이 모델에는 게시물 식별자를 포함하는 속성이 있는 각 메모에 대한 항목이 있습니다. 이 모델을 사용하면 게시물에 원하는 수의 메모를 포함하고 효율적으로 성장할 수 있습니다. 가장 최근 댓글보다 더 많이 보고 싶은 사용자는 댓글 컨테이너의 파티션 키가 되어야 하는 postId를 전달하여 이 컨테이너를 쿼리할 것입니다.
데이터를 포함하는 것이 좋지 않은 또 다른 경우는 포함된 데이터가 여러 항목에서 자주 사용되고 자주 변경되는 경우입니다.
다음 JSON 코드 조각을 예로 들어 보겠습니다.
{
"id": "1",
"firstName": "Thomas",
"lastName": "Andersen",
"holdings": [
{
"numberHeld": 100,
"stock": { "symbol": "zbzb", "open": 1, "high": 2, "low": 0.5 }
},
{
"numberHeld": 50,
"stock": { "symbol": "xcxc", "open": 89, "high": 93.24, "low": 88.87 }
}
]
}
이 예는 개인의 주식 포트폴리오를 나타낼 수 있습니다. 우리는 각 포트폴리오 문서에 주식 정보를 포함하기로 결정했습니다. 관련 데이터가 자주 변경되는 환경에서 자주 변경되는 데이터를 포함한다는 것은 각 포트폴리오를 지속적으로 업데이트해야 한다는 것을 의미합니다. 주식 거래 애플리케이션을 예로 들면, 주식이 거래될 때마다 각 포트폴리오 항목을 업데이트하게 됩니다.
주식 zbzb는 하루에 수백 번 거래될 수 있으며, 수천 명의 사용자가 포트폴리오에 zbzb를 보유할 수 있습니다. 이러한 데이터 모델을 사용하면 시스템은 매일 수천 개의 포트폴리오 문서를 여러 번 업데이트해야 하며, 이는 크기 조정이 잘 수행되지 않습니다.
참조 데이터
데이터를 포함하는 것은 많은 경우에 효과적이지만, 데이터를 비정규화하면 그 가치에 비해 더 많은 문제가 발생하는 경우도 있습니다. 그러면 무엇을 할 수 있나요?
관계형 데이터베이스뿐만 아니라 문서 데이터베이스에서도 엔터티 간의 관계를 만들 수 있습니다. 문서 데이터베이스에서 한 항목에는 다른 문서의 데이터와 연결되는 정보가 포함될 수 있습니다. Azure Cosmos DB는 관계형 데이터베이스와 같은 복잡한 관계를 위해 설계되지 않았지만 항목 간의 간단한 연결은 가능하며 도움이 될 수 있습니다.
JSON에서 우리는 앞서 언급한 주식 포트폴리오의 예를 사용하지만, 이번에는 주식 항목을 포함하는 대신 포트폴리오의 주식 항목을 참조하세요. 이렇게 하면 주식 항목이 하루 중 자주 변경되더라도 업데이트해야 하는 문서는 단일 주식 항목뿐입니다.
Person document:
{
"id": "1",
"firstName": "Thomas",
"lastName": "Andersen",
"holdings": [
{ "numberHeld": 100, "stockId": 1},
{ "numberHeld": 50, "stockId": 2}
]
}
Stock documents:
{
"id": "1",
"symbol": "zbzb",
"open": 1,
"high": 2,
"low": 0.5,
"vol": 11970000,
"mkt-cap": 42000000,
"pe": 5.89
},
{
"id": "2",
"symbol": "xcxc",
"open": 89,
"high": 93.24,
"low": 88.87,
"vol": 2970200,
"mkt-cap": 1005000,
"pe": 75.82
}
이 방식의 한 가지 단점은 애플리케이션이 개인의 포트폴리오에 있는 각 주식에 대한 정보를 가져오기 위해 여러 번 데이터베이스 요청을 해야 한다는 것입니다. 이런 디자인을 사용하면 업데이트가 자주 이루어지므로 데이터 쓰기 속도가 빨라집니다. 하지만 이로 인해 데이터를 읽거나 쿼리하는 속도가 느려지는데, 이는 이 시스템에서는 그다지 중요하지 않습니다.
참고
정규화된 데이터 모델은 서버에 대해 더 많은 라운드 트립이 요구될 수 있습니다.
외래 키의 경우는 어떻게 될까요?
외래 키와 같은 제약 조건의 개념은 없으므로 데이터베이스는 문서의 문서 간 관계를 확인하지 않습니다. 이러한 링크는 사실상 "약한" 링크입니다. 항목이 참조하는 데이터가 실제로 존재하는지 확인하려면 애플리케이션에서 또는 Azure Cosmos DB에서 서버 쪽 트리거 또는 저장 프로시저를 사용하여 이 단계를 수행해야 합니다.
참조하는 경우
일반적으로 정규화된 데이터 모델은 다음과 같은 경우에 사용합니다.
- 일대다 관계를 나타내는 경우
- 다대다 관계를 나타내는 경우
- 관련 데이터가 자주 변경되는 경우
- 참조 데이터가 제한이 없는 경우
참고
일반적으로 정규화에서는 쓰기 성능이 더 뛰어납니다.
관계는 어디에 배치해야 할까요?
관계의 성장은 참조를 어떤 항목에 저장할지 결정하는 데 도움이 됩니다.
발행자와 책을 모델링하는 JSON을 관찰하는 경우.
Publisher document:
{
"id": "mspress",
"name": "Microsoft Press",
"books": [ 1, 2, 3, ..., 100, ..., 1000]
}
Book documents:
{"id": "1", "name": "Azure Cosmos DB 101" }
{"id": "2", "name": "Azure Cosmos DB for RDBMS Users" }
{"id": "3", "name": "Taking over the world one JSON doc at a time" }
...
{"id": "100", "name": "Learn about Azure Cosmos DB" }
...
{"id": "1000", "name": "Deep Dive into Azure Cosmos DB" }
게시자당 도서 수가 적고 성장이 제한적이라면 게시자 항목 내부에 도서 참조를 저장하는 것이 유용할 수 있습니다. 하지만 발행자당 책 수가 제한적이지 않으면, 이 데이터 모델의 경우 발행자 문서 예제와 같이 배열이 변하기 쉽고 계속 증가할 수 있습니다.
구조를 바꾸면 동일한 데이터를 나타내는 모델이 생성되지만 대규모 변경 가능한 컬렉션은 피할 수 있습니다.
Publisher document:
{
"id": "mspress",
"name": "Microsoft Press"
}
Book documents:
{"id": "1","name": "Azure Cosmos DB 101", "pub-id": "mspress"}
{"id": "2","name": "Azure Cosmos DB for RDBMS Users", "pub-id": "mspress"}
{"id": "3","name": "Taking over the world one JSON doc at a time", "pub-id": "mspress"}
...
{"id": "100","name": "Learn about Azure Cosmos DB", "pub-id": "mspress"}
...
{"id": "1000","name": "Deep Dive into Azure Cosmos DB", "pub-id": "mspress"}
이 예에서 게시자 문서에는 더 이상 무제한 컬렉션이 포함되지 않습니다. 대신, 각 책 문서에는 게시자에 대한 참조가 포함되어 있습니다.
다대다 관계는 어떻게 모델링하나요?
관계형 데이터베이스에서 다대다 관계는 종종 조인 테이블로 모델링됩니다. 이러한 관계는 다른 테이블의 레코드를 조인합니다.
여기에서도 문서를 사용해서 동일한 방식을 따르고 다음과 비슷하게 보이는 데이터 모델을 만들고 싶을 수도 있습니다.
Author documents:
{"id": "a1", "name": "Thomas Andersen" }
{"id": "a2", "name": "William Wakefield" }
Book documents:
{"id": "b1", "name": "Azure Cosmos DB 101" }
{"id": "b2", "name": "Azure Cosmos DB for RDBMS Users" }
{"id": "b3", "name": "Taking over the world one JSON doc at a time" }
{"id": "b4", "name": "Learn about Azure Cosmos DB" }
{"id": "b5", "name": "Deep Dive into Azure Cosmos DB" }
Joining documents:
{"authorId": "a1", "bookId": "b1" }
{"authorId": "a2", "bookId": "b1" }
{"authorId": "a1", "bookId": "b2" }
{"authorId": "a1", "bookId": "b3" }
이 방법은 효과적이지만, 작성자와 그들의 책을 로드하거나 책과 작성자를 로드하려면 항상 최소 두 개의 추가 데이터베이스 쿼리가 필요합니다. 조인 항목에 대해 쿼리를 한 번 수행하고 조인되는 실제 항목을 인출하기 위해 또 다른 쿼리를 수행해야 합니다.
이 조인이 두 데이터 조각만 함께 붙이는 경우 완전히 삭제하지 않는 이유는 무엇일까요? 아래 예제를 고려해 보세요.
Author documents:
{"id": "a1", "name": "Thomas Andersen", "books": ["b1", "b2", "b3"]}
{"id": "a2", "name": "William Wakefield", "books": ["b1", "b4"]}
Book documents:
{"id": "b1", "name": "Azure Cosmos DB 101", "authors": ["a1", "a2"]}
{"id": "b2", "name": "Azure Cosmos DB for RDBMS Users", "authors": ["a1"]}
{"id": "b3", "name": "Learn about Azure Cosmos DB", "authors": ["a1"]}
{"id": "b4", "name": "Deep Dive into Azure Cosmos DB", "authors": ["a2"]}
이 모델을 사용하면 작성자가 쓴 문서를 살펴보면서 어떤 책을 썼는지 쉽게 알 수 있습니다. 책 문서를 확인하면 어떤 작가가 책을 썼는지 볼 수도 있습니다. 별도의 조인 테이블을 사용하거나 추가 쿼리를 만들 필요가 없습니다. 이 모델을 사용하면 애플리케이션에서 필요한 데이터를 더 빠르고 간편하게 가져올 수 있습니다.
하이브리드 데이터 모델
데이터의 포함(또는 비정규화)과 참조(또는 정규화)를 살펴보았습니다. 각 방식에는 이점이 있지만 그에 따른 상충도 있습니다.
항상 둘 중 하나만 선택해야 하는 것은 아닙니다. 조금 변화를 주는 것도 주저하지 마세요.
애플리케이션의 특정 사용 패턴과 워크로드에 따라 포함된 데이터와 참조된 데이터를 혼합하는 것이 합리적일 수 있습니다. 이러한 방식을 사용하면 애플리케이션 논리를 간소화하고, 서버 왕복을 줄이고, 좋은 성능을 유지할 수 있습니다.
다음과 같은 JSON을 고려해보세요.
Author documents:
{
"id": "a1",
"firstName": "Thomas",
"lastName": "Andersen",
"countOfBooks": 3,
"books": ["b1", "b2", "b3"],
"images": [
{"thumbnail": "https://....png"}
{"profile": "https://....png"}
{"large": "https://....png"}
]
},
{
"id": "a2",
"firstName": "William",
"lastName": "Wakefield",
"countOfBooks": 1,
"books": ["b1"],
"images": [
{"thumbnail": "https://....png"}
]
}
Book documents:
{
"id": "b1",
"name": "Azure Cosmos DB 101",
"authors": [
{"id": "a1", "name": "Thomas Andersen", "thumbnailUrl": "https://....png"},
{"id": "a2", "name": "William Wakefield", "thumbnailUrl": "https://....png"}
]
},
{
"id": "b2",
"name": "Azure Cosmos DB for RDBMS Users",
"authors": [
{"id": "a1", "name": "Thomas Andersen", "thumbnailUrl": "https://....png"},
]
}
여기에서는 주로 포함된 모델을 따랐으며, 다른 엔터티의 데이터가 최상위 문서에 포함되지만 다른 데이터는 참조됩니다.
책 문서를 보면 저자 배열을 볼 때 몇 가지 흥미로운 필드를 발견할 수 있습니다. 저자 문서를 다시 참조하는 데 사용하는 필드인 id 필드가 있습니다. 이는 정규화된 모델의 표준 사례이지만 name 및 thumbnailUrl도 있습니다. 애플리케이션에서 "링크"를 사용하여 해당 작성자 항목에서 필요한 추가 정보만 id 검색하도록 할 수 있습니다. 그러나 애플리케이션은 모든 책과 함께 저자의 이름과 썸네일 그림을 표시하므로 작성자의 일부 데이터를 비정규화하면 목록의 책당 서버 왕복 횟수가 줄어듭니다.
작성자의 이름이 변경되거나 사진이 업데이트되면 해당 작성자가 게시한 모든 책을 업데이트해야 합니다. 그러나 이 애플리케이션의 경우 작성자가 이름을 거의 변경하지 않는다고 가정하면 이러한 손상안은 수락 가능한 디자인 결정입니다.
이 예에서는 읽기 작업 중에 비용이 많이 드는 처리를 절약하기 위해 미리 계산된 집계 값이 있습니다. 이 예에서 작성자 항목에 포함된 일부 데이터는 런타임에 계산되는 데이터입니다. 새 책이 게시될 때마다, 책 항목이 생성됩니다. 그리고 countOfBooks 필드가 특정 저자에 대해 존재하는 책 문서 번호를 기준으로 계산된 값으로 설정됩니다. 이러한 최적화는 읽기를 최적화하기 위해 읽기 계산을 수행할 수 있는 읽기에 집중된 시스템에서 효과적일 수 있습니다.
모델에 사전 계산된 필드를 포함할 수 있는 기능은 Azure Cosmos DB에서 다중 문서 트랜잭션이 지원되기 때문에 가능합니다. 많은 NoSQL 저장소는 문서 간 트랜잭션을 수행할 수 없으므로 이러한 제한으로 인해 디자인상 "항상 모든 것을 포함"하도록 결정하는 것이 좋습니다. Azure Cosmos DB에서는 서버 쪽 트리거 또는 저장 프로시저를 사용해서 ACID 트랜잭션 내에서 책을 삽입하고 저자를 업데이트할 수 있습니다. 이제 데이터의 일관성을 유지하기 위해 모든 것을 하나의 항목에 포함할 필요가 없습니다.
다양한 항목 종류 구분
어떤 경우에는 동일한 컬렉션에 서로 다른 항목 종류를 혼합하려고 할 수도 있습니다. 이러한 디자인 선택은 일반적으로 여러 개의 관련 문서를 동일한 파티션에 두려는 경우에 해당합니다. 예를 들어 동일한 컬렉션에 책과 책 리뷰를 모두 배치하고 bookId로 분할할 수 있습니다. 이런 상황에서는 일반적으로 문서에 필드를 추가하여 문서의 형식을 구별하고 싶어합니다.
Book documents:
{
"id": "b1",
"name": "Azure Cosmos DB 101",
"bookId": "b1",
"type": "book"
}
Review documents:
{
"id": "r1",
"content": "This book is awesome",
"bookId": "b1",
"type": "review"
}
{
"id": "r2",
"content": "Best book ever!",
"bookId": "b1",
"type": "review"
}
Microsoft Fabric 및 Azure Cosmos DB 미러링을 위한 데이터 모델링
Azure Cosmos DB 미러링(Azure Cosmos DB 미러링 )은 Azure Cosmos DB의 운영 데이터에 대해 거의 실시간으로 분석을 실행할 수 있는 클라우드 네이티브 HTAP(하이브리드 트랜잭션 및 분석 처리) 기능입니다. 패브릭 미러링이 Azure Cosmos DB와 Microsoft Fabric의 OneLake 간에 원활한 통합을 만듭니다.
이 통합을 통해 대규모 데이터 집합에서 빠르고 저렴한 쿼리를 실행할 수 있습니다. 데이터를 복사하거나 트랜잭션 워크로드에 영향을 주는 것에 대해 걱정할 필요가 없습니다. 컨테이너에 대해 미러링을 켜면 데이터에 대한 모든 변경 내용이 OneLake에 거의 즉시 복사됩니다. 변경 피드를 설정하거나 ETL(추출, 변환, 로드) 작업을 실행할 필요가 없습니다. 이 시스템은 두 저장소를 동기화된 상태로 유지합니다.
Azure Cosmos DB 미러링을 사용하면 이제 Microsoft Fabric에서 Azure Cosmos DB 컨테이너에 직접 연결할 수 있으며, SQL 엔드포인트를 통해 T-SQL 쿼리를 사용하여 요청 단위 비용 없이 데이터에 액세스하거나 OneLake에서 직접 Spark에 액세스할 수 있습니다.
자동 스키마 유추
Azure Cosmos DB 트랜잭션 저장소는 행 지향 반구조화된 데이터이며 Microsoft Fabric의 OneLake는 열 형식과 구조화된 형식을 사용합니다. 이 변환은 고객을 위해 자동으로 수행됩니다. 변환 프로세스에는 최대 중첩 수준 수, 최대 속성 수, 지원되지 않는 데이터 형식 등의 제한이 있습니다.
참고
분석 저장소 컨텍스트에서는 다음 구조를 속성으로 간주합니다.
- JSON "요소" 또는 "
:으로 구분된 문자열-값 쌍" -
{및}로 구분된 JSON 개체 -
[및]로 구분된 JSON 배열
다음 기술을 사용하면 스키마 유추 변환의 영향을 최소화하고 분석 기능을 최대화할 수 있습니다.
표준화
Microsoft Fabric을 사용하면 T-SQL 또는 Spark SQL을 사용하여 컨테이너를 조인할 수 있으므로 정규화는 관련성이 떨어집니다. 정규화에 대해 예상되는 이점은 다음과 같습니다.
- 더 작은 데이터 공간.
- 더 작은 트랜잭션
- 더 적은 문서당 속성 수
- 중첩 수준이 더 적은 데이터 구조
데이터의 속성과 수준이 적을수록 분석 쿼리가 더 빨라집니다. 또한 데이터의 모든 부분이 OneLake에 포함되어 있는지 확인하는 데 도움이 됩니다. OneLake에 표시되는 수준 및 속성 수에는 제한이 있습니다.
정규화의 또 다른 중요한 요소는 OneLake가 최대 1,000개의 열이 있는 결과 집합을 지원하고 중첩된 열을 노출하는 것도 해당 제한에 해당한다는 점입니다. 즉, 패브릭의 SQL 엔드포인트는 1,000개의 속성으로 제한됩니다.
하지만 비정규화가 Azure Cosmos DB의 중요한 데이터 모델링 기술이므로 어떻게 해야 할까요? 대답은 트랜잭션 및 분석 워크로드에 적합한 균형을 찾아야 한다는 것입니다.
파티션 키
Azure Cosmos DB PK(파티션 키)는 Microsoft Fabric에서 사용되지 않습니다. 이러한 격리로 인해 데이터 수집 및 지점 읽기에 중점을 두고 트랜잭션 데이터에 대한 PK를 선택할 수 있으며 파티션 간 쿼리는 Microsoft Fabric에서 수행할 수 있습니다. 한 가지 예를 살펴보겠습니다.
가상의 글로벌 IoT 시나리오에서 device id는 모든 디바이스가 비슷한 양의 데이터를 생성하기 때문에 핫 파티션 문제를 방지하는 좋은 파티션 키 역할을 합니다. 그러나 "어제의 모든 데이터" 또는 "도시당 합계"와 같이 둘 이상의 디바이스의 데이터를 분석하려는 경우 파티션 간 쿼리이므로 문제가 있을 수 있습니다. 이러한 쿼리는 요청 단위에서 처리량의 일부를 사용하여 실행하므로 트랜잭션 성능이 저하될 수 있습니다. 그러나 Microsoft Fabric을 사용하면 요청 단위 비용 없이 이러한 분석 쿼리를 실행할 수 있습니다. OneLake의 델타 형식은 분석 쿼리에 최적화되어 있습니다.
데이터 형식 및 속성 이름
자동 스키마 유추 규칙 문서에는 지원되는 데이터 형식이 나와 있습니다. Microsoft Fabric 런타임은 지원되는 데이터 형식을 다르게 처리할 수 있지만 지원되지 않는 데이터 형식은 분석 저장소의 표현을 차단합니다. 한 가지 예로, ISO 8601 UTC 표준을 따르는 DateTime 문자열을 사용할 때 Microsoft Fabric의 Spark 풀은 이러한 열을 string로 나타내고, SQL 서버리스는 이러한 열을 varchar(8000)로 나타냅니다.
데이터 평면화
Azure Cosmos DB 데이터의 최상위에 있는 모든 속성은 분석 저장소의 열이 됩니다. 중첩된 개체 또는 배열 내의 속성은 OneLake에 JSON으로 저장됩니다. 중첩 구조는 데이터를 평면화하기 위해 Spark 또는 SQL 런타임에서 추가 처리를 요구합니다. 이렇게 하면 매우 많은 양의 데이터를 처리할 때 컴퓨팅 비용 및 대기 시간이 추가됩니다. 이렇게 하는 것이 간단한 경우 데이터에 대해 플랫 데이터 모델을 사용합니다. 최소한 데이터 모델에서 과도한 데이터 중첩을 방지합니다.
항목은 OneLake에 두 개의 열, id 및 contactDetails, 만 있습니다. 다른 모든 데이터, email, 및 phone는 SQL 또는 Spark 함수를 통해 추가 처리가 필요합니다.
{
"id": "1",
"contactDetails": [
{"email": "thomas@andersen.com"},
{"phone": "+1 555 555-5555"}
]
}
평면화는 이러한 요구 사항을 제거합니다. 여기서는 idemailphone 추가 처리 없이 열로 직접 액세스할 수 있습니다.
{
"id": "1",
"email": "thomas@andersen.com",
"phone": "+1 555 555-5555"
}
데이터 계층화
Microsoft Fabric을 사용하면 다음과 같은 관점에서 비용을 절감할 수 있습니다.
- 트랜잭션 데이터베이스에서 실행되는 쿼리 수가 줄어듭니다.
- 데이터 수집 및 데이터 요소 읽기에 최적화된 PK는 데이터 공간, 핫 파티션 시나리오 및 파티션 분할을 줄입니다.
- 사용자 환경에서 ETL 작업이 실행되지 않으므로 해당 작업에 대한 요청 단위를 할당할 필요가 없습니다.
제어된 중복성
이 기술은 데이터 모델이 이미 존재하고 변경할 수 없는 상황에 대한 훌륭한 대안입니다. 또는 데이터가 너무 복잡하여 중첩 수준이 너무 많거나 속성이 너무 많은 경우 이 시나리오의 경우 Azure Cosmos DB 변경 피드 를 사용하여 데이터를 다른 컨테이너에 복제하고, 필요한 변환을 적용한 다음, 분석을 위해 해당 컨테이너에 대한 미러링을 Microsoft Fabric에 구성할 수 있습니다. 한 가지 예를 살펴보겠습니다.
시나리오
컨테이너 CustomersOrdersAndItems 는 청구 주소, 배달 주소, 배달 방법, 배달 상태, 항목 가격 등 고객 및 항목 세부 정보를 포함한 온라인 주문을 저장하는 데 사용됩니다. 처음 1,000개의 속성만 표시되고 주요 정보가 포함되지 않으므로 Fabric에서 분석이 불가능합니다. 컨테이너에는 페타바이트의 데이터가 있으므로 애플리케이션을 변경하고 데이터를 리모델링할 수 없습니다.
문제의 또 다른 측면은 데이터 볼륨이 크다는 것입니다. 수십억 개의 행이 분석 부서에서 지속적으로 사용되므로 tttl을 오래된 데이터 삭제에 사용할 수 없습니다. 분석 요구 사항으로 인해 트랜잭션 데이터베이스에서 전체 데이터 기록을 유지 관리하면 RU/s가 지속적으로 증가하여 비용에 영향을 줍니다. 트랜잭션 및 분석 워크로드는 동시에 동일한 리소스를 얻기 위해 경쟁합니다.
무엇을 할 수 있나요?
변경 피드가 있는 솔루션
- 솔루션은 변경 피드를 사용하여 세 개의
CustomersOrdersItems새 컨테이너를 채우는 것입니다. 변경 피드를 사용하면 데이터를 정규화 및 평면화하고 데이터 모델에서 불필요한 정보를 제거할 수 있습니다. - 컨테이너
CustomersOrdersAndItems의 TTL(수명)이 이제 6개월 동안만 데이터를 보관하도록 설정되어 Azure Cosmos DB에서 GB당 최소 하나의 요청 단위가 있으므로 요청 단위 사용량을 또 다른 방식으로 줄일 수 있습니다. 데이터가 적으면 요청 단위도 적습니다.
핵심 내용
이 문서에서 가장 중요한 점은 스키마 없는 시나리오에서 데이터 모델링을 하는 것이 그 어느 때보다 중요하다는 것입니다.
데이터를 화면에 표현하는 방법이 하나만 있지 않은 것처럼 데이터를 모델링하는 방법도 하나만 있는 것이 아닙니다. 애플리케이션을 이해하고 애플리케이션이 데이터를 생성, 소비, 처리하는 방식을 이해해야 합니다. 여기에 제시된 지침을 적용하면 애플리케이션의 즉각적인 요구 사항을 해결하는 모델을 만들 수 있습니다. 애플리케이션이 변경되면 스키마 없는 데이터베이스의 유연성을 활용하여 데이터 모델을 쉽게 적응하고 발전시킬 수 있습니다.