Pokazywanie postów oznaczonych etykietą index. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą index. Pokaż wszystkie posty

czwartek, 26 września 2013

[FullTextSearch] ElasticSearch: Mapping

Po dodaniu dokumentów do indeksu ElasticSearch stworzy za nas domyślny mapping na podstawie dodawanych danych. Mappingi założone na danym indeksie możemy obejrzeć wysyłając zapytanie REST-owe.

GET http://localhost:9200/books/_mapping?pretty HTTP/1.1

Zwrócony nam zostanie mapping dla wszystkich typów założonych na danym indeksie.

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=UTF-8
Content-Length: 325

{
  "books" : {
    "book" : {
      "properties" : {
        "author" : {
          "type" : "string"
        },
        "price" : {
          "type" : "double"
        },
        "title" : {
          "type" : "string"
        },
        "year" : {
          "type" : "long"
        }
      }
    }
  }
}

Jeżeli kolejne dokumenty naruszą wcześniej zdefiniowany mapping (np. zmieniając typ jednego z pól w JSONie"), ElasticSearch zwróci nam błąd.

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=UTF-8
Content-Length: 129

{"error":"MapperParsingException[failed to parse [year]]; nested: NumberFormatException[For input string: \"a\"]; ","status":400}

Mappingi możemy także definiować ręcznie. Przed wysłaniem jakichkolwiek dokumentów wysyłamy żadanie typu POST pod adres zawierający nazwę nowego indeksu.

POST http://localhost:9200/books2/ HTTP/1.1
User-Agent: Fiddler
Host: localhost:9200
Content-Type: application/json
Content-Length: 384

{
  "mappings": {
    "book" : {
      "_source": {
        "enabled": false 
      },
      "properties" : {
        "author" : {
          "type" : "string"
        },
        "price" : {
          "type" : "double"
        },
        "title" : {
          "type" : "string"
        },
        "year" : {
          "type" : "long"
        }
      }
    }
  }
}

Ustawienie_source.enabled na false spowoduje, że zapytania zwracać nam będą jedynie identyfikatory dokumentów bez ich zawartości. Inny przykład pokazujący, co można ustawić dla poszczególnych properties.

{
  "mappings": {
    "book" : {
      "properties" : {
        "author" : {
          "type" : "string",
          "index" : "not_analyzed"
        },
        "price" : {
          "type" : "double",
          "store" : "yes"
        },
        "title" : {
          "type" : "string"
        },
        "year" : {
          "type" : "long"
        }
      }
    }
  }
}

Pole index ustawione na analyzed sprawi że wartości zostaną przeanalizowane i zaindeksowane przez ElastcSearch. Dla pól typu string ustawienie wartości not_analyzed spowoduje, że pole zostanie zaindeksowane, ale nie będzie przetwarzane przez analyzer, tak więc search zwróci wyniki tylko w przypadku, gdy podamy dokładnie któryś z wyrazów zawartych w tym polu. Nie będzie zatem możliwe na przykład przeszukiwanie po przedrostkach typu "Mi*". Pole store odpowiada temu, czy oryginalna wartość pola powinna być wpisywana do indeksu. Dodatkowo dla dat możemy ustawiać format zgodnie z tą listą. Przy bardziej zaawansowanych problemach możemy także dla danego indeksu wybrać specjalny analizator.

Maping dynamiczny można także w pewien sposób konfigurować. Jeżeli chcemy wymusić, aby pola wysyłane jako string były rzutowane do long-ów wtedy gdy to możliwe, przy tworzeniu indeksu wysyłamy następujący obiekt.

POST http://localhost:9200/books5 HTTP/1.1
User-Agent: Fiddler
Host: localhost:9200
Content-Type: application/json
Content-Length: 83

{
  "mappings": {
    "book" : {
      "numeric_detection" : true
    }
  }
}

czwartek, 12 września 2013

[FullTextSearch] ElasticSearch: Wprowadzenie



ElasticSearch to open sourcowy projekt serwera wspierającego wyszukiwanie pełnotekstowe. Zapoczątkowany został w 2010 roku w oparciu o popularne rozwiązanie Apache Lucene. Obecnie jest jednym z najpopularniejszych engine'ów do Full Text Search obok Solr i Lucene. Komunikacja z ES odbywa się poprzez REST. Niektórzy wykorzystują go także jako dokumentową bazę danych, zapewniającą podobnie jak MongoDB dobrą skalowalność i możliwość rozproszenia danych przez mechanizm shardingu.

Podstawowe pojęcia związane z ElasticSearch:

Indeks
W indeksach trzymane są dane, może on być interpretowany jako odpowiednik SQL-owej tabeli, z tą różnicą, że indeks jest zoptymalizowany pod kątem szybkiego przeszukiwania i pobierania danych.

Dokument
Dokument to pojedyncza encja danych, odpowiednik rekordu z SQL. Dokumenty składają się z pól odpowiadających kolumnom. Każde pole jest typowane (numer, string, data itd). Należy pamiętać, że dokumenty nie muszą zachowywać takiej samej struktury w obrębie indeksu.

Typ dokumentu
Pozwala rozróżniać rodzaje dokumentów trzymane w obrębie jednego indeksu.

Node'y i klastry
Umożliwiają rozproszenie serwera ElasticSearch na wiele fizycznych maszyn. Grupa takich maszyn jest nazywana klastrem, natomiast każdy z nich jest nazywany nodem.

Shard
Porcja danych. Pojedynczy shard jest osobnym indeksem Lucene i może być umieszczony na osobnym serwerze, ElasticSearch otrzymując zapytanie wie, do którego sharda je przekierować. Przykładowo nazwiska można pogrupować alfabetycznie tak, jak kilkutomową encyklopedię. Jeżeli zapytanie zaczyna się od A to trafia do pierwszego sharda, jeżeli od np. J to do drugiego.

Replica
Replika to kopia danych z głównego sharda, która musi być z nim synchronizowana. Kiedy główny shard padnie, jego replika będzie promowana do bycia głównym shardem i do niej zostaną przekierowane zapytania. Rozwiązanie takie zapewnia niezawodność danych.

Aby skorzystać z ElasticSearch wystarczy pobrać plik .zip ze strony http://www.elasticsearch.org/ , a następnie rozpakować go w dowolnym miejscu na dysku. Serwer uruchamiany z command line poleceniem elasticsearch.bat wywołanym z poziomu folderu bin. Budowane indeksy są przechowywane w folderze data. ElasticSearch domyślnie działa na porcie 9200.

Do poprawnego działania ElasticSearch potrzebuje zainstalowany Java Development Kit, oraz ustawioną ścieżkę do JAVA_HOME. Instrukcja na stronie Oracle.

niedziela, 24 lutego 2013

[HTML|JS|CSS] HTML5: IndexedDb

Ciekawym trendem, jaki można zaobserwować w standardzie HTML5 jest możliwość składowania coraz większej ilości danych po stronie klienta. Przestarzałe ciasteczka zastępowane są przez znacznie lepszy mechanizm localStorage. To jednak nie wszystko. Pojawia się także możliwość składowania danych w dokumentowej bazie IndexedDb. Podobnie jak w WebStorage, dane zapisywane są dla danej strony internetowej i tylko z jej poziomu mogą być odczytane. Tym razem możemy jednak składować całe obiekty JavaScriptowe, a nie tylko ciągi znaków. Co więcej, zgodnie z dokumentacją IndexedDb, pojemność takiej bazy praktycznie ograniczona jest jedynie miejscem na dysku. Same zapytania wykonywane są w sposób asynchroniczny, programista zatem musi ich wynik obsłużyć w callbackach.

Pracę z indexedDb, technologią mocno eksperymentalną, nad którą prace cały czas trwają, warto rozpocząć od  przypisania sobie dla wygody każdej z możliwych implementacji bazy do jednej zmiennej.

window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;

Jak widać ostatnim silnikiem przeglądarek, który nie wspiera indexedDb jest Presto, na którym tworzona była Opera (obecnie opera ma także być tworzona w oparciu o webkit, więc problem znika dla nowszych wersji).

Zapisywanie obiektów do bazy odbywa się poprzez odpowiednie callbacki.

$("#store").click(function(){
 var value = $("#msg").val();
 var request = indexedDB.open("MyDb", 1); //1
 request.onsuccess = function (evt) {
  db = request.result;
  var transaction = db.transaction("messages", "readwrite"); //2
  var objectStore = transaction.objectStore("messages"); //3                   
  var req = objectStore.add({ date: new Date().toLocaleString(), message: value });
  req.onsuccess = function (evt) {
   console.log('successfully saved');
  };
 };
 request.onerror = function (evt) {
  console.dir(evt);
 };

 request.onupgradeneeded = function (evt) {
  //...
 };
});

Ważne są trzy linie:
  1. Otwieramy bazę danych o nazwie MyDb, opcjonalnie jako drugi parametr podajemy wersję schematu bazy, do której chcemy się łączyć
  2. Pierwszy argument może być także tablicą i jest listą kontenerów, na których będzie wykonywana transakcja
  3. Żeby dodać obiekt do pojedynczego kontenera potrzebujemy obiekt typu objectStore, o nazwie messages
Powyższy kod sprawdza się pod warunkiem, że mamy już utworzoną bazę i kontener, w przeciwnym wypadku wywołana zostanie funkcja onupgradeneeded. 

request.onupgradeneeded = function (evt) {
 var objectStore = evt.currentTarget.result.createObjectStore("messages", 
                             { keyPath: "id", autoIncrement: true 
                         });

    objectStore.createIndex("date", "date", { unique: false });
    objectStore.createIndex("message", "message", { unique: false });
};

Tworzymy objectStore o nazwie messages, posiadający unikalny autoinkrementujący się identyfikator. Dodatkowo, na potrzeby wyszukiwania, można utworzyć dwa indeksy.

Odczyt danych

Podczas odczytu wykorzystuje się indeksy, zwracające wartość i wskaźnik na następny element z kolekcji.

$("#restore").click(function(){
 var request = indexedDB.open("MyDb");
 request.onsuccess = function(evt){
  var db = request.result;
  var transaction = db.transaction("messages");
  var objectStore = transaction.objectStore("messages");
  var req = objectStore.openCursor();
  req.onsuccess = function(evt){
   var cursor = evt.target.result;
   if(cursor){
    var obj = cursor.value;
    var li = $("<li>");
    li.html(obj.date + ", " + obj.message);
    items.append(li);
    cursor.continue();
   }
  }
 }
});

Usuwanie

Korzystając z unikalnych indeksów, usuwanie staje się bardzo proste.Na przykład dla elementu o indeksie 1


$("#removeFirst").click(function(){
 var request = indexedDB.open("MyDb");
 request.onsuccess = function (evt) {
  db = request.result;
  var req = db.transaction(["messages"], "readwrite")
            .objectStore("messages").delete(1);
        }
});

Na koniec warto wspomnieć o tym, jak wygodnie można oglądać stan bazy w przeglądarce Google Chrome, w zakładce Resources.