Python 2 App Engine NDB 클라이언트 라이브러리 개요

Google Datastore NDB 클라이언트 라이브러리를 사용하면 App Engine Python 앱을 Datastore에 연결할 수 있습니다. NDB 클라이언트 라이브러리는 다음과 같은 Datastore 기능을 추가하여 이전 DB Datastore 라이브러리에서 빌드됩니다.

  • StructuredProperty 클래스 - 항목이 중첩 구조를 가질 수 있도록 허용합니다.
  • 통합 자동 캐싱 - 일반적으로 컨텍스트 내 캐시 및 Memcache를 통해 빠르면서 저렴한 읽기를 제공합니다.
  • 동기식 API 외에도 동시 작업을 수행할 수 있도록 비동기식 API를 지원합니다.

이 페이지에서는 App Engine NDB 클라이언트 라이브러리를 소개하고 간략하게 설명합니다. Python 3를 지원하는 Cloud NDB로 마이그레이션하는 방법은 Cloud NDB로 마이그레이션을 참조하세요.

항목, 키, 속성 정의

Datastore는 항목이라는 데이터 객체를 저장합니다. 항목에는 지원되는 여러 데이터 유형 중 하나에 대해 이름을 지정한 값인 속성이 하나 이상 있습니다. 예를 들어 속성은 문자열, 정수이거나 다른 항목에 대한 참조일 수 있습니다.

각 항목은 애플리케이션의 데이터 저장소 내에서 고유한 식별자인 로 식별됩니다. 키에는 다른 키인 상위 요소가 있을 수 있습니다. 이 상위 요소는 자체의 상위 요소를 가질 수 있습니다. 이 상위 요소 '체인'의 맨 위에는 루트라고 하는 상위 요소가 없는 키가 있습니다.

항목 그룹에서 루트 항목과 하위 항목 간의 관계 표시

키의 루트가 동일한 항목은 항목 그룹 또는 그룹을 형성합니다. 항목이 다른 그룹에 있는 경우 해당 항목을 변경하면 '순서가 잘못'되어 문제가 발생한 것처럼 보일 수 있습니다. 이는 항목이 애플리케이션의 의미 체계와 관련이 없는 경우에는 문제가 되지 않습니다. 하지만 일부 항목의 변경사항이 일관성을 유지해야 하는 경우 애플리케이션은 항목을 만들 때 해당 항목을 동일한 그룹에 포함시켜야 합니다.

다음 항목 관계 다이어그램과 코드 샘플은 Guestbook에 각각 contentdate 속성이 있는 여러 Greetings를 포함시키는 방법을 보여줍니다.

포함된 코드 샘플을 통해 생성된 항목 관계 표시

이 관계는 아래 코드 샘플에서 구현됩니다.

import cgi
import textwrap
import urllib

from google.appengine.ext import ndb

import webapp2

class Greeting(ndb.Model):
    """Models an individual Guestbook entry with content and date."""
    content = ndb.StringProperty()
    date = ndb.DateTimeProperty(auto_now_add=True)

    @classmethod
    def query_book(cls, ancestor_key):
        return cls.query(ancestor=ancestor_key).order(-cls.date)

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.out.write('<html><body>')
        guestbook_name = self.request.get('guestbook_name')
        ancestor_key = ndb.Key("Book", guestbook_name or "*notitle*")
        greetings = Greeting.query_book(ancestor_key).fetch(20)

        greeting_blockquotes = []
        for greeting in greetings:
            greeting_blockquotes.append(
                '<blockquote>%s</blockquote>' % cgi.escape(greeting.content))

        self.response.out.write(textwrap.dedent("""\
            <html>
              <body>
                {blockquotes}
                <form action="/sign?{sign}" method="post">
                  <div>
                    <textarea name="content" rows="3" cols="60">
                    </textarea>
                  </div>
                  <div>
                    <input type="submit" value="Sign Guestbook">
                  </div>
                </form>
                <hr>
                <form>
                  Guestbook name:
                    <input value="{guestbook_name}" name="guestbook_name">
                    <input type="submit" value="switch">
                </form>
              </body>
            </html>""").format(
                blockquotes='\n'.join(greeting_blockquotes),
                sign=urllib.urlencode({'guestbook_name': guestbook_name}),
                guestbook_name=cgi.escape(guestbook_name)))

