komputer czytający myśli nadzieją dla okaleczonych

w 1996 roku kilku naukowców zaczęło pracować nad systemem umożliwiającym odczytywanie bezpośrednio z mózgu prostych komend oraz ich przetwarzanie na postać cyfrową.

w 2001 założyli firmę, zdobyli 5milionów dolarów kapitału i wystąpili do fda o zgodę na badania kliniczne. w 2002 udało im sie spowodować, że małpy z wszczepionym chipem mogłu kontrolować kursor na ekranie komputera nie wykonując żadnego ruchu.

w 2004 rozpoczęły się testy kliniczne na ludziach.

chip jest inwazyjny – ma wielkość 4mm x 4mm i jest “zanurzony" na głębokość 1mm w mózg, w obszar odpowiedzialny za świadomy ruch.

wyłapuje sygnały, które (w fazie treningu) są przetwarzane na ruch w dwóch wymiarach.

testy są bardzo obiecujące – pierwszy pacient potrafi już przy pomocy myśli narysować kółko w programie graficznym, zagrać w ponga czy modyfikować głośność telewizji – nawet jeśli w czasie wykonywania tego rozmawia!

na razie testy wykonuje się na osobach chorych – pierwszy pacjent, mający 25 lat, ma jakieś (kiepsko rozumiem angielski medyczny) poważne uszkodzenia kręgosłupa/rdzenia nerwowego i jest sparaliżowany od 2001 roku.

na razie technologia jest jeszcze w powijakach. poza małym chipem do działania wymaga olbrzymiego sprzętu obok, długiego treningu (wzory aktywności neuronów mogą się zmieniać z dnia na dzień, ze względu na pogodę, humor czy cokolwiek innego). ale już teraz wygląda to mocno obiecująco. następny krok to spowodowanie by chip działał bezprzewodowo i rozbudowanie logiki odpowiedzialnej za rozpoznawanie wzorów aktywności. dzięki temu będzie możliwe np. zrobienie wózka inwalidzkiego sterowanego myślami chorego.

to jedna strona medalu. druga jest taka, że taka technologia ma całkiem inne, i też interesujące, zastosowania wojskowe. w dodatku – wojsko ma wyższy budżet. kiedy się dowiemy, że wojsko nad tym pracuje? a może pracuje od dawna, tylko jest to tajne?

jak monitorować postgresa?

po udanym postawieniu serwera, przychodzi kolej na to by go nadzorować. spora część ludzi używa w tym celu softu który automatycznie robi wykresy różnych parametrów. przykładem (którego używam np. ja) jest mrtg. są też inne pakiety, lepsze czy gorsze. używam i lubię mrtg.

mrtg potrafi rysować wykresy nie tylko ruchu na interfejsach, ale praktycznie dowolnych danych podawanych w postaci liczbowej.

dzięki temu używam go do rysowania wykresów:

  • loadu systemowego
  • zużycia pamięci
  • zużycia procesorów
  • wolnego miejsca na dyskach
  • ruchu na interfejsach sieciowych
  • ruchu na swapie (in/out)

to monitoruję na każdym serwerze – niezależnie czy to webserwer, serwer aplikacyjny z jbossem, webproxy, firewall czy serwer bazodanowy.

ale na serwerach bazodanowych przydają się też inne parametry.

zanim przejdę do ich omówienia krótki wtręt.

mrtg potrafi pobierać dane poprzez uruchamianie programów które mu dane zwracają, lub poprzez odpytywanie zdalnego serwera snmp. wybrałem to drugie rozwiązanie, bo daje mi trochę więcej możliwości – np. mogę wykresy rysować na zupelnie innej maszynie niż ta której wykresy dotyczą.

do każdej znanej mi implementacji snmp jest możliwość podpięcia zewnętrznych skryptów/programów pod tzw. mib'y. dzięki temu mogę dowolnie rozszerzać funkcjonalność snmp i dodawać monitorowanie parametrów o których twórcy snmpd nie mieli pojęcia 🙂

pierwszym niestandardowym parametrem który monitoruję na serwerach bazodanowych jest ilość operacji i/o w podsystemie dyskowym.

postgresql jest mocno czuły na obciążenie dysków i tzw. iowaity powodują, że maszyna zachowuje się jakby ktoś jej podciął skrzydła. procesor jest wolny, wszystko działa ok, ale postgres się wlecze.

w używanym przeze mnie net-snmpd, odpowiednie wartości są dostępne pod mib'ami: ssIORawSent.0 i ssIORawReceived.0.

odpowiedni kawałek configu mrtg:

Target[db_sys_iorawsent]: ssIORawSent.0&ssIORawSent.0:public@db
Options[db_sys_iorawsent]: growright
MaxBytes[db_sys_iorawsent]: 100000000
Title[db_sys_iorawsent]: IORawSent at db
PageTop[db_sys_iorawsent]: IORawSent at db

(można umieścić oba wykresy na jednym obrazku, ale z przyczyn pozamerytorycznych wolę mieć każdy oddzielnie).

następnymi (i ostatnimi ogólno systemowymi) parametrami jakie monitoruję jest ilość context-switchy i przerwań w systemie.

dostępne jest to u mnie pod mibami: ssRawContexts.0 i ssRawInterrupts.0

i teraz przechodzimy do części najzabawniejszych.

co monitorować w samym postgresie.

przede wszystkim – jak? przygotowałem sobie prosty program który wykonuje jedno zadane polecenie w bazie danych i zwraca wynik na stdout. ten skrypt podłączam do snmp wykorzystując funkcję “exec" z snmpd.conf.

