Nice programing

데이터 비정규 화는 마이크로 서비스 패턴에서 어떻게 작동합니까?

nicepro 2020. 11. 2. 19:37
반응형

데이터 비정규 화는 마이크로 서비스 패턴에서 어떻게 작동합니까?


방금 마이크로 서비스 및 PaaS 아키텍처 에 대한 기사를 읽었습니다 . 그 기사에서 약 1/3 정도 아래에있는 저자는 ( Denormalize like Crazy에서 ) 다음 과 같이 말합니다 .

데이터베이스 스키마를 리팩터링하고 모든 것을 비정규 화하여 데이터를 완전히 분리하고 분할 할 수 있습니다. 즉, 여러 마이크로 서비스를 제공하는 기본 테이블을 사용하지 마십시오. 여러 마이크로 서비스에 걸쳐있는 기본 테이블을 공유해서는 안되며 데이터도 공유해서는 안됩니다. 대신 여러 서비스가 동일한 데이터에 액세스해야하는 경우 서비스 API (예 : 게시 된 REST 또는 메시지 서비스 인터페이스)를 통해 공유해야합니다.

이것은 이론적으로는 훌륭하게 들리지만 실제로는 극복해야 할 몇 가지 심각한 장애물이 있습니다. 가장 큰 이유는 종종 데이터베이스가 밀접하게 결합되어 있고 모든 테이블이 적어도 하나의 다른 테이블과 일부 외래 키 관계를 가지고 있다는 것입니다. 이로 인해 데이터베이스를 n 개의 마이크로 서비스로 제어되는 n 개의 하위 데이터베이스로 분할하는 것이 불가능할 수 있습니다 .

그래서 저는 묻습니다. 전적으로 관련 테이블로 구성된 데이터베이스를 감안할 때이를 더 작은 조각 (테이블 그룹)으로 비정규 화하여 조각이 별도의 마이크로 서비스에 의해 제어 될 수 있도록하는 방법은 무엇입니까?

예를 들어, 다음과 같은 (다소 작지만 모범적 인) 데이터베이스가 있습니다.

[users] table
=============
user_id
user_first_name
user_last_name
user_email

[products] table
================
product_id
product_name
product_description
product_unit_price

[orders] table
==============
order_id
order_datetime
user_id

[products_x_orders] table (for line items in the order)
=======================================================
products_x_orders_id
product_id
order_id
quantity_ordered

내 디자인을 비판하는 데 너무 많은 시간을 소비하지 마십시오. 요점은이 데이터베이스를 3 개의 마이크로 서비스로 분할하는 것이 논리적으로 합리적이라는 것입니다.

  1. UserService-시스템에서 CRUDding 사용자를 위해; 궁극적으로 [users]테이블을 관리해야 합니다.
  2. ProductService-시스템의 CRUDding 제품 궁극적으로 [products]테이블을 관리해야 합니다.
  3. OrderService-시스템의 CRUDding 주문 궁극적으로 [orders][products_x_orders]테이블을 관리해야합니다.

그러나 이러한 모든 테이블에는 서로 외래 키 관계가 있습니다. 비정규 화하고 단일체로 취급하면 의미 론적 의미를 모두 잃게됩니다.

[users] table
=============
user_id
user_first_name
user_last_name
user_email

[products] table
================
product_id
product_name
product_description
product_unit_price

[orders] table
==============
order_id
order_datetime

[products_x_orders] table (for line items in the order)
=======================================================
products_x_orders_id
quantity_ordered

이제 누가 무엇을, 어떤 수량으로, 언제 주문했는지 알 수있는 방법이 없습니다.

그렇다면이 기사는 전형적인 학문적 헐라 발루입니까, 아니면이 비정규 화 접근 방식에 대한 실제 실용성이 있습니까? 그렇다면 어떻게 생겼습니까 (답에 내 예제를 사용하는 데 대한 보너스 포인트)?


이것은 주관적이지만 다음 솔루션이 저와 저의 팀 및 DB 팀에 효과적이었습니다.

  • 애플리케이션 계층에서 마이크로 서비스는 의미 론적 기능으로 분해됩니다.
    • 예를 들어 Contact서비스는 연락처를 CRUD 할 수 있습니다 (연락처에 대한 메타 데이터 : 이름, 전화 번호, 연락처 정보 등).
    • 예를 들어 User서비스는 로그인 자격 증명, 권한 부여 역할 등을 가진 사용자를 CRUD 할 수 있습니다.
    • 예를 들어 Payment서비스는 지불을 CRUD하고 Stripe 등과 같은 타사 PCI 준수 서비스를 통해 내부에서 작동 할 수 있습니다.
  • DB 계층에서는 테이블을 구성 할 수 있지만 devs / DBs / devops 사람들은 테이블 구성을 원합니다.

문제는 계단식 및 서비스 경계에 있습니다. 결제시 사용자가 결제하는 사람을 알아야 할 수 있습니다. 다음과 같이 서비스를 모델링하는 대신 :

interface PaymentService {
    PaymentInfo makePayment(User user, Payment payment);
}

다음과 같이 모델링하십시오.

interface PaymentService {
    PaymentInfo makePayment(Long userId, Payment payment);
}

이 방법 만 다른 microservices에 속하는 개체 참조 하지 객체 참조하여 ID에 의해 특정 서비스의 내부. 이를 통해 DB 테이블은 모든 곳에서 외래 키를 가질 수 있지만 앱 계층에서 "외부"엔티티 (즉, 다른 서비스에있는 엔티티)는 ID를 통해 사용할 수 있습니다. 이렇게하면 개체 캐스 케이 딩이 통제 범위를 벗어나는 것을 방지하고 서비스 경계를 ​​명확하게 정의합니다.