class SubmitForm(webapp2.RequestHandler):
    def post(self):
        # We set the parent key on each 'Greeting' to ensure each guestbook's
        # greetings are in the same entity group.
        guestbook_name = self.request.get('guestbook_name')
        greeting = Greeting(parent=ndb.Key("Book",
                                           guestbook_name or "*notitle*"),
                            content=self.request.get('content'))
        greeting.put()
        self.redirect('/?' + urllib.urlencode(
            {'guestbook_name': guestbook_name}))

app = webapp2.WSGIApplication([
    ('/', MainPage),
    ('/sign', SubmitForm)
])

데이터 저장에 모델 사용

모델은 속성의 유형 및 구성을 포함하여 항목의 유형을 설명하는 클래스이며, SQL의 테이블과 거의 유사합니다. 모델의 클래스 생성자를 호출하여 항목을 만든 다음 put() 메서드를 호출하여 저장할 수 있습니다.

이 샘플 코드는 모델 클래스 Greeting을 정의합니다. 각 Greeting 항목에는 인사말의 텍스트 콘텐츠 및 인사말을 만든 날짜의 두 가지 속성이 있습니다.

class Greeting(ndb.Model):
    """Models an individual Guestbook entry with content and date."""
    content = ndb.StringProperty()
    date = ndb.DateTimeProperty(auto_now_add=True)
class SubmitForm(webapp2.RequestHandler):
    def post(self):
        # We set the parent key on each 'Greeting' to ensure each guestbook's
        # greetings are in the same entity group.
        guestbook_name = self.request.get('guestbook_name')
        greeting = Greeting(parent=ndb.Key("Book",
                                           guestbook_name or "*notitle*"),
                            content=self.request.get('content'))
        greeting.put()

새 인사말을 만들고 저장하기 위해 애플리케이션은 새로운 Greeting 객체를 만들고 put() 메서드를 호출합니다.

방명록의 인사말이 '잘못된 순서'로 표시되지 않도록 하기 위해 애플리케이션은 새로운 Greeting을 만들 때 상위 키를 설정합니다. 따라서 새 인사말은 동일한 방명록의 다른 인사말과 동일한 항목 그룹에 속하게 됩니다. 애플리케이션은 쿼리할 때 이 사실을 고려하여 상위 쿼리를 사용합니다.

쿼리 및 색인

애플리케이션은 쿼리를 수행하여 특정 필터와 일치하는 항목을 찾을 수 있습니다.

    @classmethod
    def query_book(cls, ancestor_key):
        return cls.query(ancestor=ancestor_key).order(-cls.date)

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.out.write('<html><body>')
        guestbook_name = self.request.get('guestbook_name')
        ancestor_key = ndb.Key("Book", guestbook_name or "*notitle*")
        greetings = Greeting.query_book(ancestor_key).fetch(20)

일반적인 NDB 쿼리는 항목을 종류별로 필터링합니다. 이 예시에서는 query_bookGreeting 항목을 반환하는 쿼리를 생성합니다. 쿼리는 항목 속성 값 및 키에 대한 필터를 지정할 수도 있습니다. 이 예에서와 같이 쿼리가 상위를 지정하여 일부 상위에 '속하는' 항목만 찾을 수 있습니다. 쿼리는 정렬 순서를 지정할 수 있습니다. 특정 항목에 필터 및 정렬 순서의 각 속성에 대한 값이 하나(null일 수 있음) 이상 있고 속성 값이 모든 필터 기준을 충족하면 해당 항목이 결과로 반환됩니다.

각 쿼리는 쿼리 결과가 원하는 순서로 포함된 테이블인 색인을 사용합니다. 기본 데이터 저장소는 간단한 색인(속성 하나만 사용하는 색인)을 자동으로 유지관리합니다.

이는 구성 파일 index.yaml에 복잡한 색인을 정의합니다. 개발 웹 서버는 아직 구성된 색인이 없는 쿼리를 발견하면 자동으로 이 파일에 제안 항목을 추가합니다.

