Zapytania i indeksy

Każde zapytanie datastore używa indeksu, tabeli, która zawiera wyniki zapytania w pożądanej formie. Aplikacja GAE definiuje swoje indeksy w pliku konfiguracyjnym index.yaml. Serwer deweloperski automatycznie dodaje indeksy dla wszystkich zapytań jakie zostaną wykonane w czasie jego użytkowania.

Mechanizm zapytań oparty o indeksy obsługuje większość pospolitych zapytań, lecz nie obsługuje niektórych zapytań znanych z powszechnie używanych baz relacyjnych.

Zapytania

Zapytanie pobiera z datastore encje, które spełniają określone kryteria. Zapytanie określa typ encji, zero lub więcej warunków bazując na wartościach właściwości (filtry) i zero lub więcej kolejności sortowań. Po wykonaniu zapytania pobierane są wszystkie encje danego typu spełniające kryteria i posortowane zgodnie z opisem.

Zapytanie może także zwrócić klucze encji, a nie same encje.

Operatorem filtrowania może być jedno z poniższych:

  • < mniejsze od
  • <= mniejsze równe od
  • = równe
  • > większe od
  • >= większe równe
  • != nie równe
  • IN równe dowolnemu elementowi z listy

Operator != wykonuje dwa zapytania. Jedno, gdzie wszystkie pozostałe filtry są nałożone, a filtr != zamieniony jest operatorem "mniejszy od" i drugie, gdzie operator != zastąpiony jest filtrem większy od. Wynik jest połączeniem tych zapytań, odpowiednio posortowany. Zapytanie może mieć tylko jeden operator !=.

Operator IN także wykonuje wiele zapytań, dla każdego elementu dostępnego w podanej liście, gdzie IN zastępowanie jest operatorem równości dla danego elementu. Wyniki są łączone w kolejności elementów na liście.

Pojedyncze zapytanie zawierające operator != oraz IN ograniczone jest do 30 podzapytań.

Indeksy

Datastore utrzymuje indeks dla każdego zapytania jakie aplikacja może wykonać. Gdy aplikacja modyfikuje encje, datastore modyfikuje stosowne indeksy. Gdy aplikacja wykonuje zapytanie - datastore pobiera wyniki prosto z indeksu.

Aplikacja posiada indeks dla każdej kombinacji typu, właściwości użytych w filtrach, operatora i kolejności sortowania. Zapytania różniące się wartościami użytymi w filtrach używają tego samego indeksu.

Datastore wykonuje zapytanie według poniższych etapów:

  • Datastore identyfikuje indeks przypisany do zapytania
  • Datastore zaczyna skanować indeks na pierwszej encji spełniającej warunki zapytania
  • Datastore skanuje indeks zwracając encje aż znajdzie encję nie spełniającą warunków filtrów, dojdzie do końca indeksu, lub gdy uzbiera maksymalną ilość encji dozwoloną w zapytaniu

Tabela indeksu zawiera kolumną dla każdej właściwości użytej w filtrach lub w sortowaniu. Wiersze są sortowane według kolejności:

  • Wartości właściwości użyte w operatorach równości
  • Wartości właściwości użyte w operatorach nierówności
  • Wartości właściwości użyte w sortowaniu

Filtry w zapytaniach nie pozwalają bezpośrednio na dopasowywanie tylko części wartości stringach, lecz można to obejść stosując takie rozwiązanie:

db.GqlQuery("SELECT * FROM MyModel WHERE prop >= :1 AND prop < :2", "abc", u"abc" + u"�")
Powyższe zapytanie pobierze encje MyModel, gdzie wartość właściwości "prop" zaczyna się na "abc". String unikodowy u"�" reprezentuje największa wartość dla znaku unikodu, więc pobrane zostaną wszystkie encje z "abc*dowolna reszta stringa*".

Encje nie posiadające właściwości podanej w zapytaniu nigdy nie zostaną zwrócone przez nie.

Indeks zawiera encje posiadające wszystkie właściwości określone w indeksie. Jeżeli encja nie ma jakiejś właściwości określonej w indeksie - nie zostanie w nim umieszczona i nigdy nie zostanie zwrócona przez niego.