발생하는 문제는 더 많은 네트워크 호출이 필요하다는 것입니다. 예를 들어 각 Payment엔터티에 User참조를 제공하면 한 번의 호출로 특정 결제에 대한 사용자를 얻을 수 있습니다.

User user = paymentService.getUserForPayment(payment);

그러나 여기에서 제안하는 것을 사용하면 두 가지 호출이 필요합니다.

Long userId = paymentService.getPayment(payment).getUserId();
User user = userService.getUserById(userId);

이것은 거래 중단 자일 수 있습니다. 하지만 현명하고 캐싱을 구현하고 각 호출에 대해 50-100ms로 응답하는 잘 설계된 마이크로 서비스를 구현한다면 이러한 추가 네트워크 호출 이 애플리케이션에 지연을 일으키지 않도록 제작 될 수 있다는 데 의심의 여지가 없습니다 .


실제로 대부분의 기사에서 생략 된 마이크로 서비스의 핵심 문제 중 하나입니다. 다행히도 이에 대한 해결책이 있습니다. 토론의 기초로 질문에서 제공 한 테이블을 만들어 봅시다. 여기에 이미지 설명 입력위의 이미지는 모놀리스에서 테이블이 어떻게 보이는지 보여줍니다. 조인이있는 테이블이 거의 없습니다.


이것을 마이크로 서비스로 리팩토링하기 위해 몇 가지 전략을 사용할 수 있습니다.

API 조인

In this strategy foreign keys between microservices are broken and microservice exposes an endpoint which mimics this key. For example: Product microservice will expose findProductById endpoint. Order microservice can use this endpoint instead of join.

여기에 이미지 설명 입력 It has an obvious downside. It is slower.

Read only views

In the second solution you can create copy of the table in the second database. Copy is read only. Each microservice can use mutable operations on its read/write tables. When it comes to read only tables which are copied from other databases they can (obviously) use only reads 여기에 이미지 설명 입력

High performance read

It is possible to achieve high performance read by introducing solutions such as redis/memcached on top of read only view solution. Both sides of join should be copied to flat structure optimized for reading. You can introduce completely new stateless microservice which can be used for reading from this storage. While it seems like a lot of hassle it is worth to note that it will have higher performance than monolithic solution on top of relational database.


There are few possible solutions. Ones which are simplest in implementation have lowest performance. High performance solutions will take few weeks to implement.


I realise this is possibly not a good answer but what the heck. Your question was:

Given a database that consists entirely of related tables, how does one denormalize this into smaller fragments (groups of tables)

WRT the database design I'd say "you can't without removing foreign keys".

That is, people pushing Microservices with the strict no shared DB rule are asking database designers to give up foreign keys (and they are doing that implicitly or explicitly). When they don't explicitly state the loss of FK's it makes you wonder if they actually know and recognise the value of foreign keys (because it is frequently not mentioned at all).

I have seen big systems broken into groups of tables. In these cases there can be either A) no FK's allowed between the groups or B) one special group that holds "core" tables that can be referenced by FK's to tables in other groups.

... but in these systems "groups of tables" is often 50+ tables so not small enough for strict compliance with microservices.

To me the other related issue to consider with the Microservice approach to splitting the DB is the impact this has reporting, the question of how all the data is brought together for reporting and/or loading into a data warehouse.

Somewhat related is also the tendency to ignore built in DB replication features in favor of messaging (and how DB based replication of the core tables / DDD shared kernel) impacts the design.

EDIT: (the cost of JOIN via REST calls)

When we split up the DB as suggested by microservices and remove FK's we not only lose the enforced declarative business rule (of the FK) but we also lose the ability for the DB to perform the join(s) across those boundaries.

In OLTP FK values are generally not "UX Friendly" and we often want to join on them.

In the example if we fetch the last 100 orders we probably don't want to show the customer id values in the UX. Instead we need to make a second call to customer to get their name. However, if we also wanted the order lines we also need to make another call to the products service to show product name, sku etc rather than product id.

In general we can find that when we break up the DB design in this way we need to do a lot of "JOIN via REST" calls. So what is the relative cost of doing this?

Actual Story: Example costs for 'JOIN via REST' vs DB Joins

There are 4 microservices and they involve a lot of "JOIN via REST". A benchmark load for these 4 services comes to ~15 minutes. Those 4 microservices converted into 1 service with 4 modules against a shared DB (that allows joins) executes the same load in ~20 seconds.

This unfortunately is not a direct apples to apples comparison for DB joins vs "JOIN via REST" as in this case we also changed from a NoSQL DB to Postgres.

Is it a surprise that "JOIN via REST" performs relatively poorly when compared to a DB that has a cost based optimiser etc.

To some extent when we break up the DB like this we are also walking away from the 'cost based optimiser' and all that in does with query execution planning for us in favor of writing our own join logic (we are somewhat writing our own relatively unsophisticated query execution plan).


I would see each microservice as an Object, and as like any ORM , you use those objects to pull the data and then create joins within your code and query collections, Microservices should be handled in a similar manner. The difference only here will be each Microservice shall represent one Object at a time than a complete Object Tree. An API layer should consume these services and model the data in a way it has to be presented or stored.

각 트랜잭션에 대해 서비스를 여러 번 호출하면 각 서비스가 별도의 컨테이너에서 실행되고 이러한 모든 호출이 병렬로 실행될 수 있으므로 영향을주지 않습니다.

@ ccit-spence, 교차로 서비스의 접근 방식이 마음에 들었지만 다른 서비스에서 어떻게 설계하고 사용할 수 있습니까? 나는 그것이 다른 서비스에 대한 일종의 의존성을 만들 것이라고 믿습니다.

의견이 있으십니까?

참고 URL : https://stackoverflow.com/questions/27007353/how-does-data-denormalization-work-with-the-microservice-pattern

반응형