애플리케이션을 업로드하기 전에 이 파일을 수정하여 색인을 수동으로 미세 조정할 수 있습니다. gcloud app deploy index.yaml을 실행하여 애플리케이션 업로드와 별도로 색인을 업데이트할 수 있습니다. 데이터 저장소에 항목이 많으면 이를 위해 새로운 색인을 생성하는 데 시간이 오래 걸립니다. 이 경우 새 인덱스를 사용하는 코드를 업로드하기 전에 색인 정의를 업데이트하는 것이 좋습니다. 관리 콘솔에서 색인 생성이 완료된 시점을 알 수 있습니다.

이 색인 메커니즘은 다양한 쿼리를 지원하고 대부분의 애플리케이션에 적합합니다. 그러나 다른 데이터베이스 기술에 일반적으로 사용되는 몇 가지 쿼리 종류는 지원하지 않습니다. 특히 조인 쿼리는 지원되지 않습니다.

NDB 쓰기 이해: 커밋, 캐시 무효화, 적용

NDB는 다음 단계를 따라 데이터를 씁니다.

  • 커밋 단계에서는 기본 데이터 저장소 서비스가 변경사항을 기록합니다.
  • NDB는 영향을 받는 항목의 캐시를 무효화합니다. 따라서 향후 읽기는 캐시에서 오래된 값을 읽는 대신 기본 Datastore에서 읽고 캐시합니다.
  • 마지막으로 약 몇 초 후에 기본 Datastore가 변경사항을 적용합니다. 이 단계를 통해 변경사항이 전역 쿼리 및 최종 일관성 읽기에 표시됩니다.

데이터를 쓰는 NDB 함수(예시: put())는 캐시 무효화 후에 반환됩니다. 적용 단계는 비동기식으로 발생합니다.

커밋 단계 중에 오류가 발생하면 자동으로 재시도됩니다. 하지만 오류가 계속되면 애플리케이션에서 예외를 수신합니다. 커밋 단계가 성공하더라도 적용 단계가 실패하면 적용 단계는 다음 중 하나가 발생할 때 완료로 롤포워드됩니다.

  • Datastore를 주기적으로 '정리'하여 완료되지 않은 커밋 작업을 확인하고 적용합니다.
  • 영향을 받은 항목 그룹에서 다음 쓰기, 트랜잭션 또는 강력한 일관성 읽기가 수행되면 아직 적용되지 않은 변경사항이 적용된 후 읽기, 쓰기 또는 트랜잭션이 수행됩니다.

이 동작은 애플리케이션에 데이터가 표시되는 방식과 시기에 영향을 줍니다. 변경사항은 NDB 함수가 반환된 후 약 수백 밀리초 동안 기본 데이터 저장소에 완전히 적용되지 않을 수 있습니다. 변경사항이 적용되는 동안 수행된 비상위 쿼리는 일관성 없는 상태로 표시될 수 있습니다(전체 변경사항이 아닌 일부만 표시).

트랜잭션 및 데이터 캐싱

NDB 클라이언트 라이브러리는 여러 작업을 단일 트랜잭션으로 그룹화할 수 있습니다. 트랜잭션 내 모든 작업이 성공하는 경우에만 트랜잭션이 성공할 수 있습니다. 작업이 하나라도 실패하면 트랜잭션이 자동으로 롤백됩니다. 이는 여러 사용자가 동시에 액세스하여 동일한 데이터를 조작할 수 있는 분산 웹 애플리케이션에 특히 유용합니다.

NDB는 Memcache를 데이터의 '핫스팟'에 대한 캐시 서비스로 사용합니다. 애플리케이션이 일부 항목을 자주 읽으면 NDB는 캐시에서 해당 항목을 신속하게 읽을 수 있습니다.

NDB와 함께 Django 사용

NDB를 Django 웹 프레임워크와 함께 사용하려면 Django settings.py 파일의 MIDDLEWARE_CLASSES 항목에 google.appengine.ext.ndb.django_middleware.NdbDjangoMiddleware를 추가합니다. 일부 다른 미들웨어가 Datastore를 호출할 수 있고 미들웨어가 이 미들웨어 앞에서 호출되면 제대로 처리되지 않으므로 다른 미들웨어 클래스 앞에 삽입하는 것이 좋습니다. Django 미들웨어에 대해 자세히 알아보세요.

다음 단계

다음에 대해 자세히 알아보기