Datastore rozróżnia encje, które nie mają danej właściwości, a które mają ją z wartością null (None).

Wartości właściwości, które nie są indeksowane nie można używać w zapytaniach (nie można filtrować, sortować po nich). Dotyczy to właściwości nieindeksowanych, długich tekstów i Blobów.

By zadeklarować właściwość nieindeksowaną należy podać jako argument indexed=False w modelu dla danej właściwości (atrybutu)

class MyModel(db.Model):
  unindexed_string = db.StringProperty(indexed=False)

Wartości właściwości o mieszanym typie są sortowane po typie.

Gdy dwie encje mają właściwość o tej samej nazwie, lecz wartości różnią się typem to indeks właściwości sortuje encje najpierw po wartości typu, a następnie po kolejności stosownej do typu. Przykładowo liczba przedstawiona jako int będzie przed liczbą przedstawioną jako łańcuch.

Należy uwzględnić że także liczby zmiennoprzecinkowe są sortowane oddzielnie od liczb całkowitych i int 38 będzie przez float 37.5.

Definiowanie indeksów w konfiguracji

App Engine buduje domyślnie indeksy dla kilku prostych zapytań. Dla pozostałych aplikacja musi podać indeksy w pliku konfiguracyjnym index.yaml. Wykonanie zapytania, dla którego nie ma właściwego indeksu nie powiedzie się.

GAE zapewnia domyślne indeksy dla poniższych typów zapytań:

  • Zapytania używający tylko filtrów równości i przodka
  • Zapytania używające filtrów nierówności (jednej właściwości)
  • Zapytania z jednym sortowaniem po właściwości
Dla innych zapytań należy podać indeksy w konfiguracji, a dotyczy to m.in.:
  • Zapytania z wieloma sortowaniami
  • Zapytania z sortowaniem malejącym po kluczach
  • Zapytania z jednym i więcej filtrów nierówności i jednym lub więcej filtrów równości na innej właściwości
  • Zapytania z filtrami nierówności i filtrami na przodków

Serwer deweloperski GAE wykrywa wykonywane zapytania i jeżeli potrzebują indeksu - dodaje wpisy do pliku konfiguracyjnego. Jeżeli więc na serwerze deweloperskim przetestowane zostaną wszystkie zapytania wykonywane przez aplikację to index.yaml będzie zawierał wszystkie potrzebne indeksy. Jeżeli takie testowanie nie będzie możliwe to należy ręcznie dodać brakujące indeksy przed załadowaniem na serwery GAE.

index.yaml definiuje każdy indeks, w skład którego wchodzi typ i właściwości encji potrzebne w zapytaniu do sortowania i filtrowania, a także zaznaczenie użycia przodków w zapytaniu (użycie Query.ancestor() lub GQL ANCESTOR IS). Właściwości wymienia się w kolejności: właściwości użyte w filtrach równości lub IN, następnie te użyte w filtrach nierówności, sortowania i kolejności sortowania. Oto przykładowe zapytanie:

SELECT * FROM Person WHERE last_name = "Smith"
                       AND height < 72
                     ORDER BY height DESC
Indeks dla tego zapytania wyglądałby tak:
- kind: Person
  properties:
  - name: last_name
  - name: height
    direction: desc

Gdy encja jest tworzona lub aktualizowana to także odpowiednie indeksy zostaną zaktualizowane. Ilość indeksów, pod które podpada encja wpływa na czas potrzebny do stworzenia lub zaktualizowania encji.

Zapytania po kluczach

Klucze encji mogą być wykorzystane do specjalnych filtrów i sortowania. Klucz encji dostępny jest podobnie jak właściwość, pod atrybutem __key__. Datastore używa w zapytaniach pełnej wartości klucza wliczając ścieżkę do rodzica, typ i unikatowy ID/nazwę encji.

Zapytanie może zamiast encji zwrócić tylko ich klucze. Dla Query wystarczy przekazać keys_only=True, a w GQL użyć SELECT __key__.

Zapytania zwracające tylko klucze zużywają mniej czasu procesora niż zwracające encje, jako że klucze są w indeksach i nie trzeba pobierać samych encji.

