2011년 8월 30일 화요일

Java development 2.0: NoSQL

http://www.ibm.com/developerworks/kr/library/j-javadev2-8/

Java development 2.0: NoSQL

Bigtable과 Groovy의 Gaelyk을 사용한 스키마 없는 데이터 모델링
Andrew Glover, 대표, Stelligent Incorporated
요약: Bigtable과 CouchDB와 같은 NoSQL 데이터 저장소는 확장성 문제를 광범위하게 해결하기 때문에 Web 2.0 시대에 변방에서 중심으로 이동하고 있습니다. Google과 Facebook은 NoSQL을 받아들인 두 개의 대표적인 사이트이며 NoSQL은 아직 초기 단계에 있습니다. 스키마 없는 데이터 저장소는 기본적으로 전형적인 관계형 데이터베이스와 다르지만 생각보다 활용하기 쉽습니다. 특히 관계형 모델이 아니라 도메인 모델로 시작하는 경우에 더 쉽습니다.
원문 게재일:  2010 년 5 월 11 일 번역 게재일:   2010 년 6 월 08 일
난이도:  중급 영어로:  보기 PDF:  A4 and Letter (154KB | 15 pages)Get Adobe® Reader®
페이지뷰: 8584 회
의견: 0 (의견 추가)

이 시리즈의 정보

처음 Java 기술이 발표된 이후로 Java를 개발하는 과정은 급속도로 변화되었다. 오픈 소스 프레임워크와 신뢰할 수 있는 임대용 전개 인프라 덕택에 Java 애플리케이션을 신속하고 저렴하게 어셈블하고 테스트하고 유지할 수 있게 되었다. 이 시리즈에서 Andrew Glover는 이러한 새로운 Java 개발 패러다임을 가능하게 하는 다양한 기술과 도구를 탐구한다.
관계형 데이터베이스는 30년 넘게 데이터 스토리지 분야를 지배해 왔지만 높아진 스키마 없는(NoSQL) 데이터베이스의 인기는 변화가 진행 중임을 시사하고 있다. RDBMS는 전형적인 클라이언트-서버 아키텍처에서 데이터를 저장하는 데 필요한 매우 견고한 기반을 제공하지만 저렴한 비용으로 쉽게 복수의 노드로 확장하지 못한다. Facebook과 Twitter과 같이 확장성이 높은 웹 애플리케이션의 시대에 이 부분은 매우 유감스러운 단점이다.
관계형 데이터베이스에 대한 초기의 대체 데이터베이스(예: 오브젝트 지향 데이터베이스)는 아주 긴박한 문제를 해결하지 못했기 때문에 Google의 Bigtable과 Amazon의 SimpleDB와 같은 NoSQL 데이터베이스가 높은 확장성을 바라는 웹의 요구에 대한 직접적인 응답으로 생겨났다. 본질적으로 NoSQL은 Web 2.0이 발전함에 따라 웹 애플리케이션 개발자가 더 많이 부딪히게 될 심각한 문제점의 킬러 애플리케이션이 될 수 있다.
Java development 2.0에 대한 이 기사에서 필자는 관계형 데이터 모델에 익숙한 많은 개발자에게 NoSQL에 대한 기본적인 문제인 스키마 없는 데이터 모델링부터 시작한다. 앞으로 알게 되겠지만 관계형 모델이 아니라 도메인 모델로 시작하는 것이 부담없이 시작할 수 있는 비결이다. 필자의 예에서와 같이 Bigtable을 사용하는 경우에는 Google App Engine에 대한 경량 프레임워크 확장인 Gaelyk의 지원을 받을 수도 있다.
NoSQL: 새로운 사고 방식?
개발자들이 비관계형 또는 NoSQL 데이터베이스에 대해 이야기할 때 가장 먼저 제기되는 질문은 이러한 데이터베이스를 사용하려면 사고 방식을 바꿔야 하는지에 대한 것이다. 필자는 이것은 처음에 데이터 모델링에 어떻게 접근하는지에 달렸다고 생각한다. 먼저 데이터베이스 구조를 모델링하여 애플리케이션을 설계(즉, 테이블과 연관된 관계를 먼저 파익함)하는 데 익숙한 경우에는 Bigtable과 같은 스키마 없는 데이터 저장소를 사용하여 데이터 모델링을 수행하려면 사고 방식을 바꿔야 한다. 하지만 도메인 모델에서 시작하여 애플리케이션을 설계하는 경우에는 Bigtable의 스키마 없는 구조가 더 자연스럽게 느껴질 것이다.

