Amazon SimpleDb przy użyciu Zend Framework

Ostatnio w pracy zajmuję się ciekawym projektem, przy okazji którego, będziemy zbierać pewne dane statystyczne. Danych tych będzie z pewnością bardzo dużo. Wobec tego bazując na swoich poprzednich doświadczeniach, zdecydowaliśmy, że najlepiej będzie przenieść nasze statystyki w chmury. Wybór padł na bardzo popularną i znaną usługę Amazona, a konkretnie – SimpleDb. Jest to nierelacyjny magazyn danych, idealnie nadający się do naszych zastosowań – nieograniczona pojemność i duża szybkość. Jego zaletą również jest to, że w każdym miesiącu dostajemy 25 maszyno-godzin i 1Gb storage’u za free. Można więc go testować w zasadzie za darmo bez ograniczeń. Ile to jest maszyno-godzina mnie nie pytajcie, ale sądzę, że do testów tak czy inaczej wystarczy :). By używać usługi SimpleDb, najłatwiej zaopatrzyć się w jakiś napisany w PHP adapter, ponieważ „czysta” komunikacja z jej API odbywa się za pomocą REST lub SOAP-a, co może być trochę uciążliwe. Mój wybór padł na komponent w ramach Zend Framework – Zend_Service_Amazon_SimpleDb. Niestety nie ma do niego żadnej oficjalnej dokumentacji. Pomyślałem więc, że to dobry temat na wpis.

By zacząć zabawę z SimpleDb, należy przede wszystkim utworzyć sobie konto, przy okazji czego zostanie nam wygenerowana para kluczy – accessKey i secretKey, które potem posłużą do autoryzacji przy łączeniu się z dowolnymi usługami Amazona. Muszę jeszcze nadmienić, że do korzystania z tej chmury potrzebujemy karty kredytowej i bez podania jej numeru nie uda nam się z niej skorzystać. Z innych kwestii bilingowo-finansowych warto jeszcze zwrócić uwagę na pewien szczegół – każdy xml (przynajmniej dla SimpleDb) zawiera pole „box usage”, w którym zwraca nam czas jaki zajęło chmurze wykonanie zapytania. Dzięki temu możemy odliczać sobie ile jeszcze z darmowych 25 maszyno-godzin nam zostało do zabawy ;).

Czas przejść do najbardziej interesującej nas części – kodu. Zanim pokaże jak używać Zend_Service_Amazon_SimpleDb, pragnąłbym ostrzec Was przed jedną rzeczą, mianowicie, z tego jak korzystałem z tej klasy mam wrażenie, że jest ona jeszcze w fazie beta i jest tam sporo błędów implementacyjnych (nie mówiąc już o mało intuicyjnym API).

By skorzystać z klasy Zend_Service_Amazon_SimpleDb należy najpierw utworzyć jej instancję w następujący sposób:


$accessKey = 'SampleAccessKey';
$secretKey = 'SampleSecretKey';

$service = new Zend_Service_Amazon_SimpleDb($accessKey, $secretKey);

Gdzie secretKey i accessKey są odpowiednio kluczami, które dostaliśmy przy zakładaniu konta.

Nim wprowadzimy jakieś dane do naszej bazy należy utworzyć tzw. domenę. Czym jest owa domena ? Domena jest tak-jakby odpowiednikiem tabeli w klasycznej bazie danych, z tą różnicą, że nie ma żadnego schematu, tj. dane które w niej przechowujemy nie muszą (ale powinny) być w jakikolwiek sposób podobne.
By pobrać listę domen należy wykonać następujący kod:

$page = $service->listDomains();
$domains = $page->getData();
var_dump($domains); //array(0 => 'domena1',  1=> 'domena2');

Oczywiście rozpoczynając naszą zabawę z SimpleDb nie mamy jeszcze żadnych domen utworzonych dla naszej instancji bazy. By je utworzyć należy wykonać:

$result = $service->createDomain('test');
var_dump($result); //boolean true

