effective finding queries to optimize

let's imagine simple situation – you have postgresql server. configuration was fine-tuned, hardware is ok. yet the system is not really as fast as it should.

most common problem – slow queries.

second most common problem – fast queries, but too many of them. for example – i once saw a system which did something like this:

  • select id from table;
  • for every id do:
  • select * from table where id = ?

reason? very “interesting" orm.

now i'll show you how i deal with these kind of situations 🙂

Continue reading effective finding queries to optimize

indexable ” field like ‘%something'”

for the long time everybody knew that you can't use index on “LIKE" operations.

then came text_pattern_ops, so we could use indexes for prefix searches:

# \d depesz_test
                         Table "public.depesz_test"
 Column |  Type   |                        Modifiers
--------+---------+----------------------------------------------------------
 id     | integer | not null default nextval('depesz_test_id_seq'::regclass)
 email  | text    | not null
Indexes:
    "depesz_test_pkey" PRIMARY KEY, btree (id)
    "x" UNIQUE, btree (email text_pattern_ops)
# EXPLAIN analyze SELECT COUNT(*) FROM depesz_test WHERE email LIKE 'dep%';
                                                      QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=96.71..96.72 ROWS=1 width=0) (actual TIME=0.983..0.985 ROWS=1 loops=1)
   ->  Bitmap Heap Scan ON depesz_test  (cost=4.68..96.65 ROWS=24 width=0) (actual TIME=0.184..0.641 ROWS=155 loops=1)
         FILTER: (email ~~ 'dep%'::text)
         ->  Bitmap INDEX Scan ON x  (cost=0.00..4.67 ROWS=24 width=0) (actual TIME=0.158..0.158 ROWS=155 loops=1)
               INDEX Cond: ((email ~>=~ 'dep'::text) AND (email ~<~ 'deq'::text))
 Total runtime: 1.067 ms
(6 ROWS)

but what if i'd like to search for ‘%something'? not prefix, but suffix. in my example – what can i do to use indexes when searching for people from given domain?

Continue reading indexable " field like ‘%something'"

print “read manual” while each %bug;

taak.

trafił mnie dziś bardzo “fajny" błąd.

w sofcie który pisałem, piszę i się zajmuję mam taki kawałek kodu:

for my $object ( @objects ) {
next unless $self->validate_object( $object );
$self->save_object_to_database( $object );
}