확장을 위해 빌드됨

확장성이 큰 웹 애플리케이션의 새로운 문제점과 함께 새로운 솔루션이 제공된다. Facebook은 스토리지 요구에 대해 관계형 데이터베이스에 의존하지 않고 키/값 저장소를 사용한다(반드시 고성능 HashMap이 어야 함). 내부 솔루션(더빙된 Cassandra)은 Twitter와 Digg에서도 사용되며 최근 Apache Software Foundation에 기부되었다. Google은 비관계형 데이터 스토리지를 찾기 위해 폭발적인 증가에 필요한 또다른 웹 엔티티이다(결과는 Bigtable임).
비관계형 데이터 저장소에는 join 테이블 또는 기본 키나 외부 키에 대한 개념도 없다(두 유형의 키는 느슨한 형식으로 존재함). 따라서 관계형 모델링을 NoSQL 데이터베이스에서 데이터 모델링의 기반으로 사용하려고 하면 실패하게 될 것이다. 도메인 모델에서 시작하면 단순해진다. 실제로 필자는 도메인 모델에서 스키마 없는 구조의 유연성이 상당히 높다는 것을 알아냈다.
관계형에서 스키마 없는 데이터 모델로 이동 시 상대적인 복잡도는 접근 방식(관계형에서 시작하는지 아니면 도메인 기반 설계에서 시작하는지 등)에 따라 다르다. CouchDB나 Bigtable과 같은 데이터 저장소로 마이그레이션하면 현재로서는 적어도 Hibernate와 같은 확립된 지속성 플랫폼의 장점을 잃게 된다. 한편으로는 자체적으로 빌드할 수 있는 효과도 있다. 프로세스를 진행하는 동안 스키마 없는 데이터 저장소에 대해 자세히 살펴본다.
엔티티와 관계
스키마 없는 데이터 저장소는 먼저 오브젝트를 사용하여 도메인 모델을 설계하는 유연성을 제공한다(Grails와 같은 새로운 프레임워크에서 자동으로 활용함). 작업이 진행된 후 기본 데이터 저장소에 대한 도메인의 맵핑이 된다. Google App Engine의 경우에는 정말 쉽게 수행된다.
"Java development 2.0: Google App Engine을 위한 Gaelyk"에서 필자는 Google의 기본 데이터 저장소에 대한 작업을 활용하는 Groovy 기반 프레임워크인 Gaelyk을 소개했다. 이 기사의 많은 부분에서는 Google의 Entity 오브젝트 활용에 대해 집중적으로 다루었다. 해당 기사의 다음 예제에서는 Gaelyk에서 오브젝트 엔티티가 어떻게 작동하는지를 보여 준다.

Listing 1. 엔티티에 대한 오브젝트 지속성
def ticket = new Entity("ticket")
ticket.officer = params.officer
ticket.license = params.plate
ticket.issuseDate = offensedate
ticket.location = params.location
ticket.notes = params.notes
ticket.offense = params.offense

오브젝트를 기준으로 한 설계

데이터베이스의 설계보다 오브젝트 모델을 선호하는 패턴이 Grails와 Ruby on Rails와 같은 최신 웹 애플리케이션 프레임워크에 등장하여 오브젝트 모델의 설계를 강조하고 기본 데이터베이스 스키마 작성을 처리하고 있다.
오브젝트 지속성에 대한 이러한 접근 방식은 효과가 있지만 티켓 엔티티를 많이 사용한 경우에는 장황해지기 쉽다(예를 들어, 다양한 서블릿에서 티켓 엔티티를 작성하거나 찾은 경우). 일반적인 서블릿(또는 Groovlet)이 태스크를 처리하도록 하면 부담을 다소 덜 수 있다. 필자가 설명할 좀 더 자연스러운 옵션은 Ticket 오브젝트를 모델링하는 것이다.
레이스로 돌아가기
Gaelyk에 대한 소개에 있는 티켓 예제를 다시 실행하는 대신 필자는 최신 상태를 유지하고 이 기사에서 실행 중인 테마를 사용하고 필자가 다루는 기술에 대해 설명하는 데 필요한 애플리케이션을 빌드한다.
그림 1의 다대다 다이어그램에 표시되는 것과 같이 Race에는 다수의 Runner가 있으며 Runner는 다수의 Race에 속할 수 있다.

