Podstawy teoretyczne datastore

Datastore przechowuje i wykonuje zapytania na obiektach danych określanych jako encje (entity). Każda encja ma jeden lub więcej właściwości (properties) - udostępniające pod określonymi nazwami różne typy danych. Atrybut może być także odniesieniem do innej encji.

Datastore potrafi wykonać wiele operacji w jednej transakcji i przywrócić stan sprzed jej rozpoczęcia jeżeli operacja w transakcji nie powiedzie się.

W odróżnieniu od baz relacyjnych Datastore używa rozproszonej architektury by skalować się przy bardzo dużych zbiorach danych. Aplikacja GAE może zoptymalizować sposób w jaki dane są rozmieszczane opisując relacje pomiędzy obiektami danych, oraz poprzez określanie indeksów dla zapytań.

Baza danych Datastore nie jest bazą relacyjną, co wymusza inne podejść przy projektowaniu struktury danych, tak by baza skalowała się automatycznie przy napływie kolejnych porcji danych.

Modelowanie danych w Pythonie

Encje w Datastore pozbawione są schematu. Dwie encje tego samego typu nie muszą mieć tych samych właściwości, czy też nie muszą używać tych samych typów danych dla tej samej właściwości. Aplikacja musi zadbać o to by dane były zgodne z przyjętym schematem jeżeli jest to wymagane. API dostępne w GAE umożliwia w bardzo prosty sposób określić taki schemat.

W Pythonie model opisuje dany typ encji. Każdy model to klasa Pythona, gdzie atrybuty określają właściwości encji. Encje reprezentowane są przez obiekty klasy modelu. Nowa encja może zostać stworzona wywołując konstruktor klasy i zapisana metodą put().

import datetime
from google.appengine.ext import db
from google.appengine.api import users

# klasa dziedzicząca db.Model - model określający dany typ encji :)
class Employee(db.Model):
	name = db.StringProperty(required=True)
	role = db.StringProperty(required=True, choices=set(["executive", "manager", "producer"]))
	hire_date = db.DateTimeProperty()
	new_hire_training_completed = db.BooleanProperty()
	account = db.UserProperty()

# a tak można dodać nowy wpis (nową encję) do bazy
e = Employee(name='test', role="manager", account=users.get_current_user())
e.hire_date = datetime.datetime.now()
e.put()

API GAE dostarcza dwa interfejsy dostępu do Datastore - zaprezentowany powyżej interfejs obiektowy, jak i interfejs zbliżony do języka SQL, nazwanego GQL (Google Query Language). Oto przykład wyświetlający dane pobrane za pomocą zapytania w szablonie:

class Test(webapp.RequestHandler):
	def get(self):
		data = db.GqlQuery('SELECT * FROM Employee')
		path = os.path.join(os.path.dirname(__file__), 'templates/test.html')
		self.response.out.write(template.render(path, {'data': data}))
<ul>
{% for i in data %}
<li>{{ i.name }}</li>
{% endfor %}
</ul>

Encje i właściwości

Każda encja ma oprócz właściwości unikatowy klucz, który ją identyfikuje. Najprostszy klucz składa się z typu i unikalnego numerycznego ID nadanego przez datastore. Wartość ID może być podana także przez aplikację.

Aplikacja może pobrać encję z bazy używając jej klucza, lub wykonując zapytanie, które dopasuje się do wartości właściwości tej encji (po wartościach). Zapytanie może zwrócić zero lub więcej encji i może zwrócić wynik posortowany po wartościach właściwości. Zapytanie może także ograniczyć ilość zwracanych encji.

Każda właściwość może mieć jedną lub więcej wartości. Właściwość z wieloma wartościami może mieć wartości o różnych typach. Zapytanie dotyczące takiej właściwości sprawdza czy któraś wartość spełnia warunki zapytania.

Zapytania i indeksy

Zapytania datastore operuje na każdej encji danego typu (z danego modelu). Zapytanie określa zero lub więcej filtrów dla wartości właściwości i kluczy encji, oraz zero lub więcej sortowań. Encja zostanie zwrócona jeżeli ma co najmniej jedną wartość dla każdej właściwości wymienionej w filtrach i sortowaniach zapytania, oraz gdy wszystkie kryteria filtrów są spełnione przez wartości tych właściwości.

Każde zapytanie do datastore używa indeksu - tabeli, która zawiera wynik zapytania i żądanej kolejności. Indeksy te określa się w pliku konfiguracyjnym. Serwer deweloperski GAE automatycznie generuje potrzebne indeksy gdy napotka na zapytanie. Gdy aplikacja wykonuje zapytanie, datastore zwróci wynik prosto z przypisanego indeksu.

Transakcje i grupy encji

Każda próba stworzenia, zaktualizowania, czy skasowania encji przeprowadzana jest w transakcji. Zapewnia ona, że każda zmiana encji jest zapisywana w całości, lub w przypadku niepowodzenia żadne zmiany (np. częściowe) nie zostaną wprowadzone. Zapewnia to integralność danych wewnątrz encji.

Można przeprowadzić wiele akcji na encji wewnątrz jednej transakcji używając dostępnego API. Przykładowo w transakcji można zwiększyć licznik jakiejś encji. Wymaga to odczytania encji, obliczenia nowej wartości licznika i zapisania wyniku. Robiąc całość w jednej transakcji mamy pewność że żaden inny proces nie nadpisał tego licznika między pobraniem a zapisem.

Można także dokonywać zmian na grupie encji wewnątrz jednej transakcji. By było to możliwe App Engine musi wiedzieć zawczasu, które encje będą należały do takiej grupy. Musisz zadeklarować w danej encji, iż należy ona do danej grupy. Wszystkie operacje pobierania, tworzenia, aktualizacji czy kasowania wielu encji w jednej transakcji muszą dotyczyć tej samej grupy encji.

Grupy encji definiowane są poprzez hierarchiczną zależność pomiędzy encjami. By stworzyć grupę deklarujesz w jednej encji, iż jest "dzieckiem" innej encji znajdującej się w grupie ("rodzic"). Encja stworzona bez rodzica jest korzeniem drzewa i jeżeli nie ma encji-dzieci - jest grupą sama dla siebie. Każda encja w grupie ma ścieżkę zależności rodzic-dziecko od korzenia do siebie.

Datastore używa algorytmu optymistycznej współbieżności (optimistic concurrency) do obsługi transakcji. Gdy jedna transakcja dokonuje zmian encji w grupie, wszystkie inne próby wprowadzania zmian encji w grupie skończą się od razu niepowodzeniem. Aplikacja może spróbować ponownie przeprowadzić transakcję by zmodyfikować dane po wykonaniu pierwszej transakcji.

Limit Datastore

Każde zapytanie wliczane jest do limitowanej puli zapytań (Datastore API Calls). Dane wysłane do bazy wliczane są do limitu "Data Sent", a dane pobrane do limitu "Data Received". Ilość danych w datastore wliczana jest do limitu "Stored Data". Ilość cykli procesora zużytych na operacje związane z datastore wliczane są do limitów "CPU Time" i "Datastore CPU Time". Dodatkowe ograniczenia to:
  • Maksymalny rozmiar encji: 1MB
  • Maksymalna ilość wartości w indeksie dla encji: 1000 wartości
  • Maksymalna ilość encji w batch put/batch delete: 500 encji
  • Maksymalna ilość encji w batch get: 1000 encji
  • Maksymalny ofset zapytania: 1000
Na podstawie Datastore Python API Overview
RkBlog

Google App Engine (GAE), 30 July 2009

Comment article
Comment article RkBlog main page Search RSS Contact