Klucze można wykorzystać do łatwego i efektywnego pobierania grup wyników (porcjowania) pobierając określoną liczbę encji o kluczu większym od klucza z ostatniej grupy pobranych encji. Oto przykład:

class MainHandler(webapp.RequestHandler):
  def get(self):
    query = Entity.gql('ORDER BY __key__')

    # Ostatni klucz przekazuje przez parametr GET
    last_key_str = self.request.get('last')
    if last_key_str:
      last_key = db.Key(last_key_str)
      query = Entity.gql('WHERE __key__ > :1 ORDER BY __key__', last_key)

    # Dla grup po 20 encji pobieramy 21
    # jeżeli istnieje 21 element to można wygenerować link do kolejnej strony/porcji encji
    entities = query.fetch(21)
    new_last_key_str = None
    if len(entities) == 21:
      new_last_key_str = str(entities[19].key())

    # Zwrócenie danych i new_last_key_str
    # Aplikacja używałaby linków typuhttp://...?last=new_last_key_str by przejść do kolejnej porcji
    # ...

Klucze sortowane są najpierw po ścieżce rodzica, następnie po rodzaju i na koniec po ID/nazwie. Typy i nazwy kluczy są łańcuchami i sortowane są według wartości bajtowej.

Zapytania używające kluczy także muszą mieć indeksy. W przypadku zapytania z operatorem równości na klucz i innymi filtrami po właściwościach indeks wygląda nieco inaczej niż dla zapytania używającego tylko właściwości. Stosowny wpis doda serwer deweloperski.

Ograniczenia zapytań

Natura zapytań opartych o indeksy nakłada pewne ograniczenia na możliwości zapytań. Sortowanie czy filtrowanie po właściwości wymaga żeby ta właściwość istniała. Filtr lub sortowanie po właściwości także wymusza istnienie wartości dla tej właściwości w encji. Encje bez wartości dla użytej w zapytaniu właściwości nie będą uwzględniane przy tworzeniu indeksu.

Filtry nierówności (<, <=, >=, >, !=) można stosować tylko na jednej właściwości.

Można stosować wiele operatorów równości w zapytaniu.

Jeżeli zapytanie ma filtr nierówności i jedno lub więcej sortowań, to zapytanie musi zawierać sortowanie po właściwości użytej w filtrze nierówności i określenie kolejności sortowania musi pojawić się przed pozostałymi sortowaniami.

Ze względu na sposób indeksowania właściwości z wieloma wartościami ich sortowanie jest nietypowe:

  • Jeżeli encje sortowane są przez właściwość o wielu wartościach malejąco, to używana jest do sortowania najmniejsza wartość
  • Jeżeli encje sortowane są przez właściwość o wielu wartościach rosnąco, to używana jest do sortowania największa wartość
  • Pozostałe wartości, ani ich ilość nie wpływa na sortowanie
  • W przypadku równorzędnych encji przy sortowaniu używany jest ich klucz do ustalenia kolejności
W efekcie np. [1, 9] zawsze będzie przed [4, 5, 6, 7] w sortowaniu malejącym i rosnącym.

W przypadku operatora równości i sortowania po właściwości o wielu wartościach - sortowanie jest pomijane.

Istnieje limit indeksów jakie jedna encja może posiadać. Zazwyczaj limit ten nie zostanie przekroczony, lecz w szczególnych przypadkach, np. encjach z wieloma właściwościami używanymi w filtrach może pojawić się ten problem. Także każda wartość właściwości o wielu wartościach przetrzymywana w indeksie jest oddzielnie, co przy kilku takich wartościach drastycznie zwiększa rozmiar indeksu ("eksplodujące indeksy").

Nadmiarowe indeksy można usunąć z index.yaml, a następnie z App Engine za pomocą polecenie appcfg.py vacuum_indexes. Po zmianach w liście indeksów należy zaktualizować je także na GAE za pomocą appcfg.py update_indexes.

Na podstawie Queries and Indexes.
RkBlog

Google App Engine (GAE), 4 August 2009

Comment article
Comment article RkBlog main page Search RSS Contact