그림 1. Race와 Runner
Race와 Runner의 관계를 보여 주는 다대다 다이어그램
필자가 이 관계를 설계하기 위해 관계형 테이블 구조를 사용해야 한다면 최소한 세 개의 테이블(세 번째는 다대다 관계를 링크하는 join 테이블임)이 필요했을 것이다. 필자는 관계형 데이터 모델에 얽매여 있지 않다는 사실에 안도한다. 대신 필자는 Gaelyk(및 Groovy 코드)을 사용하여 이 다대다 관계를 Google App Engine을 위한 Google의 Bigtable 추상화에 맵핑한다. Gaelyk을 사용하면 EntityMap처럼 처리할 수 있다는 사실 때문에 프로세스가 매우 단순해진다.

샤드(shard)를 사용한 스케일링

샤딩(sharding)은 여러 노드에서 테이블 구조를 복제하지만 이러한 노드 사이에서 데이터를 논리적으로 나누는 파티셔닝의 한 양식이다. 예를 들어, 하나의 노드에 미국에 있는 계정과 관련된 모든 데이터가 있고 다른 노드에 유럽에 있는 모든 계정과 관련된 데이터가 있을 수 있다. 노드에 관계가 포함된 경우 (즉, 상호 샤드(shard) 결합) 샤드(shard) 문제가 발생한다. 이러한 문제는 해결하기 쉽지 않으며 많은 경우에 지원을 받지 못한다. (관계형 데이터베이스에 대한 확장성 문제와 샤딩(sharding)과 관련한 Google Max Ross에 대한 필자의 의견은 참고자료의 링크를 참조한다.)
스키마 없는 데이터 저장소의 한 가지 매력은 사전에 알고 있어야 할 내용이 없다는 것이다. 즉, 관계형 데이터베이스 스키마를 사용할 때보다 훨씬 쉽게 변경사항을 수용할 수 있다. (스키마를 변경할 수 없다는 의미가 아니다. 필자는 단지 변경사항을 더 쉽게 수용한다는 것만 말하려고 한다.) 필자는 도메인 오브젝트에 특성을 정의하지 않는다. 필자는 Groovy의 동적 특성(이를 통해 본질적으로 필자는 Google의 Entity 오브젝트에 대해 필자의 도메인 오브젝트 프록시를 작성할 수 있음)에 이러한 정의를 미룬다. 대신 필자는 오브젝트를 찾고 관계를 처리하는 방법을 파악하는 데 시간을 투자한다. 이것은 NoSQL과 스키마 없는 데이터 저장소를 활용하는 다양한 프레임워크에서 아직 빌드하지 않은 것이다.
모델 기본 클래스
필자는 Entity 오브젝트의 인스턴스를 보유하는 기본 클래스를 작성하여 시작할 것이다. 그런 다음 Groovy의 편리한 setProperty 메소드를 통해 해당 Entity 인스턴스에 추가될 동적 특성을 서브클래스가 포함할 수 있도록 할 것이다. 오브젝트에 실제로는 존재하지 않는 특성 setter에 대해 setProperty가 호출된다. (이상하게 보일 수도 있지만 걱정할 것 없다. 실제로 해 보면 이해할 수 있다.)
Listing 2에서는 필자의 예제 애플리케이션에 대한 Model 인스턴스에서의 첫 번째 시도를 보여 준다.

Listing 2. 단순 기본 모델 클래스
package com.b50.nosql

import com.google.appengine.api.datastore.DatastoreServiceFactory
import com.google.appengine.api.datastore.Entity

abstract class Model {

 def entity
 static def datastore = DatastoreServiceFactory.datastoreService

 public Model(){
  super()
 }

 public Model(params){
  this.@entity = new Entity(this.getClass().simpleName)
  params.each{ key, val ->
   this.setProperty key, val
  }
 }

