czwartek, 28 lutego 2013

Konkurs OneWebSQL

Ostatnimi czasy zajmuje sie glownie budowaniem mojego ego :))
Pierwszy raz od dawien dawna udalo mi sie nawet cos wygrac
http://onewebsql.com/konkurs-spring
http://e-point.pl/co-nowego?news_id=1500144,rozstrzygniecie-konkursu-na-najlepsza-aplikacje-webowa

Zaproponowane przeze mnie rozwiazanie jest upublicznione na githubie. Przyznam szczerze ze zmienilbym w nim jeszcze pare rzeczy, glownie zwiazanych z warstwa widoku. Najlepiej przepisal JSP na AngularJS.

wtorek, 26 lutego 2013

MongoDB 10gen Kurs


Kurs wystartował kolejny raz.
Przeszedłem instalacje MongoDB, podstawowe query z mongo shell i aplikacji Javowej.
Poznałem Spark Micro Web Framework i FreeMarket templaty.
Jak narazie OK, niezbyt trudne cwiczenia do przejscia w jedną noc.

Założyłem projekt github na potrzeby szkolenia https://github.com/piczmar/MongoDB4JavaDev/

2 czesc kursu
to podstawy CRUD i zapoznanie z Mongo Shell - interaktywna konsola dostepu do bazy, ktora jest interpretorem JavaScript.
Przydatne skroty klawiszowe:
- przywolanie ostatniej komendy: strzalka do gory
- przeskoczenie na poczatek linii: CTRL+A
- przeskoczenie na koniec linii: END
- dokonczenie polecenia/metody: TABrepli

Przydatne komendy:
help -  wyswietla liste komend
help keys - wyswietla skroty klawiszowe

Mozna wykonywac pliki json z konsoli, np stworzmy plik o nazwie popAvg.json ktory chcemy wykonac na bazie o nazwie week5


use week5
db.zips.aggregate([{

$group:{
_id:'$state',
average_pop:{$avg:'$pop'}
}
}])

plik mozemy uruchomic:
mongo < popAvg.json



Zapoznanie z podstawami JavaScript, np definiowanie obiektow i odwolywanie sie do ich wlasnosci:
x = {a:"test"} // tworzy nowy obiekt
x.a // odwolanie do atrybutu a jako literalu
z="a"
x[z] //odwolanie do atrybutu jako wartosci slownikowej (ang. dictionary) klucz : wartość

Mongo zapisuje obiekty w formacie BSON
Podstawowe typy danych:
NumberInt - integer 32bity
NumerLong - integer 64bity
ISODate - date, new Date() tworzy ten typ danych


Zapisywanie obiektow:
db - zmienna trzymajaca aktualna baze
db.nazwa - kolekcja o nazwie 'nazwa' w bazie, zeby do niej zapisac nalezy wykonac metode insert np:
db.nazwa.insert(doc)
Wyszukiwanie:
db.people.find() - zwraca wszystkie obiekty, jesli podamy kryteria wyniki beda przefiltrowane, np:
db.people.find({name:"John"})
db.scores.find({score:200}).pretty() - wyswietla sformatowany wynik
db.people.findOne() - zwraca pierwszy lepszy rekord z kolekcji jesli nie podamy kryteriow, drugi argument po kryteriach prezycuje jakie pola chcemy w wyniku

> db.people.findOne({name:"Smith"})
{ "_id" : ObjectId("513ba7e947631d66ce571e99"), "name" : "Smith" }
> db.people.findOne({name:"Smith"},{"name":true, "_id":false})
{ "name" : "Smith" }

Wypisanie obiektu jako json:
var obj = db.people.findOne()
printjson(obj)

Wyszukiwanie za pomoca wyrazen:
 db.scores.find({score:{$gt:10, $lte:20}}) - znajdz rekordy ktorych  'score'>10 && 'score'<20


