본문 바로가기
App 설계

DDIA - 데이터를 위한 질의(query) 언어

by ocwokocw 2021. 10. 31.

- 이 글은 마틴 클레프만의 데이터 중심 애플리케이션 설계를 기반으로 작성되었습니다.

- 데이터를 위한 질의 언어

관계형 모델이 등장했을 때 데이터를 질의하는 새로운 방법이 등장하였다. SQL 은 선언형 질의언어인 반면 IMS와 코다실은 명령형 코드를 사용해 DB 에 질의한다.

 

동물의 종 목록중에서 상어만 반환하는 코드를 작성한다고 가정해보자. 이를 명령형 코드로 작성하면 아래와 같이 작성할 수 있다.

 

public List<Animal> getSharks(List<Animal> animals) {
	
	List<Animal> sharks = new ArrayList<>();
	
	for(Animal animal : animals) {
		
		if("Sharks".equals(animal.getFamily())) {
			sharks.add(animal);
		}
	}
	
	return sharks;
}

 

반면 SQL 로도 작성할 수 있는데 아래처럼 표현하면 된다.

 

SELECT * FROM animals WHERE family = 'Sharks';

 

명령형 언어는 특정 순서로 특정 연산을 '지시' 한다. 여기서 지시라고 함은 실행을 하며, 조건을 평가하고 변수를 갱신하는 작업들이 포함된다.

 

반면 SQL 은 방법에 대한 얘기는 하지 않고 알고자 하는 데이터의 패턴만 선언한다. 결과가 충족해야 하는 조건과 어떻게 변환되어야 하는지(정렬, 그룹화)를 지정한다.

 

선언형 질의는 명령형 질의보다 간결하다. 단순하게 간결함에 의미가 있는것이 아니라 DB 엔진의 상세구현을 은닉하므로 성능을 향상시킬 때 질의 변경을 하지 않고도 이를 수행할 수 있다. 또한 병렬성에서도 차이가 있는데 명령형은 특정 순서로 연산을 지시하므로 다중코어나 다중의 장비에서 병렬 처리가 까다롭다. 하지만 선언형은 알고리즘이 아닌 결과의 패턴만 지정하므로 병렬 실행에 적합하다.


- 웹에서의 선언형 질의

선언형 언어의 대표주자로 SQL 만 자주 언급되다보니 DB 에만 국한되는것으로 오인하는 경우가 있는데 이는 오해이다. CSS 도 문서의 스타일을 지정하기 위한 선언형 언어라고 볼 수 있다.

 

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

<ul>
	<li class='selected'>
		<p>Sharks</p>
		<ul>
			<li>Tiger Shark</li>
			....
		</ul>
	</li>
	<li>
		<p>Whales</p>
		<ul>
			<li>Blue Whale</li>
			....
		</ul>
	</li>
</ul>

 

위 html 은 현재 상어 페이지를 보고있다는 것을 파란색 글씨로 나타내주는 코드이다. 이런 기능을 꼭 HTML 로만 작성해야 하는것은 아니다. 명령형 방식의 javascript 로도 동일 기능을 구현할 수 있다.

 

var liElements = document.getElementsByTagName('li');

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

 

코드를 보면 알겠지만 선언형 보다 이해하기가 좀 난해한면이 있다. 문제는 단순히 이해하기 어렵다는점이 아니다. 우선 selected 클래스가 삭제되어도(사용자가 다른 페이지 click시) 전체 page가 re-load 되지 않는 이상 파란색 style 이 삭제되지 않는다. 또한 신규 API 로 성능을 향상시키고 싶다면 코드를 재작성 해야한다.


- 맵리듀스(MapRecude) 질의

맵리듀스는 많은 컴퓨터에서 대량의 데이터를 처리하기 위한 프로그래밍 모델이라고 할 수 있다. 맵리듀스는 선언형 질의 언어도 아니고 완전한 명령형 질의 API 도 아닌 중간 어디쯤인가에 있다. 질의 로직은 반복적으로 호출하는 조각 코드로 표현한다. 맵리듀스는 함수형 프로그래밍 언어에 있는 map과 reduce 함수를 기반으로 한다.

 

예를 들어 '1 달에 상어를 몇 마리나 관측했는가?' 를 알고 싶어한다고 가정하자. 이를 SQL로 나타내면 다음과 같이 나타낼 수 있다.

 

SELECT 
  observation_month,
  sum(num_animals) as total_animals
FROM 
(
  SELECT
    date_trunc('month', obervation_timestamp) as observation_month,
    num_animals
  FROM observations
  WHERE family = 'Sharks'
)
GROUP BY observation_month

 

이를 몽고 DB의 맵리듀스 기능을 이용하면 아래와 같이 표현할 수 있다.

 

db.observations.mapReduce(
	function map() {
		var yaer = 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'
	}
)

 

필자도 몽고 DB를 사용해본 경험은 없다.  하지만 기초적인 javascript 문법과 Java8 을 이용해봤다면 이해하기 아주 어렵지는 않다.

  • query: {family: 'Sharks'} - 상어종만 거르는 필터
  • map - this 는 문서 객체로 설정된다. 해당 문서객체를 어떻게 변환하는지를 나타내는데 YYYY-MM 의 key 와 관측된 동물의 수 value 를 emit(방출) 한다.
  • reduce - map 이 emit 한 key, value 쌍들이 key 로 그룹화 된다. 같은 key 를 갖는 모든 key, value 쌍은 reduce 함수를 한 번 호출한다.
  • out: 'monthlySharkReport' - 결과를 monthlySharkReport 컬렉션에 기록한다.

몽고 DB에는 약간의 제약사항이 존재하는데 map과 reduce 가 순수함수 여야 한다는것이다. 주어진 인자외에 다른값을 사용해서는 안되며 추가 DB 질의를 수행하면 안된다. 이는 주어진 인자가 같다면 반환결과도 같아야 한다는 함수형 프로그래밍의 사상과 같다. 이 두 함수가 순수함수여야 실행순서가 상관이 없어지고 어디서나 수행가능하며 side effect 가 나지 않고, 이를 만족할 때 병렬처리도 수월하게 할 수 있다는 사상에 기반한다.

 

클러스터 환경에서 분산 실행을 위한 프로그래밍인 모델인 맵리듀스는 저수준의 프로그래밍 모델이다. 이 문장의 역을 사실처럼 잘못해석하면 분산 질의 수행을 꼭 맵리듀스로 구현해야한다고 오해할 수 있는데 이는 사실이 아니다. SQL 같은 고수준의 질의 언어도 맵리듀스 연산의 파이프라인으로 구현할 수 있으며 맵리듀스를 사용하지 않은 분산 SQL 구현도 많다.

 

맵리듀스는 사용할 때 주의해야할 점이 있는데 연계된 js 함수를 신중하게 작성해야 한다는 점이다. 더욱이 선언형 질의 언어는 optimizer 가 성능향상의 기회를 제공하는데 맵리듀스는 그렇지 못하다. 이런 이유로 몽고DB 2.2 에서는 '집계 파이프라인' 이라고 부르는 선언형 질의 언어를 추가했다.

 

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

 

SQL 처럼 영어 문장이 아닌 JSON 기반이라 선언형인지 헷갈릴수는 있지만 표현만 JSON 으로 되어있을뿐 명령 순서나 지시를 언급하고 있지 않기 때문에 선언형 질의 언어라고 볼 수 있다. 중요한점은 문법보다 NoSQL 도 SQL 을 재발견하여 이를 반영했다는점에 있다.

댓글