 def getProperty(String name) {
  if(name.equals("id")){
   return entity.key.id
  }else{
   return entity."${name}"
  }
 }

 void setProperty(String name, value) {
  entity."${name}" = value
 }

 def save(){
  this.entity.save()
 }	
}

추상 클래스가 특성의 Map을 가져오는 생성자를 어떻게 정의하는지 유의한다. 필자는 언제나 나중에 생성자를 추가할 수 있으며 곧 추가할 것이다. 이 설정은 양식에서 매개변수가 제출되는 웹 프레임워크에 매우 편리하다. Gaelyk과 Grails는 이러한 매개변수를 params라는 오브젝트에 원활하게 랩핑한다. 생성자는 이 Map을 반복하고 각각의 키/값 쌍에 대해 setProperty 메소드를 호출한다.
setProperty 메소드를 살펴보면 키는 기본 entity의 특성 이름으로 설정되어 있고 해당 값은 entity의 값으로 설정되어 있는 것을 알 수 있다.
Groovy 트릭
앞서 언급했듯이 Groovy의 동적 특성으로 인해 필자는 getset Property 메소드를 통해 존재하지 않는 특성에 대한 메소드 호출을 캡처할 수 있다. 따라서 Listing 2에 있는 Model의 서브클래스는 자체 특성을 정의하지 않아도 된다. 이러한 서브클래스는 특성에 대한 모든 호출을 기본 entity 오브젝트에 단순히 위임한다.
Listing 2에 있는 코드에서는 짚고 넘어갈 만한 Groovy의 다른 몇 가지 고유 작업을 수행한다. 먼저 필자는 특성 앞에 @를 추가하여 특성의 액세서 메소드를 생략할 수 있다. 생성자의 entity 오브젝트 참조에 대해 이를 수행해야 한다. 그렇지 않으면 setProperty 메소드를 호출한다. 이때 setProperty를 호출하면 setProperty 메소드에 있는 entity 변수가 null이 되기 때문에 패턴이 명백하게 파괴된다.
두 번째로 생성자에서의 this.getClass().simpleName 호출은 entity의 "유형"을 설정한다. simpleName 특성은 패키지 접두어가 없는 서브클래스의 이름을 생성한다(simpleNamegetSimpleName에 대한 호출이지만 Groovy는 해당 JavaBeans 형식의 메소드 호출 없이 특성에 액세스할 수 있도록 허용하는 점에 유의).
마지막으로 id 특성(즉, 오브젝트의 키)에 대한 호출이 작성되면 getProperty 메소드는 기본 keyid를 요구한다. Google App Engine에서는 entitieskey 특성이 자동으로 생성된다.
Race 서브클래스
Race 서브클래스를 정의하는 것은 Listing 3에 보여지는 것처럼 쉽다.

Listing 3. Race 서브클래스
package com.b50.nosql

class Race extends Model {
 public Race(params){
  super(params)
 }
}

매개변수 목록(즉, 키/값 쌍이 포함된 Map)으로 서브클래스가 인스턴스화되면 해당 entity가 메모리에 작성된다. 이를 지속시키려면 save 메소드를 호출하기만 하면 된다.

Listing 4. Race 인스턴스를 작성하여 GAE의 데이터 저장소에 저장하기
import com.b50.nosql.Runner

def iparams = [:]
                              
def formatter = new SimpleDateFormat("MM/dd/yyyy")
def rdate = formatter.parse("04/17/2010")
              
iparams["name"] = "Charlottesville Marathon"
iparams["date"] = rdate
iparams["distance"] = 26.2 as double

def race = new Race(iparams)
race.save()

Groovlet인 Listing 4에서는 세 가지 특성(레이스의 이름, 날짜 및 거리)이 포함된 Map(더빙된 iparams)이 작성된다. (Groovy에서는 비어 있는 Map[:]를 통해 작성된다는 것에 유의한다.) Race의 새 인스턴스가 작성되어 save 메소드를 통해 기본 데이터 저장소에 저장된다.
그림 2와 같이 Google App Engine 콘솔을 통해 기본 데이터 저장소를 검사하여 데이터가 실제로 기본 데이터 저장소에 있는지 확인할 수 있다.