Kryteria uzywaja porownania leksygraficznego tzn ze jesli w tej samej kolekcji pole jest typu string dla jednego rekordy a typu numerycznego dla innego i w kryterium uzywamy porownania dla stringow to rekordy w ktorych to pole nie jest stringiem sa w ogole pomijane.

Mozna tez przefiltrowac rekordy ktore maja dany atrybut, np:
db.people.find({profession: {$exists: true}}) - zwraca tylko te rekordy ktore maja atrybut 'profession'

lub atrybut okreslonego typu:
 db.people.find({profession: {$type: 2}}) - rekordy z atrybutem 'profession' typu string, numery dla typow sa zdefiniowane w specyfikacji BSON

lub uzyc wyrazenie regularne:
db.people.find({name: {$regex: "m$"}}) - tylko rekordy ktorych 'name' konczy sie na litere 'm'
db.people.find({name: {$regex:'q'}})- rekordy ktorych 'name' zawiera litere 'q'
Unia OR:
db.people.find( { $or: [{name:"Adam"}, {profession:{$lte:"programmer"}}] })

Koniunkcja AND:
db.people.find( { $and: [{name:{$gt:"C"}}, {name:{$regex:"a"}}]}) - mozna to zapisac prosciej jako:
db.people.find( { name:{$gt:"C", $regex:"a"} })

Kryteria dla kolekcji

{ "_id" : ObjectId("513bb7374802e675111ba1c9"), "name" : "Howard", "favourites" : [ "pretzels", "beer" ] }


db.accounts.find({favourites:{$in:["pretzels", "chease"]}}) - wrzystkie rekordy zawierajace w polu 'favourites' wartosci 'pretzels' lub 'chease'
db.accounts.find({favourites:{$all:["pretzels", "chease"]}}) - wrzystkie rekordy zawierajace w polu 'favourites' wartosci 'pretzels' i 'chease' w dowolnej kolejnosci

Kryteria dla zagnieżdżonych dokumentow:

{ product : "Super Duper-o-phonic", 
  price : 100000000000,
  reviews : [ { user : "fred", comment : "Great!" , rating : 5 },
              { user : "tom" , comment : "I agree with Fred, somewhat!" , rating : 4 } ],
  ... }

db.catalog.find({price: {$gt:10000}, "reviews.rating": {$gte:5}}) - zwraca wszystkie produkty z price > 10000 i rating >=5

metoda find() zwraca kursor ktory ma metody: hasNext() i next() przydatne do nawigowania po wynikach, np:
> cur = db.people.find(); null; // null jako druga komenda zapobiega drukowaniu wyniku
> cur.limit(5); null; // limit do 5 rekordow
> cur.skip(2); null; // pomiń 2 recordy z wyniku
> cur.sort({name: -1}); null; // sortuj wynik po 'name' w odwrotnym porządku leksykograficznym
> cur.hasNext()
> cur.next()


Liczenie recordow:
db.scores.count({type:"essay", score: {$gt:90}})

Update:
 db.people.update({name:"Adam"}, {profession: "seller"}) - update bierze 2 argumenty, 1: query, 2: nowe wartosci pól, pola pominiete zostana usunięte z dokumentu

np.:

{ "_id" : "Texas", "population" : 2500000, "land_locked" : 1 }


db.foo.update({_id:"Texas"},{population:30000000})
wynik: { "_id" : "Texas", "population" : 30000000 }


Inny sposob ktory modyfikuje tylko wybrane pola:
db.foo.update(_id:"Texas", {$set: {population: 30000}})
lub inkrementacja:
db.foo.update(_id:"Texas", {$inc: {population: 1}})

Jesli nie podamy kryterium to domyslnie zostanie updatowany pierwszy znaleziony a nie wszystkie jak to jest w SQL. Zeby updatowac wszystkie trzeba podac dodatkowy argument {mutil:true}

db.people.update({}, {$set:{title:"Dr"}}, {multi : true})
db.people.update({}, {$set:{title:"Dr"}}, {upsert : true}) //updatuje istniejace dokumenty lub utworzy nowy jesli nie znalazl dokumentow do updatowania