kod ten pobiera z podanej listy obiekty (tak naprawdę to nie obiekty tylko struktury (hasze haszy). potem waliduje zawartość i jeśli walidacja się udała – wpisuje do bazy.

trywiał.

jednym z elementów walidacji jest podmiana pewnych wartości na wartości słownikowe.

powiedzmy, że mamy w “obiekcie" element “region" i ma on wartość “WARSZAWA". w odpowiednim słowniku sprawdzam czy wartość WARSZAWA jest dopuszczalna. jak nie – walidacja się nie udała. jak tak, zamiast stringu “WARSZAWA" wstawiam numeryczny identyfikator z bazy. np. 15.

kod który to robi:

sub validate_object {
my $self = shift;
my $object = shift;
while (my ($param, $dictionary) = each %{ $self->validation_rules }) {
next unless defined $object->{ $param };
my $object_value = $object->{ $param };
if ( $self->dictionaries->{ $dictionary }->{ $object_value } ) {
$object->{ $param } = $self->dictionaries->{ $dictionary }->{ $object_value };
next;
}
$self->log("error at validation ...");
return;
}
return 1;
}

może nie jest to najbardziej czytelne, ale po kolei:

hash $self->validation_rules ma pary klucz/wartość, gdzie klucz jest nazwą klucza (elementu) z obiektu, a wartość jest nazwą słownika którym mamy dany element walidować.

przykładowo hash ten może zawierać:

"region" => 'REGION_LIST'

reguł jest standardowo około 10.

słowniki są zwracane z metody $self->dictionaries(). zwracana struktura to hashref, mający jako klucz nazwę słownika, a jako wartość – hashref z parami – tekstowa wartość => numeryczny identyfikator.

przykładowo:

{
'REGION_LIST' => { 'WARSZAWA' => 1, 'KRAKÓW' => 2, ...},
'CATEGORIES_LIST' => { 'MOTO' => 15, 'AGD' => 21, ... },
...
}

proste.

czy widzicie błąd w kodzie funkcji validate_object() ?

nie?

podpowiem. objaw który do mnie trafił, to to, że metoda save_object_to_database() zwracała bład sql'a, który mówił, że wartość ‘WARSZAWA' nie jest prawidłowa dla pola numerycznego.

nadal nie wiecie?

otóż metoda each(). zapamiętuje ona w haszu ostatnio zwrócony element. tak aby przy następnym wywołaniu zwrócić kolejny.

co się więc stanie gdy któryś z obiektów sie nie zwaliduje?

załóżmy, że mamy 10 reguł. od 1 do 10. przy obiekcie “a" zwalidowały się reguły 1, 2, 3, 4, a przy regule 5 pojawił się błąd. został zalogowany ($self->log), metoda validate_object się skończyła pustym returnem. więc w głównej metodzie został pobrany kolejny obiekt – “b".

przy walidowaniu obiektu “b", wywołujemy each(), który zwraca którą regułę? 6! potem 7, 8, 9, 10 i na tym skończy. czyli reguły 1-5 w ogóle nie są sprawdzone. i nawet jeśli obiekt jest poprawny – wartości odpowiednich pól nie zostają zamienione na id'y. i stąd błąd przy insercie.

czemu o tym piszę?

dwa powody.

po pierwsze: może się to komuś przyda.

po drugie: może to spowoduje, że o tym nie zapomnę. i następnym razem zamiast bawić się each() użyję po prostu keys.

parsowanie rss’ów

prace nad dnews postępują powoli. bardzo powoli.

pisałem dziś kawałek kodu do czytania rss'ów/atom'ów/rdf'ów.

prosty test na rss'ach planety ubuntu i wynik:

undefined entity at line 527, column 19, byte 70093 at /usr/lib/perl5/XML/Parser.pm line 187

wrrrrrr.

a co jest w tej linii?

=> head -n 527 planet.ubuntulinux.org.xml | tail -n 1
<title>Sebastian K&uuml;gler: We are out of real life</title>

jeśli nie zgadłeś – problemem jest ten &uuml;.

co z tym zrobić? popytałem na ircu, poszukałem na googlu. w końcu trafiłem na stronę która mówi o przyczynach i jak zapobiec.

w związku z tym musiałem zastosować taki kawałek hacka:

$content =~ s{
\A
(
\s*
<\?.*\?>
\s*
)
<
([A-Z0-9:_-]+)
}{$1 . get_doctype($2) . "<" . $2}eixms;

gdzie funkcja get_doctype wygląda:

sub get_doctype {
my $tag = shift;
return <<__DOCTYPE__;
<!DOCTYPE $tag [
<!ENTITY % HTMLlat1 PUBLIC "-//W3C//ENTITIES Latin 1 for XHTML//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent"> %HTMLlat1;
<!ENTITY % HTMLspecial PUBLIC "-//W3C//ENTITIES Special for XHTML//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent"> %HTMLspecial;
<!ENTITY % HTMLsymbol PUBLIC "-//W3C//ENTITIES Symbols for XHTML//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent"> %HTMLsymbol;
]>
__DOCTYPE__
}

co to powoduje?

wstawia przed pierwszy tag w xml'u deklarację używania entity z html'a.

po tym – xml::feed działa mi już poprawnie nie marudząc o nic 🙂

wniosek? część (zgaduję, że większość) feedów rdf/rss/atom jest zwalona jeśli chodzi o poprawność xml'a.

drobny błąd

a dziś napiszę o tym jakim to łosiem można być. niechcący.

na potrzeby jednego z projektów napisałem własnego orm'a (object-relationship mapper). nie znacie? takie cos co pozwala widziec rekordy z tabel jako obiekty. ogólnie – każdy orm jest bez sensu. mój tym bardziej, ale służył do prostego celu – uproszczenia robienia eksportów.

działał.

do czasu.

ostatnio na jednej z maszyn eksporter zaczął zżerać cały ram. calutki. i wywalać maszynę. usiadłem do debugowania. i oto co ujrzałem:

...
sub _table { my $self = shift; RETURN $self->{ 'table_name' } }
sub _db    { my $self = shift; RETURN $self->{ 'db' } }
sub _log   { my $self = shift; RETURN $self->{ 'log' } }
sub _refresh {
my $self   = shift;
my $sql    = sprintf 'SELECT * FROM %s WHERE id = ?', $self->_table;
my $record = $self->_db->get_single_record( $sql, $self->{ 'data' }->{ 'id' } => 'INT8' );
IF ( $record ) {
$self->{ 'data' }    = $record;
$self->{ 'fetched' } = 1;
RETURN;
}
$self->log->critical(
"Cannot refresh record data from table " . $self->_table . " for id = " . $self->{ 'data' }->{ 'id' } );
croak( "DB Error" );
}
sub _get {
my $self = shift;
my ( $field ) = @_;
$self->_refresh unless $self->{ 'fetched' };
RETURN $self->{ 'data' }->{ $field };
}
sub AUTOLOAD {
my $self   = shift;
my $method = $AUTOLOAD;
$method =~ s/.*:://;
RETURN unless $method =~ m{ \A [a-z][a-z0-9_]* \z }xmso;
RETURN $self->_get( $method );
}

zasada działania bardzo prosta – jeśli mam obiekt klasy dziedziczącej z tego orm'a, i wykonam na nim metodę:

$obiekt->jakies_pole

(gdzie jakies_pole nie jest nazwa istniejacej metody), to request trafi do autoloada, który wywoła _get. _get sprawdzi czy obiekt załadował z bazy dane. jak tak – zwróci odpowiednie pole i po sprawie.

a co jeśli nie?

wtedy kod trafia na _refresh(). refresh odczytuje cały rekord z bazy w oparciu o id (które musi być). fajne.

jedno pytanie: co się stanie gdy rekordu w bazie nie będzie?

tradycyjna odpowiedź: kod zaloguje informacje o błędzie i wykona croak(). czyli taki die.

ale nie. niestety. złośliwy los i brak dobrych oczu spowodował, że napisałem $self->log->(), podczas gdy obiekt loggera jest dostępny przed $self->_log.

efekt? metody log nie ma. trafiamy na autoloada. autoload odsyła do _get'a, _get do _refresha. _refresh znowu nie znajduje rekordu, więc … i kółeczko się zamyka.

nieskończona rekurencja, 3 giga zużytej pamięci, kilka godzin pracy paru osób. przez brak jednego “_".

a czemu o tym piszę? abym pamiętał. i sprawdzał kod. i bym miał okazję się pochwalić jakie “fajne" błędy potrafię wygenerować, a potem wykryć. i pochwalić “perl -d" – debugger jest wkurzający, mało sympatyczny. i ratuje d… jak oczy zawiodą.

aha. i jak fajnie wygląda “T" pod debuggerem po np. 50 przebiegach tej rekurencji 🙂 (T == stack trace).

perl best practices critic

jakiś czas temu damian conway napisał "perl best practices" ("perl. najlepsze rozwiązania"). zawarł w niej szereg sugestii jak pisać.
książkę ogólnie polecam, choć nie zgadzam się ze wszystkim co napisał. ale to już inna bajka.
w oparciu o to co napisał, powstał program: perlcritic (można go zainstalować przez "install Perl::Critic" w shellu cpanowym).
program ten analizuje twój program perlowy i wypisuje błędy (błędy czytaj konstrukcje inne niż zalecane przez damiana). wraz z odnośnikami do numerów stron w książce!
warto zobaczyć jak to wygląda. przykładowo. dla jednego z moich (działających!) programów wynik perlcritica wygląda tak:

=> perlcritic archiveMails.pl
Code before strictures are enabled at line 9, column 1.  See page 429 of PBP.  (Severity: 5)
Integer with leading zeros at line 75, column 55.  See page 58 of PBP.  (Severity: 5)
Don't modify $_ in list functions at line 94, column 37.  See page 114 of PBP.  (Severity: 5)

nie najgorzej. nie? zobaczmy co się dzieje jak każę mu wyświetlać wszystkie błędy, a nie tylko krytyczne:
ojć. całości nie pokażę. za dużo, ale to mogę pokazać:

=> perlcritic -1 archiveMails.pl  | wc -l
69

to mało mówi. więc zróbmy prostą statystykę:

=> perlcritic -1 archiveMails.pl  | perl -pe 's/at line \d+, column \d+/at line X, column Y/' | sort | uniq -c | sort -nr
     14 Mixed-case variable name(s) at line X, column Y.  See page 44 of PBP.  (Severity: 1)
     10 Regular expression without "/x" flag at line X, column Y.  See page 236 of PBP.  (Severity: 3)
      9 Regular expression without "/m" flag at line X, column Y.  See page 237 of PBP.  (Severity: 2)
      6 Builtin function called with parens at line X, column Y.  See page 13 of PBP.  (Severity: 1)
      4 Postfix control "unless" used at line X, column Y.  See pages 96,97 of PBP.  (Severity: 2)
      3 Useless interpolation of literal string at line X, column Y.  See page 51 of PBP.  (Severity: 1)
      3 Subroutine does not end with "return" at line X, column Y.  See page 197 of PBP.  (Severity: 4)
      2 "unless" block used at line X, column Y.  See page 97 of PBP.  (Severity: 2)
      2 Mixed-case subroutine name at line X, column Y.  See page 44 of PBP.  (Severity: 1)
      2 File handle for "print" is not braced at line X, column Y.  See page 217 of PBP.  (Severity: 1)
      1 RCS keywords $Revision$, $Source$, $Date$ not found at line X, column Y.  See page 441 of PBP.  (Severity: 2)
      1 RCS keywords $Revision$, $HeadURL$, $Date$ not found at line X, column Y.  See page 441 of PBP.  (Severity: 2)
      1 RCS keywords $Id$ not found at line X, column Y.  See page 441 of PBP.  (Severity: 2)
      1 Postfix control "if" used at line X, column Y.  See pages 93,94 of PBP.  (Severity: 2)
      1 Package variable declared or used at line X, column Y.  See pages 73,75 of PBP.  (Severity: 3)
      1 No "VERSION" variable found at line X, column Y.  See page 404 of PBP.  (Severity: 2)
      1 Integer with leading zeros at line X, column Y.  See page 58 of PBP.  (Severity: 5)
      1 Forbid $b before $a in sort blocks at line X, column Y.  See page 152 of PBP.  (Severity: 1)
      1 Double-sigil dereference at line X, column Y.  See page 228 of PBP.  (Severity: 2)
      1 Don't modify $_ in list functions at line X, column Y.  See page 114 of PBP.  (Severity: 5)
      1 Code is not tidy at line X, column Y.  See page 33 of PBP.  (Severity: 1)
      1 Code before warnings are enabled at line X, column Y.  See page 431 of PBP.  (Severity: 4)
      1 Code before strictures are enabled at line X, column Y.  See page 429 of PBP.  (Severity: 5)
      1 Capture variable used outside conditional at line X, column Y.  See page 253 of PBP.  (Severity: 3)

łał. a przypominam – ten kod działa i robi co trzeba.
tak czy inaczej – warto spojrzeć. to co perlcritic pokazuje, to czasem całkowicie nieistotne szczegóły (jak np. mixed case variable name), ale czasem jest to coś co warto poprawić. sam po przeczytaniu best practices sporo zmieniłem w swoim stylu kodowania.

golf z nagrodami pieniężnymi

wow. pierwszy w historii (a przynajmniej pierwszy o którym wiem) perlgolf z którym można wygrać kasę! fonality – twórcy kmercyjnego rozwiązania telefonicznego stworzonego w oparciu o asterisk'a, zaprezentowali zadanie i nagrodzą pierwsze 20 osób.
zadanie – napisać kalkulator działający w notacji liczb rzymskich. prosty – od 1 do 3999, tylko 2 operatory (minus i plus).
na wypadek jeśli nie wiecie co to golf: konkurs perlowy w którym wygrywa osoba która napisze najkrótszy kod realizujący daną funkcjonalność. większość golfów ma rozwiązania w granicach do 100 znaków. jak na razie w tym golfie prowadzi "alien" ton hospel – już ma 100 znaków, i jeszcze prawie tydzień na wymyślenie optymalizacji.

szybkie sprawdzanie zainstalowania modułu

na postgresowym blogu grega sabino mullane przeczytałem fajną sprawę.
co jakiś czas sprawdzam czy mam jakiś moduł perlowy i jak tak to jaką wersję.
zazwyczaj robię to np. tak:

perl -e 'use DBI; 42'

jak się wywali to modułu nie ma.
sprawdzenie wersji:

 perl -e 'use DBI; print $DBI::VERSION'

fajne, ale skomplikowane.
greg zaproponował by dopisać sobie takiego aliasa do bashrc:

 alias modver="perl -e\"eval qq{use \\\$ARGV[0];\\\\\\\$v=\\\\\\\$\\\${ARGV[0]}::VERSION;};
print\\\$@?qq{No module found\\n}:\\\$v?qq{Version \\\$v\\n}:qq{Found.\\n};\"\$1"

po czym wystarczy:

 modver DBI

alias jest skomplikowany, ale wystarczy go wpisać raz. i wszystko działa ok 🙂