그림 2. 새로 작성된 레이스 보기
Google App Engine 콘솔에서 새로 작성된 레이스 보기
파인더 메소드에서 지속된 엔티티를 생성함
Entity를 저장했으므로 이를 검색할 수 있는 기능이 있으면 유용하다. 이에 따라 필자는 "파인더" 메소드를 추가할 수 있다. 여기서는 클래스 메소드(static)를 파인더 메소드로 하며 이름으로 Race를 찾을 수 있도록 할 것이다(즉, name 특성을 기반으로 검색함). 나중에 언제든지 다른 특성을 사용하여 다른 파인더를 추가할 수 있다.
또한 필자는 이름에 all이라는 단어가 없는 파인더는 하나의 인스턴스를 찾도록 지정하는 파인더에 대한 규칙을 채택할 것이다. findAllByName에서와 같이 all이라는 단어가 포함된 파인더는 인스턴스의 Collection 또는 List를 리턴할 수 있다. Listing 5에서는 findByName 파인더를 보여 준다.

Listing 5. 엔티티 이름을 기반으로 검색하는 단순 파인더
static def findByName(name){
 def query = new Query(Race.class.simpleName)
 query.addFilter("name", Query.FilterOperator.EQUAL, name)
 def preparedQuery = this.datastore.prepare(query)
 if(preparedQuery.countEntities() > 1){
  return new Race(preparedQuery.asList(withLimit(1))[0])
 }else{
  return new Race(preparedQuery.asSingleEntity())
 }
}

이 단순 파인더에서는 Google App Engine의 QueryPreparedQuery 유형을 사용하여 이름이 전달된 이름과 정확하게 일치하는 "Race" 유형의 엔티티를 찾는다. 둘 이상의 Race가 이 기준을 충족하면 파인더에서는 페이지 표시 제한 1(withLimit(1))에 의해 지시된 대로 목록의 첫 번째 항목을 리턴한다.
해당 findAllByName은 비슷하지만 Listing 6과 같이 원하는 수량이라는 매개변수가 추가된다.

Listing 6. 이름을 기준으로 모두 찾기
static def findAllByName(name, pagination=10){
 def query = new Query(Race.class.getSimpleName())
 query.addFilter("name", Query.FilterOperator.EQUAL, name)
 def preparedQuery = this.datastore.prepare(query)
 def entities = preparedQuery.asList(withLimit(pagination as int))
 return entities.collect { new Race(it as Entity) }
}

앞서 정의한 파인더와 같이 findAllByName은 이름을 기준으로 Race 인스턴스를 찾지만 모든 Race를 리턴한다. 하지만 Groovy의 collect 메소드는 필자가 Race 인스턴스를 작성하는 해당 루프에 들어갈 수 있도록 한다. Groovy가 어떻게 메소드 매개변수의 기본값도 허용하는지에 유의한다. 두 번째 값을 전달하지 않으면 pagination의 값은 10이 된다.

Listing 7. 작동 중인 파인더
def nrace = Race.findByName("Charlottesville Marathon")
assert nrace.distance == 26.2

def races = Race.findAllByName("Charlottesville Marathon")
assert races.class == ArrayList.class

Listing 7에 있는 파인더는 예상대로 작동한다. findByName은 하나의 인스턴스를 리턴하고 findAllByNameCollection을 리턴한다(둘 이상의 "Charlottesville Marathon"이 있다고 가정함).
Runner 오브젝트도 많이 다르지 않음
이제는 Race의 인스턴스를 쉽게 작성하고 찾을 수 있기 때문에 빠른 Runner 오브젝트를 작성할 준비가 되어 있다. 프로세스는 초기 Race 인스턴스를 작성할 때만큼 쉽다. Listing 8과 같이 Model을 확장하기만 하면 된다.

Listing 8. Runner는 매우 쉬움
package com.b50.nosql

class Runner extends Model{
 public Runner(params){
  super(params)
 }
}

