<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Athlan • Piotr Pelczar • blog programisty &#187; Przemyślenia</title>
	<atom:link href="http://athlan.pl/kategoria/przemyslenia/feed/" rel="self" type="application/rss+xml" />
	<link>http://athlan.pl</link>
	<description>Napisać kod zrozumiały dla komputera potrafi byle głupek. Dobrzy programiści tworzą kod zrozumiały dla człowieka...</description>
	<lastBuildDate>Sun, 12 Jun 2011 16:43:13 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3</generator>
		<item>
		<title>PHP cache, semafory</title>
		<link>http://athlan.pl/php-cache-semafory/</link>
		<comments>http://athlan.pl/php-cache-semafory/#comments</comments>
		<pubDate>Sat, 11 Jun 2011 22:11:46 +0000</pubDate>
		<dc:creator>Athlan</dc:creator>
				<category><![CDATA[Databases]]></category>
		<category><![CDATA[Optymalizacja]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Planeta]]></category>
		<category><![CDATA[Przemyślenia]]></category>
		<category><![CDATA[Publikacje]]></category>
		<category><![CDATA[Server]]></category>
		<category><![CDATA[Solutions]]></category>

		<guid isPermaLink="false">http://athlan.pl/?p=728</guid>
		<description><![CDATA[Praktyka cache&#8216;owania danych jest powszechna wśród programistów aplikacji webowych ze względu na optymalizację dostępu do danych bezpośrednio ze źródła ich pochodzenia, a w szczególności: trudność dostępu (np. wykonanie skomplikowanych połączeń), ograniczenia dostępu (np. limit odpytywania), długi czas oczekiwania na dane; powodów jest wiele. O ile tematyką stworzenia samego mechanizmu cache zajęli się m.in. Nospor, możecie [...]]]></description>
			<content:encoded><![CDATA[<p>Praktyka <strong>cache</strong>&#8216;owania danych jest powszechna wśród programistów aplikacji webowych ze względu na optymalizację dostępu do danych bezpośrednio ze źródła ich pochodzenia, a w szczególności:</p>
<ul>
<li>trudność dostępu (np. wykonanie skomplikowanych połączeń),</li>
<li>ograniczenia dostępu (np. limit odpytywania),</li>
<li>długi czas oczekiwania na dane; powodów jest wiele.</li>
</ul>
<p>O ile tematyką stworzenia samego mechanizmu cache zajęli się m.in. <a href="http://forum.php.pl/klasa-Cache-t49472.html" rel="nofollow">Nospor</a>, możecie podejrzeć jak to wygląda w <a href="http://framework.zend.com/manual/en/zend.cache.html" rel="nofollow">Zend_Cache</a>, Symfony, czy <a href="http://docs.kohanaphp.com/libraries/cache" rel="nofollow">Kohana</a>; tak ja chciałbym zwrócić uwagę na jeszcze jedną rzecz.</p>
<p>Zazwyczaj schemat kodu wygląda mniej więcej tak:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #339933;">&lt;</span> ?php
<span style="color: #000088;">$oCache</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> Cache<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #666666; font-style: italic;">// tworzony jest jakis obiekt cache</span>
&nbsp;
<span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$oCache</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">expired</span><span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">3600</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">||</span> <span style="color: #339933;">!</span><span style="color: #990000;">is_array</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$aData</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$oCache</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">load</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #666666; font-style: italic;">// sprawdzamy, czy jest cache i nie wygasł</span>
<span style="color: #009900;">&#123;</span>
  <span style="color: #000088;">$aData</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$oModel</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">GetSomething</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #666666; font-style: italic;">// zbieramy dane z bazy danych</span>
  <span style="color: #000088;">$oCache</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">save</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$aData</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// $aData przechowuje nasze dane do użytku</span>
<span style="color: #000000; font-weight: bold;">?&gt;</span></pre></div></div>

<h2>Symulacja, parę linijek kodu, a ile nieszczęść.</h2>
<p>Wszystko działa pięknie, dopóki nie spotkamy się z sytuacją, gdy setki osób (procesów) jednocześnie zechcą zbierać takie dane z bazy danych. Przeprowadźmy zatem krótką dywagację. Załóżmy, że użytkownik #1 wchodzi na stronę, stwierdza, że nie ma cache, lub jest nieświeży, wówczas przechodzi do połączenia się z bazą danych i zaczyna zbierać dane. W tym samym czasie, zanim użytkownikowi #1 zostaną zwrócone dane wchodzi użytkownik #2, który stwierdza, że nie ma cache, bo użytkownik #1 jeszcze nie zebrał danych, postanawia połączyć się z bazą i zrobić to samo, co użytkownik #1, powtarzając niepotrzebnie czynność i dodatkowo obciążając bazę. Można by iść dalej i wprowadzić <em>n</em> użytkowników, którzy powtarzają czynność, dopóki dane nie pojawią się w cache i kolejni użytkownicy będą z niego korzystać. Co się stanie natomiast, gdy kolejka tak narośnie, że użytkownikowi #1 zabraknie zasobów systemowych, aby ukończyć proces zbierania danych, co spowoduje, że pozostałym też? Kolejka będzie wydłużała się w nieskończoność, póki system operacyjny nie podejmie żadnych działań (np. odłączy bazę danych, lub po prostu wyłączy serwer, np. w <a href="http://pl.wikipedia.org/wiki/IIS" rel="nofollow">IIS7</a> wyłączy cały application pool). Aby doszło do tej kolizji nie jest potrzebne wcale natężenie użytkowników, serwer może akurat np. zajmować się wysyłką maili lub nieoptymalnie zrobionym procesem, który zajmuje zasoby, a w tym czasie wejdzie tylko pięciu użytkowników.</p>
<p>Parę linijek kodu, a ile nieszczęść.</p>
<h2>Pojęcie semafora.</h2>
<blockquote><p>Semafor w informatyce &#8211; jest chronioną zmienną lub abstrakcyjnym typem danych, który stanowi klasyczną metodę kontroli dostępu przez wiele procesów do wspólnego zasobu w środowisku programowania równoległego.</p></blockquote>
<p>Więcej na temat semaforów na <a href="http://pl.wikipedia.org/wiki/Semafor_(informatyka)" rel="nofollow">Wikipedii</a>, bądź w <em>Podstawy informatyki / Stefan Węgrzyn. &#8211; Warszawa : Państwowe Wydawnictwo Naukowe, 1982</em>.</p>
<h2>Podejście do problemu.</h2>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #339933;">&lt;</span> ?php
<span style="color: #000088;">$oCache</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> Cache<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$oCache</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">expired</span><span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">3600</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">||</span> <span style="color: #339933;">!</span><span style="color: #990000;">is_array</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$aData</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$oCache</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">load</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
  <span style="color: #000088;">$oCache</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">savePrepare</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #666666; font-style: italic;">// stawiamy semafor</span>
&nbsp;
  <span style="color: #000088;">$aData</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$oModel</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">GetSomething</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #000088;">$oCache</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">save</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$aData</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #666666; font-style: italic;">// metoda save() może (nie musi) od razu zwolnić semafor, gdy próba zapisu się zakończy</span>
  <span style="color: #666666; font-style: italic;">// jeżeli metoda save() nie zwalnia zasobu, możemy np. użyć:</span>
  <span style="color: #666666; font-style: italic;">// $oCache-&gt;saveFinalize();</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// $aData przechowuje nasze dane do użytku</span>
<span style="color: #000000; font-weight: bold;">?&gt;</span></pre></div></div>

<p>Rozwiązaniem jest zastosowanie <strong>semafora</strong> blokującego dostęp do zasobu (w tym przypadku abstrakcyjnie &#8220;cache&#8221;, mniej abstrakcyjnie może być to plik na dysku, przestrzeń w pamięci operacyjnej, rekord w bazie danych, cokolwiek, co cache przerzymuje). Dla wartości semafora = 1 zasób jest wolny (nieużywany, jest 1 cache), gdy jest mniejszy/równy 0 zasób jest zajęty, ktoś z niego &#8220;korzysta&#8221;. Zajętość zasobu powinna być sprawdzana przy próbie odczytu. Dopóki zasób nie zostanie zwolniony, nie będzie można określić, czy są dane w cache. Jeżeli nie można określić, czy dane są w cache, należy zaczekać na zwolnienie zasobu.</p>
<p>Teraz nasze rozwiązanie nie dopuści do przytoczonej w powyższym przykładzie sytuacji. Zanim cache nie zostanie odblokowany po próbie zapisu, nie uzyskamy odczytu, czekając na niego i nie przechodząc w skrypcie nigdzie dalej.</p>
<p>Gdy <code>save()</code> się nie powiedzie? Można zastosować timeouty odczytu na <code>load()</code>. Wówczas złapalibyśmy wyjątek i przeszli dalej do realizacji zapisu, tak, jakby semafora nie było.</p>
<h2>Implementacja.</h2>
<p>Do swoich kodów podchodzę jak najbardziej abstrakcyjnie (tutaj idealnie nada się <a href="http://pl.wikipedia.org/wiki/Fabryka_abstrakcyjna_(wzorzec_projektowy)">wzorzec fabryki</a>), zatem stworzyłem klasę <em>Cache</em>, która obsługuje &#8216;silniki&#8217; implementujące interfejs <em>Cache_Engine</em>. Jednym z nich jest silnik <em>Cache_Engine_File</em>, który wykorzystuje pliki na dysku do składowania cache.</p>
<p>Najprostszym semaforem dla plików jest funkcja <code><a href="http://php.net/flock">flock()</a></code> (gotowe, sprawdzone rozwiązanie, w dodatku na poziomie systemu plików, nic tylko implementować). Sprawa wygląda bardzo prosto, dopóki nie zwolnimy flagi <code>LOCK_EX</code> po jej założeniu, ludzie nie będą czytali z pliku, czekając na zwolnienie dostępu. Ktoś powie: truizm, blokować pliki powinno się przed wykonywaniem na nich operacji. Tak. Ale grunt, w którym miejscu to zablokowanie nastąpi. Wykorzystujemy blokowanie do wyższego celu.</p>
<p>Wg. dokumentacji nie można polegać na <code>flock()</code> w przypadku Windows98 oraz systemów FAT32. Zbyt dużym poziomem abstrakcji jest dla mnie stawianie serwisu na pamięci flash lub Win98, ale faktycznie, najprostsza pamięć flash z systemem FAT32 może się czasem zdarzyć w serwerowniach i nie jest to wcale taki głupi pomysł. Co wtedy? Jako semafor możemy stworzyć plik z suffiksem <em>.lock</em> obok tworzonego pliku cache. Gdy plik istnieje oznacza to, że cache jest zablokowany, jeżeli nie &#8211; jest wolny. Czekamy tak długo, aż zostanie usunięty plik <em>.lock</em>.</p>
<h2>Przykładowy kod źródłowy.</h2>
<p>Przykładowy kod źródłowy obsługuje <em>Cache_Engine_File</em> oraz <em>Cache_Engine_Filelock</em>, gdzie w drugim przypadku można klasy użyć spokojnie na partycjach FAT32. Kod jest przykładowy, dlatego nie obsługuje m.in. zagnieżdżania plików w katalogach, usuwanie cache&#8217;u itd, zaimplementowałem tylko zapis i odczyt.</p>
<p>Klasy zostały napisane tak, aby zgłaszane przez nie błędy były <a href="http://www.php.net/manual/en/spl.exceptions.php">zgodnie z ideologią hierarchiczną Exceptions w PHP</a>, przy okazji zapraszam do lektury wpisu <a href="http://www.zyxist.com/pokaz.php/wyjatki_w_php">&#8220;Wyjątki w PHP&#8221;</a> autorstwa Tomasza Jędrzejewskiego (Zyxits).</p>
<ul>
<li><a href="http://athlan.pl/code/lib-cache/Cache">Klasa Cache</a>, Cache_Exception, abstrakcja Cache_Engine</li>
<li><a href="http://athlan.pl/code/lib-cache/Cache/Engine/File">Klasa Cache_Engine_File</a>, która bazuje na plikach</li>
<li><a href="http://athlan.pl/code/lib-cache/Cache/Engine/Filelock">Klasa Cache_Engine_Filelock</a>, która bazuje na plikach + tworzy pliki <em>.lock</em></li>
</ul>
<p>Przykładowe czekanie na zwolnienie pliku <em>.lock</em>:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #339933;">&lt;</span> ?php
&nbsp;
<span style="color: #000000; font-weight: bold;">protected</span> <span style="color: #000000; font-weight: bold;">function</span> _waitUnlock<span style="color: #009900;">&#40;</span><span style="color: #000088;">$iWaitTimeout</span><span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
  <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$iWaitTimeout</span><span style="color: #009900;">&#41;</span>
  <span style="color: #009900;">&#123;</span>
    try
    <span style="color: #009900;">&#123;</span>
      <span style="color: #666666; font-style: italic;">// quick first check</span>
      <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">is_file</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span>_path<span style="color: #009900;">&#40;</span><span style="color: #009900; font-weight: bold;">false</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'lock'</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span>
      <span style="color: #009900;">&#123;</span>
        <span style="color: #666666; font-style: italic;">// wait for unlock file</span>
        <span style="color: #000088;">$iWaitTimeout</span> <span style="color: #339933;">/=</span> <span style="color: #cc66cc;">1000000</span><span style="color: #339933;">;</span>
        <span style="color: #000088;">$iLockTime</span> <span style="color: #339933;">=</span> <span style="color: #990000;">microtime</span><span style="color: #009900;">&#40;</span><span style="color: #009900; font-weight: bold;">true</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
        <span style="color: #000088;">$bLockWait</span> <span style="color: #339933;">=</span> <span style="color: #009900; font-weight: bold;">true</span><span style="color: #339933;">;</span>
&nbsp;
        <span style="color: #666666; font-style: italic;">// wait for the file</span>
        try
        <span style="color: #009900;">&#123;</span>
          <span style="color: #b1b100;">while</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">is_file</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span>_path<span style="color: #009900;">&#40;</span><span style="color: #009900; font-weight: bold;">false</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'lock'</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span>
          <span style="color: #009900;">&#123;</span>
            <span style="color: #000088;">$iLockWaitDelta</span> <span style="color: #339933;">=</span> <span style="color: #990000;">microtime</span><span style="color: #009900;">&#40;</span><span style="color: #009900; font-weight: bold;">true</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">-</span> <span style="color: #000088;">$iLockTime</span><span style="color: #339933;">;</span>
&nbsp;
            <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$iLockWaitDelta</span> <span style="color: #339933;">&gt;</span> <span style="color: #000088;">$iWaitTimeout</span> <span style="color: #339933;">&amp;&amp;</span> <span style="color: #000088;">$iWaitTimeout</span> <span style="color: #339933;">!==</span> <span style="color: #009900; font-weight: bold;">true</span><span style="color: #009900;">&#41;</span>
              <span style="color: #009900;">&#123;</span> <span style="color: #000088;">$bLockWait</span> <span style="color: #339933;">=</span> <span style="color: #009900; font-weight: bold;">false</span><span style="color: #339933;">;</span> <span style="color: #b1b100;">break</span><span style="color: #339933;">;</span> <span style="color: #009900;">&#125;</span>
&nbsp;
            <span style="color: #990000;">usleep</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">rand</span><span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">1</span><span style="color: #339933;">,</span> <span style="color: #cc66cc;">999</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
          <span style="color: #009900;">&#125;</span>
        <span style="color: #009900;">&#125;</span>
        <span style="color: #666666; font-style: italic;">// cache lock path does not exists</span>
        catch<span style="color: #009900;">&#40;</span>Cache_Exception_Runtime <span style="color: #000088;">$oE</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><span style="color: #009900;">&#125;</span>
&nbsp;
        <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span><span style="color: #000088;">$bLockWait</span><span style="color: #009900;">&#41;</span>
          <span style="color: #b1b100;">throw</span> <span style="color: #000000; font-weight: bold;">new</span> Cache_Exception_Runtime<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'Unable to access cache, it is totally locked, after &quot;'</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$iWaitTimeout</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'&quot; s.'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
      <span style="color: #009900;">&#125;</span>
    <span style="color: #009900;">&#125;</span>
    <span style="color: #666666; font-style: italic;">// cache lock path does not exists</span>
    catch<span style="color: #009900;">&#40;</span>Cache_Exception_Runtime <span style="color: #000088;">$oE</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><span style="color: #009900;">&#125;</span>
  <span style="color: #009900;">&#125;</span>
  <span style="color: #b1b100;">else</span>
  <span style="color: #009900;">&#123;</span>
    try
    <span style="color: #009900;">&#123;</span>
      <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">is_file</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span>_path<span style="color: #009900;">&#40;</span><span style="color: #009900; font-weight: bold;">false</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'lock'</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span>
        <span style="color: #b1b100;">throw</span> <span style="color: #000000; font-weight: bold;">new</span> Cache_Exception_Runtime<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'Unable to access cache, it is currently locked, after &quot;'</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$iWaitTimeout</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'&quot; s.'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
    <span style="color: #666666; font-style: italic;">// cache path does not exists</span>
    catch<span style="color: #009900;">&#40;</span>Cache_Exception_Runtime <span style="color: #000088;">$oE</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><span style="color: #009900;">&#125;</span>
  <span style="color: #009900;">&#125;</span>
&nbsp;
  <span style="color: #b1b100;">return</span> <span style="color: #009900; font-weight: bold;">true</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">?&gt;</span></pre></div></div>

]]></content:encoded>
			<wfw:commentRss>http://athlan.pl/php-cache-semafory/feed/</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
		<item>
		<title>Indeksowanie baz danych, funkcje mieszające</title>
		<link>http://athlan.pl/indeksowanie-baz-danych-funkcje-mieszajace/</link>
		<comments>http://athlan.pl/indeksowanie-baz-danych-funkcje-mieszajace/#comments</comments>
		<pubDate>Wed, 13 Apr 2011 19:20:40 +0000</pubDate>
		<dc:creator>Athlan</dc:creator>
				<category><![CDATA[Databases]]></category>
		<category><![CDATA[Matematyka]]></category>
		<category><![CDATA[Optymalizacja]]></category>
		<category><![CDATA[Planeta]]></category>
		<category><![CDATA[Przemyślenia]]></category>
		<category><![CDATA[Publikacje]]></category>
		<category><![CDATA[Solutions]]></category>
		<category><![CDATA[baza]]></category>
		<category><![CDATA[baza danych]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[indeksowanie]]></category>
		<category><![CDATA[optymalizacja]]></category>

		<guid isPermaLink="false">http://athlan.pl/?p=691</guid>
		<description><![CDATA[Występowanie złożonych baz danych jest coraz bardziej popularne, a komercyjne rozwiązania praktykują składowanie informacji nie tylko na pojedynczych bazach, przestrzeniach, dyskach, czy nawet serwerach. Szybki dostęp do danych to podstawa, dlatego pochylimy się nad czysto teoretycznym problemem dostępu do informacji, które wprawdzie są rozwiązane i zaszyte w mechanizmach poruszania się po większości baz, natomiast ich [...]]]></description>
			<content:encoded><![CDATA[<p>Występowanie złożonych baz danych jest coraz bardziej popularne, a komercyjne rozwiązania praktykują składowanie informacji nie tylko na pojedynczych bazach, przestrzeniach, dyskach, czy nawet serwerach. Szybki dostęp do danych to podstawa, dlatego pochylimy się nad czysto teoretycznym problemem dostępu do informacji, które wprawdzie są rozwiązane i zaszyte w mechanizmach poruszania się po większości baz, natomiast ich znajomość pozwoli dodatkowo zoptymalizować struktury, które projektujemy. Z góry podkreślam, że artykuł jest bynajmniej wyczerpujący, specjalistycznej i bardziej szczegółowo zarysowanej teorii baz danych należy szukać w publikacjach i tu zachęcam do odwiedzenia politechnicznych bibliotek.</p>
<p>Baza danych jako zbiór informacji powinna oferować trzy podstawowe operacje:</p>
<ul>
<li><strong>Szukanie</strong> jako dostęp do pojedynczego, unikatowego „obiektu” (zazwyczaj rekordu) w bazie danych.</li>
<li><strong>Wyszukiwanie</strong> jako dostęp do elementów spełniających dane kryteria.</li>
<li><strong>Modyfikacja</strong> danych.</li>
</ul>
<h2>Pojęcie klucza</h2>
<p><strong>Kluczem</strong> w bazie danych jest atrybut każdego elementu/rekordu jakiejś klasy (np. pojedynczej tabli danych), który pomoże go zidentyfikować przy szukaniu lub wyszukiwaniu w sposób jednoznaczny (wtedy mówi się o kluczu podstawowym <em>Primary Key</em>) lub niejednoznaczny (wtedy mówi się o indeksie <em>Index</em>). Za pomocą klucza jesteśmy w stanie dostać się do rekordu przeszukując tylko strukturę indeksów, zamiast samą bazę.</p>
<h2>Skrócenie drogi dostępu</h2>
<p>Przy szukaniu konkretnego elementu, którego unikalny atrybut jest z góry znany używamy właśnie <em>kluczy podstawowych</em>. Jest to gigantyczne przyspieszenie procesu wyszukania elementu. Jak wiemy, niektóre klucze zachowują pewną prawidłowość, na przykład stale rosną. Najprostszym przykładem jest identyfikator rekordu, którego wartość zazwyczaj się inkrementuje. Klucze to nic innego jak para informacji: wartość klucza oraz adres komórki pamięci, do której klucz należy.</p>
<p>Wykorzystanie klucza podstawowego to wskazanie miejsca ulokowania rekordu w dowolnej pamięci  (na przykład adresu pamięci, adresu na dysku, offset w pliku, itd.)</p>
<h2>Uporządkowany zbiór kluczy podstawowych</h2>
<p>Co nam daje uporządkowany zbiór kluczy? Żeby dowiedzieć się, gdzie w pamięci jest ulokowany nasz rekord, najpierw trzeba dostać się do wartości klucza. W przypadku uporządkowanego zbioru danych wartości kluczy możemy w łatwy sposób go odnaleźć, na przykład <em>metodą połowienia zbioru</em> lub dostępu do wcześniej ułożonego <em>drzewa</em>. Najoptymalniejsze do odszukiwania jest <em>drzewo ważone</em>, ponieważ w przypadku nieważonego przy stale wzrastającej wartości klucza przy prawidłowości, że prawe gałęzie drzewa mają wartości większe, drzewo rosłoby tylko w jedną stronę, a w rezultacie otrzymalibyśmy listę, w której odszukiwanie nie jest optymalnym rozwiązaniem. Ważenie drzewa nie jest jednak rozsądnym rozwiązaniem w bazach danych, w których jest więcej żądań (a właściwie czasu propagacji) do zapisu danych, niż odczytu.</p>
<p>Suma sumarum, w zależności od typu bazy (relacyjna, obiektowej, strumieniowej, itd.) oraz jej złożoności, należy wybrać odpowiedni mechanizm układania indeksów.</p>
<h2>Funkcja mieszająca</h2>
<p>Rodzi się pytanie. Gdy mamy taką strukturę składowania indeksów niejednoznacznych, tj. kilka rekordów może mieć dokładnie taką samą wartość klucza. Prostym przykładem jest indeksowanie dłuższych ciągów znaków, do których chcemy mieć natychmiastowy dostęp bez wyszukiwania ich w bazie danych w sposób bezpośredni. Do identyfikacji takich struktur służą <a href="http://pl.wikipedia.org/wiki/Tablica_mieszaj%C4%85ca">funkcje mieszające</a>. <em>Przykład</em>. Wyobraźmy sobie, że podczas zapisu danych podając dane jako argument funkcji mieszającej, zamieniamy każdy znak ciągu znaków na odpowiadający mu kod ASCII, następnie sumujemy liczby i dzielimy modulo 90, nasz wynik to wartość indeksu. Tę samą operację wykonujemy dla kryterium późniejszego wyszukiwania podając go jako argument funkcji mieszającej. Wystarczy porównać nasze kryterium z kluczami. Mamy 90 możliwości otrzymanych wyników.</p>
<p>Działanie funkcji mieszającej:</p>
<p><img class="aligncenter size-full wp-image-693" title="funkcja-mieszajaca-dzialanie" src="http://athlan.pl/wp-content/uploads/funkcja-mieszajaca-dzialanie.png" alt="" width="232" height="225" /></p>
<p>Im większe modulo, tym bardziej rozległy indeks i bardziej unikatowy indeks. Niestety, pod jednym kluczem może znajdować się wiele rekordów, przykładowo: ABC, CAB, AAD, AE, F, itd&#8230; wówczas występuje tzw. <em>kolizja</em>. Podstawową wadą funkcji mieszającej może być złożoność obliczeniowa dla zwracanej wartości. Ponadto obecne systemy baz danych zapewniają ciągłość z góry zadeklarowanej pamięci, zatem przeszukiwanie takich komórek może być znacznie szybsze, od przeszukiwania kluczy. Funkcja mieszająca plus indeksowanie adresów jest zdecydowanie dobrym rozwiązaniem, gdy przeszukiwanie indeksów jest korzystniejsze pod względem czasu dostępu do informacji (np. czas propagacji dysku, odszukanie fragmentu pliku, etc.).</p>
<h2>Występowanie kolizji</h2>
<p>Metod <a href="http://pl.wikipedia.org/wiki/Tablica_mieszaj%C4%85ca#Rozwi.C4.85zywanie_problemu_kolizji">obsługi kolizji</a> jest bardzo wiele. Podstawową jest stworzenie listy elementów, które są przypisane do danego klucza. Może ich być wiele, natomiast to i tak bardzo dobra optymalizacja przeszukiwania bazy danych.</p>
<p><img class="aligncenter size-full wp-image-694" title="funkcja-mieszajaca-kolizje" src="http://athlan.pl/wp-content/uploads/funkcja-mieszajaca-kolizje.png" alt="" width="668" height="367" /></p>
<p>Częstotliwość występowania kolizji w grubym przybliżeniu obrazuje wykres. Zauważmy, że jeżeli wyczerpiemy ~60% możliwości wystąpienia tych samych kluczy, kolizyjność wzrasta wykładniczo, a używanie funkcji mieszących przy wstawianiu rekordu bazy danych staje się nieoptymalne, w zależności od implementacji korekcji kolizji (powtórzenia rozwiązania kolizji). Przy dołączaniu elementu do listy jednokierunkowej (wcześniejszy obraz) nie stanowi to jednak większego problemu. Gdy &lt; 60% możliwości kluczy jest niewykorzystanych, występowanie kolizji jest znikome.</p>
<p><img class="aligncenter size-full wp-image-695" title="funkcja-mieszajaca-kolizje-wykres" src="http://athlan.pl/wp-content/uploads/funkcja-mieszajaca-kolizje-wykres.png" alt="" width="500" height="371" /></p>
<p><strong>Idealna funkcja mieszająca</strong></p>
<p>Mówiąc o idealnej funkcji mieszającej mamy na myśli skonstruowanie takiej funkcji, która przyporządkuje mniej więcej po tej samej liczbie swoich zwracanych wartości, tj. dla naszego przykładu modulo 90, każdy klucz będzie miał porównywalną liczbę rekordów przypisanych do danego indeksu. Intuicyjnie: <span style="text-decoration: underline;">można to wykonać tylko wtedy, kiedy z góry znamy dziedzinę tej funkcji</span> bądź w przybliżeniu spodziewamy się znanych danych wejściowych. Budowanie idealnych funkcji mieszających jest skomplikowaną operacją matematyczną. Jednym ze sposobów do naszego przykładu, przy znanej dziedzinie funkcji mieszającej jest przypisywanie kolejnym literom wag, które po zsumowaniu i podzieleniu przez modulo, jest wygenerowanie takiej kombinacji wag, żeby zwracane wartości były równie często obliczane dla całej dziedziny funkcji (każda liczba modulo jest wykorzystywana po mniej więcej N razy).</p>
<h2>Zakończenie</h2>
<p>Temat teoretycznych rozważań budowy baz danych na pewno będę kontynuował. Tak, jak zaznaczyłem we stępie, artykuł bynajmniej wyczerpuje tematykę, a zainteresowanych zapraszam do przekroczenia progów politechnicznych bibliotek.</p>
]]></content:encoded>
			<wfw:commentRss>http://athlan.pl/indeksowanie-baz-danych-funkcje-mieszajace/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Optymalizacja zapytań MySQL dla koniunkcji wielu danych</title>
		<link>http://athlan.pl/optymalizacja-zapytan-mysql/</link>
		<comments>http://athlan.pl/optymalizacja-zapytan-mysql/#comments</comments>
		<pubDate>Sat, 15 Jan 2011 15:54:17 +0000</pubDate>
		<dc:creator>Athlan</dc:creator>
				<category><![CDATA[Databases]]></category>
		<category><![CDATA[Matematyka]]></category>
		<category><![CDATA[Optymalizacja]]></category>
		<category><![CDATA[php.pl]]></category>
		<category><![CDATA[Planeta]]></category>
		<category><![CDATA[Przemyślenia]]></category>
		<category><![CDATA[Publikacje]]></category>
		<category><![CDATA[Solutions]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[InnoDB]]></category>
		<category><![CDATA[md5]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[optymalizacja]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://athlan.pl/?p=670</guid>
		<description><![CDATA[Nie raz, nie dwa mieliśmy sytuację, która wymagała od nas koniunkcji warunków większej ilości danych lub dane te były tekstowe, ale niedługie. Niby nic, klucze załatwiają sprawę, ale sięgając do kodu gry bukmacherskiej, musiałem ją nieco zoptymalizować pod względem częstego wyciągania danych. Baza rozrosła się dość szybko, dlatego niezbędna była lekka modyfikacja jej struktury. Moim zadaniem było bardzo częste [...]]]></description>
			<content:encoded><![CDATA[<p>Nie raz, nie dwa mieliśmy sytuację, która wymagała od nas koniunkcji warunków większej ilości danych lub dane te były tekstowe, ale niedługie. Niby nic, klucze załatwiają sprawę, ale sięgając do kodu gry bukmacherskiej, musiałem ją nieco zoptymalizować pod względem częstego wyciągania danych. Baza rozrosła się dość szybko, dlatego niezbędna była lekka modyfikacja jej struktury.</p>
<p>Moim zadaniem było bardzo częste wyciągnięcie ID meczu, który musiał na raz (AND) być zgodny z żądaną datą, nazwą drużyny pierwszej oraz drugiej. Informacji do warunków dostarczał system. Oprócz daty, są to dane tekstowe, więc połączyłem je ze sobą <a href="http://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_concat">CONCAT</a> i stworzyłem z nich sumę md5. Indeks, po którym baza szukała, był już krótszy od warunków, bo zawierał zawsze 32 znaki. Pierwszym warunkiem koniunkcji zawsze była suma md5 wymienionych wcześniej pól rekordu, nazwałem to <strong>suma kontrolna </strong><strong>rekordu</strong>, potem faktyczna wartość pól, aby w razie zdublowania sumy kontrolnej (czego się nie spodziewamy, bo zakres wariacji jest ogromny, ale dla idei) wybrać prawidłowy rekord. Do tej pory wystarczało&#8230;</p>
<p>Gdy baza rozrasta się, problemem staje się wyszukiwanie. O ile suma kontrolna to już krok w stronę optymalizacji, dla &gt;100k rekordów, baza danych potrzebowała co najmniej 0.05 sekundy na zwrócenie wyniku. Postanowiłem dodać <strong>odcisk palca sumy kontrolnej</strong>. Najlepszym rozwiązaniem okazało się <strong>dodanie jednego bajtu</strong>, który zrobił magię w bazie danych. Jedno pole TINYINT &#8211; 8 bitów, zakres 0-255 bez znaku. Założenia odcisku palca:</p>
<ul>
<li>jest wartością liczbową oraz zajmuje tylko jeden bajt, aby oszczędzić miejsca w rekordach oraz indeksach bazy danych,</li>
<li>nie musi być uniwersalny (unikalny), a jedynie grupować odciski palców w mniejsze, a liczniejsze zbiory.</li>
</ul>
<p>Rozwiązanie, które zastosowałem przy generowanu odcisku palca sumy kontrolnej, również nie jest skomplikowane:</p>
<ol>
<li><strong>Odcisk palca to suma kolejnych znaków sumy kontrolnej rekordu</strong>, gdzie 0 &#8211; 9 zachowują swoje wartości, a litery [a-f] przyjmują kolejno [10-15], dokładnie jak w przeliczaniu pojedynczych wyrazów systemu liczbowego o podstawie 16 (HEX) na dziesiętny.</li>
<li>Skoro jest to suma, to wartość minimalna jest dla samych zer, zatem <em>MIN</em> = 0.</li>
<li>Wartość maksymalną można stworzyć podając same maksymalne wartości F, zatem <em>MAX</em> = 480.</li>
<li>480 mieści się na 9 bitach (min. 2 bajty, zakres 0-65535 bez znaku, tracimy 65055 wartości), dzieląc liczbę przez 2 tracimy unikalność odcisku dwukrotnie, ale zmieścimy się na ośmiu bitach, czyli jednym bajcie &#8211; możemy użyć typu TINYINT (zakres 0-255 bez znaku, nasza to 0-240), zatem tracimy tylko 15 niewykorzystanych wartości.</li>
</ol>
<p><strong>Przeprowadzamy testy naszego rozwiązania.</strong></p>
<p>Stwórzmy przykładową tabelę danych <em>test_md5_index</em>, która będzie przechowywała wartości tekstowe w polach <em>data_content</em>, <em>data_content2</em>, <em>data_content3</em>. Tabela może zawierać pole dodatkowe, ale te trzy będziemy wykorzystywać w naszym wyszukiwaniu. Ważnym jest to, że warunkiem jest koniunkcja (AND), dlatego możemy stworzyć sumę (analogicznie do sumy logicznej) md5 jako odcisk palca tych pól, który zapiszemy w <em>data_sum</em> varchar(32). Dodatkowo stworzymy odcisk palca odcisku palca &#8211; jednobajtowe pole <em>data_sum_index</em> TINYINT.</p>
<p>Od razu zakładamy klucz podstawowy na <em>data_id</em> oraz klucz dla zapytania, który będzie go wykorzystywał, czyli szukanie wspólnie po <em>data_sum_index</em> oraz <em>data_sum</em>.</p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;"><span style="color: #993333; font-weight: bold;">CREATE</span> <span style="color: #993333; font-weight: bold;">TABLE</span> test_md5_index <span style="color: #66cc66;">&#40;</span>
  data_id <span style="color: #993333; font-weight: bold;">INT</span><span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">11</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">UNSIGNED</span> <span style="color: #993333; font-weight: bold;">NOT</span> <span style="color: #993333; font-weight: bold;">NULL</span> <span style="color: #993333; font-weight: bold;">AUTO_INCREMENT</span><span style="color: #66cc66;">,</span>
  data_sum_index tinyint<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">1</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">UNSIGNED</span> <span style="color: #993333; font-weight: bold;">NOT</span> <span style="color: #993333; font-weight: bold;">NULL</span><span style="color: #66cc66;">,</span>
  data_sum <span style="color: #993333; font-weight: bold;">VARCHAR</span><span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">32</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">NOT</span> <span style="color: #993333; font-weight: bold;">NULL</span><span style="color: #66cc66;">,</span>
  data_contents text <span style="color: #993333; font-weight: bold;">NOT</span> <span style="color: #993333; font-weight: bold;">NULL</span><span style="color: #66cc66;">,</span>
  data_contents2 text <span style="color: #993333; font-weight: bold;">NOT</span> <span style="color: #993333; font-weight: bold;">NULL</span><span style="color: #66cc66;">,</span>
  data_contents3 text <span style="color: #993333; font-weight: bold;">NOT</span> <span style="color: #993333; font-weight: bold;">NULL</span><span style="color: #66cc66;">,</span>
  <span style="color: #993333; font-weight: bold;">PRIMARY</span> <span style="color: #993333; font-weight: bold;">KEY</span> <span style="color: #66cc66;">&#40;</span>data_id<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">,</span>
  <span style="color: #993333; font-weight: bold;">KEY</span> data_index <span style="color: #66cc66;">&#40;</span>data_sum_index<span style="color: #66cc66;">,</span> data_sum<span style="color: #66cc66;">&#41;</span>
<span style="color: #66cc66;">&#41;</span> ENGINE<span style="color: #66cc66;">=</span>MyISAM <span style="color: #993333; font-weight: bold;">DEFAULT</span> CHARSET<span style="color: #66cc66;">=</span>utf8 <span style="color: #993333; font-weight: bold;">AUTO_INCREMENT</span><span style="color: #66cc66;">=</span><span style="color: #cc66cc;">1</span>;</pre></div></div>

<p>Pora stworzyć funkcję, która przeliczy nam nowy, krótszy odcisk palca na podstawie poprzedniego:</p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;"><span style="color: #993333; font-weight: bold;">CREATE</span> <span style="color: #993333; font-weight: bold;">FUNCTION</span> TestIndexChecksum<span style="color: #66cc66;">&#40;</span>sSum <span style="color: #993333; font-weight: bold;">VARCHAR</span><span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">32</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">RETURNS</span> TINYINT
<span style="color: #993333; font-weight: bold;">BEGIN</span>
&nbsp;
  <span style="color: #993333; font-weight: bold;">DECLARE</span> sSumPart <span style="color: #993333; font-weight: bold;">VARCHAR</span><span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">1</span><span style="color: #66cc66;">&#41;</span>;
  <span style="color: #993333; font-weight: bold;">DECLARE</span> iSumPart TINYINT;
  <span style="color: #993333; font-weight: bold;">DECLARE</span> iSum <span style="color: #993333; font-weight: bold;">SMALLINT</span> <span style="color: #993333; font-weight: bold;">DEFAULT</span> <span style="color: #cc66cc;">0</span>;
  <span style="color: #993333; font-weight: bold;">DECLARE</span> i <span style="color: #993333; font-weight: bold;">INT</span>;
&nbsp;
  <span style="color: #993333; font-weight: bold;">IF</span> <span style="color: #66cc66;">&#40;</span><span style="color: #993333; font-weight: bold;">SELECT</span> sSum <span style="color: #993333; font-weight: bold;">NOT</span> <span style="color: #993333; font-weight: bold;">REGEXP</span> <span style="color: #ff0000;">'^([a-z0-9]){32}$'</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">THEN</span> <span style="color: #993333; font-weight: bold;">RETURN</span> <span style="color: #cc66cc;">0</span>; <span style="color: #993333; font-weight: bold;">END</span> <span style="color: #993333; font-weight: bold;">IF</span>;
&nbsp;
  <span style="color: #993333; font-weight: bold;">SET</span> i <span style="color: #66cc66;">=</span> <span style="color: #cc66cc;">1</span>;
&nbsp;
  WHILE i &amp;lt;<span style="color: #66cc66;">=</span> <span style="color: #993333; font-weight: bold;">LENGTH</span><span style="color: #66cc66;">&#40;</span>sSum<span style="color: #66cc66;">&#41;</span> DO
    <span style="color: #993333; font-weight: bold;">SET</span> sSumPart <span style="color: #66cc66;">=</span> SUBSTR<span style="color: #66cc66;">&#40;</span>sSum<span style="color: #66cc66;">,</span> i<span style="color: #66cc66;">,</span> <span style="color: #cc66cc;">1</span><span style="color: #66cc66;">&#41;</span>;
    <span style="color: #993333; font-weight: bold;">SET</span> iSumPart <span style="color: #66cc66;">=</span> <span style="color: #66cc66;">&#40;</span><span style="color: #993333; font-weight: bold;">SELECT</span> <span style="color: #66cc66;">&#40;</span><span style="color: #993333; font-weight: bold;">CASE</span> <span style="color: #993333; font-weight: bold;">WHEN</span> sSumPart <span style="color: #66cc66;">=</span> <span style="color: #ff0000;">'a'</span> <span style="color: #993333; font-weight: bold;">THEN</span> <span style="color: #cc66cc;">10</span> <span style="color: #993333; font-weight: bold;">WHEN</span> sSumPart <span style="color: #66cc66;">=</span> <span style="color: #ff0000;">'b'</span> <span style="color: #993333; font-weight: bold;">THEN</span> <span style="color: #cc66cc;">11</span> <span style="color: #993333; font-weight: bold;">WHEN</span> sSumPart <span style="color: #66cc66;">=</span> <span style="color: #ff0000;">'c'</span> <span style="color: #993333; font-weight: bold;">THEN</span> <span style="color: #cc66cc;">12</span> <span style="color: #993333; font-weight: bold;">WHEN</span> sSumPart <span style="color: #66cc66;">=</span> <span style="color: #ff0000;">'d'</span> <span style="color: #993333; font-weight: bold;">THEN</span> <span style="color: #cc66cc;">13</span> <span style="color: #993333; font-weight: bold;">WHEN</span> sSumPart <span style="color: #66cc66;">=</span> <span style="color: #ff0000;">'e'</span> <span style="color: #993333; font-weight: bold;">THEN</span> <span style="color: #cc66cc;">14</span> <span style="color: #993333; font-weight: bold;">WHEN</span> sSumPart <span style="color: #66cc66;">=</span> <span style="color: #ff0000;">'f'</span> <span style="color: #993333; font-weight: bold;">THEN</span> <span style="color: #cc66cc;">15</span> <span style="color: #993333; font-weight: bold;">ELSE</span> <span style="color: #cc66cc;">0</span> <span style="color: #993333; font-weight: bold;">END</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span>;
&nbsp;
    <span style="color: #993333; font-weight: bold;">IF</span> iSumPart <span style="color: #66cc66;">=</span> <span style="color: #cc66cc;">0</span> <span style="color: #993333; font-weight: bold;">THEN</span>
      <span style="color: #993333; font-weight: bold;">SET</span> iSumPart <span style="color: #66cc66;">=</span> sSumPart;
    <span style="color: #993333; font-weight: bold;">END</span> <span style="color: #993333; font-weight: bold;">IF</span>;
&nbsp;
    <span style="color: #993333; font-weight: bold;">SET</span> iSum <span style="color: #66cc66;">=</span> iSum <span style="color: #66cc66;">+</span> iSumPart;
    <span style="color: #993333; font-weight: bold;">SET</span> i <span style="color: #66cc66;">=</span> i <span style="color: #66cc66;">+</span> <span style="color: #cc66cc;">1</span>;
  <span style="color: #993333; font-weight: bold;">END</span> WHILE;
&nbsp;
  <span style="color: #993333; font-weight: bold;">RETURN</span> iSum <span style="color: #66cc66;">/</span> <span style="color: #cc66cc;">2</span>;
<span style="color: #993333; font-weight: bold;">END</span>;</pre></div></div>

<p>Aby przeprowadzać testy, stwórzmy sobie procedurę, która wstawi nam N losowo, jakkolwiek wypełnionych rekordów do bazy danych:</p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;"><span style="color: #993333; font-weight: bold;">CREATE</span> <span style="color: #993333; font-weight: bold;">PROCEDURE</span> TestIndexesPrepareTest<span style="color: #66cc66;">&#40;</span><span style="color: #993333; font-weight: bold;">IN</span> i <span style="color: #993333; font-weight: bold;">INT</span><span style="color: #66cc66;">&#41;</span>
<span style="color: #993333; font-weight: bold;">BEGIN</span>
  <span style="color: #993333; font-weight: bold;">TRUNCATE</span> <span style="color: #993333; font-weight: bold;">TABLE</span> test_md5_index;
&nbsp;
  WHILE i &amp;gt; <span style="color: #cc66cc;">0</span> DO
&nbsp;
    <span style="color: #993333; font-weight: bold;">INSERT</span> <span style="color: #993333; font-weight: bold;">INTO</span> test_md5_index <span style="color: #993333; font-weight: bold;">SET</span>
      data_contents  <span style="color: #66cc66;">=</span> <span style="color: #66cc66;">&#40;</span><span style="color: #993333; font-weight: bold;">SELECT</span> <span style="color: #993333; font-weight: bold;">REPLACE</span><span style="color: #66cc66;">&#40;</span>CONCAT<span style="color: #66cc66;">&#40;</span>RAND<span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">*</span> <span style="color: #cc66cc;">32</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">,</span> <span style="color: #ff0000;">&quot;.&quot;</span><span style="color: #66cc66;">,</span> <span style="color: #ff0000;">&quot;&quot;</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">,</span>
      data_contents2 <span style="color: #66cc66;">=</span> <span style="color: #66cc66;">&#40;</span><span style="color: #993333; font-weight: bold;">SELECT</span> <span style="color: #993333; font-weight: bold;">REPLACE</span><span style="color: #66cc66;">&#40;</span>CONCAT<span style="color: #66cc66;">&#40;</span>RAND<span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">*</span> <span style="color: #cc66cc;">32</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">,</span> <span style="color: #ff0000;">&quot;.&quot;</span><span style="color: #66cc66;">,</span> <span style="color: #ff0000;">&quot;&quot;</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">,</span>
      data_contents3 <span style="color: #66cc66;">=</span> <span style="color: #66cc66;">&#40;</span><span style="color: #993333; font-weight: bold;">SELECT</span> <span style="color: #993333; font-weight: bold;">REPLACE</span><span style="color: #66cc66;">&#40;</span>CONCAT<span style="color: #66cc66;">&#40;</span>RAND<span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">*</span> <span style="color: #cc66cc;">32</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">,</span> <span style="color: #ff0000;">&quot;.&quot;</span><span style="color: #66cc66;">,</span> <span style="color: #ff0000;">&quot;&quot;</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">,</span>
      data_sum <span style="color: #66cc66;">=</span> CONCAT<span style="color: #66cc66;">&#40;</span>data_contents<span style="color: #66cc66;">,</span> data_contents2<span style="color: #66cc66;">,</span> data_contents3<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">,</span>
      data_sum_index <span style="color: #66cc66;">=</span> TestIndexChecksum<span style="color: #66cc66;">&#40;</span>data_sum<span style="color: #66cc66;">&#41;</span>;
&nbsp;
    <span style="color: #993333; font-weight: bold;">SET</span> i <span style="color: #66cc66;">=</span> i <span style="color: #66cc66;">-</span> <span style="color: #cc66cc;">1</span>;
  <span style="color: #993333; font-weight: bold;">END</span> WHILE;
<span style="color: #993333; font-weight: bold;">END</span>;</pre></div></div>

<p>Po wykonaniu <code>CALL TestIndexesPrepareTest(100000)</code> mamy przygotowane małe środowisko testowe.</p>
<p>Przygotujmy kilka zapytań do bazy danych, wybieramy losowy rekord, na którym będziemy testowali wyniki. Wykonujemy zapytanie z ręcznie wpisaną wartością warunku wybranego rekordu, sprawdźmy, jak szybko zostanie odnaleziony:</p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;"><span style="color: #993333; font-weight: bold;">SELECT</span> <span style="color: #66cc66;">*</span> <span style="color: #993333; font-weight: bold;">FROM</span> test_md5_index <span style="color: #993333; font-weight: bold;">WHERE</span> data_sum <span style="color: #66cc66;">=</span> <span style="color: #ff0000;">&quot;24045771412594250684228176888212&quot;</span>;</pre></div></div>

<p><em>Average: ~0.0506 sec</em></p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;"><span style="color: #993333; font-weight: bold;">SELECT</span> <span style="color: #66cc66;">*</span> <span style="color: #993333; font-weight: bold;">FROM</span> test_md5_index <span style="color: #993333; font-weight: bold;">WHERE</span> data_sum_index <span style="color: #66cc66;">=</span> <span style="color: #cc66cc;">68</span> <span style="color: #993333; font-weight: bold;">AND</span> data_sum <span style="color: #66cc66;">=</span> <span style="color: #ff0000;">&quot;24045771412594250684228176888212&quot;</span>;</pre></div></div>

<p><em>Average: ~0.0004 sec </em>(<strong>UWAGA!</strong> Specjalnie w warunku nie użyłem zwróconej warości funkcji, tylko dałem ją na sztywno, ręcznie wpisaną &#8211; funkcja by była wykonywana dla każdego porównania rekordu z osobna!).</p>
<p>Nasze zapytanie działa znacznie szybciej (~120 razy dla 100k rekordów) kosztem niewielkiej pamięci &#8211; po 1 bajcie do rekordu oraz po 1 bajcie do jego indeksu.</p>
<p>Zapewne istnieją szybkie silniki indeksowania danych, natomiast, gdy jesteśmy skazani np. na InnoDB z założeń technicznych &#8211; nie oznacza, że się nie da.</p>
<p>Mam nadzieję, że komuś się przyda.</p>
]]></content:encoded>
			<wfw:commentRss>http://athlan.pl/optymalizacja-zapytan-mysql/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>[meta]dane dla Facebooka</title>
		<link>http://athlan.pl/metdane-dla-facebooka/</link>
		<comments>http://athlan.pl/metdane-dla-facebooka/#comments</comments>
		<pubDate>Mon, 06 Dec 2010 15:17:18 +0000</pubDate>
		<dc:creator>Athlan</dc:creator>
				<category><![CDATA[Internet]]></category>
		<category><![CDATA[Planeta]]></category>
		<category><![CDATA[Przemyślenia]]></category>
		<category><![CDATA[Usablity]]></category>
		<category><![CDATA[XHTML]]></category>

		<guid isPermaLink="false">http://athlan.pl/?p=507</guid>
		<description><![CDATA[Ostatnimi czasy tworząc projekty coraz częściej zwracam uwagę na usability, poprawiając przy tym grafików. Dziś krótko, bo o tym, jak dostosować nagłówki &#60;meta&#62;, aby jak najlepiej były przeklejane do okienka udostępniania linków na Facebook&#8217;u. Czasem cennymi danymi są te, które widzi Google oraz użytkownik (meta description i bezpośrednio w pasku przeglądarki title), czasem zależy nam na dostosowaniu [...]]]></description>
			<content:encoded><![CDATA[<p>Ostatnimi czasy tworząc projekty coraz częściej zwracam uwagę na <em>usability</em>, poprawiając przy tym grafików. Dziś krótko, bo o tym, jak dostosować nagłówki &lt;meta&gt;, aby jak najlepiej były przeklejane do okienka udostępniania linków na Facebook&#8217;u.</p>
<p>Czasem cennymi danymi są te, które widzi Google oraz użytkownik (meta description i bezpośrednio w pasku przeglądarki title), czasem zależy nam na dostosowaniu prezentacji danych agregatorów, w naszym przypadku Facebook&#8217;a, aby użytkownik wklejając linka nie musiał się dodatkowo w nic angażować.</p>
<ol>
<li><strong>Wykryj, czy odwiedza Cię Facebook</strong>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">preg_match</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/^facebookexternalhit/'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$_SERVER</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'HTTP_USER_AGENT'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span></pre></div></div>

</li>
<li>Dostosuj swój <em>meta description</em>. Pamiętaj, że Facebook ucina spore części tekstów, dlatego sprecyzuj najbardziej chwytliwe fragmenty contentu, które mogą przyciągnąć użytkownika. Dodatkowo możesz zmodyfikować <em>title</em> dodając do niego dziwne znaczki (encje), które na pewno przyciągną wzrok.</li>
<li>Przygotuj odpowiedni obraz o odpowiedniej rozdzielczości, a następnie zaserwuj go w tagu:<br />
<code>&lt;link rel="image_src" href=" ... " /&gt;</code><br />
Gdy tego nie zrobisz, użytkownik, który przekleja linka będzie sam musiał zdecydować o obrazie, który zostanie mu zaproponowany z puli dostępnych na stronie. Ta praktyka na szczęście jest często wykorzystywana przez programistów, wydaje się być trywialna.</li>
<li>Jeżeli chcesz wyeksponować ramkę Facebook&#8217;a na swoją stronę www, ustaw transparentność na <em>iframe</em>. Wbuduj ją w diva i nie zapominaj o możliwościach <em>CSS</em> takich jak <em>position</em>: absolute; <em>position</em>: relative; <em>top</em>: -1px; <em>left</em>: -1px; <em>overflow</em>: hidden; aby ukryć czasem kłopotliwe obramowanie <em>iframe&#8217;a</em>.</li>
</ol>
<p>Efekt? Sprawdź sam na <a href="http://www.nowiny365.pl">Nowiny365.pl</a>.</p>
<p style="border: 3px solid red; padding: 10px;">Przeczytaj również o <a href="http://developers.facebook.com/docs/opengraph">OpenGraph</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://athlan.pl/metdane-dla-facebooka/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Zmiana operatora komórkowego bez straty numeru</title>
		<link>http://athlan.pl/zmiana-operatora-bez-straty-numeru/</link>
		<comments>http://athlan.pl/zmiana-operatora-bez-straty-numeru/#comments</comments>
		<pubDate>Thu, 16 Sep 2010 21:52:56 +0000</pubDate>
		<dc:creator>Athlan</dc:creator>
				<category><![CDATA[Private]]></category>
		<category><![CDATA[Przemyślenia]]></category>
		<category><![CDATA[Real live]]></category>
		<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://athlan.pl/?p=471</guid>
		<description><![CDATA[Podczas świadczenia usług abonamentowych podejmujemy się analizy konkurencji, bądź dostajemy bodźce od operatorów. Stara oferta może nam jak najbardziej odpowiadać, często konsultanci przed możliwością przedłużenia umowy proponują różnego rodzaju bonusy, czasem traktowanie abonenta to kpina. Zatem najczęściej wtedy jesteśmy szarpani na smyczy i zastanawiamy się nad zmianą dostawcy usług. Od jakiegoś czasu można przejść do [...]]]></description>
			<content:encoded><![CDATA[<p>Podczas świadczenia usług abonamentowych podejmujemy się analizy konkurencji, bądź dostajemy bodźce od operatorów. Stara oferta może nam jak najbardziej odpowiadać, często konsultanci przed możliwością przedłużenia umowy proponują różnego rodzaju bonusy, czasem traktowanie abonenta to kpina. Zatem najczęściej wtedy jesteśmy szarpani na smyczy i zastanawiamy się nad zmianą dostawcy usług. Od jakiegoś czasu można przejść do innego operatora sieci telefonii komórkowej, nie tracąc przy tym swojego numeru. Ważnym jest <em>przejść</em> do innego operatora, a nie <em>odstąpić od umowy</em> u operatora macierzystego. Miałem okazję przechodzić ten proces, a że jestem pedantem wypytałem o wszystkie szczegóły i sytuacje krytyczne, zatem podzielę się wiedzą, którą zgromadziłem na temat tej materii. Żeby nikomu się nie zdarzyło popełnić idiotycznego błędu. Potencjalne <span style="color: #ff0000;">błędy oznaczyłem na czerwono</span>, <strong>kroki pogrubiłem</strong>, a <span style="text-decoration: underline;">istotne rzeczy podkreśliłem</span>.</p>
<p><strong>1. Rejestracja numeru telefonu</strong></p>
<p>W momencie, gdy nie jesteśmy użytkownikami abonamentowymi lub tzw. mix, nie jesteśmy automatycznie właścicielami numeru telefonu. Karty pre-paid (na doładowania na czas nieokreślony) nie wiążą klienta w żaden sposób z operatorem &#8211; możemy taką kartę wyrzucić w każdej chwili.</p>
<p>Jeżeli jesteśmy użytkownikami abonamentu, bądź mixa, problem mamy z głowy. W przypadku kart pre-paid&#8217;owych, możemy za darmo <span style="text-decoration: underline;">zarejestrować numer telefonu na nasze nazwisko</span>. <span style="color: #ff0000;">Robimy to ostrożnie</span>, bowiem właściciel numeru musi mieć zdolność abonamentową u operatora, do którego przejść. Tj. mieć stałe źródło dochodów lub okazać legitymację studencką. Jeżeli nie masz płynności finansowej, najlepiej zarejestrować numer na rodziców.</p>
<p><strong>2. Aktualizacja danych osobowych u swojego operatora</strong></p>
<p>Pierwszym krokiem, jaki powinien wykonać klient (Ty) to <span style="text-decoration: underline;">aktualizacja danych osobowych u operatora macierzystego</span> (który w tej chwili świadczy Ci usługi). Najczęstszą przyczyną nieaktualnych danych osobowych jest zmiana miejsca zamieszkania, aktualizacja dowodu osobistego, który wygasł, zgubiliśmy, zniszczył się, etc. W każdym z tych przypadków ulega co najmniej numer i seria dowodu osobistego, o czym <span style="color: #ff0000;">koniecznie trzeba poinformować swojego operatora przed podjęciem procedury migracji</span>. Jeżeli jesteś święcie przekonany, że nie nastąpiła zmiana danych w dowodzie &#8211; i tak pro forma idź je zaktualizować, nic Cię to nie kosztuje, a zaoszczędzisz stresu i utwierdzisz się w przekonaniu, że wszystko będzie ok.</p>
<p>Proces aktualizacji danych trwa do 24 godzin. Zazwyczaj aktualizacja następuje od razu, ale inni operatorzy, którzy mają wgląd do globalnej bazy, zmiany zaobserwują za maksymalnie 24 godziny. Warto mieć na uwadze te opóźnienie, przed podjęciem kolejnego kroku.</p>
<p><strong>3. Podpisanie umowy</strong></p>
<p><span style="color: #ff0000;">Ważnym jest, żeby <strong>nie wypowiadać umowy operatorowi macierzystemu</strong></span>! Tracimy wówczas prawo do swojego numeru telefonu. <strong>Umowę &#8220;wypowiada&#8221; nowy operator</strong>, a raczej prosi o przepisanie numeru wraz z końcem świadczonych usług. <strong><span style="text-decoration: underline;">Idziesz zatem do nowego operatora!</span></strong> U nowego operatora otrzymasz dwa dokumenty: umowę abonamentową oraz pełnomocnictwo wobec wykonanie czynności prawnych dotyczących Twojego numeru telefonu, którego jesteś właścicielem. W skrócie: przenosisz wszystkie obowiązki na nowego operatora. To Cię nic nie kosztuje, a nawet możesz otrzymać bonus, o który warto negocjować.</p>
<p>Kolejną istotną rzeczą jest to, że <span style="color: #ff0000;">dane wpisane na nowej umowie i pełnomocnictwie <strong>muszą zgadzać się</strong> z danymi u starego operatora</span>. W przeciwnym wypadku nowy użytkownik nie ma praw do przejęcia numeru, bo de facto nie jest w jego posiadaniu.</p>
<p>Jeżeli umowę u starego operatora masz podpisaną na rodzica/opiekuna, niech ta sama osoba podpiszę umowę u nowego operatora. Analogicznie: w przypadku karty pre-paid, jeżeli nie masz płynności finansowej, bądź nie jesteś studentem, zarejestruj numer na osobę, która spełnia wymagania, a następnie idź z nią do nowego operatora.</p>
<p>Po całym procesie można (nie trzeba) w każdej chwili wykonać cesję umowy &#8211; zmianę danych osobowych wobec której jest ona świadczona. Usługa ta jest bezpłatna, ale czasochłonna, na szczęście nas proszą tylko o zapłacenie pierwszej faktury (choć jedna musi być zapłacona &#8220;za kadencji&#8221; starego abonenta), a następnie o skan dowodu osobistego. Kolejna faktura przyjdzie już na nowego abonenta.</p>
<p><strong>4. Uregulowanie faktur za ostatnie miesiące u starego operatora</strong></p>
<p>Bez bałaganu, bo mogą nagle wyłączyć nam nowy abonament. Oczywista oczywistość.</p>
<p>Mam nadzieję, że przybliżyłem temat osobom, które biorą pod uwagę zmianę operatora, bądź mieli niewystarczającą wiedzę na temat migracji. Leave feedback if u like it.</p>
]]></content:encoded>
			<wfw:commentRss>http://athlan.pl/zmiana-operatora-bez-straty-numeru/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>MySQL DATE() dla pola DATETIME</title>
		<link>http://athlan.pl/mysql-date-function-datetime/</link>
		<comments>http://athlan.pl/mysql-date-function-datetime/#comments</comments>
		<pubDate>Sat, 17 Jul 2010 10:04:39 +0000</pubDate>
		<dc:creator>Athlan</dc:creator>
				<category><![CDATA[Databases]]></category>
		<category><![CDATA[Optymalizacja]]></category>
		<category><![CDATA[Planeta]]></category>
		<category><![CDATA[Przemyślenia]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://athlan.pl/?p=472</guid>
		<description><![CDATA[Oblicza MySQL nie są do końca znane przy tworzeniu aplikacji, a problemy optymalizacyjne stają się nie lada problemem przy funkcjonowaniu wersji produkcyjnej projektu. Nie sposób przewidzieć wszystkich możliwości użycia pól, założenia zarówno wspólnych, jak i pojedynczych indeksów posiadających zakładaną przez nas moc i zajętą pamięć na dysku. Ostatnimi czasy budowałem dość skomplikowany projekt, jeżeli chodzi [...]]]></description>
			<content:encoded><![CDATA[<p>Oblicza <strong>MySQL</strong> nie są do końca znane przy tworzeniu aplikacji, a problemy optymalizacyjne stają się nie lada problemem przy funkcjonowaniu wersji produkcyjnej projektu. Nie sposób przewidzieć wszystkich możliwości użycia pól, założenia zarówno wspólnych, jak i pojedynczych indeksów posiadających zakładaną przez nas moc i zajętą pamięć na dysku.</p>
<p>Ostatnimi czasy budowałem dość skomplikowany projekt, jeżeli chodzi o złożoność zapytań i wykonywanych przez nie operacje matematyczne. Pomimo tego, że aplikacja była doskonale przemyślana, a struktury bazy danych perfekcyjnie jej podporządkowane, gdzieś tkwił problem, bowiem jedno z zapytań generowało pozornie prosty (wizualnie) rezultat, baza reagowała na zapytanie dopiero po 2.5 sekundy dla 30k+ rekordów. Patrząc na strukturę kluczy i zapytania, zwłaszcza, że pola, na których operowałem były różnego rodzaju liczbami i datami zacząłem się poważnie martwić i rozkładać zapytanie na czynniki pierwsze, kończąc na warunkach. Wyobraźcie sobie moje zdziwienie, gdy doszedłem do tego, że całe obciążenie (ponad 2.3 sekundy) generował warunek:</p>

<div class="wp_syntax"><div class="code"><pre class="mysql" style="font-family:monospace;"><span style="color: #990099; font-weight: bold;">WHERE</span> <span style="color: #000099;">DATE</span><span style="color: #FF00FF;">&#40;</span>ticket_date<span style="color: #FF00FF;">&#41;</span> <span style="color: #CC0099;">&gt;=</span> <span style="color: #008000;">&quot; ... &quot;</span></pre></div></div>

<p>Gdzie ticket_date to pole typu <strong>DATETIME</strong>. Od razu doszedłem do wniosku, że w parze idzie złe przygotowanie danych przez PHP, a angażowana jest w to wszystko baza, na której forsuje się użycie funkcji <strong>DATE()</strong>. Przynajmniej dla 30k+ rekordów zindeksowanego pola. Prosty zabieg zamiany jednej linijki kodu na drugą przyniósł porządane efekty.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000088;">$aTerms</span><span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'DATE(ticket_date) &gt;= &quot;'</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$sDate</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'&quot;'</span></pre></div></div>


<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000088;">$aTerms</span><span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'ticket_date &gt;= &quot;'</span> <span style="color: #339933;">.</span> <span style="color: #990000;">date</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'Y-m-d H:i:s'</span><span style="color: #339933;">,</span> <span style="color: #990000;">strtotime</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$sDate</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'&quot;'</span></pre></div></div>

<p>Budując aplikację zwracam szczególną uwagę na strukturę bazy, indeksowanie pól, rysuję diagramy przewidujące wykorzystanie danych pod różne zapytania, ale&#8230; tak banalny błąd przy przeanalizowanej aplikacji rozłożył mnie na łopatki. Z drugiej strony, zapomniałem o jednej bardzo ważnej rzeczy: maksymalnym odciążeniu bazy danych przy preparowaniu argumentów warunków, skoro warunki te mogą być w odpowiedni i przede wszystkim szybki sposób spreparowane na poziomie modelu (abstrakcyjnie rzecz ujmując, pozbywam się pojęcia PHP), który przygotują zapytanie tylko do wykonania operacji na surowych danych, bez konieczności ich ewentualnego przeliczania. Oczywiście nie zawsze taki efekt da się uzyskać, ale należy to maksymalnie <strong>optymalizować</strong>.</p>
<p>Jedno jest wiadome: przeliczanie DATE() dla rekordów w warunku jest nieoptymalne dla pola DATETIME.</p>
]]></content:encoded>
			<wfw:commentRss>http://athlan.pl/mysql-date-function-datetime/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Allegro nie zawsze wygodne</title>
		<link>http://athlan.pl/allegro-nie-zawsze-wygodne/</link>
		<comments>http://athlan.pl/allegro-nie-zawsze-wygodne/#comments</comments>
		<pubDate>Wed, 29 Jul 2009 23:51:57 +0000</pubDate>
		<dc:creator>Athlan</dc:creator>
				<category><![CDATA[Internet]]></category>
		<category><![CDATA[Private]]></category>
		<category><![CDATA[Przemyślenia]]></category>
		<category><![CDATA[allegro]]></category>
		<category><![CDATA[aukcja]]></category>
		<category><![CDATA[aukcje]]></category>
		<category><![CDATA[oszustwo]]></category>
		<category><![CDATA[play station]]></category>
		<category><![CDATA[policja]]></category>
		<category><![CDATA[shopping]]></category>
		<category><![CDATA[zakupy]]></category>

		<guid isPermaLink="false">http://athlan.pl/?p=378</guid>
		<description><![CDATA[Jestem zwolennikiem ułatwiania sobie życia wykorzystując Internet do robienia zakupów i zarządzania swoim kapitałem. Natomiast po ostatniej akcji na Allegro, zacząłem wątpić w moje ograne ideały, uznaję to za ostrzeżenie i poprawiam markerem cienką linię pomiędzy realnym shoppingiem, a dokonywania zakupów via Allegro na znacznie grubszą. Zamiast kupić pada do Play Station 3 kiloma kliknięciami, musiałem [...]]]></description>
			<content:encoded><![CDATA[<p>Jestem zwolennikiem ułatwiania sobie życia wykorzystując <strong>Internet</strong> do robienia zakupów i zarządzania swoim kapitałem. Natomiast po ostatniej akcji na <strong>Allegro</strong>, zacząłem wątpić w moje ograne ideały, uznaję to za ostrzeżenie i poprawiam markerem cienką linię pomiędzy realnym shoppingiem, a dokonywania zakupów via Allegro na znacznie grubszą. Zamiast kupić pada do Play Station 3 kiloma kliknięciami, musiałem ruszać tyłek po kilku instytucjach.</p>
<p>Wszystko zaczęło się 25 czerwca, kiedy postanowiłem zakupić wcześniej wspominanego pada. Mam zwyczaj, że jeżeli jest opisana procedura płatności za przedmioty, a kontrahent jest <em>Super Sprzedawcą</em>, robię to od razu po dokonaniu zakupu &#8211; tak było i tym razem. System <a href="http://www.sello.pl/">Sello</a> wysłał mi potwierdzenie złożenia zamówienia.</p>
<p>Od 26.06 do 07.07 obserwowałem status wysyłki. Przelewu nie zaksięgowano po 13 dniach, stąd status wysyłki zakolorowany na czerwono. Sprawa zaczyna śmierdzieć, bowiem nie mogę się dodzwonić do kontrahenta (number busy), a gdy już mi się to udaje &#8211; nikt nie odbiera. Kilkukrotna próba kontaktu telefonicznego i mailowego pozostawiona bez odpowiedzi motywuje mnie, żeby&#8230;</p>
<p>09.07 wypełniłem wniosek w <strong>Centrum Sporów Allegro</strong>. Tego samego dnia wybrałem się na <strong>Policję</strong>, aby złożyć zawiadomienie o popełnieniu przestępstwa na tle wyłudzenia internetowego. Na komendzie czekałem około 40 minut zanim mnie przesłuchali, potem straciłem 30 minut na przesłuchanie i sporządzenie raportu.</p>
<p>Czas płynie i płynie&#8230;</p>
<p>27.07 &#8211; kontrahent dzwoni z wyjaśnieniami. &#8220;Okazało się&#8221;, że odwiedziła go Policja. Tłumaczenie: padła im baza klientów i zamówień, przez co nie mogli mnie zidentyfikować, pomimo szukania transakcji w historii sprzedaży na Allegro (przecież jest lista czarno na białym). Na moim koncie powinny być już zaksięgowane zwrócone pieniążki, ale niestety nie mogą wysłać mi mojego pada ze względu na to, że są zablokowani przez skarbówkę&#8230; muszą się rozliczyć. Za wszystko jeszcze raz przeprasza&#8230;</p>
<p>28.07 godz 11:00, telefon z Policji. Wezwanie na jutro, punkt 9 rano w pokoju numer 53. Sprawdzam konto, pieniążki są. Drukuję potwierdzenie zaksięgowania zwrotu jako załącznik do jutrzejszych zeznań.</p>
<p>29.07 godz 9:00 (wakacje są, daliby pospać). Zaniechujemy ściganie, dziękuję za pomoc. Kończę spór transakcyjny w panelu na Allegro i wystawiam negatywny komentarz:</p>
<blockquote><p>Towar nie dotarł do mnie do dziś. Po interwencji Policji sprawa się wyjaśniła. Natomiast nieuczciwym wobec innych użytkowników Allegro byłoby wystawienie poz. lub neut. komentarza za transakcję, która nie doszła do skutku. Miesiąc w plecy.</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://athlan.pl/allegro-nie-zawsze-wygodne/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>Asus Eee PC 1000H &#8211; recenzja</title>
		<link>http://athlan.pl/asus-eee-pc-1000h-recenzja/</link>
		<comments>http://athlan.pl/asus-eee-pc-1000h-recenzja/#comments</comments>
		<pubDate>Sat, 14 Feb 2009 22:58:24 +0000</pubDate>
		<dc:creator>Athlan</dc:creator>
				<category><![CDATA[Private]]></category>
		<category><![CDATA[Przemyślenia]]></category>
		<category><![CDATA[Recenzje]]></category>
		<category><![CDATA[asus]]></category>
		<category><![CDATA[eeepc 1000H]]></category>
		<category><![CDATA[netbook]]></category>
		<category><![CDATA[recenzja]]></category>

		<guid isPermaLink="false">http://athlan.pl/?p=219</guid>
		<description><![CDATA[Od dwóch dni jestem posiadaczem netbooka Asus Eee PC 1000H. Intensywnie sprawdzałem jego możliwości i powiem szczerze, że jestem mile zaskoczony. Pomimo tego, że niektórzy jego minimalizm przedstawiają jako minus &#8211; ja jestem zadowolony. Z łatwością mieści się do wewnętrznej kieszeni kurtki zimowej, posiada eleganckie etui. Podstawowym minusem jest brak napędu optycznego, ale problem rozwiązuję [...]]]></description>
			<content:encoded><![CDATA[<p>Od dwóch dni jestem posiadaczem netbooka <strong>Asus Eee PC 1000H</strong>. Intensywnie sprawdzałem jego możliwości i powiem szczerze, że jestem mile zaskoczony. Pomimo tego, że niektórzy jego minimalizm przedstawiają jako minus &#8211; ja jestem zadowolony. Z łatwością mieści się do wewnętrznej kieszeni kurtki zimowej, posiada eleganckie etui. Podstawowym minusem jest brak napędu optycznego, ale problem rozwiązuję udostępnieniem DVD z komputera PC jako zasób sieciowy. Rzadko potrzebuję cokolwiek zainstalować, w moim przypadku do transferu najczęściej używam standardowych złącz USB (włącznie z <a href="/obudowa-na-dysk/">dyskiem twardym</a> peceta) oraz standardowo Internetu, najczęściej ftp.</p>
<p><strong>Design i obudowa.</strong></p>
<p>Cały komputer wygląda przyzwoicie, pomimo tego zdecydowałem się kupić oklejkę na klapę, aby odkleić ją po jakimś czasie i mieć niezarysowaną powierzchnię obudowy. Klapa jest wykonana w &#8220;modnym&#8221; stylu serii HP, czyli krótko mówiąc &#8211; błyszcząca. Minusem są pozostawione odciski palców. Jest zima&#8230; palce mniej się pocą, strach pomyśleć, jak będzie wyglądała w lato. Pozostała część obudowy jest matowa, co dla mnie jest wielkim plusem &#8211; nie pozostawiamy odcisków palców, na takiej powierzchni mniej widoczne są zarysowania. Autorzy dobrze wkomponowali zawiasy obudowy, tworząc walec zakończony metalowymi kołami z zębatą krawędzią, co mnie się podoba. Denerwująca dla oka jest metalowa obudowa na touchpad, bowiem widoczne są podłużne pasy frezarki. Nie wiem, czy jest to zamierzony efekt &#8211; mnie nie przypadł do gustu. Na marginesie: <strong>Eee PC</strong> ma brzydkie logo :P</p>
<p><strong>Kontrowersyjny touchpad&#8230;</strong></p>
<p>&#8230; a raczej <em>pushpad</em>. Na początku miałem wrażenie, że trzeba go naciskać, nie dotykać, ale po 2 dniach używania już się przyzwyczaiłem. W moim poprzednim laptopie fizyka działania tego elementu była nieco inna. Często wymienianym minusem w recenzjach są przyciski w touchpadzie&#8230; <em>mocno trzeba je wciskać</em>. Nie, wciska się je łatwo i są bardzo wygodne, pod warunkiem, że przyciskamy je pod kątem 45 stopni w stosunku do klawiatury. Na początku wydawać się, że jest to nietypowe ułożenie ręki, musiałem się przyzwyczaić, ale teraz ciężko mi działać na jakimkolwiek innym touchpadzie &#8211; bardzo wygodna pozycja dłoni.</p>
<p>Klawiatura&#8230; bardzo mały odskok, nieznacznie mniejsze klawisze niż w standardowym notebooku, duży plus. Dodatkowo znalazło się miejsce na hotkeye (kombinacje w Fn) takie jak: sleep, włącz/wyłącz bluetooth oraz manager zadań. Standardowo brighnes, volume, mute i przełączenie monitora na zewnętrzne złącze karty graficznej (np rzutnik lub zewnętrzny monitor).  I minusy się znajdą, brak osobnych przycisków PgUp/Down oraz Home/End. Zamiast tego ulokowane są na strzałkach, używamy ich z wciśniętym Fn &#8211; niewygodne. Klawisz Fn ustawiony jest obok Ctrl po jego prawej stronie &#8211; plus. W Fujitsu-Siemens który miałem poprzednio było odwrotnie, często się myliłem.</p>
<p><strong>Praca i entertejment.</strong></p>
<p>Nie można wymagać wiele od procesora 1,6 GHz, 1GB RAM DDR2, ale daje radę. Produkt jest przedstawiony jako notatnik, ale świetnie radzi sobie z jednocześnie odpalonym softem takim jak Office 2007, Photoshop CS3, Google Chrome. Moje ulubione gry (NFS Underground, <a href="http://diablo.phx.pl">Diablo 2</a>, GTA 2) działają bez zarzutów, więc pretensji nie mam. W tle działa <a href="/jak-moze-wygladac-windows/">Windows Blinds</a> oraz <a href="http://dobreprogramy.pl/index.php?dz=2&amp;id=2148&amp;Yodm+3D+1.4">Yomd3d</a>.</p>
<p><strong>10 cali, nie za mało?</strong></p>
<p>Do programowania i photoshopa &#8211; tak, do przeglądania Internetu, pisania notatek etc. w zupełności wystarczy. Jednak nawet przy najprostszych czynnościach warto minimalistycznie podchodzić do zagospodarowania przestrzeni na ekranie. W tym celu właśnie zainstalowany jest Windows Blinds &#8211; skin imitujący Mac OS posiada bardzo niskie belki. Z pomocą przychodzi nam również <a href="/google-chrome/">Google Chrome</a>, który minimalnie wykorzystuje ekran, w przeciwieństwie do FireFox&#8217;a. Zaletą Chroma jest to, że posiada ukryty pasek narzędzi oraz zakładki na pasku tytułu.</p>
<p><strong>Bateria.</strong></p>
<p>6600 mAh Li-ion. Przy włączonym trybie Asus Safe Energy i wyłączonym bluetooth bateria faktycznie trzyma 5 godzin. Przy odpaleniu ciężkiego softu, wifi oraz wyłączonym trybie oszczędzania energii bateria utrzymuje netbooka przy życiu przez 3h i 40 min. Bardzo dobry wynik, jeżeli chodzi w ogóle o netbooki. Standardowo na netbooku pracuję 4h i 20 min.</p>
<p>Ważnym jest to, aby dobrze używać baterii, tj. wyjąć ją, gdy netbook podłączony jest do prądu, a stan baterii jest równy 100%. Warto wcześniej  kilka razy (wolno) rozładować do zera i ponownie naładować na max. Obecnie wykonuje taki zabieg włączając stan czuwania na noc podładowując baterię już na następny wieczór. Cykl będę powtarzał tydzień.</p>
<p><strong>Podsumowanie.</strong></p>
<p>Moje zadowolenie to 95%, czyli netbook spełnił moje oczekiwania prawie celująco. Kupiłem go przez Allegro, miałem fart, niska cena, aukcja wystawiona przed diametralnym spadkiem PLN.</p>
<ul>
<li>Wygląd: 4/5</li>
<li>Cena: 5/5</li>
<li>Wydajność: 4/5</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://athlan.pl/asus-eee-pc-1000h-recenzja/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Logowanie po nickname i email (usability)</title>
		<link>http://athlan.pl/logowanie-nickname-email-usability/</link>
		<comments>http://athlan.pl/logowanie-nickname-email-usability/#comments</comments>
		<pubDate>Sun, 28 Dec 2008 14:35:13 +0000</pubDate>
		<dc:creator>Athlan</dc:creator>
				<category><![CDATA[Internet]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Planeta]]></category>
		<category><![CDATA[Przemyślenia]]></category>
		<category><![CDATA[Usablity]]></category>
		<category><![CDATA[email]]></category>
		<category><![CDATA[logowanie]]></category>
		<category><![CDATA[usability]]></category>
		<category><![CDATA[username]]></category>

		<guid isPermaLink="false">http://athlan.pl/?p=169</guid>
		<description><![CDATA[Projektując serwisy zaczynam ostrożnie podchodzić do usability. Temat jest bardzo wrażliwy, każdy webmaster to inne zdanie. Dziś chciałbym przedstawić problem logowania do serwisu. Oprócz hasła, przy logowaniu używamy: standardowo nazwy użytkownika, w niektórych serwisach adresu email, rzadziej spotykane zjawisko. W trosce o użytkowników w kilku moich serwisach zastosowałem możliwość logowania się na adres email lub [...]]]></description>
			<content:encoded><![CDATA[<p>Projektując serwisy zaczynam ostrożnie podchodzić do usability. Temat jest bardzo wrażliwy, każdy webmaster to inne zdanie. Dziś chciałbym przedstawić problem logowania do serwisu. Oprócz hasła, przy logowaniu używamy:</p>
<ul>
<li>standardowo <strong>nazwy użytkownika</strong>,</li>
<li>w niektórych serwisach <strong>adresu email</strong>, rzadziej spotykane zjawisko.</li>
</ul>
<p>W trosce o użytkowników w kilku moich serwisach zastosowałem możliwość logowania się na adres email lub nazwę użytkownika. Wszystko po to, aby ułatwić dostęp do ukrytej części witryny, aby nikt nie &#8220;zwątpił&#8221; bo nazwy użytkownika, lub adresu email, który podał przy zakładaniu profilu. Sam używam w sieci kilku adresów email oraz kilku prefixów i suffixów do nicka <strong>Athlan</strong>. Mam problem z zalogowaniem, gdy danie nie pasują, a potrzebuję dostęp tylko na chwilę.</p>
<p>Przygotujmy zatem tok myślenia programu, który pobierze dane, w zależności od tego, jakie dane podał użytkownik. Pomijam walidację hasła etc:</p>
<ol>
<li>Wykrycie, czy nick jest adresem email.</li>
<li>Jeżeli tak, pobierz dane użytkownika po polu <em>user_mail</em> i zapisz je do zmiennej <code>$aUser</code>.</li>
<li>Jeżeli nie, pobierz dane identyfikując rekord po kluczu <em>user_name</em> i zapisz pobrane dane do zmiennej <code>$aUser</code>.</li>
</ol>
<p>Jak możemy zauważyć, w obu przypadkach dane zapisujemy do tej samej zmiennej <code>$aUser</code>, więc możemy je dalej tak samo wykorzystywać. Różni się tylko pobieranie, dlateg nie trzeba w żadnym wypadku powielać kodu.</p>
<ul>
<li><a href="http://athlan.pl/code/UsablityLogin">Przykładowy kod logowania</a>.</li>
</ul>
<p>Jak słusznie zauważył <a href="http://greensky.pl">devnull</a>, <span style="text-decoration: underline;">należy wykluczyć możliwość użycia znaku małpy w loginie przy rejestracji użytkownika</span>. Wyjaśnienie znajdziecie w komentarzach.</p>
<p>Dla programisty nie jest wiele pracy, a warto ułatwić dostęp użytkownikowi do serwisu. <strong>Usability</strong> na pierwszym miejscu ;-)</p>
]]></content:encoded>
			<wfw:commentRss>http://athlan.pl/logowanie-nickname-email-usability/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Podtrzymanie sesji</title>
		<link>http://athlan.pl/podtrzymanie-sesji/</link>
		<comments>http://athlan.pl/podtrzymanie-sesji/#comments</comments>
		<pubDate>Fri, 26 Dec 2008 19:35:39 +0000</pubDate>
		<dc:creator>Athlan</dc:creator>
				<category><![CDATA[Internet]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Planeta]]></category>
		<category><![CDATA[Przemyślenia]]></category>
		<category><![CDATA[Publikacje]]></category>
		<category><![CDATA[Solutions]]></category>
		<category><![CDATA[Usablity]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[sesje]]></category>
		<category><![CDATA[sessions]]></category>

		<guid isPermaLink="false">http://athlan.pl/?p=159</guid>
		<description><![CDATA[Dla niektórych wygasanie sesji jest zabezpieczeniem (banki, etc.). Realizując jeden z projektów oczekiwałem od systemu tego, aby użytkownik nigdy nie gubił sesji, gdy ma otwarte okno w przeglądarce. Dlaczego? Może dodaje posta, być może uzupełnia dość obszerny tekst na stronie. Gdy klika zapisz, przerzuca go do strony logowania, a cały tekst zniknął za sprawą tego, [...]]]></description>
			<content:encoded><![CDATA[<p>Dla niektórych wygasanie sesji jest zabezpieczeniem (banki, etc.). Realizując jeden z projektów oczekiwałem od systemu tego, aby użytkownik nigdy nie gubił sesji, gdy ma otwarte okno w przeglądarce. Dlaczego? Może dodaje posta, być może uzupełnia dość obszerny tekst na stronie. Gdy klika <em>zapisz</em>, przerzuca go do strony logowania, a cały tekst zniknął za sprawą tego, że jego przeglądarka nie zapisuje wartości pól formularza. Skąd to znamy.</p>
<p><strong>Jak użytkownik gubi sesję?</strong></p>
<ol>
<li>Jego ciastko wygasa, więc serwer nie może go zidentyfikować z sesją.</li>
<li>Po jakimś czasie, choćby odtworzył ciastko, plik sesji znika z naszego serwera (<a href="http://pl.php.net/manual/pl/session.configuration.php#ini.session.gc-divisor">garbage collection</a>).</li>
</ol>
<p><strong>Rozwiązania:</strong></p>
<ol>
<li>Wydłużenie czasu wygasania ciastka i sesji.</li>
<li>Odświeżenie strony w interwale mniejszym, niż wynosi czas wygasania sesji i ciastka.</li>
</ol>
<p>Rozmyślając nad podtrzymaniem sesji, próbowałem znaleźć wszystkie metody oraz wybrać najlepszą. Wszystkie sprowadzają się do &#8220;odświeżenia&#8221; strony lub jej fragmentu tak, aby nasz silnik wykonał tylko potrzebne session_start(); czyli podtrzymanie aktywności sesji. Jest kilka mniej lub bardziej zadowalających sposobów:</p>
<ol>
<li><strong>Odświeżenie całej strony.</strong><br />
To może spowodować, że dane wprowadzane przez użytkownika w formularzu zostaną utracone. Ponad to, jeżeli użytkownik czyta newsy, denerwującym może być fakt, że lista nagle zostanie przescrollowana do góry (prócz opery).</li>
<li><strong>Wysłanie requestu ajax w tle.</strong><br />
Minusem jest to, że trzeba używać biblioteki ajax lub pisać dodatkowy kod javascriptu. Jeżeli ktoś na stronie używa jakiegoś ajaxa &#8211; co za różnica. Poza tym same plusy.</li>
<li><strong>Odświeżanie ukrytej ramki</strong> iframe lub elementu frameset.<br />
Minusów usablity prawie brak. Brak potrzeby instalacji javascriptów i ajaxa. Odświeżacz powinien wysłać nagłówek <em>Refresh</em> lub odpowiedni metatag.</li>
</ol>
<p>Sposób 3 wydaje mi się najlepszy. Można go ulepszyć w ten sposób, aby ramka nie wysyłała żądania zaraz po załadowaniu strony. Powodowałoby to podwójne requesty do serwera.</p>
<ul>
<li><a href="http://athlan.pl/code/PingIframe">Przykład z ukrytym iframe</a>.</li>
<li><a href="http://athlan.pl/code/PingAdvajax">Przykład z Advajax</a>.</li>
<li>plik <code>ping.php</code> wygląda wówczas następująco: <span style="color: #999999; font-size: 9px;">aplikacje nie używające frameworków ingerujących w standardowe działanie sesji</span><br />
<code>session_start(); header('Refresh: 60');</code></li>
</ul>
<p><span style="font-size: 9px">Zapewne znajdą się osoby, które powiedzą: a co z użytkownikami, którzy mają wyłączone ramki, lub ich przeglądarki w ogóle ich nie obsługują. Zapytam wówczas: a co z użytkownikami, którzy nie akceptują cisteczek (wówczas sesje nie są dla nich użyteczne, chyba, że użyjemy przesłyki jej identyfikatora w adresie url). Dopytam również: a co z użytkownikami, którzy mają wyłączony Javascript? Patologiczne przepadki się po prostu pomija ;)</span></p>
]]></content:encoded>
			<wfw:commentRss>http://athlan.pl/podtrzymanie-sesji/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>Upload filmów z Zend_Gdata_YouTube</title>
		<link>http://athlan.pl/upload-filmow-zend_gdata_youtube/</link>
		<comments>http://athlan.pl/upload-filmow-zend_gdata_youtube/#comments</comments>
		<pubDate>Sat, 20 Dec 2008 14:00:56 +0000</pubDate>
		<dc:creator>Athlan</dc:creator>
				<category><![CDATA[Planeta]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Przemyślenia]]></category>
		<category><![CDATA[Server]]></category>
		<category><![CDATA[Zend framework]]></category>
		<category><![CDATA[gdata]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[youtube]]></category>
		<category><![CDATA[zend]]></category>

		<guid isPermaLink="false">http://athlan.pl/?p=148</guid>
		<description><![CDATA[Pisząc nowy projekt natknąłem na problem z procesem uploadu filmiku do serwisu YouTube. Sam upload jest bardzo łatwy do napisania z Zend_Gdata_YouTube &#8211; przykład można znaleźć w manualu. Myślę, że zainteresowani przeczytają manual i wszystko będzie jasne. Więc jeżeli to takie proste, to w czym problem? Zakładamy, że userzy uloadują filmiki bezpośrednio na nasz serwis. [...]]]></description>
			<content:encoded><![CDATA[<p>Pisząc <a href="http://athlan.pl/videoblog/">nowy projekt</a> natknąłem na problem z procesem uploadu filmiku do serwisu YouTube. Sam upload jest bardzo łatwy do napisania z <a href="http://framework.zend.com/apidoc/core/Zend_Gdata/App/Zend_Gdata_YouTube.html">Zend_Gdata_YouTube</a> &#8211; <a href="http://framework.zend.com/manual/en/zend.gdata.youtube.html#zend.gdata.youtube.uploads.example">przykład</a> można znaleźć w manualu. Myślę, że zainteresowani przeczytają manual i wszystko będzie jasne. Więc jeżeli to takie proste, to w czym problem?</p>
<p>Zakładamy, że userzy uloadują filmiki bezpośrednio na nasz serwis. Nasz serwer ma za zadanie:</p>
<ol>
<li>Skompresować video i zapisać go w formacie flv.</li>
<li>Nałożyć watermark.</li>
<li>Wysłać obrobiony film do serwisu Youtube logując się na zdefiniowane przez użytkownika konto lub założone przez administratora strony.</li>
</ol>
<p>Pierwsze 2 kroki wykonają się błyskawicznie w porównaniu do trzeciego. Kompresja i nałożenie watermarku na 30 megowy plik z wykorzystaniem FFMPEG to nic nadzwyczajnego. Natomiast wysyłka pliku na serwery Youtube&#8217;a może zawiesić apache&#8217;a, gdy jest ich kilka.</p>
<p>Rozwiązanie? Wpadłem na pomysł, aby upload filmików ustawiony był w pewnego rodzaju kolejce, która uruchamiana by była co minutę (cron), a czas jednego wysłania elemntu nie mógłby przekroczyć 50 sekund. Oczywiście takie działanie uruchamiałoby swój osobny proces apache&#8217;a. Ten sposób jest ograniczony dwoma limitami: wielkością pliku oraz czasem jego uploadu na serwer (jednocześniem interwałem uruchamiania kolejki).</p>
]]></content:encoded>
			<wfw:commentRss>http://athlan.pl/upload-filmow-zend_gdata_youtube/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Automatyczne przekierowanie po zalogowaniu</title>
		<link>http://athlan.pl/automatyczne-przekierowanie-po-zalogowaniu/</link>
		<comments>http://athlan.pl/automatyczne-przekierowanie-po-zalogowaniu/#comments</comments>
		<pubDate>Thu, 02 Oct 2008 21:21:31 +0000</pubDate>
		<dc:creator>Athlan</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Planeta]]></category>
		<category><![CDATA[Przemyślenia]]></category>

		<guid isPermaLink="false">http://athlan.pl/?p=141</guid>
		<description><![CDATA[Denerwujące jest, że na niektórych portalach: nie ma automatycznego przekierowania do strony wymagającej autoryzacji po zalogowaniu się, w adresie strony jest parametr typu back_url=/bardzo/glugi/parametr/ze/czasem-to/wogole/szok.html Ostatnio programując sklep internetowy zastosowałem najprostszą technikę, która uwzględni oba powyższe punkty. W moim przypadku, gdy kontroler wymaga zalogowania się, warstwa ACL wskazuje na kontroler logowania (w zależności od konfiguracji). Natomiast [...]]]></description>
			<content:encoded><![CDATA[<p>Denerwujące jest, że na niektórych portalach:</p>
<ul>
<li>nie ma automatycznego przekierowania do strony wymagającej autoryzacji po zalogowaniu się,</li>
<li>w adresie strony jest parametr typu back_url=/bardzo/glugi/parametr/ze/czasem-to/wogole/szok.html</li>
</ul>
<p>Ostatnio programując sklep internetowy zastosowałem najprostszą technikę, która uwzględni oba powyższe punkty. W moim przypadku, gdy kontroler wymaga zalogowania się, warstwa ACL wskazuje na kontroler logowania (w zależności od konfiguracji). Natomiast URL zostaje, tj:</p>
<p>http://example.com/konto/dodaj-produkt.html</p>
<p>Pierwsze co trzeba zrobić, to sprawdzić, gdzie jest użytkownik. Jeżeli wywołujemy kontroler logowania pod powyższym adresem, trzeba porównać aktualny url do adresu logowania. W moim przypadku adresem logwania jest:</p>
<p>http://example.com/zaloguj.html</p>
<p>Aktualną pozycję użytkownika możemy sprawdizć w zmiennej $_SERVER['REQUEST_URI'], a adres logowania mamy z góry ustalony w naszej aplikacji. U mnie za adresu odpowiada router, stąd poniższy przykład przekierowania do strony logowania:</p>
<p><code>if(($sReq = $_SERVER['REQUEST_URI']) != ($sUrl = Vframe_Router::Route('auth_login')))<br />
{<br />
$this-&gt;_oUser-&gt;login_backlink = $sReq;<br />
return $this-&gt;_redirect($sUrl);<br />
}</code></p>
<p><em>UWAGA! Jeżeli przekierowujemy użytkownika do strony logowania, zamiast <code>$_SERVER['REQUEST_URI']</code> sprawdzamy <code>$_SERVER['HTTP_REFERER']</code>.</em></p>
<p>Metoda _redirect to nic innego, jak wysłanie <a href="http://www.google.pl/search?hl=pl&amp;q=location+header">header Location</a>. Dodatkowo do sesji użytkownika zapisuję adres, z którego użytkownik został przekierowany do strony logowania. Bardzo ważne jest to, żeby zapisać lokalizację tylko wtedy, gdy adres nie wskazuje na URL logowania, bo użytkownik po zalogowaniu zamiast powrócić do przegladanej strony, wróci do podstrony logowania.</p>
<p>Tuż po autoryzacji ustawiamy w sesji <code>login_backlink</code> na null, aby w przyszłości, gdy użytkownik wejdzie bezpośrednio na podstronę logowania, nie został odesłany do zapamiętanej wcześniej lokalizacji.</p>
<ul>
<li><a href="http://athlan.pl/code/LoginController">Przykładowy kod kontrolera</a> w mojej aplikacji.</li>
<li><a href="http://athlan.pl/code/LoginControllerSimple">Prostszy kod</a> dla ludzi nie używających frameworków.</li>
</ul>
<p>Mała modyfikacja aplikacji i nie denerwujemy użytkownika.</p>
]]></content:encoded>
			<wfw:commentRss>http://athlan.pl/automatyczne-przekierowanie-po-zalogowaniu/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Gazeta.pl nie potrafi korzystać z Windowsa?</title>
		<link>http://athlan.pl/gazeta-nie-potrafi-korzystac-z-windowsa/</link>
		<comments>http://athlan.pl/gazeta-nie-potrafi-korzystac-z-windowsa/#comments</comments>
		<pubDate>Sat, 02 Aug 2008 15:47:37 +0000</pubDate>
		<dc:creator>Athlan</dc:creator>
				<category><![CDATA[Internet]]></category>
		<category><![CDATA[Przemyślenia]]></category>
		<category><![CDATA[Recenzje]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[Wykop]]></category>

		<guid isPermaLink="false">http://athlan.pl/?p=127</guid>
		<description><![CDATA[Podczas przerwy na kawę w godzinach pracy często przeglądam sieć relaksując się czytając newsy. Po kolei włączam onet, interię, tv.wp.pl oraz gazetę. To co ujrzały moje oczy dziś, nie mieści się w głowie: 10 najbardziej wkurzających rzeczy w Windows (serwis sciagnij.pl, subserwis gazeta.pl). Totalna amatorka? Może nietrafny artykuł sponsorowany (dla wymienionego softu)? 1. Dymki z [...]]]></description>
			<content:encoded><![CDATA[<p>Podczas przerwy na kawę w godzinach pracy często przeglądam sieć relaksując się czytając newsy. Po kolei włączam onet, interię, tv.wp.pl oraz gazetę. To co ujrzały moje oczy dziś, nie mieści się w głowie:</p>
<p><a href="http://www.sciagnij.pl/programy/1,90718,5501790,10_najbardziej_wkurzajacych_rzeczy_w_Windows.html">10 najbardziej wkurzających rzeczy w Windows</a> (serwis sciagnij.pl, subserwis gazeta.pl).</p>
<p>Totalna amatorka? Może nietrafny artykuł sponsorowany (dla wymienionego softu)?</p>
<blockquote><p><span class="txt_srodtytul"><strong>1. Dymki z podpowiedziami i ostrzeżeniami</strong></span></p>
<p>Standardowo skonfigurowany Windows co chwilę wyświetla komunikaty o kurczącej się wolnej przestrzeni na dysku czy powiększaniu zapisanego na HDD pliku wymiany. W większości przypadków wyskakujące dymki są zbędne &#8211; informują o rzeczach, o których użytkownik wie albo nie musi wiedzieć.Najprostszym sposobem pozbycia się podpowiedzi jest zainstalowanie bezpłatnego programu XP-AntiSpy (działa także pod Vistą) i zaznaczenie w sekcji &#8220;Inne ustawienia&#8221; opcji &#8220;Nigdy nie pokazuj porad w chmurkach&#8221;.</p>
<p><span style="text-decoration: line-through;">Do pobrania: <a href="http://www.sciagnij.pl/programy/p/XP-AntiSpy/3115">XP-AntiSpy</a></span></p></blockquote>
<p><strong>Komentarz:</strong> Użytkownik powinien być świadomy, że 200 mega miejsca na HDD systemowi może nie wystarczyć. Właśnie na partycji systemowej najczęściej przechowywane są tymczasowe pliki instalacyjne. Szkoda, że MS nie udostępnił narzędzia, w którym można określić ilość krytycznego miejsca na dysku. Ja bym zarezerwował 1.5 giga + miejsce na plików stron.</p>
<blockquote><p><span class="txt_srodtytul"><strong>2. Awanturujące się Centrum Zabezpieczeń</strong></span></p>
<p>W Panelu Sterowania Windows posiada moduł o nazwie Centrum Zabezpieczeń. Ułatwia on zarządzanie zaporą sieciową, antywirusem i automatycznymi aktualizacjami. Kiedy te programy są wyłączone w zasobniku systemowym pojawiają się komunikaty ostrzegające przed zagrożeniami i zachęcające do odwiedzenia Centrum.</p>
<p>Osoby korzystające z samodzielnych zapór <span style="text-decoration: line-through;">(<a href="http://www.sciagnij.pl/programy/p/ZoneAlarm/362">ZoneAlarm</a>, <a href="http://www.sciagnij.pl/programy/p/Ashampoo_Firewall_Free/354">Ashampoo</a>)</span> i chcące w pełni kontrolować instalowanie patchów powinny po otwarciu zakładki Centrum w Panelu Sterowania kliknąć w link &#8220;Zmień sposób informowania mnie&#8230;&#8221; i w kolejnym okienku odznaczyć wszystkie trzy opcje (&#8220;Zapora&#8221;, &#8220;Aktualizacje automatyczne&#8221;, &#8220;Ochrona przed wirusami&#8221;).</p></blockquote>
<p><strong>Komentarz:</strong> Większość programów antywirusowych/zapór informuje system Windows o swojej działalności. Przykład? Avast, Kaspersky. Wówczas chmurki pokazują się tylko wtedy, gdy jest faktyczne zagrożenie komputera (np. gdy wyłączymy zaporę programu antywirusowego lub samą aplikację programu). Użytkownik powinien wiedzieć, że jego system nie jest bezpieczny, jeżeli nie ma zapewnionej ochrony antywirusowej, chmurki są jak najbardziej wskazane, jeżeli je ukrywa, akceptuje to, że sam zatroszczy się o swoje bezpieczeństwo. Potem za szkodliwe działanie wirusa obwiniany jest Windows i Microsoft :)</p>
<blockquote><p><span class="txt_srodtytul"><strong>3. Raportowanie błędów</strong></span></p>
<p>Po &#8220;padzie&#8221; systemu czy aplikacji Windows ma zwyczaj pytania użytkownika o zgodę na przekazanie raportu firmie Microsoft. Większość ludzi podenerwowana klika w tym momencie przycisk &#8220;Nie wysyłaj&#8221;. Problem eliminuje wspomniany już program <a href="http://www.sciagnij.pl/programy/p/XP-AntiSpy/3115">XP-AntiSpy</a>. Wystarczy zaznaczyć opcje &#8220;Wyłącz raporty o błędach&#8221; i &#8220;Office XP: Nie wysyłaj raportu o błędach&#8221; w sekcji &#8220;Raporty o błędach&#8221;.</p></blockquote>
<p><strong>Komentarz:</strong> W zupełności słuszna uwaga, powinna być wbudowana opcja widoczna w ukazującym się oknie o tym, aby zapamiętać wybór.</p>
<blockquote><p><span class="txt_srodtytul"><strong>4. Brak dostępu do skrótów na pulpicie</strong></span></p>
<p>Kiedy użytkownik ma otwarte tuzin okien i potrzebuje dostępu do skrótu na pulpicie musi minimalizować wszystkie foldery oraz programy. Problem ten można rozwiązać włączając pasek szybkiego uruchamiania obok przycisku Start (PKM w pasek Start, polecenie &#8220;Paski narzędzi&#8221; / &#8220;Szybkie uruchamianie&#8221;).</p>
<p>Alternatywą są aplikacje dokletowe. Dzięki nim użytkownicy Windows mogą korzystać z animowanego zestawu skrótów podobnego do tego spotykanego w Mac OS-ie. Takie oprogramowanie obciąża nieco system, ale właściciele nowych maszyn nie powinni zauważyć różnicy. Doklety to estetyczne, w pełni konfigurowalne narzędzie ułatwiające dostęp do programów czy folderów.</p>
<p><span style="text-decoration: line-through;">Pobierz <a href="http://www.sciagnij.pl/programy/p/ObjectDock/3118">ObjectDock</a>, <a href="http://www.sciagnij.pl/programy/p/RocketDock/3121">RocketDock</a></span></p></blockquote>
<p><strong>Komentarz:</strong> Polecam skrót <em>WIN+d</em>.</p>
<blockquote><p><span class="txt_srodtytul"><strong>5. Przyciski chowające się w zasobniku systemowym</strong></span></p>
<p>Aplikacje działające w tle nie są widoczne jako przyciski na pasku Start. Reprezentują je ikony w zasobniku systemowym obok zegara, po prawej stronie ekranu. Niektóre z tych symboli są widoczne, inne chowają się za charakterystyczną strzałką. Problem polega na tym, że prawie zawsze te potrzebne są ukryte.</p>
<p>Większą kontrolę nad ikonami da się uzyskać dzięki opcjom paska Start &#8211; PKM w pasek, polecenie &#8220;Właściwości&#8221;, przycisk &#8220;Dostosuj&#8221; obok pola &#8220;Ukryj ikony nieaktywne&#8221;. Każdemu programowi można przyporządkować jedną z trzech opcji &#8211; &#8220;Zawsze ukryty&#8221; (np. zapora sieciowa), &#8220;Ukryty, gdy nieaktywny&#8221; (np. odtwarzacz MP3) oraz &#8220;Zawsze widoczny&#8221; (np. komunikator internetowy).</p></blockquote>
<p><strong>Komentarz: </strong>Microsoft oddał do dyspozycji perfekcyjne narzędzie &#8211; pasek tray w pełni do zmodyfikowania. Zresztą sam autor neguje swoją wypowiedź w następnym akapicie. Bardzo dobrze, że samemu trzeba wybrać sobie to, co ma być widoczne. Podobne zastosowanie ma miejsce w Firefox 3. Niektóre zakładki są widoczne na pasku zakładek, inne nie. Te bardziej przydatne samemu trzeba sobie &#8220;przeciągnąć&#8221; metodą drag &#8216;n&#8217; drop.</p>
<blockquote><p><span class="txt_srodtytul"><strong>6. Animacje przy minimalizowaniu i maksymalizowaniu okien</strong></span></p>
<p>Kiedy animacje są włączone, minimalizowanie i maksymalizowanie folderów czy programów trwa nieco dłużej. Bajer ten można łatwo wyłączyć zyskując ułamek sekundy &#8211; wszystko dzięki ustawieniom &#8220;Wydajności&#8221; w zakładce &#8220;Zaawansowane&#8221; we właściwościach systemu (można je znaleźć w Panelu Sterowania).</p>
<p>Fanatycy szybkości mogą przejrzeć także pozostałe elementy w oknie &#8220;Opcje wydajności&#8221;. Wyłączenie cieni pod menu czy kursorem, gładkiego przewijania pól list czy wizualnych stylów okien oraz przycisków może pomóc osobom borykającym się z problemem ograniczonych zasobów.</p></blockquote>
<p><strong>Komentarz:</strong> Opcja w pełni do skonfigurowania.</p>
<blockquote><p><span class="txt_srodtytul"><strong>7. Animowany piesek w wyszukiwarce plików</strong></span></p>
<p>Denerwuje i spowalnia użytkownika. Pieska można wyłączyć poprzez polecenie &#8220;Zmień preferencje&#8221; / &#8220;Bez animowanej postaci ekranowej&#8221; (dostępne po wciśnięciu klawisza F3 w dowolnym oknie folderu). Dodatkowo dezaktywuje się tam usługę indeksowania, która w założeniu ma przyspieszać wyszukiwanie plików, a w praktyce często je spowalnia. Użytkowników przywiązanych do starego interfejsu (Windows 95 i 98/98SE) zainteresuje zakładka &#8220;Zmień zachowanie wyszukiwania plików i folderów&#8221;, gdzie można wybrać opcję &#8220;Zaawansowane&#8221;. Dzięki temu &#8220;okienka&#8221; przestaną zadawać głupie pytania typu &#8220;Co chcesz wyszukać?&#8221;. Od razu po wciśnięciu F3 pojawi się pole tekstowe, gdzie wpisuje się słowa kluczowe.</p></blockquote>
<p><strong>Komentarz: </strong>Right-click, wyłącz animowaną postać. Wybór zostanie zapamiętany.</p>
<blockquote><p><span class="txt_srodtytul"><strong>8. Brak możliwości włączania nieautoryzowanych stylów wizualnych</strong></span></p>
<p>Standardowo &#8211; za pośrednictwem właściwości ekranu w Panelu Sterowania &#8211; użytkownik może wybrać jedną z trzech wersji kolorystycznych &#8220;Stylu systemu Windows XP&#8221; lub jedną z kilkunastu klasycznych wariacji. Tymczasem w internecie znajdują się tysiące darmowych layoutów (http://www.themexp.org, http://www.xpthemes.com, http://customize.org/xpthemes).Windows pozwala jednak korzystać tylko ze stylów zatwierdzonych przez Microsoft. To ograniczenie da się obejść, choć wymaga to nieco wysiłku. Kluczowy jest w tym przypadku plik uxtheme.dll zlokalizowany w katalogu C:\Windows\system32. Trzeba go podmienić na &#8220;wolną&#8221; wersję.W razie problemów z aktualizacją (Windows chroni pliki systemowe) można przeprowadzić całą procedurę przy użyciu programów Replacer lub Uxtheme Multi-patcher. Gdyby &#8220;okienka&#8221; chciały przywrócić oryginalną wersję &#8211; wyświetlając specjalny komunikat &#8211; należy wybrać polecenie zachowujące nowy plik.</p>
<p>Pobrane style wizualne trzeba umieścić w katalogu C:\Windows\Resources\Themes. Pojawią się wtedy na rozwijalnej liście wyboru we właściwościach ekranu (zakładka &#8220;Wygląd&#8221;).</p>
<p><span style="text-decoration: line-through;">Pobierz: <a href="http://www.sciagnij.pl/programy/p/Replacer/3124">Replacer</a>, <a href="http://www.sciagnij.pl/programy/p/UXTheme_Multi-Patcher/3127">Uxtheme Multi-patcher</a></span></p></blockquote>
<p><strong>Komentarz:</strong> Zgadzam się, opcja powinna być odblokowana. Polecam <a href="http://athlan.pl/jak-moze-wygladac-windows/">Windows Blinds</a>. Niestety płatne. Dla mniej wymagających: <a href="http://www.sciagnij.pl/programy/p/UXTheme_Multi-Patcher/3127">Uxtheme Multi-patcher</a>.</p>
<blockquote><p><span class="txt_srodtytul"><strong>9. Automatyczne aktualizacje</strong></span></p>
<p>&#8220;Okienka&#8221; potrafią same pobierać z internetu i instalować publikowane przez Microsoft łaty. Procedura aktualizacji rozpoczyna się zwykle w najgorszym możliwym momencie &#8211; na przykład wtedy, gdy jest uruchomione dziesięć innych aplikacji. Spowalnia to system &#8211; może prowadzić nawet do jego zawieszenia.Dodatkowo nie wszystkie łatki są potrzebne. Niektórych programów (chociażby Internet Explorera 7) po prostu nie warto instalować. Dlatego lepiej mieć pełną kontrolę nad aktualizacjami. Samoistne sprawdzanie dostępności łatek wyłącza się w oknie &#8220;Aktualizacje automatyczne&#8221; (Centrum Zabezpieczeń w Panelu Sterowania). Procedurę da się przeprowadzić ręcznie wybierając polecenie &#8220;Windows Update&#8221; z Menu Start.</p></blockquote>
<p><strong>Komentarz: </strong>Zarówno czas, sposób jak i instalowanie poszczególnych łatek można w pełni konfigurować we własnym zakresie. Microsoft daje wolną rękę użytkownikowi.</p>
<blockquote><p><span class="txt_srodtytul"><strong>10. Niedziałające kodeki</strong></span></p>
<p><a href="http://www.sciagnij.pl/programy/p/Windows_Media_Player/1055">Windows Media Player</a> to wielki program z mnóstwem funkcji, który ma problemy podczas odtwarzania nawet najbardziej popularnych formatów plików. Bardzo często aplikacja chce łączyć się z siecią i pobierać dodatkowe kodeki. W większości przypadków taka operacja kończy się znanym bardzo dobrze użytkownikom &#8220;okienek&#8221; niepowodzeniem.Aby nie mieć kłopotów z odtwarzaniem nawet egzotycznych formatów audio i wideo najlepiej zainstalować <a href="http://www.sciagnij.pl/programy/p/K-Lite_Mega_Codec_Pack/148">K-Lite Mega Codec Pack</a>. Zawiera on wszystko, co potrzebne, aby słuchać radia w standardzie Real czy oglądać filmy DivX albo MKV. WMP często nie rozpoznaje alternatywnych kodeków. Wtedy z pomocą przychodzi niewielki Media Player Classic, część pakietu K-Lite.</p>
<p>Pobierz <a href="http://www.sciagnij.pl/programy/p/K-Lite_Mega_Codec_Pack/148">K-Lite Mega Codec Pack</a>.</p></blockquote>
<p><strong>Komentarz:</strong> WMP sam pobierze sobie odpowiednie kodeki, jeżeli nie zainstalujemy Mega Codec Packa. Uzupełnianie zasobów przez WMP to minus? Ooops. Chyba zły punkt numer 10. Chyba żeby była okrągła liczba, nie 9.</p>
<p><strong>Podsumowanie:</strong></p>
<p>System windows nie atakuje, a ostrzega użytkownika. Znaczną większość powiadomień można wyłączyć na swoją odpowiedzialność. Ale potem często pojawiają się opinie: &#8220;jaki ten Windows jest lipny&#8221;. A ostrzegał.</p>
<p>Jeżeli podoba Ci się artykuł, <a href="http://www.wykop.pl/link/82922/gazeta-pl-nie-potrafi-korzystac-z-windowsa">wykop go</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://athlan.pl/gazeta-nie-potrafi-korzystac-z-windowsa/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>WordPress sitemap plugin</title>
		<link>http://athlan.pl/wordpress-sitemap-plugin/</link>
		<comments>http://athlan.pl/wordpress-sitemap-plugin/#comments</comments>
		<pubDate>Tue, 28 Aug 2007 21:28:23 +0000</pubDate>
		<dc:creator>Athlan</dc:creator>
				<category><![CDATA[Internet]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Przemyślenia]]></category>
		<category><![CDATA[Solutions]]></category>
		<category><![CDATA[Wordpress]]></category>

		<guid isPermaLink="false">http://athlan.vgroup.pl/wordpress-sitemap-plugin/</guid>
		<description><![CDATA[Na potrzeby pewnego projektu o pewnej nazwie, o której niebawem się przekonacie, powstał nowy plugin. Generuje on sitemapa przyjaznego dla Google. Po co to wszystko&#8230; Rejestrując się w google mamy możliwość skorzystać z wielu narzędzi webmastera. Jednym z nich jest &#8220;pomaganie&#8221; robotowi w indeksowaniu naszej strony, poprzez podanie mu sitemapu po którym powinien się poruszać. [...]]]></description>
			<content:encoded><![CDATA[<p><img SRC="http://vgroup.pl/www/img/projects/project_contexlink_sitemap.jpg" ALIGN="left" BORDER="1" HEIGHT="160" HSPACE="10" VSPACE="10" WIDTH="160" />Na potrzeby pewnego projektu o pewnej nazwie, o której niebawem się przekonacie, powstał nowy plugin. Generuje on sitemapa przyjaznego dla <a HREF="http://google.com">Google</a>. Po co to wszystko&#8230; Rejestrując się w google mamy możliwość skorzystać z <a HREF="https://www.google.com/webmasters/tools/siteoverview">wielu narzędzi webmastera</a>. Jednym z nich jest &#8220;pomaganie&#8221; robotowi w indeksowaniu naszej strony, poprzez podanie mu sitemapu po którym powinien się poruszać. Po <a HREF="http://www.google.com/support/webmasters/bin/topic.py?topic=8472">zweryfikowaniu</a> naszej strony w systemie google, proszeni jesteśmy o podanie sitemapu (<a HREF="http://www.google.com/support/webmasters/bin/topic.py?topic=8472">więcej o weryfikacji</a>).</p>
<p ALIGN="left">W tym miejscu z pomocą przychodzi nam <strong>WordPress ContexlinkSitemap Plugin</strong>. Plugin przede wszystkim:</p>
<ul>
<li>tworzy mapę witryny uwzględniając wszystkie notki i podstrony na blogu oznaczone jako &#8220;published&#8221;,</li>
<li> uwzględnia strukturę permalinków, URL&#8217;i przyjaznych dla wyszukiwarek sprecyzowanych w konfiguracji bloga</li>
<li>datę ostatnich zmian na stronie głównej oraz w każdym linku z osobna</li>
<li>dostosowany do mapy google:<br />
<a HREF="https://www.google.com/webmasters/tools/docs/pl/protocol.html">https://www.google.com/webmasters/tools/docs/pl/protocol.html</a></li>
<li>jest bezpłatny : -)</li>
</ul>
<p ALIGN="left"><strong>Instalacja pluginu:</strong></p>
<ol>
<li><a HREF="http://athlan.vgroup.pl/wp-content/uploads/ContextlinkSitemap.rar">Pobierz paczkę pluginu stąd (format ZIP)</a></li>
<li>Folder <em>ContextlinkSitemap </em>skopiuj do folderu <em>wp-content/plugins/</em></li>
<li>Plik sitemap.xml skopiuj do folderu głównego bloga i nadaj mu chmod 777</li>
<li>Aktywuj plugin w zakładce <em>Plugins</em> wpanelu administracyjnym bloga</li>
</ol>
<p ALIGN="left"><strong>Dodanie mapy witryny do google:</strong></p>
<ol>
<li>Zaloguj się na google.com na swoje Google Account lub Gmail Account.</li>
<li>W nagłówku strony głównej google.com przejdź w sekcję &#8220;Moje konto&#8221;</li>
<li>Z listy &#8220;Moje usługi&#8221; wybierz pozycję &#8220;Narzędzia dla webmasterów&#8221;</li>
<li>Dodaj swoją stronę.</li>
<li>Wybierz formę weryfikacji strony i postępuj ze wskazówkami google</li>
<li>Gdy strona pojawi się w tabeli oraz jej status zostanie oznaczony jako &#8220;zweryfikowana&#8221;, kliknij w opcję &#8220;Dodaj mapę&#8221; i podaj adres URL mapy strony <em>(defaultowo http://twojblog.pl/sitemap.xml, czyli tam, gdzie wrzuciłeś plik)</em>.</li>
</ol>
<p ALIGN="left"><strong>Dodatkowa konfiguracja:</strong></p>
<p ALIGN="left">W celu szczegółowej konfiguracji pluginu, możesz:</p>
<ul>
<li>zmienić ścieżkę mapy w stałej CONTEXLINK_MAP_FILE</li>
<li>zmienić częstotliwość automatycznej aktualizacji mapy w stałej CONTEXLINK_MAP_UPDATE, wartość podawana w sekundach, defaultowo co 2 dni: 3600 * 24 * 2.</li>
<li>zmienić miejsce wykonywania aktualizacji, zwykle jest to przy ładowaniu sekcji head na blogu. Aby dokonać zmian, należy zmienić miejsce akcji: add_action(&#8216;wp_head&#8217;, &#8216;ContexlinkSitemapAutogenerate&#8217;); Pełną listę miejsc, w których można wykonać operację możesz znaleźć tutaj: <a HREF="http://codex.wordpress.org/Plugin_API/Action_Reference">http://codex.wordpress.org/Plugin_API/Action_Reference</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://athlan.pl/wordpress-sitemap-plugin/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>
<!-- WP Super Cache is installed but broken. The path to wp-cache-phase1.php in wp-content/advanced-cache.php must be fixed! -->