Gdy mamy już utworzoną domenę, możemy zapisywać dane w naszej bazie. By coś zapisać, najpierw należałoby się zapoznać ze strukturą danych jakie przyjmuje SimpleDb. Każdy „wiersz” w tej bazie składa się z identyfikatora (nazwy) oraz atrybutów. Pojedynczy atrybut dla wiersza, może mieć wiele wartości (być tablicą). Nazwę / identyfikator niestety (a może i stety) musimy wygenerować sobie sami. Osobiście moja strategia generacji ID polega na hashowaniu aktualnego czasu z mikrosekundami (microtime(true)). Skoro już wiemy jak wygląda struktura naszych danych, czas by coś wpisać.

$itemName = sha1(microtime(true));
$data = array(
    'foo' => 'bar',
    'baz' => 'boo',
    'gaz' => array(1,2,3,4,5)
);

$attributes = array();
foreach($data as $name => $values){
    $attributes [] = new Zend_Service_Amazon_SimpleDb_Attribute($itemName, $name, $values);
}
$service->putAttributes('test', $itemName, $attributes);

Jedna mała uwaga co do zapisywania pól, które mają więcej niż jedną wartość – jeżeli będzie to tablica asocjacyjna to klucze zostaną utracone. W takim przypadku najlepiej po prostu serializować takie pole (o ile nie będziemy po nim wyszukiwać).

Zapisane dane można wydobywać z bazy na dwa sposoby.
Pierwszy:

$attributes = $service->getAttributes($domainName, $itemName);
$data = array();
foreach($attributes as $name => $attribute){
    $data[$name] = $attribute->getValues();
}

Drugi:

$query = "select * from bannerStats where foo = 'bar'";
$page = $service->select($query);
$result = $page->getData();

$data = array();
foreach($result as $itemName => $attributes){
    $data[$itemName] = array();
    $foreach($attributes as $name => $attribute){
        $data[$itemName][$name] = $attribute->getValues();
    }
}

Nie dajcie się zwieść składnią selecta – nie jest to SQL (ale jest wizualnie do niego podobny). Jest to bardzo prosty język zapytań, który nie zawiera w sobie żadnych narzędzi do przeprowadzania obliczeń na danych.
Jedna uwaga co do pobierania danych via select. Jeżeli ilość danych bądź czas ich przetwarzania przekroczy pewne wartości zapisane w specyfikacji, wynik będzie automatycznie paginowany. Wtedy by pobrać całość należy użyć tzw. tokena:

while (!$page->isLast()) {
            $page = $service->select($query, $page->getToken());
            $result += $page->getData();
        }

Btw. w metodzie Zend_Service_Amazon_SimpleDb_Page::isLast() jest błąd, należy zmienić ją na:

public function isLast()
    {
        return empty($this->token);
    }

To by było na tyle z moich ostatnich doświadczeń na temat klasy Zend_Service_Amazon_SimpleDb. Jak widać spełnia swoje zadanie, jednak ma trochę błędów i jak dla mnie trochę zbyt bardzo eksponuje swoją wewnętrzną implementację – konieczność użycia Zend_Service_Amazon_SimpleDb_Attribute przy manipulacji danymi oraz Zend_Service_Amazon_SimpleDb_Page przy pobieraniu wyniku. Moim zdaniem interfejs klasy mógłby również dla spójności być trochę bardziej upodobniony do Zend_Db_Table.

Jeżeli jesteście zainteresowani składnią języka zapytań SimpleDb bądź innymi szczegółami technicznymi bazy, polecam dość szczegółową dokumentację.

  1. batman pisze:

    Po ostatniej wtopie Amazona radziłbym dobrze zastanowić się na wyborem chmury lub zabezpieczyć się na ewentualną awarię – http://don.blogs.smugmug.com/2011/04/24/how-smugmug-survived-the-amazonpocalypse/

    P.S.
    Sprawdź jak działa Zend_Cloud. Wprawdzie jest to nakładka na używane przez Ciebie klasy, niemniej daje możliwość szybkiej migracji do innej chmury.

  2. Właśnie popatrzyłem na ta klasę i rzeczywiście wygląda dobrze, nawet lepiej niż ta której ja użyłem. Co do failu Amazona to nie mamy żadnych krytycznych rzeczy tam ;). Dzięki za sugestie 😉

  1. There are no trackbacks for this post yet.

Leave a Reply

Informuj mnie o odpowiedziach poprzez e-mail. Możesz również subskrybować wpis bez zostawiania komentarza.