Listing 8을 보면서 필자는 결승선에 거의 다 왔음을 느낀다. 여전히 러너와 레이스 사이에 링크를 작성해야 한다. 물론 러너가 둘 이상의 레이스에 참여하기를 바라기 때문에 다대다 관계로 모델링할 것이다.
스키마 없는 도메인 모델링
Bigtable 위의 Google App Engine 추상화는 오브젝트 지향적이지 않다. 즉, 관계를 있는 그대로 저장할 수는 없지만 키를 공유할 수 있다. 결국 RaceRunner 사이의 관계를 모델링하기 위해 Runner 키 목록을 Race의 각 인스턴스에 저장한다(또한 그 반대로도 저장함).
하지만 발생하는 API가 자연스럽길 바라기 때문에 키 공유 메커니즘에 약간의 논리를 추가할 것이다. Runner 키 목록에 대해 Race를 요구하지 않고 Runner 목록을 원한다. 다행히 이러한 작업은 어렵지 않다.
Listing 9에서는 Race 인스턴스에 두 개의 메소드를 추가했다. Runner 인스턴스가 addRunner 메소드에 전달되면 해당 id가 기본 entityrunners에 있는 idCollection에 추가된다. runners의 기존 collection이 있는 경우에는 새 Runner 인스턴스 키가 추가된다. 그렇지 않으면 새 Collection이 작성되고 Runner의 키(엔티티의 id 특성)가 추가된다.

Listing 9. 러너 추가 및 검색하기
def addRunner(runner){
 if(this.@entity.runners){
  this.@entity.runners << runner.id
 }else{
  this.@entity.runners = [runner.id]
 }
}

def getRunners(){
 return this.@entity.runners.collect {
  new Runner( this.getEntity(Runner.class.simpleName, it) )
 }
}

Listing 9에 있는 getRunners 메소드가 호출되면 Runner 인스턴스의 콜렉션이 id의 기본 콜렉션에서 작성된다. 따라서 Listing 10과 같이 새 메소드(getEntity)가 Model 클래스에 정의된다.

Listing 10. ID로부터 엔티티 작성하기
def getEntity(entityType, id){
 def key = KeyFactory.createKey(entityType, id)			
 return this.@datastore.get(key)
}

getEntity 메소드는 Google의 KeyFactory 클래스를 사용하여 데이터 저장소에서 개별 엔티티를 찾는 데 사용할 수 있는 기본 키를 작성한다.
마지막으로 Listing 11과 같이 엔티티 유형을 승인하는 새 생성자가 정의된다.

Listing 11. 새로 추가된 생성자
public Model(Entity entity){
 this.@entity = entity
}

Listing 9, 1011그림 1의 오브젝트 모델에서 알 수 있듯이 RunnerRace에 추가할 수 있으며 Race에서 Runner 인스턴스 목록을 가져올 수도 있다. Listing 12에서는 등식의 Runner 쪽에 비슷한 연결을 작성한다. Listing 12에서는 Runner 클래스의 새 메소드를 보여 준다.

Listing 12. 러너와 해당 레이스
def addRace(race){
 if(this.@entity.races){
  this.@entity.races << race.id
 }else{
  this.@entity.races = [race.id]
 }
}

def getRaces(){
 return this.@entity.races.collect {
  new Race( this.getEntity(Race.class.simpleName, it) )
 }
}

이 방식으로 스키마 없는 데이터 저장소를 사용하여 두 개의 도메인 오브젝트를 모델링했다.
일부 러너와 함께 레이스 끝내기
이제는 Runner 인스턴스를 작성하여 Race에 추가하기만 하면 된다. 그림 1의 오브젝트 모델이 보여 주는 것과 같이 양방향 관계를 원하는 경우에는 Listing 13과 같이 Race 인스턴스를 Runner에 추가할 수 있다.

Listing 13. 레이스가 있는 러너
def runner = new Runner([fname:"Chris", lname:"Smith", date:34])
runner.save()

race.addRunner(runner)
race.save()

runner.addRace(race)
runner.save()

Runnerrace에 추가하고 Racesave를 호출한 후 그림 3의 스크린샷과 같이 데이터 저장소가 ID 목록으로 업데이트되었다.

그림 3. 레이스에 있는 러너의 새 특성 보기
레이스에 있는 러너의 새 특성 보기
Google App Engine에서 데이터를 자세히 살펴보면 그림 4와 같이 Race 엔티티에는 이제 Runnerlist가 포함되어 있음을 알 수 있다.

