Data Engineering

데이터 중심 애플리케이션 설계 2장

나미-IT 2022. 2. 21. 17:58

1. 데이터 모델

- 다양한 유형의 데이터 모델이 있고 각 데이터 모델은 사용 방법에 대한 가정이 있다. 

  즉 어떤 동작은 쉽고 어떤 연산은 빠르고 다른 연산은 느리고, 어떤 데이터 변환은 자연스럽고 어떤 데이터 변환은 부자연스럽다.

- 하나의 데이터 모델을 완전히 익히는 것도 어려운 일이다. 그러나 데이터 모델은 그 위에서 소프트웨어가 할 수 있는 일과 할 수 없는 일에 많은 영향을 주므로 애플리케이션에 적합한 데이터 모델을 선택하는 작업이 상당히 중요하다.

- 관계형 데이터 모델, 문서 모델, 그래프 기반 데이터 모델(속성 그래프, 트리플 저장소 모델)을 2장에서 다룬다. 

 

관계형 모델(SQL)

 - 관계(relation=sql에서 table)로 구성되고 각 관계는 순서없는 튜플(tuple, sql = row)의 모음

 

비관계형 데이터 모델(NoSQL)

- 임피던스 불일치(impedence mismatch) 해결을 위한 NoSQL 출현

  * 임피던스 불일치: 애플리케이션 코드와 데이터베이스 모델 객체(테이블,로우,컬럼)사이에 전환 계층이 필요한 경우

- NoSQL 모델을 쓰면 불필요한 계층 변환 작업이 사라짐

 

 1 문서 모델

   - 애플리케이션이 주로 일대다 관계(트리 구조 데이터)거나 레코드간 관계가 없는 경우 적합

   - 데이터가 문서 자체에 포함돼 있으면서 하나의 문서와 다른 문서 간 관계가 거의 없는 경우  

 

 2 그래프형 데이터 모델

   - 다대다 관계가 매우 일반적일 경우 적합

 

 

어떤 모델을 사용하는 것이 적합할까?

 - 예시: 관계형 스키마에서 이력서의 표현

  방법 1: 전통적인 SQL 모델을 사용, 

            정규화된 테이블을 만들기(상단 그림)

 

  방법 2: XML 데이터 지원이 추가된 SQL 데이터타입 사용,

            단일 로우에 다중 값을 저장하고 문서 내 질의와 색인이 가능해짐 

 

  방법 3: 직업,학력,연락처 정보를 json이나 xml 문서로 부호화하여 데이터베이스 텍스트 칼럼에 저장하고, 애플리케이션이 구조와 내용을 해석하게 함

            일반적으로 부호화된 칼럼의 값을 질의하는 데 데이터베이스를 사용할 수 없음

 

  방법 4: 문서 데이터베이스 사용(지역성 UP)

 

{
     "user_id": 251,
     "first_name": "Bill",
     "last_name": "Gates",
     "summary": "Co-chair of the Bill & Melinda Gates... Active blogger.",
     "region_id": "us:91",
     "industry_id": 131,
     "photo_url": "/p/7/000/253/05b/308dd6e.jpg",
     "positions": [
         {"job_title": "Co-chair", "organization": "Bill & Melinda Gates Foundation"},
         {"job_title": "Co-founder, Chairman", "organization": "Microsoft"}
     ],
     "education": [
         {"school_name": "Harvard University", "start": 1973, "end": 1975},
         {"school_name": "Lakeside School, Seattle", "start": null, "end": null}
     ],
     "contact_info": {
         "blog": "http://thegatesnotes.com",
         "twitter": "http://twitter.com/BillGates"
     }
}

            관계형 데이터베이스와 달리 조인 필요 X 질의 하나로 충분 

            

문서 데이터베이스

 - 스키마리스(Schemaless)?

   정확히는 schema-on-read (반대말 schema-on-write)         

 

스키마리스는 언제 강점을 가지는가? 

 - 예시: 하나의 필드에 사용자의 전체 이름 저장 --> 성과 이름을 분리하여 저장하도록 변경

 - schema on read의 경우: 간단함

if (user && user.name && !user.first_name) {
     // Documents written before Dec 8, 2013 don't have first_name
     user.first_name = user.name.split(" ")[0];
}

 - schema on write(rdbms)의 경우: migration 필요, 느리고 중단시간을 요구하여 좋지 않음