i lecimy z zapytaniami:

  1. select pg_database_size(‘nazwa_bazy_której_wielkość_chcesz_monitorować');
    zwraca wielkość bazy w bajtach
  2. select cast(extract(epoch from now() – query_start) * 1000 as int8) from pg_stat_activity where current_query !~ ‘stats_command_string w postgresql.conf
  3. select pg_relation_size(‘nazwa_relacji');
    zwraca wielkość w bajtach dowolnej relacji – czyli tabeli czy indeksu. warto w ten sposób monitorować tabele czy indeksy które mają tendencje do puchnięcia (np. jakieś tabele z kolejkami zadań do wykonania).
  4. select sum(xact_commit) + sum(xact_rollback) from pg_stat_database;
    zapytanie to pokazuje ile transakcji (od ostatniego restartu) postgres wykonał (niezależnie czy były one zatwierdzone czy wycofane).
    zbierając tę ilośc co jakiś czas można prosto policzyć ile transakcji na sekundę postgres robi. można też zmodyfikować to zapytanie tak aby liczyło nie transakcje w ogóle przetworzone przez postgresa tylko w konkretnej bazie – modyfikacje zapytania pozostawiam czytelnikom, jest trywialna 🙂

jeśli używacie replikacji, a do replikacji slony'ego, warto monitorować opóźnienie replikacyjne. opóźnienie to wyraża się w dwóch wartościach:

  • lag_events
  • lag_time

lag_events jest to ilość zdarzeń modyfikujących bazę które jeszcze nie zostały przesłane na bazy podrzędne. lag_time jest to opóźnienie replikacji wyrażone jako czas.

wyciągnąć obie wartości można tak:

SELECT st_lag_num_events, st_lag_time FROM _slony.sl_status

przy czym aby móc to rysować lepiej jest przeliczyć lag_time na sekundy:

SELECT st_lag_num_events, CAST(EXTRACT(epoch FROM st_lag_time) * 1000 AS int8) FROM _slony.sl_status

zwrócić należy uwagę, iż jeśli lag_events jest równy 0, to lag_time jest nieistotny.
na koniec coś na zewnątrz sql'a.

w postgresql.conf jest taka opcja: log_min_duration_statement.

pozwala ona na logowanie długich zapytań. tu uwaga – ta opcja jest zupełnie niezależna od log_statement czy log_duration!

wartość opcji log_min_duration_statement oznacza liczbę milisekund jaką zapytanie się wykonywało by zostało uznane za zbyt długie, i zalogowane. ja osobiście wpisuję tam wartość 1000 – co powoduje, że każde zapytanie trwające powyżej 1 sekundy zostanie zapisane w logach. np. tak:

[2006-07-14 00:00:04 CEST] [25989] LOG: duration: 1133.235 ms statement: SELECT id, file_extension FROM flickr_images WHERE id > ‘0' AND size_x_orig = 0 and status >= 0 ORDER BY id ASC

zwracam tylko uwagę na fakt iż to, że zapytanie się długo wykonuje niekoniecznie jest problemem zapytania. najczęstszą sytuacją jest to, że pojawia się zapytanie które wykonuje się dosyć długo, ale poza tym przyblokowuje ono zupełnie inne zapytania – które też nagle zaczynają się długo wykonywać.

tak więc nie należy patrzeć na listę “długich" zapytań jako listę rzeczy do poprawienia, tylko jako na wsad do analizy statystycznej jakie zapytania pojawiają się najczęściej. i dlaczego.

to zasadniczo kończy temat. monitorować można sporo więcej – choćby ilość procesów postgresa, czy ilości odczytów via seq-scan czy index-scan. natomiast wydaje mi się, że te parametry mogą być istotne “punktowo", a niekoniecznie muszą być od razu rysowane.

zdrowy rozsądek wygrał z kaso-chłonnymi prawnikami

kilka firm zaskarżyło ostatnio google'a, że zaniża ich pozycję w wynikach wyszukiwarki, przez co maleją im obroty.

nie znam tła historii (nie wiem czemu google obniżył im pageranka, ale nie uważam tego za przesadnie istotne).

istotne dla mnie jest to, że firma x chciała wywrzeć na firmie y modyfikację działania na korzystaniejsze dla x – mimo, że ich rynki się nie pokrywają, nie ma nieuczciwej konkurencji.

osobiście wychodzę z założenia, że indeksy google'a są własnością google'a i to co w nich robią jest ich prywatną sprawą.

interesujące było to jak do sprawy podejdzie system prawny stanów zjednoczonych. i okazało się, że zdrowy rozsądek wygrał. sędzie rozpatrujący sprawę ją oddalił.

komentarze po procesowe informują, że jednyną drogą jaką “pokrzywdzone" firmy mogą się walczyć o poprawienie wyników jest stwierdzenie, że obniżenie ich pageranka to zniesławienie. ale to będzie bardzo ciężko udowodnić.

przechowywanie historii zmian

zapewne każdy kto choć raz robił coś ciut większego w bazach danych zetknął się z problemem przechowywania zmian w tabelkach.

ci co nie mają triggerów, muszą używać kodu po stronie aplikacji klienckiej. co ma swoje wady.

z drugiej strony – w postgresie triggery są. i działają 🙂 więc można do roboty zaprząc postgresa.

załóżmy, że mamy bardzo prostą tabelkę z danymi:

CREATE TABLE objects (
id BIGSERIAL,
o_type TEXT NOT NULL DEFAULT '',
PRIMARY KEY (id)
);
chcemy zapisywać sobie modyfikacje. robimy więc tabelkę na zapis historii zmian:
CREATE TABLE history_objects (
id BIGSERIAL,
modified_on TIMESTAMP NOT NULL DEFAULT now(),
modification_type TEXT NOT NULL DEFAULT '',
modified_id INT8,
modified_o_type TEXT,
PRIMARY KEY (id)
);

rekordy w history_object zawierają:

  • modified_on – kiedy dana modyfikacja miała miejsce
  • modification_type – typ modyfikacji – insert, update czy delete.
  • modified_id – id zmodyfikowanego rekordu
  • modified_o_type – o_type w tym rekordzie

proste i miłe. tworzymy więc dwa triggery:

CREATE OR REPLACE FUNCTION trg_objects_ui() RETURNS TRIGGER AS
$BODY$
DECLARE
BEGIN
INSERT INTO history_objects (modified_id, modified_o_type, modification_type) VALUES (NEW.id, NEW.o_type, TG_OP);
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER trg_objects_ui AFTER INSERT OR UPDATE ON objects FOR EACH ROW EXECUTE PROCEDURE trg_objects_ui();
 
CREATE OR REPLACE FUNCTION trg_objects_d() RETURNS TRIGGER AS
$BODY$
DECLARE
BEGIN
INSERT INTO history_objects (modified_id, modified_o_type, modification_type) VALUES (OLD.id, OLD.o_type, TG_OP);
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER trg_objects_d AFTER DELETE ON objects FOR EACH ROW EXECUTE PROCEDURE trg_objects_d();i testujemy:

testujemy:

> SELECT * FROM objects;
id | o_type
----+--------
(0 ROWS)
 
> SELECT * FROM history_objects;
id | modified_on | modification_type | modified_id | modified_o_type
----+-------------+-------------------+-------------+-----------------
(0 ROWS)
 
> INSERT INTO objects (o_type) VALUES ('bleble');
INSERT 0 1
 
> INSERT INTO objects (o_type) VALUES ('bleble2');
INSERT 0 1
 
> UPDATE objects SET o_type = 'xxx' WHERE o_type = 'bleble';
UPDATE 1
 
> DELETE FROM objects WHERE o_type = 'bleble2';
DELETE 1
 
> SELECT * FROM objects;
id | o_type
----+--------
1 | xxx
(1 ROW)
 
> SELECT * FROM history_objects;
id |        modified_on         | modification_type | modified_id | modified_o_type
----+----------------------------+-------------------+-------------+-----------------
1 | 2006-07-15 12:14:56.138695 | INSERT            |           1 | bleble
2 | 2006-07-15 12:14:56.138695 | INSERT            |           2 | bleble2
3 | 2006-07-15 12:14:56.138695 | UPDATE            |           1 | xxx
4 | 2006-07-15 12:14:56.138695 | DELETE            |           2 | bleble2
(4 ROWS)

działa ślicznie.

ale jest jedno ale.

czasem chcielibyśmy logować więcej informacji. w projekcie nad którym pracują koledzy w firmy chcieli logować nazwę akcji webowej (coś jakby url) i nazwę użytkownika które spowodowały taką modyfikację.

tu pojawiają się schody – baza danych nie zna urla. baza danych nie zna nazwy użytkownika który się zalogował na www – wie tylko jaki user jest zalogowany do bazy danych, ale to jest zazwyczaj zawsze jeden i ten sam user – niezależnie od tego na kogo się użytkownik loguje via www.

hmm. trzeba więc logować z poziomu aplikacji, a nie bazy.

no tak, ale jeśli user w jednej akcji powoduje modyfikacje wielu rekordów czy wielu tabel – sprawa zaczyna się komplikować.

na szczęście jest rozwiązanie.

wystarczy założyć dodatkową tabelkę do której będziemy dodawać nowy rekord przy rozpoczynaniu procedury obsługi każdego requestu, i będziemy tam wpisywać url'a i nazwę usera. a potem tylko baza musi umieć z tego skorzystać.

aby baza mogła powiązać dane z tej nowej tabelki (nazwijmy ją sobie “actions"), musi być w stanie jakoś powiązać późniejsze insert'y, update'y i delete'y z wpisami w actions. najlepiej to zrobić używając pidu (numer identyfikacyjny procesu) backendu postgresa.

w postgresie jest funkcja która zwraca ten pid:

SELECT pg_backend_pid();

dodajmy więc tabelkę actions, od razu z indeksem na to po czym będziemy szukać:

CREATE TABLE actions (
id BIGSERIAL,
backend_pid INT4 NOT NULL DEFAULT pg_backend_pid(),
action_on TIMESTAMP NOT NULL DEFAULT now(),
action_url TEXT,
action_user TEXT,
PRIMARY KEY (id)
);
CREATE INDEX ui_actions_bpao ON actions (backend_pid, action_on);

tabelkę history_objects modyfikujemy tak aby zawierała pola na urla i username (zamiast tego można dodać action_id, ale dodanie całych pól daje nam dodatkowe możliwości):

CREATE TABLE history_objects (
id BIGSERIAL,
modified_on TIMESTAMP NOT NULL DEFAULT now(),
modification_type TEXT NOT NULL DEFAULT '',
modification_url TEXT,
modification_user TEXT,
modified_id INT8,
modified_o_type TEXT,
PRIMARY KEY (id)
);

pozostało zmodyfikować triggery:

CREATE OR REPLACE FUNCTION trg_objects_ui() RETURNS TRIGGER AS
$BODY$
DECLARE
temprec actions%ROWTYPE;
BEGIN
SELECT * INTO temprec FROM actions WHERE backend_pid = pg_backend_pid() ORDER BY backend_pid DESC, action_on DESC LIMIT 1;
IF NOT FOUND THEN
temprec.action_url := 'unknown url. direct database access?';
temprec.action_user := 'database user: ' || CURRENT_USER;
END IF;
INSERT INTO history_objects (modified_id, modified_o_type, modification_type, modification_url, modification_user) VALUES (NEW.id, NEW.o_type, TG_OP, temprec.action_url, temprec.action_user);
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER trg_objects_ui AFTER INSERT OR UPDATE ON objects FOR EACH ROW EXECUTE PROCEDURE trg_objects_ui();
 
CREATE OR REPLACE FUNCTION trg_objects_d() RETURNS TRIGGER AS
$BODY$
DECLARE
temprec actions%ROWTYPE;
BEGIN
SELECT * INTO temprec FROM actions WHERE backend_pid = pg_backend_pid() ORDER BY backend_pid DESC, action_on DESC LIMIT 1;
IF NOT FOUND THEN
temprec.action_url := 'unknown url. direct database access?';
temprec.action_user := 'database user: ' || CURRENT_USER;
END IF;
INSERT INTO history_objects (modified_id, modified_o_type, modification_type, modification_url, modification_user) VALUES (OLD.id, OLD.o_type, TG_OP, temprec.action_url, temprec.action_user);
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
CREATE TRIGGER trg_objects_d AFTER DELETE ON objects FOR EACH ROW EXECUTE PROCEDURE trg_objects_d();

no to pora na test. najpierw zobaczymy jak to działa gdy nie zapiszemy nic do actions:

> SELECT * FROM objects;
id | o_type
----+--------
(0 ROWS)
 
> SELECT * FROM history_objects;
id | modified_on | modification_type | modification_url | modification_user | modified_id | modified_o_type
----+-------------+-------------------+------------------+-------------------+-------------+-----------------
(0 ROWS)
 
> SELECT * FROM actions;
id | backend_pid | action_on | action_url | action_user
----+-------------+-----------+------------+-------------
(0 ROWS)
 
> INSERT INTO objects (o_type) VALUES ('bleble');
INSERT 0 1
 
> INSERT INTO objects (o_type) VALUES ('bleble2');
INSERT 0 1
 
> UPDATE objects SET o_type = 'xxx' WHERE o_type = 'bleble';
UPDATE 1
 
> DELETE FROM objects WHERE o_type = 'bleble2';
DELETE 1
 
> SELECT * FROM objects;
id | o_type
----+--------
1 | xxx
(1 ROW)
 
> SELECT * FROM history_objects;
id |        modified_on         | modification_type |           modification_url           |   modification_user   | modified_id | modified_o_type
----+----------------------------+-------------------+--------------------------------------+-----------------------+-------------+-----------------
1 | 2006-07-15 12:58:54.16915  | INSERT            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           1 | bleble
2 | 2006-07-15 12:58:58.46624  | INSERT            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           2 | bleble2
3 | 2006-07-15 12:59:04.454578 | UPDATE            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           1 | xxx
4 | 2006-07-15 12:59:09.519242 | DELETE            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           2 | bleble2
(4 ROWS)

działa ładnie. to teraz zapiszmy coś do actions i powtórzmy test:

> INSERT INTO actions (action_url, action_user) VALUES ('/jakis_url.html', 'admin');
INSERT 0 1
 
> INSERT INTO objects (o_type) VALUES ('A:bleble');
INSERT 0 1
 
> INSERT INTO objects (o_type) VALUES ('A:bleble2');
INSERT 0 1
 
> UPDATE objects SET o_type = 'A:xxx' WHERE o_type = 'A:bleble';
UPDATE 1
 
> DELETE FROM objects WHERE o_type = 'A:bleble2';
DELETE 1
 
> SELECT * FROM objects;
id | o_type
----+--------
1 | xxx
3 | A:xxx
(2 ROWS)
 
> SELECT * FROM history_objects;
id |        modified_on         | modification_type |           modification_url           |   modification_user   | modified_id | modified_o_type
----+----------------------------+-------------------+--------------------------------------+-----------------------+-------------+-----------------
1 | 2006-07-15 12:58:54.16915  | INSERT            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           1 | bleble
2 | 2006-07-15 12:58:58.46624  | INSERT            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           2 | bleble2
3 | 2006-07-15 12:59:04.454578 | UPDATE            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           1 | xxx
4 | 2006-07-15 12:59:09.519242 | DELETE            | UNKNOWN url. direct DATABASE access? | DATABASE USER: depesz |           2 | bleble2
5 | 2006-07-15 13:00:15.098039 | INSERT            | /jakis_url.html                      | admin                 |           3 | A:bleble
6 | 2006-07-15 13:00:18.315004 | INSERT            | /jakis_url.html                      | admin                 |           4 | A:bleble2
7 | 2006-07-15 13:00:21.304025 | UPDATE            | /jakis_url.html                      | admin                 |           3 | A:xxx
8 | 2006-07-15 13:00:24.483061 | DELETE            | /jakis_url.html                      | admin                 |           4 | A:bleble2
(8 ROWS)

działa. i to całkiem ładnie. oczywiście mechanizm można dalej rozszerzać. dodawać nowe pola (ip, browser) czy z części rezygnować.

zmienna lista “cech” – ciąg dalszy

jakiś czas temu pisałem jak poradzić sobie z przechowywaniem zmiennej listy cech obiektów w bazach relacyjnych.

okazuje się, że nie tylko ja zetknąłem się z tym problemem (szokujące, czyż nie?).

dwóch, znanych w świecie postgresowym ludzi – teodor sigaev i oleg bartunov – zajęli się tym problemem.

jeśli ich nie kojarzycie – to są ludzie którzy stworzyli i rozwinęli tsearcha. zajmują się  optymalizacjami indeksów typu gist, wymyślili własny typ indeksów (gin) i ogólnie siedzą w źródłach postgresa chyba 24 godziny na dobę. ich prace są częściowo sponsorowane przez rosyjską (aha, obaj są z rosji) agencję do wspierania badań (taki ichniejszy komitet badań naukowych).

problemem cech się zajęli, pogłówkowali i zrobili. nowy typ danych do postgresa – hstore.

co potrafi?

w jednym polu może przechowywać dowolnie wiele par klucz/wartość. wyszukiwać rekordów mających określone pola w sposób który korzysta z indeksów.

operować na tych danych i ogólnie – dać programiście wreszcie dobre rozwiązanie tego problemu.

jak się tego używa? trywialnie. nie chcę tutaj kopiować dokumentacji, odsyłam was do przykładów. warto się zainteresować, bo soft działa ładnie, skutecznie i szybko.

jak dojść do posiadania domu. prawie za darmo.

trochę ponad rok temu pewien kanadyjczyk kyle mcdonald wpadł na nietypowy pomysł.

wymyślił mianowicie, że wymieni czerwony spinacz do papieru (taki zwykły spinacz, kawałek odpowiednio zakręconego drutu) na dom.

wariat? nie. doszedł do wniosku, że o ile oczywiście nikt mu nie da od razu domu, to o ile będzie się wymieniał non stop, to wartość rzeczy którą aktualnie ma będzie rosła i w końcu może dojdzie do posiadania domu.

12 lipca 2005 (rok temu) ogłosił na swojej stronie, że chce się wymienić. da komuś ten spinacz za coś innego. opisał po co to robi, jaki ma cel itd. i czekał.

nie czekał długo. 2 dni później podjął decyzję o wymianie spinacza na długopis w kształcie rybki. jeszcze tego samego dnia wymienił długopis na ceramiczny, robiony ręcznie uchwyt do drzwiczek z wyrysowanym na nim coś co miało wyglądać jak e.t. na prochach 🙂

uchwyt został następnie (po prawie 2 tygodniach) wymieniony na grilla. miesiąc i kolejną wymianę później kyle stał się posiadaczem kolejnego czerwonego przedmiotu – generatora prądowego o mocy 1000 wat.

potem kolejna wymiana – znowu miesiąc później. generator został wymieniony na beczkę piwa (a dokładniej beczkę bez piwa, która miała zostać napełniona na koszt poprzedniego właściciela gdy nowy właściciel powie jakie piwo chce mieć w środku :)) plus neon budweisera.

w międzyczasie kyle stał się sławny. na tyle sławny, że kolejna wymiana miała wymiar medialny.

kolejną osobą do wymiany okazał się być michel barrette – podobno tam (kanada) znana postać, mająca własny program w radiu.

przedmiot wymiany – skuter śnieżny. całość wzbudziła takie zainteresowanie, że moment wymiany miał widzów w postaci ekip filmowych kilku ogólnokrajowych (cbc) czy światowych (cnn) ekip telewizyjnych! programy o wymianie i o kyle'u zostały wyemitowane, a on stał się właścicielem “skutera który kiedyś należał do 2 znanych osób" (tzn. barrette'a i samego kyle'a).

w trakcie jednego z wywiadów telewizyjnych z kyle'em padło pytanie czy jest jakieś miejsce gdzie by nie pojechał aby dokonać wymiany. odpowiedź: do yakh w kolumbii brytyjskiej.

to oczywiście spowodowało zainteresowanie ludzi stamtąd. już następnego dnia kyle dostał telefon w którym zaoferowano mu:

  • bilety lotniczne w obie strony z dowolnego miejsca w ameryce północnej do cranbrook (45 minut drogi od yahk)
  • 1 dniowy pobyt obejmujący wyżywienie, jazdę na nartach, i oczywiście podróż skuterem śnieżnym do yahk.

oferującym był magazyn “SnoRiders West" wydawany w tamtych okolicach.

wiązał się z tym pewien haczyk – wydawcy snoriders west wymagali by wymiana nastąpiła właśnie w yahk, co – biorąc pod uwagę to co kyle powiedział wcześniej, że tam nie pojedzie, powodowało, że albo zrobi coś co powiedział, że nie zrobi, albo straci niezły deal :). kyle sobie oczywiście poradził – jakbyście nie zauważyli “ma łeb".

wycieczka do yahk okazała się tak kusząca, że już niedługo kyle dostał ofertę wymiany na samochód. działającego dostawczaka. używanego, ale w pełni na chodzie. obiecał, że za darmo zawiezie go gdziekolwiek w ameryce północnej na kolejną wymianę.

trochę czasu minęło – wszystkie te zamiany trwały tyle, że kolejna – bardzo istotna – miała miejsce dopiero 22 lutego 2006.

tym razem kyle wymienił (dostawczaka) na: kontrakt nagraniowy. kontrakt ten obejmował:

  • do 30 godzin w studiu nagraniowym
  • do 50 godzin miksowania i postprodukcji.
  • transport do nagrania do i z toronto skądkolwiek na świecie
  • utrzymanie w toronto w czasie nagrywania/miksowania
  • stworzony album zostanie zaprezentowany szefom sony-bmg (jednej z największych wytwórni muzycznych), oraz puszczany w satelitarnym radiu xm.

no i oczywiście będziesz miał za darmo reklamę jako jeden z uczestników wymiany w łańcuszku “oneredpaperclip" 🙂

kolejna wymiana zajęła więcej czasu – w końcu nie każdy musi być od razu zainteresowany nagraniem płyty, a dodatkowo kyle chciał zawsze wymieniać się na coś lepszego niż to co ma.

ale w końcu, niejaka judy gnant (na jej stronie można m.in. kupić jej płytę 🙂 złożyła ofertę której kyle nie mógł odrzucić.

mianowicie – oferta dotyczyła nieruchomości! domu. niestety nie oddawanego na własność, ale wynajmowanego za darmo na okres jednego roku!

dom mieści się w phoenix, w arizonie. ma 65 metrów, kominek i dębowe posadzki. i wygląda uroczo. do tego judy dorzuca darmową podróż dla 2 osób tam i z powrotem z/do dowolnego większego lotniska w ameryce północnej.

już kilka dni później kyle dokonał kolejnej wymiany. wymienił “rok w phoenix" na “popołudnie z alice'em cooperem". całe. dla fana alice'a to gratka nie lada.

dodatkowo kyle znowu trafił do telewizji, a na koncercie alice'a został zaproszony na scenę na której alice trzymał olbrzymi czerwony spinacz (film z tego jest na stronie kyle'a).

potem przez miesiąc nic się nie działo. a następnie kjyle wykonał zamianę która była mocno kontrowersyjna – wymienił popołudnie z alice cooperem na coś co (na pierwszy rzut oka) było zdecydowanie mniej warte! śnieżną kulę! nie byle jaką, bo zmotoryzowaną, podświetlaną różnymi kolorami no i jest to kula śnieżna nie z byle mikołajem czy reniferami, ale z muzykami grupy kiss!

wiele osób uważało, że ta zmiana to koniec wymian. albo przynajmniej start od prawie początku. w końcu wartość takiej kuli jest mała.

a jednak.

znalazła się osoba która chciała taką kulę, bo jest jednym z największych kolekcjonerów kul śnieżnych na świecie. i rzeczona osoba ( corbin bernsen) dała coś niesamowitego na wymianę. coś czego nie da się kupić. rolę w filmie.

jako rolę w filmie uznano:

  • jedną opłacaną w/g standardowych stawek, wymienioną w “liście płac", mówioną rolę w filmie “donna on demand" który to film będzie kręcony na jesieni.
  • utrzymanie w trakcie kręcenia (w tym miejsce do mieszkania)
  • bilet lotniczy tam i z powrotem skądkolwiek na świecie

ta wymiana (kula kiss za rolę w filmie) miała miejsce 2 czerwca 2006. a już 7 lipca – niecały rok od pierwszej wymiany – kyle wymienił rolę w filmie na dom. prawdziwy, stojący dom 🙂
dom jest w kanadzie, w kipling, saskatchewan. ma około 100 metrów kwadratowych w dwóch kondygnacjach. 3 sypialnie, 1.5 łazienki, kuchnię, pokój dzienny i jadalnię.

dodatkowo:

  • kyle dostanie “klucze do miasta"
  • zostanie honorowym burmistrzem na 1 dzień
  • zostanie uznany dożywotnio za honorowego obywatela miasta kipling
  • dzień w którym podpiszą wymianę zostanie obwieszczony dniem czerwonego spinacza
  • miasto wybuduje największy na świecie czerwony spinacz jako pomnik projektu.

i wielkie “joł" dla tego pana.
zdjęcia domu sa już na sieci i mozna pooglądać – jest tam też ten największy spinacz. muszę przyznać, że myślałem, że będzie większy 🙂

interesujące jest to, że miasto nie może zagrać w filmie, a burmistrz nie jest zainteresowany. cóż więc zrobią?

zorganizują w mieście przesłuchania w stylu idola – zwycięzca dostanie rolę. w komisji ma być corbin barnsen. plus jest spora szansa, że pojawi się alice cooper. a biorąc pod uwagę jak bardzo znany jest ten projekt to pewnie transmisje z przesłuchań będą w każdej większej stacji telewizyjnej w stanach i kanadzie.

tak trzymać! facet zrobił coś co wielu uważało za niemozliwe. nawet u mnie w firmie ktoś stwierdził, że tylko idioci by się wymieniali.

w tym co zrobił widać 100% odwrócenie historii stryjka co to siekierke na kijek zamieniał.

nowa wersja slony’ego!

jak nie wiesz co to slony, to polecam zajrzenie na strone projektu i poczytanie. w skrócie – silnik replikacyjny do postgresa, działający jako replikacja asynchroniczna master-multi_slave.

silnik co trzeba dodać działający stabilnie, wygodnie i szybko.

ostatnio pojawiła się nowa wersja. a właściwie zapowiedź nowej wersji – prerelease wersji 1.2.

w niej – sporo ulepszeń. tym razem nie są to ulepszenia które pomagają zestawić replikację (jak większośc wersji 1.1.x), ale modyfikacje tego jak sam core slony'ego działa. efekt ma być murowany – szybciej, skuteczniej, mniej zasobożernie, bardziej monitorowalnie :):

  • najważniejsze – spore zmiany w zarządzaniu pamięcią. slony teraz wykrywa kiedy ma do czynienia z dużymi rekordami i replikuje je w inny sposób dzięki czemu nie zżera tyle pamięci co poprzednie wersje
  • szybko zapychana tabela sl_log_1 została wreszcie “dopalona". slony będzie co jakiś czas przełączał się między sl_log_1 i sl_log_2, i jak upewni się, że nic z tej nieaktualnej nie jest już używane, to będzie ją czyścił poprzez TRUNCATE – dzięki czemu rozmiar tych tabel zostanie mocno ograniczony
  • poprawki tyczące się wykonywania poleceń modyfikujących bazę danych poprzez mechanizmy slony'ego – teraz kolejne polecenia będą wykonywane oddzielnie dzięki czemu zniknie problem z odwoływaniem się do dopiero-co utworzonych tabel i/lub pól.
  • poprawki tyczące się uruchamiania slony'ego jako serwisu w windowsach (nie używam więc nie jestem w stanie zweryfikować)
  • sporo poprawek tyczących się stabilności działania slonyego. teraz ma już być zbędne uruchamianie slonego przez watchdoga.

pełna lista zmian dostępna jest w tej chwili via cvsweb.

własne operatory upraszczające życie

na jednej z list postgresowych jakiś koleś zamarudził, że o ile warunek:

where pole!=3

działa poprawnie, to już

where pole!=-3 nie działa poprawnie.

wiele osób mu wyjaśniło, że:

  • standard sql nie oferuje operatora != w ogóle. do tego jest <>
  • != w postgresie to dodatek. takie “nice to have"
  • gdy doda się spację między “!=" a “-3" to działa ok (where pole!= -3)
  • całość działa tak dlatego, że system nie ma jak rozróżnić czy chodzi o “pole" “!=" “-3" czy może “pole" “!=-" “3", a to drugie jest w pełni legalną nazwę operatora – którego domyślnie nie ma, ale może być – w końcy postgres ma architekturę mocno otwartą i można sobie dodefiniować własne operatory czy cokolwiek.

facet oczywiście marudził dalej, że powinno działa.

postanowiłem pomóc. jak? dopisać operator !=- który zadziała tak jak facet chce.

najpierw potrzebna była funkcja. 30 sekund później miałem:

CREATE FUNCTION not_equals_minus(int8, int8) RETURNS bool AS $BODY$
SELECT $1 <> -$2;
$BODY$ LANGUAGE SQL IMMUTABLE STRICT;

proste i szybkie. działa dla liczb typu int8 (bigint), ale przerobienie do innych typów to trywiał.

to teraz definiojemy operator. jak? nie pamiętam. ale szybki search w manualu dał mi odpowiednią stronę, z jej pomocą napisałem:

CREATE OPERATOR !=- (
leftarg = int8,
rightarg = int8,
PROCEDURE = not_equals_minus,
commutator = !=-
);

efekt? działa. można teraz zrobić WHERE pole <> -3, WHERE pole != -3, lub WHERE pole!=-3. kocham postgresa 🙂

chory regexp do dat

tak się złożyło, że brałem udział w projekcie którego częścią miało być przygotowanie formatu wymiany danych opartego na xml'u, a także przygotowanie do tego schemy walidującej.

trywiał.

zrobiłem xml'a, wszystko fajnie. w kilku miejscach trzeba było podać datę i godzinę.

klient chciał by było human-readable, więc wyspecifikowałem coś takiego:

format jasny i prosty. okazało się, że nie. xml schema aby móc walidować datę musi mieć ją podaną tak: 2006-07-13T20:47:00 (a przynajmniej tak mówi nasz firmowy magik od schemy).

jak format jest inny to można walidować regexp'em.

oops. myślę sobie.

walidacja miała być mocna więc te daty i godziny trzeba by jakoś dobrze sprawdzać.

formatu zmienić nie mogę bo zatwierdzony przez komisję w skład której wchodzili ludzie z 4 krajów (taki trochę międzynarodowy projekt).

no to kombinujemy z regexpem.

godzina była relatywnie prosta:

  • sekundy i minuty są z zakresu 00 do 59. więc najprostszy regexp na nie: [0-5]\d (używam tu notacji perlowej bo w perlu tego regexpa testowaliśmy, ale finalnie został przerobiony na xml-schemowego regexpa przez zamianę \d na [0-9]).
  • godziny – no tu trudniej od 00 do 19 to nie problem. potem jeszcze 20-23. da się: ( [01]\d | 2[0-3] ) (używam notacji regexpów perla z rozszerzeniem /x dzięki czemu jest to trochę czytelniejsze.
  • cały regexp na godzinę: ( [01]\d | 2[0-3] ) : [0-5]\d : [0-5]\d . tak. wiem, że mogłem na końcu dać: ( : [0-5]\d){2}, ale wiedziałem, że będę to potem konwertował do regexpów ze schemy, więc wolałem nie przeginać z “cudami".

data. no to jest trudniejsze. haczyk tkwi w latach przestępnych. w dodatku na przestępność są 3 reguły: %4, %100 i %400.

nic to. twardym trzeba być, nie miętkim.

stwierdziłem, że zrobię tego regexpa bazując na ciągu wielu alternatyw:

  • dla dowolnego roku, część miesięcy (01, 03, 05, 07, 08, 10, 12) ma 31 dni, więc można użyć: \d\d\d\d – (0[13578]|1[02]) – ([012]\d|3[01])
  • dla dowolnego roku, część miesięcy (04, 05, 09, 11) ma 30 dni, więc używam: \d\d\d\d – (0[469]|11) – ([012]\d|30)
  • w każdym roku luty ma co najmniej 28 dni: \d\d\d\d – 02 – ([01]\d|2[0-8])

no i teraz zaczynają się schody.

zacznijmy od prostszego – przestępne są te lata które są podzielne przez 4, ale nie przez 100.

ponieważ 100 jest podzielne przez 4, to znaczy, że:

  • pierwsze dwie cyfry roku są nieistotne
  • ostatnie dwie – nie mogą być 00 (bo wtedy rok jest podzielny przez 100)
  • ostatnie dwie muszą być podzielne przez 4

no to już mamy pewne zręby. w jaki sposób regexpem poznać czy liczba jest podzielna przez 4?

zobaczmy jakie to liczby. $ perl -e ‘$_%4||printf “%02u, “, $_ for 0..99'

00, 04, 08, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96

co tu widać? niewiele. ale może jakbym tak zapisał to w ten sposób:

00, 04, 08, 12, 16,

20, 24, 28, 32, 36,

40, 44, 48, 52, 56,

60, 64, 68, 72, 76,

80, 84, 88, 92, 96

WOW! mam regułkę. jeśli pierwsza cyfra jest parzysta, to druga musi być jedną z 0, 4, 8. a jeśli nieparzysta, to druga musi być równa 2 lub 6.

zapis regexpowy: ( [02468][048] | [13579][26] )

no tak. ale na to trzeba nałożyć warunek, że te cyfry to nie może być 00. to powoduje, że jeśli pierwszą cyfrą (z dwóch) jest 0, to druga może być tylko 4 lub 8. tak więc regexp na liczbę 2 cyfrową podzielną przez 4, różną od 00 jest:

( 0[48] | [2468][048] | [13579][26]

tak więc pierwszy warunek na rok przestępny – podzielny przez 4, ale niepodzielny przez 100 wygląda tak:

\d\d ( 0[48] | [2468][048] | [13579][26] ) – 02 – [012]\d

ok. pozostało wziąć warunek na lata podzielne przez 100, ale podzielne też przez 400. i to sie okazuje trywialne. po tak jak przed chwilą liczyłem lata podzielne przez 4, tak lata podzielne przez 400 liczy się tak samo:

( 0[48] | [2468][048] | [13579][26] ) 00 – 02 – [012]\d
finalny regexp walidujący datę:

(
\d\d\d\d – (0[13578]|1[02]) – ([012]\d|3[01])
|
\d\d\d\d – (0[469]|11) – ([012]\d|30)
|
\d\d\d\d – 02 – ([01]\d|2[0-8])
|
\d\d ( 0[48] | [2468][048] | [13579][26] ) – 02 – [012]\d
|
( 0[48] | [2468][048] | [13579][26] ) 00 – 02 – [012]\d
)
dodać do tego godzinę i już 🙂 super.

najzabawniejsze w tym, że przeprowadzony przeze mnie test pokazał, że ten regexp jest szybszy niż walidowanie bazujące na porównywaniu liczb!

zrobiłem testowy program. odpaliłem i dostałem taki wynik:

  • function  5348/s
  • regexp   14045/s

dziwne.

oczywiście nie polecam walidowania daty regexpami. czytelniejsze są normalne funkcje. a prędkość 5300/s to i tak aż za dużo.

nowy browser – z mocno ukierunkowanym zastosowaniem

na rynku pojawiła się nowa przeglądarka www. zasadniczo – po co komu kolejna?

ale tak jak flock jest ukierunkowany na “social networks", tak heatseek jest przeglądarką ukierunkowaną na pornografię.

działa tylko pod windows i bazuje na silniku renderującym explorera – czyli większość stron xxx będzie się wyświetlała dobrze.

a co takiego eXXXtra ma heatseek?

  • aby uruchomić program trzeba podać hasło. wszystkie ściągnięte pliki (obrazki, filmy) są automatycznie szyfrowane, i ich oglądanie jest możliwe jedynie poprzez heatseek. koniec z przypadkowym odkryciem twojej kolekcji przez żonę/dziewczynę/rodziców.
  • eliminują spyware, wirusy i popupy. pewnie nie wszystkie, bo technologie się zmieniają, ale przynajmniej walczą.
  • wygoda użytkowania – ściąganie plików/filmów inicjalizuje się jednym kliknięciem. po ściągnięciu można tworzyć playlisty czy grupować materiał w dowolny sposób
  • przy odtwarzaniu filmów przeglądarka obsługuje bookmarkowanie – dzięki czemu można szybko skoczyć do ulubionej sceny
  • “panic button" – jeden klik i aplikacja się zamyka. bez pytania, czy czegokolwiek innego

wersja podstawowa przeglądarki jest za darmo. wersja premium kosztuje jednorazowo $20, i daje kilka dodatkowych funkcji z których najważniejszą jest możliwość odszyfrowania ściągniętych plików i zapisania w dowolnym miejscu aby móc np. odtwarzać czymś innym nić heatseek.

pozostaje jedynie postawić sobie gdzieś windows 🙂