그림 4. 새 러너 목록 보기
새 러너 목록 보기
마찬가지로 그림 5와 같이 Race를 새로 작성된 Runner 인스턴스에 추가하기 전에는 특성이 존재하지 않는다.

그림 5. 레이스가 없는 러너
레이스가 없는 러너
RaceRunner에 연관시킨 후 데이터 저장소는 새 race idlist를 추가한다.

그림 6. 레이스를 시작한 러너
레이스를 시작한 러너
스키마 없는 데이터 저장소의 유연성은 새로운 느낌을 준다. 특성은 요구 시 기본 저장소에 자동으로 추가된다. 개발자로서 필자는 스키마를 업데이트하거나 변경하지 않아도 되고 전개도 하지 않아도 된다.
NoSQL에 대한 찬반 논쟁
물론 스키마 없는 데이터 모델링에도 찬반 논쟁이 있다. Back to the Races 애플리케이션의 한 가지 장점은 매우 유연하다는 것이다. Runner에 새 특성(예: SSN)을 추가하려고 하는 경우 많은 작업을 수행하지 않아도 된다. 해당 특성을 생성자의 매개변수에 포함시키면 거기에 해당 매개변수가 존재한다. SSN을 사용하여 작성되지 않은 이전 인스턴스에는 무슨 일이 생기는가? 아무일도 생기지 않는다. null인 필드가 존재하게 된다.

빠른 리더

속도는 NoSQL 대 관계형 논쟁에서 중요한 요소이다. 잠재적으로 수백만 명의 사용자를 위해 데이터를 전달하는 최신 웹 사이트(Facebook의 사용자 수 4억 명을 떠올려 보자)의 경우 관계형 모델은 너무 느릴 뿐만 아니라 비용도 많이 든다. 반면에 NoSQL의 데이터 저장소는 읽기 속도에 있어서는 엄청나게 빠르다.
한편으로는 효율성을 위해 일관성 및 무결성의 저하를 감수했다. 애플리케이션의 현재 데이터 아키텍처는 제한조건을 두지 않는다. 이론적으로는 동일한 오브젝트의 인스턴스를 수에 제한없이 작성할 수 있다. Google App Engine의 키 처리 하에서는 모두 고유 키를 가지지만 그 외는 모두 동일하다. 더욱이 연계 삭제는 존재하지 않기 때문에 동일한 기법을 사용하여 일대다 관계를 모델링한 경우 상위가 제거되면 무효인 하위만 남게 될 수 있다. 물론 자체 무결성 검사를 구현할 수 있다. 하지만 이것이 핵심이다. 다른 모든 사항을 수행한 것과 마찬가지로 이 검사를 자체적으로 해야 한다.
스키마 없는 데이터 저장소를 사용하려면 규율이 필요하다. 다양한 유형의 Races(일부는 이름이 있고 일부는 이름이 없으며 일부는 date 특성이 있고 일부는 race_date 특성이 있음)를 작성하면 필자와 필자의 코드를 활용하는 모든 사용자의 발등을 찍는 것이 된다.
물론 Google App Engine과 함께 JDO 및 JPA를 사용할 수도 있다. 여러 프로젝트에 관계형 모델과 스키마 없는 모델을 둘 다 사용한 경우에는 Gaelyk의 하위 레벨 API가 가장 작업하기 쉽고 유연하다. Gaelyk을 사용하면 Bigtable과 스키마 없는 데이터 저장소에 대해 전반적으로 이해할 수 있는 장점도 있다.
결론
유행은 변하기 때문에 가끔은 이러한 유행을 무시하는 것이 낫다(옷장에 레저용 옷만 가득한 사람이 제공하는 현명한 조언이다). 하지만 NoSQL은 유행이라기 보다는 확장성 높은 웹 애플리케이션 개발의 새로운 기반이다. 하지만 NoSQL 데이터베이스는 RDBMS를 대체하지는 않고 보완한다. 무수히 많은 성공적인 도구와 프레임워크가 관계형 데이터베이스 위에서 활발하게 사용되고 있으며 RDBMS 자체는 인기가 시드는 위험에 처해 있지 않다.
결국 NoSQL 데이터베이스는 오브젝트-관계형 데이터 모델에 대한 적절한 대안으로 존재한다. NoSQL 데이터베이스는 다른 방식을 사용할 수 있으며 매우 뛰어난 구체적인 유스 케이스의 경우에는 다른 방식이 더 낫다는 것을 보여 준다. 스키마 없는 데이터베이스는 빠른 데이터 검색 및 확장성이 필요한 멀티노드 웹 애플리케이션에 가장 적합하다. 개발자가 관계형 지향 관점이 아니라 도메인 지향 관점에서 데이터 모델링에 접근하도록 교육한다는 부수적인 효과도 있다.