Iindywidualny dokument jest updatowany atomowo, ale Mongo nie wspiera izolowanych tranzakcji przy updacie wielu dokumentow na raz.


Usuwanie pol:
db.foo.update(_id:"Texas", {$unset: {population: 1}})

Operatory tablicowe:

$push, $pop, $pull, $pushAll, $pullAll, $addToSet

np:

> db.arrays.insert({_id:0, a:[1, 2, 3, 4]}) //tworzymy nowy obiekt z tablicą 'a'
> db.arrays.findOne()
{ "_id" : 0, "a" : [ 1, 2, 3, 4 ] }
> db.arrays.update({_id:0}, {$set: {"a.2":5}}) //zmien element tbalicy o indeksie 2
> db.arrays.findOne()
{ "_id" : 0, "a" : [ 1, 2, 5, 4 ] }
> db.arrays.update({_id:0}, {$push:{a:6}}) //dodaj nowy element na koniec tablicy
> db.arrays.findOne()
{ "_id" : 0, "a" : [ 1, 2, 5, 4, 6 ] }
> db.arrays.update({_id:0}, {$pop:{a:1}}) //usun element z konca tablicy
> db.arrays.findOne()
{ "_id" : 0, "a" : [ 1, 2, 5, 4 ] }
> db.arrays.update({_id:0}, {$pop:{a:-1}}) // usun element z poczatku tablicy
> db.arrays.findOne()
{ "_id" : 0, "a" : [ 2, 5, 4 ] }
> db.arrays.update({_id:0}, {$pushAll:{a:[7,8,9]}}) // dodaj kilka elem. na koniec tablicy
> db.arrays.findOne()
{ "_id" : 0, "a" : [ 2, 5, 4, 7, 8, 9 ] }
> db.arrays.update({_id:0}, {$pullAll:{a:[7,8,9]}}) //usun wszystkie elementy jak podane w liscie
^[db.arrays.findOne()
{ "_id" : 0, "a" : [ 2, 5, 4 ] }
> db.arrays.update({_id:0}, {$pull:{a:5}}) // usun pojedynczy element
> db.arrays.findOne()
{ "_id" : 0, "a" : [ 2, 4 ] }
> db.arrays.update({_id:0}, {$addToSet:{a:5}}) // dodaj nowy element jesli jeszcze nie istnieje
> db.arrays.findOne()
{ "_id" : 0, "a" : [ 2, 4, 5 ] }
> db.arrays.update({_id:0}, {$addToSet:{a:5}}) //dodaj nowy element jesli jeszcze nie istnieje
> db.arrays.findOne()
{ "_id" : 0, "a" : [ 2, 4, 5 ] }


Usuwanie rekordow: 
db.people.remove({}) //usuwa wszystkie dokumenty z kolekcji, jeden po jednym
db.people.drop() //usuwa wszystkie dokumenty z kolekcji bardziej efektywnie niz powyzsze remove({}) bo nie pojedynczo tylko cala kolekcje
db.people.remove({name: "Adam"}) // usuwa dokumenty spelniajace okreslone kryteria

Usuwanie wielu rekordow nie jest atomowe, ale pojedynczego juz tak. Zaden reader nie zobaczy dokumenty Mongo w polowie usunietego.

Jak sprawdzic czy ostatnio wykonana operacja przebiegla poprawnie czy nie: 

db.runCommand({getLastError: 1})
przyklad wyniku:
{
"err" : "E11000 duplicate key error index: test.people.$_id_  dup key: { : \"Smith\" }",
"code" : 11000,
"n" : 0,
"connectionId" : 3,
"ok" : 1
}

To samo co w konsoli mozemy zrobic przy użyciu Java API, demo dostepne na githubie.

W dalszej czesci kursu bedziemy budowac aplikacje webowa. Zasa dzialania zostala opisana na filmie:





Tydzien 4 dotyczy wydajnosci bazy i indeksow jako podstawowego elementu wplywiajacego na wydajnosc podczas przeszukiwania bazy.
Porzyteczne narzedzia do profilowania w Mongo to:
z konsoli Mongo:
db.getProfilingStatus() - zwtaca aktualny status logowania
db.setProfilingLevel(1,3) - poziom logowania 1, trwajace dluzej niz 3ms, dostepne poziomy:
0 - logowanie wylaczone
1 - pokazuj tylko wolne operacje
2- pokazuj wszystkie operacje
Logi sa zapisywane do db.system.profile i moga byc przeszukiwane np:
db.system.profile.find({millis:{$gt: 1000}}).sort({ts:-1})

programy ktore sa instalowane razem z mongo:
mongotop - dostarcza statystyki na poziomie kolekcji mowiace ile Mongo spedza czasu na operacjach zapisu/odczytu




mongostat -statustyki operacji mongo zwlaszcza pozyteczne - wykorzystanie indeksow





Tydzien 5:

Agregacje w Mongo.
W SQL odpowiednikiem jest GROUP BY.
Zalozmy ze mamy kolekcje car zawierajaca dokumenty:
{name: 'car', price: '12}
{name: 'car', price: '2'}


> db.manuf.aggregate([{$group: { _id:'$name', num_car:{$sum:1}}}])
{ "result" : [ { "_id" : "car", "num_car" : 2 } ], "ok" : 1 }
dostajemy w wyniku liczbe dokumentow z name='car'

jesli chcielibysmy zsumowac ceny w grupie:

> db.manuf.aggregate([{$group: { _id:'$name', prices:{$sum: '$price'}}}])


Elementy pipline aggregacji:
$project - wybiera ktore atrybuty chcemy w wyniku
$match - filtruje kolekcje po jakims kryterium, jak WHERE w SQL
$group - grupuje wynik poprzedniego kroku pipeline
$sort - jak ORDER BY w SQL, sortuje wynik
$skip - omija iles poczatkowych rekordow
$limit - ogranicza wynik do iluś rekordow.
$inwind - normalizuje dane, jesli dokument ma tablice 3 rekordow to inwind utworzy 3 oddzielne dokumenty z jednym elementem tablicy

Compound grouping by multiple group:
mamy dokumenty:

{ "_id" : ObjectId("5154ba3f7b1fda1c6b22429b"), "category" : "drive", "name" : "car", "price" : 12 }
{ "_id" : ObjectId("5154ba437b1fda1c6b22429c"), "category" : "drive", "name" : "car", "price" : 23 }
{ "_id" : ObjectId("5154bf547b1fda1c6b22429d"), "category" : "drive", "name" : "bike", "price" : 10 }
{ "_id" : ObjectId("5154c1247b1fda1c6b22429e"), "category" : "swim", "name" : "canoo", "price" : 100 }
{ "_id" : ObjectId("5154c12f7b1fda1c6b22429f"), "category" : "swim", "name" : "boat", "price" : 101 }


db.manuf.aggregate([{$group:{_id:{name:'$name',categ:'$category'},sum:{$sum:1}}}])

dostaniemy:
{
"result" : [
{
"_id" : {
"name" : "boat",
"categ" : "swim"
},
"sum" : 1
},
{
"_id" : {
"name" : "canoo",
"categ" : "swim"
},
"sum" : 1
},
{
"_id" : {
"name" : "bike",
"categ" : "drive"
},
"sum" : 1
},
{
"_id" : {
"name" : "car",
"categ" : "drive"
},
"sum" : 2
}
],
"ok" : 1
}

Inne wyrazenia uzywane w grupowaniu:
$sum
$avg
$min
$max
$push
$addToSet
$first - uzywane z sort, daje pierwszy dokument
$last - uzywane z sort, daje ostatni dokument


Przyklady zastosowania niektorych operatorow na githubie.

Ograniczenia aggregacji:
- rozmiar wyniku ograniczony do 16MB (jako że wynik jest pojedynczym dokumentem)
- nie mozna uzyc wiecej niz 10% pamieci maszyny
- sharding: kolekcje sa na rozych nodach klastra, po pierwszym pipe $sort lub $group wynik musi byc zwrocony na klienta (tam gdzie dziala process 'mongos')

Jesli z powodu tych ograniczen nie mizna uzyc agregacji mozemy zamiast tego uzyc:
- mapreduce
- hadoop (jest konektor do mongo)


Week 6: replication

Tworzenie klastra na pojedynczej maszynie - koniecznie na roznych portach, normalnie stworzylibysmy na roznych serwerach:

1. tworzymy foldery danych dla 3ch replik:
mkdir c:\data\rs1
mkdir c:\data\rs2
mkdir c:\data\rs3

mongod --replSet rs1 --logpath "1.log" --dbpath c:\data\rs1 --port 27017
mongod --replSet rs1 --logpath "2.log" --dbpath c:\data\rs2 --port 27018
mongod --replSet rs1 --logpath "3.log" --dbpath c:\data\rs3 --port 27019
re
wszystie sa czescia tego samego zbiory replik rs1

Tworzymy konfiguracje dla zbioru w pliku init_replica.js:



config = { _id: "rs1", members:[
          { _id : 0, host : "localhost:27017", priority:0, slaveDelay:5},
          { _id : 1, host : "localhost:27018"},
          { _id : 2, host : "localhost:27019"} ]
};

rs.initiate(config);
rs.status();


Plik ten ladujemy do jednego z wezlow:
mongo --port 27018 < init_replica.js

Teraz laczymy sie do jednego z nodow i drukujemy status
mongo --port 27018

rs.status()


Mozemy sie polaczyc do jednego z SECONDARY node i sprawdzic ze nie mozemy do nich pisac, tylko mozna pisac do PRIMARY.

Jesli chcemy czytac z SECONDARY musimy najpierw powiadomic go o tym i po polaczeniu sie do niego wywolac:
rs.slaveOK()


rs.isMaster() - sprawdzenie czy node do ktoredo sie zalogowalismy jest MASTERem

Jak sprawdzic co sie dzieje podczas replikacji? Mamy w tym celu kolekcje oplog.rs:

use local
show collections



Tydzien 6. Sharding

Co to jest sharding? Jest to dzielenie kolekcji na wiele serwerów.





Po polaczeniu do mongo konsoli na domyslnym porcie ('mongo') mozemy sprawdzic status sharda:

sh.status()

oraz informacje o shardowanej kolekcji:

db.grades.stats()

Kazdy dokument miec shard key.
Shard key jest niezmienny.
Indexy musza sie zaczynac od shard key, np jesli jest shard key 'student_id'
to pozostale indeksy np:
(student_id, class)
(student_id, name)
Przy zapytaniach jesli nie uzyjemy shard key to zapytanie bedzie rozpropagowane na wszystkie nody w klastrze zamiast do poprawnego sharda.




Egzamin koncowy zdany na 95%, chyba powinienem czuć sie MongoDB expertem, jednak czuje ze ta wiedza szybko wyparuje jeśli nie będę jej używać.

sobota, 23 lutego 2013

Wykonanie SQLi przy starcie aplikacji Grails

Jak wrzucic do Grails dataSource zdefiniowanego w DataSource.groovy jakies dane wykonujac skrypty SQL?
1. Dodaj nowy plik grails-app/config/spring/resources.xml a w nim:

    <jdbc:initialize-database data-source="dataSource" ignore-failures="DROPS">
        <jdbc:script location="classpath:/com/initdb/data.sql" />
        <jdbc:script location="classpath:/com/initdb/data2.sql" />
    </jdbc:initialize-database>


2. Umiesc skrypty zdefiniowane w kroku 1. w class path, czyli stworz nowy folder w src/java lub src/groovy

com/initdb

 i dodaj w nim swoje skrypty SQL: data.sql, data2.sql