ALTER TABLE users ADD COLUMN first_name text;
UPDATE users SET first_name = split_part(name, ' ', 1); -- PostgreSQL
UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL

            

2. 질의 언어

 

명령형 언어

  • 보통의 프로그래밍 언어
  • 특정 순서로 특정 연산을 수행하도록 컴퓨터에 지시함. (방법)

선언형 언어

  • SQL, (CSS, XSL : 문서의 스타일을 지정하기 위 한 선언형 언어 )
  • 알고자 하는 데이터의 패턴, 즉 결과가 충족해야 하는 조건과 데이터를 어떻게 변환(예를 들어 정렬, 그룹화, 집계) 할지를 지정 (목적)
  • 어떤 색인과 어떤 조인 함수를 사용할지, 질의의 다양한 부분을 어떤 순서로 실행할지(방법)를 결정하는 것은 데이터베이스 시스템의 질의 최적화기가 할 일이다.
  • 장점
    • 일반적으로 명령형 API보다 더 간결하고 쉽게 작업할 수 있음
    • 데이터베이스 엔진의 상세 구현이 숨겨져 있어 질의를 변경하지 않고도 데이터베이스 시스템의 성능을 향상시킬 수 있음 

명령형 언어 vs 선언형 언어

 - 예시: 동물 목록에서 상어만 반환하기

 - 명령형 언어 (대부분의 프로그래밍 언어가 명령형 언어임)

function getSharks() {
    var sharks = [];
    for (var i = 0; i < animals.length; i++) {
        if (animals[i].family === "Sharks") {
            sharks.push(animals[i]);
        }
    }
    return sharks;
}

 - 선언형 언어

SELECT * FROM animals WHERE family = 'Sharks';

 

선언형, 명령형 질의

 - 선언형: DB, Web Browser에서 Good

 - 명령형: 문서형 DB에서 Good (절대적인것은 아님)

 

 - ex. CSS, XSL

 - ex. 다음 웹 문서에서

<ul>
     <li class="selected">
         <p>Sharks</p>
         <ul>
             <li>Great White Shark</li>
             <li>Tiger Shark</li>
             <li>Hammerhead Shark</li>
         </ul>
     </li>
     <li>
         <p>Whales</p>
         <ul>
             <li>Blue Whale</li>
             <li>Humpback Whale</li>
             <li>Fin Whale</li>
         </ul>
     </li>
</ul>

 - 선언형 질의 사용시(CSS) : 간단

li.selected > p {
 background-color: blue;
}

 - 명령형 질의 사용시 : 복잡

var liElements = document.getElementsByTagName("li");
for (var i = 0; i < liElements.length; i++) {
  if (liElements[i].className === "selected") {
    var children = liElements[i].childNodes;
    for (var j = 0; j < children.length; j++) {
      var child = children[j];
      if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "P") {
        child.setAttribute("style", "background-color: blue");
      }
    }
  }
}

 

맵리듀스 질의

* postgre sql : 선언형

SELECT date_trunc('month', observation_timestamp) AS observation_month,
              sum(num_animals) AS total_animals
FROM observations
WHERE family = 'Sharks'
GROUP BY observation_month;

 

 

* MongoDB 맵리듀스 기능

db.observations.mapReduce(
    function map() {
      var year = this.observationTimestamp.getFullYear();
      var month = this.observationTimestamp.getMonth() + 1;
      emit(year + "-" + month, this.numAnimals);
    },
    function reduce(key, values) {
      return Array.sum(values);
    },
    {
      query: { family: "Sharks" },
      out: "monthlySharkReport"
    }
);

* 데이터 예시(문서 2개)

{

    observationTimestamp: Date.parse("Mon, 25 Dec 1995 12:34:56 GMT"),

    family: "Sharks",

    species: "Carcharodon carcharias",

    numAnimals: 3

}

{

    observationTimestamp: Date.parse("Tue, 12 Dec 1995 16:17:18 GMT"),

    family: "Sharks",

    species: "Carcharias taurus",

    numAnimals: 4

}

 

* 맵리듀스 질의 결과

 emit("1995-12", 3)

 emit("1995-12", 4)

 => reduce("1995-12", [3,4])

 

 => return 7 (monthlySharkReport)

 

* 몽고 DB에서 지원하는 집계파이프라인 (선언형 질의)