참고자료
교육
  • Java development 2.0: 이 developerWorks 시리즈에서 Gaelyk(2009년 12월), Google App Engine(2009년 8월) 및 CouchDB(2008년 11월)를 포함하여 Java 개발 환경을 재정의하는 기술과 도구에 대해 살펴보자.
  • "NoSQL Patterns"(Ricky Ho, Pragmatic Programming Techniques, 2009년 11월): NoSQL 데이터베이스의 개요 및 목록을 확인할 수 있고 NoSQL 데이터 저장소의 일반적인 아키텍처에 대한 자세한 설명을 볼 수 있다.
  • "Saying Yes to NoSQL; Going Steady with Cassandra"(John Quinn, Digg Blogs, 2010년 3월): 엔지니어링에 대한 Digg의 VP에서는 MySQL에서 Cassandra로 전환하는 결정에 대해 설명한다.
  • "Sharding with Max Ross"(JavaWorld 팟캐스트, 2008년 7월): Andrew Glover가 Google의 Max Ross와 함께 나눈 샤딩(sharding) 기법과 Hibernate Shards의 개발에 대한 대화를 들어보자.
  • "Is the Relational Database Doomed?"(Tony Bain, ReadWriteEnterprise, 2009년 2월): 클라우드의 내부와 외부 모두에서 비관계형 데이터베이스가 등장하면서 "광범위한 온디맨드 확장성을 원하는 경우에는 비관계형 데이터베이스가 필요하다."라는 명확한 메시지가 제시되고 있다.
  • Google App Engine for Java: Part 3: 영속성과 관계"(Richard Hightower, developerWorks, 2009년 8월): Rick Hightower가 Google App Engine의 현재 Java 기반 지속성 프레임워크의 단점과 일부 대안에 대해 설명한다.
  • "Amazon Web Services를 사용한 클라우드 컴퓨팅, Part 5: SimpleDB를 통해 클라우드의 데이터세트 처리하기"(Prabhakar Chaganti, developerWorks, 2009년 2월): Amazon SimpleDB의 개념에 대해 알아보고 Amazon SimpleDB와 상호작용하기 위한 오픈 소스 Python 라이브러리인 boto에서 제공하는 일부 기능을 살펴보자.
  • "Bigtable: A Distributed Storage System for Structured Data"(Fay Chang 등, Google, 2006년 11월): Bigtable은 매우 큰 크기(수천 개의 상용 서버에 걸쳐있는 페타바이트 크기의 데이터)까지 범위를 확장하도록 설계된 구조화된 데이터를 관리하는 데 필요한 분산 스토리지 시스템이다.
  • "The Vietnam of Computer Science"(Ted Neward, 2006년 6월): 관계형 모델에 대한 오브젝트 맵핑과 연관된 과제를 해결한다.
  • 기술 서점에서 다양한 기술 주제와 관련된 서적을 살펴보자.
  • developerWorks Java 기술 영역: Java 프로그래밍과 관련된 모든 주제를 다루는 여러 편의 기사를 찾아보자.
제품 및 기술 얻기
  • Gaelyk: Google App Engine을 위한 Groovy의 경량 애플리케이션 개발 프레임워크를 사용해 보자.
토론
필자소개
Andrew Glover 사진Andrew Glover는 Stelligent Incorporated의 사장이다. 회사들이 코드 품질을 일찍 그리고 자주 모니터할 수 있게 하는 효과적인 개발자 테스팅 전략과 지속적 통합 기법으로 소프트웨어 품질 문제를 해결하는 것을 돕고 있다. Andy의 저서 목록은 그의 블로그를 보라.

댓글 없음:

댓글 쓰기