db.observations.aggregate([
    { $match: { family: "Sharks" } },
    { $group: {
        _id: {
            year: { $year: "$observationTimestamp" }, 
            month: { $month: "$observationTimestamp" }
    },
        totalAnimals: { $sum: "$numAnimals" }
    } }
]);

 -> 분산환경이라고 꼭 맵리듀스를 사용하는 건 아님

 -> map, reduce 함수 선정이 어려워서 몽고DB에서 집계 파이프라인 제공하고 있음. (질의 최적화기가 질의 성능을 높임)

 

3. 그래프형 데이터 모델 

속성 그래프

 

사이퍼 질의 언어 : 속성 그래프에 적합

데이터 생성

CREATE
    (NAmerica:Location {name:'North America', type:'continent'}),
    (USA:Location {name:'United States', type:'country' }),
    (Idaho:Location {name:'Idaho', type:'state' }),
    (Lucy:Person {name:'Lucy' }),
    (Idaho) -[:WITHIN]-> (USA) -[:WITHIN]-> (NAmerica),
    (Lucy) -[:BORN_IN]-> (Idaho)

질의: 미국에서 유럽으로 이민 온 사람의 이름을 질의 

MATCH
    (person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (us:Location {name:'United States'}),
    (person) -[:LIVES_IN]-> () -[:WITHIN*0..]-> (eu:Location {name:'Europe'})
RETURN person.name

관계형 스키마를 사용해 속성 그래프 표현하기

CREATE TABLE vertices (
    vertex_id integer PRIMARY KEY,
    properties json
);
CREATE TABLE edges (
    edge_id integer PRIMARY KEY,
    tail_vertex integer REFERENCES vertices (vertex_id),
    head_vertex integer REFERENCES vertices (vertex_id),
    label text,
    properties json
);
CREATE INDEX edges_tails ON edges (tail_vertex);
CREATE INDEX edges_heads ON edges (head_vertex);

SQL로 속성 그래프 질의 : with recursive - 복잡도 UP

WITH RECURSIVE
    -- in_usa is the set of vertex IDs of all locations within the United States
    in_usa(vertex_id) AS (
        SELECT vertex_id FROM vertices WHERE properties->>'name' = 'United States'
        UNION
        SELECT edges.tail_vertex FROM edges
        JOIN in_usa ON edges.head_vertex = in_usa.vertex_id
        WHERE edges.label = 'within'
        ), 
    -- in_europe is the set of vertex IDs of all locations within Europe
    in_europe(vertex_id) AS (
        SELECT vertex_id FROM vertices WHERE properties->>'name' = 'Europe'
        UNION
        SELECT edges.tail_vertex FROM edges
        JOIN in_europe ON edges.head_vertex = in_europe.vertex_id
        WHERE edges.label = 'within'
    ),
    -- born_in_usa is the set of vertex IDs of all people born in the US
    born_in_usa(vertex_id) AS (
        SELECT edges.tail_vertex FROM edges
        JOIN in_usa ON edges.head_vertex = in_usa.vertex_id
        WHERE edges.label = 'born_in'
    ),
    -- lives_in_europe is the set of vertex IDs of all people living in Europe
    lives_in_europe(vertex_id) AS (
        SELECT edges.tail_vertex FROM edges
        JOIN in_europe ON edges.head_vertex = in_europe.vertex_id
        WHERE edges.label = 'lives_in'
    )
SELECT vertices.properties->>'name'
FROM vertices
-- join to find those people who were both born in the US *and* live in Europe
JOIN born_in_usa ON vertices.vertex_id = born_in_usa.vertex_id
JOIN lives_in_europe ON vertices.vertex_id = lives_in_europe.vertex_id;

 

트리플 저장소 모델

(주어, 서술어, 목적어) 형식으로 저장함

- 터틀 형식으로 데이터 표현시

@prefix : <urn:example:>.
_:lucy a :Person; :name "Lucy"; :bornIn _:idaho.
_:idaho a :Location; :name "Idaho"; :type "state"; :within _:usa.
_:usa a :Location; :name "United States"; :type "country"; :within _:namerica.
_:namerica a :Location; :name "North America"; :type "continent".

스파클 질의 언어

 - RDF(Resource Description Framework. 자원 기술 프레임워크) 데이터 모델을 사용한 트리플 저장소 질의 언어 

 - 사이퍼와 구조가 매우 유사함