Funky caching in WordPress + nginx/php-fpm without any plugins

If you ask me if I am against  doing everything yourself  or using the available solutions, my answer usually is going to be that it is best to use what’s available and save yourself the time and efforts to do (and maintain) a solution of your own. „Usually“. Here’s a cautionary tale of how tried to go by that rule and did the opposite.

A foreword.

I have this pet project which is 10 WordPress sites on a 512MB VPS. Each of those sites has several thousand posts and few thousand tags/categories. It used to be no problem for WordPress to handle such sites, but with each new version WordPress is getting slower and slower. Last configuration was on a 512MB slice from Slicehost, which at the time (late 2008) seemed like the best solution.

Using APC.

To make it run as fast as possible I deployed APC, although any other opcode cache would do fine. The „project“ is a set of 10 WordPress sites. Unfortunately this made it run slower than originally. Why ? Imagine having 2 sites,  abc.com and xyz.net, both running the  latest WordPress,  so almost everything inside them is the same – everything but their own unique themes and their own set of plugins. Now, when those run on the same machine APC will have to store the files for both of them, e.g. /var/www/abc.com/html/wp-login.php and /var/www/xyz.net/html/wp-login.php – and this is the same for every other file from the WordPress distribution. At first it is obvious that this is just waste of space since those are identical. At second look however you see the worse downside:  these identical files are going to take twice as much space.  This will cause the APC memory to deplete really fast and because of this decreasing the performance boost we are expecting to get from having a opcode cache. Now instead of having just 2 sites imagine having 10 sites: the cached copies inside APC were cleaned up before even being hit, because the space in the bytecode cache was not enough.

Using APC with symlinked WordPress.

The solution for the problem above was to symlink everything for each site except the configuration from wp-config.php – in this way APC will not have to deal with duplicate copies of the same files. The plugins and themes are also placed with one another. Here’s what a ls -la looks like:

lrwxrwxrwx 1 www-data root   27 Feb 18 16:36 crossdomain.xml -> /var/www/wp/crossdomain.xml
-rw-r--r-- 1 www-data 1000  246 Dec  4  2007 favicon.ico
lrwxrwxrwx 1 www-data root   21 Feb 18 16:36 index.php -> /var/www/wp/index.php
lrwxrwxrwx 1 www-data root   23 Feb 18 16:36 license.txt -> /var/www/wp/license.txt
lrwxrwxrwx 1 www-data root   23 Feb 18 16:36 readme.html -> /var/www/wp/readme.html
lrwxrwxrwx 1 www-data root   22 Feb 18 16:36 robots.txt -> /var/www/wp/robots.txt
lrwxrwxrwx 1 www-data root   27 Feb 18 16:36 wp-activate.php -> /var/www/wp/wp-activate.php
lrwxrwxrwx 1 www-data root   20 Feb 18 16:36 wp-admin -> /var/www/wp/wp-admin
lrwxrwxrwx 1 www-data root   22 Feb 18 16:36 wp-app.php -> /var/www/wp/wp-app.php
lrwxrwxrwx 1 www-data root   23 Feb 18 16:36 wp-atom.php -> /var/www/wp/wp-atom.php
lrwxrwxrwx 1 www-data root   30 Feb 18 16:36 wp-blog-header.php -> /var/www/wp/wp-blog-header.php
lrwxrwxrwx 1 www-data root   32 Feb 18 16:36 wp-comments-post.php -> /var/www/wp/wp-comments-post.php
lrwxrwxrwx 1 www-data root   31 Feb 18 16:36 wp-commentsrss2.php -> /var/www/wp/wp-commentsrss2.php
-rw-r--r-- 1 www-data 1000 1048 Feb 23 20:08 wp-config.php
lrwxrwxrwx 1 www-data root   22 Feb 18 16:36 wp-content -> /var/www/wp/wp-content
lrwxrwxrwx 1 www-data root   23 Feb 18 16:36 wp-cron.php -> /var/www/wp/wp-cron.php
...

How is that going to help? Very simple: there are no more duplicates and APC only works with one set of files. A little tweaking was required to make wp-super-cache plugin work, but for a while everything was OK.

The new setup.

Recently I moved the project from Slicehost to Linode:  it is a lot more affordable – 32bit setup for almost half the money (for 512MB). For the new setup I decided to ditch Apache and try something new – nginx + phpfpm. That was not enough and decided to ditch wp-super-cache and try w3-total-cache with all the different layers of performance boost that it offers. Linode makes very easy to deploy a LEMP setup, and with some help from a friend of mine I was ready to go.  Then I installed w3-total-cache and the problems started:

  • you got to do your own rewrite rules for nginx
  • the „page cache“ was not using the domain name for the site, so the wrong cached pages popped up at the wrong places, e.g. abc.com/2008/10/ opened the page from xyz.net/2008/10/
  • the performance was degraded, not improved

I spend the best of the last 2 days to try and find a way around this issue.  It seems that W3TC prepends the domain name only when the WordPress is a WPMU/Multisite installation. I do not wanted to dive into that, so after 30 mins ot thinking I decided to ditch W3TC and do a „page cache“ (funky caching really) on my own. After several hours I was ready, and it works like a charm.  It pretty much works like wp-super-cache, but without all the settings and admin pages – I know how my projects behaves so I didn’t need all the stuff for purging cache on adding comments, changing statuses, writing new posts or whatever. I customized part of W3TC for cleaning up the static files on disk, but instead of relying on WordPress’ pseudo-cron I decided to use the VPS crontab instead: and it is really easy – you just have to call the script ;)

The script and how to set it up yourself ?

http://pastie.org/1633881 (download)

First you got to enable WP_CACHE in your wp-config.php file, because this is the requirement to make WordPress include our script. The script is called advanced-cache.php (WordPress named it like that) and it has to be placed inside your wp-content/ folder. You can find the rewrite rules for nginx inside the script as a comment (if you are wondering what HB stands for, that’s an abbreviation for the pet project).

So, fuck you W3TC ;)

PS. It turns out somebody else has that symlink solution figured out long time before myself, and it is actually documented on WordPress’ Codex:

This system is based on Allan Mertner’s original symbolic link hack.

http://codex.wordpress.org/Installing_Multiple_Blogs#Virtual_Multiblog

Quarantine Logging

Certainly these days everyone knows that your PHP project should be developed with error reporting set to report everything with E_ALL|E_STRICT (I guess the sloppy freelance days are over). When the project is deployed on a live server you can set the error reporting to log only the severe errors (fatal errors and exceptions), or you can leave it with the detailed error reporting. While I was not a fan of the latter, I did appreciate how useful it is when tracking bugs, since there is certainly a notice or a warning to hint you in the right direction. It is twice that important if your project is a SaaS solution, and you are responsible not only for the development of the application, but for hosting it and making it available to your customers. In that situation detailed logging is as priceless as doing backups: probably in 95% of the time you will not need it, but when the „fuck-up fairy“ comes knocking it is better to be prepared.

Speaking of backups, detailed logging probably shares one more feature with them – the huge space that it occupies. For an application that works without hick-ups for several months the volume of the logs is several times bigger the rest of the application data. The volume of the detailed logging data gets even bigger when 3rd party apps, plug-ins or add-ons are used, since they not always comply with the E_ALL|E_STRICT policy, and some of them might produce tons and tons of repetitive warnings and notices. Depending on the media on which the detailed logging is stored, it might cause performance degradation in the application when its volume gets out of proportions.

That’s the problem I’ve been faced with recently. I want application to run as fast as possible, not burdened by the detailed logging, yet I want to be able to have all the data I need for when tracking bugs and issues. The solution I came up with is called „Quarantine Logging“.

In general, I want to keep all the data that is required for tracking and fixing issues. So, when are these „issues“ tend to surface ? I am sure that you share the same observations as myself: issues happen when the application is deployed: when installed, or when updated. When it starts running you fix the issues that pop up, and the longer it runs, there is less chance for something to go south. Anyway, that’s not always – that’s just for most of the time: usually bugs show their ugly head when you least expect them ;) So, if we can expect problems with a certain probability for some period of time (e.g. after deployment), then let’s turn on the detailed logging only for that time. Hence, the „quarantine“ ;)

The quarantine is the time after the application has been updated, or in other words the time after the supposed problems have been introduced. Until the application is under quarantine, it will use detailed logging to report everything that’s happening. Once the quarantine is over, we can reduce the logging to only the severe emergencies: errors and uncaught exceptions. It is a fair trade-off: you get all the detailed logs for N days (a week) while under quarantine, and then only the most ugly stuff ;)

To make this work after each deployment, install or update, you have to put the application under quarantine, where you choose what period will fit your needs – in mine it is (as suggested above) 7 days. That’s not all: while not being under quarantine, an error or an uncaught exception can happen. The severe-only logging will report it, but in addition to that it will put the application in a state of „self-quarantine“ for M days (2 for example): during that time we expect the error/exception to happen again, so we are again doing the detailed logging while under quarantine.

So, that’s it for Quarantine Logging. I will not bother you with a concrete implementation, I am sure you can manage to do it yourself for your own error reporting. I myself just implemented this feature and will see how it behaves in the next few months.

Свободно за индексиране огледално копие на сайта на правителството

Schtrack! в действие: огледално копие на сайта на правителството, свободен за индексиране…
Schtrack! в действие: огледално копие на сайта на правителството, свободен за индексиране…

Моят допълнителен принос в кампанията „SEO срещу правителството“ е огледалното копие на правителствение сайт, което вече може да се обхожда безпроблемно от паяците на търсещите машини. Сайтът така или иначе е счупен на повече от едно място, и е пълен провал, но пък това да може да се индексира от Гугъл е една малка победа на научно-техническия прогрес ;) Чудя се, колко време ще им трябва да оправят robots.txt, дали ще смогнат преди изборите ;)

Както и да е, сайтът е http://government.bg.kaloyan.info/, и страниците в огледалното копие се попълват когато се посети съответния линк от сайта: например като посетите началната страница на копието ще се изтегли началната страница на оригинала. Дал съм време за живот на огледалните копия от 12 часа, така че ако информацията не се опреснява често, няма страшно – не е бъг ;) Връзките със „cgi-bin“ в тях са леееко променени, за да се заобиколи недостатък от хостинга (изключване на ScriptAlias „cgi-bin“ или каквато и да е тъм магия), така че и това не е бъг ;)

Скриптът, Schtrack!,  който прави възможно функционирането на огледалното копие, ми се върти в главата някъде от 2004, обаче все нямам време или повод, за да го направя. Е, ето сега се появи добър повод, и резултата е налице. На който му се занимава, може да намери кода на приложението тук (а самото приложение тук), и да си го ползвате за каквото си искате (в рамките на GPL, разбира се).

1 февруари 2009: Една седмица по-късно вече има стотина страници индексирани от огледалото на правителствения сайт, което може да се отбележи като някакъв относителен успех, особено като се има предвид некадърните адреси на сайта (под некадърни разбирайте такива, които не прилагат най-добрите практики, а точно обратното – супер антични). Друг успех е променения robots.txt, който вече позволява индексиране в дълбочина. Хайде да видим колко други неща ще се променият до преди изборите ;)

Сертифициране за Zend Framework

[author]Zend Framework Certification[/author][/photo]

Роб Алън (Rob Allen) пише, че от Zend са започнали процедури за сертифициране за Zend Framework. По този начин ще може да се види, че вие (като притежател на такъв сертификат) можете и знаете как да работите и използвате Zend Framework. Като резултат се е получил един не много лесен изпит, и за да го вземете трябва да познавате „платформата“ в детайли, в цялото и разнообразие от решения и възможостти – нещо което няма да е лесно само ако сте хвърлили едно око върху нея ;)

Излезе CakePHP 1.2RC3

live.cakephp.org
live.cakephp.org

От блога на Крис Хартджис (Chris Hartjes):

Thanks to the power of Twitter I found out that The Show for CakePHP was resuscitated and brought back to life last night. I listened to the (surprisingly short) show, the main thrust of which was that CakePHP 1.2RC3 was released last night. Why should you care?

Въпреки, че винаги е добре да видиш, че един проект се развива и пуска нови версии, трябва да призная, че веждам как CakePHP забавя развитието си. В същото време предполагам, че това не  е защото изведнъж всички за изгубили мотивация или вяра в проекта, а че интересното ни настояще ни притиска, и не остава време да се занимаваме с подобни интересни проекти. Интересно това ли е влиянието на „глобалната икономическа криза“ – по-малко време за проектите с отворен код и хобитата ;) Разбира се, това е повърхностно изказване, защото за много хора проектите с отворен код са им работата и препитанието. Както и да е – да пожелаем успех на CakePHP проекта и скорошно излизане на стабилната 1.2 версия!
ьяаоьяао

Намиране на линковете в HTML код

Преди малко прочетох ето това:

Do not use REGEX to parse HTML

Perhaps the biggest mistake people make when trying to get URLs or link text from a web page is trying to do it using regular expressions. The job can be done with regular expressions, however, there is a high overhead in having preg loop over the entire document many times. The correct way, and the faster, and infinitely cooler ways is to use DOM.

By using DOM in the getLinks functions it is simple to create an array containing all the links on a web page as keys, and the link names as values. This array can then be looped over like any array and a list created, or manipulated in any way desired.

Note that error suppression is used when loading the HTML. This is to suppress warnings about invalid HTML entities that are not defined in the DOCTYPE. But of course, in a production environment, error reporting would be disabled and error reporting set to none.

По принцип виждам логиката, но в много случаи логиката е била изобличавана от няколко теста. Наистина ми е интересно от двата метода (RegExp и Dom), кой какви резултати ще даде. Също така е важен и обема на информацията – някои страници са наистина огромни „прасета“ ;) Както и да, ако ми остане малко време днес ще се взема да пробвам какви резултати ще се получат. Интересно е и как ще се спряват със „счупен“ HTML, и колко стабилно ще работят. Точно за големи по обем файлове, където е за предпочитане работата на „парче“ от файла, за да се пести памет (вместо да заредим цялото „прасе“ наведнъж), ми е интересно как може да се използват двата метода; докато решението с RegExp е повече от очевидно за мен, то това с Dom е по-… „по-екзотично“, защото трябва да се види ка кможе да се заобиколи проблема с цялостта на документа. Може би да се използват някакви междинни модули, като KSES и HtmlPurifier, ще помогнат да се изгладят парчетата от HTML-а. Абе ще видим, може и нещо друго да ми хрумне ;)

Версия 0.3.4 на приставката за добавяне на Svejo.net бутони

Готова е и следващата версия, която представя няколко малки подобрения. Първо, променен е начина по който се отпечатва бутона (HTML кода е променен). Второ, оправен е бъг, който не позволяваше да се добавя допълнителния “опасващ” HTML код към бутона (от секция “Оформление”). Трето, заради зачестилите оплаквания от Internet Explorer, за момента съм добавил временна “кръпка”, която слага празно заглавие и описание на бутона, за да може капризния Internet Explorer да работи както трябва. Това е само за потребителите с Internet Explorer – всички други с нормални браузъри ще виждат всичко. И най-накрая, кода за бутона вече има няколко CSS класа, които да помогнат на по-сръчните да поукрасят бутона. Бутонът винаги има клас svejo и в зависимост от това на коя страница се показва, се добавят допълнителни класове като svejo_home (когато се показва на първа страница), svejo_tag (когато се показва на страница за етикет/таг)  и т.н.

Благодаря на Красьо за докладваните проблеми и идеите за подобрения. Новата версия може да изтеглите от тук:

а повече за проекта може да прочетете тук:

Ако и вие сте забелязали проблеми, направете като Красьо и не се притеснявайте да ги докладвате ;)

PHP се развива … корпоративно!

ZendCon 2008[author]Michael Coté[/author][/photo]

Да не съм чул повече, че PHP разработчиците са аматьори ;) E, наистина повечето са наистина безнадежни драскачи, но все пак започва да се оформя едно професионално ядро, с много по-добра дисциплина и производителност. Днес прочетох нещо, което потвърди моите наблюдения. Скорошно проучване на Gartner Research установило, че 10 процента от PHP общонстта са корпоративни IT разработчици, и предвиждат, че през следващите пет години това число ще се увеличи до 40 процента. Това са отлични новини за PHP разработчици които търсят „корпоративна кариера“, и още по-добри новини за „компанията зад PHP“ Zend Technologies, която цитира това „откритие“ на Gartner Research на  Zend/PHP Conference предната семица като доказателство за все по-широко разпространяващото се, стратегическо усвояване и приемане на PHP в големите компании.

Повече може да прочете тук:

Zend Framework 1.6

Zend Framework 1.6

Докато се наканя да напиша за някои от дребните си разочарования в развитието на Zend Framework, подбудени от от Preview варианта на Zend Framework 1.6, то взе че вече излезе и официално. Днес RSS емисиите изглежда разтикаха вчерашното задръстване от представянето на Google Chrome, и днес новото задръстване ще е за Zend Framework 1.6!

За начало, ето официалното съобщение от Кал Евънс (който е бил изпреварен от Анди Гутмънс, но това след малко):

Zend Framework 1.6 is now available and a significant upgrade in several areas… his version of Zend Framework gives developers a few new tools for their ever expanding toolbox.

  • Zend_Tool
  • Lucene 2.3 Index File Format Support
  • Zend_Session save handler for Database Tables
  • Paginator Component
  • Figlet Support
  • ReCaptcha Service
  • Captcha Form Element
  • Zend_Config_Xml Attribute Support
  • Zend_File_Transfer Component
  • File Upload Form Element
  • Zend_Wildfire Component with FireBug Log Writer

Other features have been added and a whopping 287 tickets closed as Zend Framework continues to mature and establish itself as the premier framework for PHP development… The entire manual has been published into a PDF.

Ако прочетете оригинала, ще видите, че са спестени някои от нововъведенията, основно интеграцията с Dojo. Общо взето няма смисъл да се копират същите неща, които Анди Гутмънс така добре е описал за тази версия:

The Zend Framework Community has delivered another feature-rich release of Zend Framework and I’m extremely proud and happy to see the energy and excitement around this project. The ZF team (Wil Sinclair, Matthew Weier O’Phinney, Ralph Schindler, Alexander Veremyev) along with many others in the ZF community and at Zend, have been doing a superb job and have been working very hard to put this release together. I’d also like to extend the team’s thanks to Alex Russell, Dylan Schiemann, and Peter Higgins from the Dojo Foundation who supported the collaboration between ZF and Dojo and helped make the integration a reality for the 1.6 release. Such a deep collaboration between a major server-side framework and a market leading client-side Javascript framework is a rarity in the Web community.

Нека и аз не ги копирам ;) Прочетете, много добре е описано всичко. Сега, въпреки че аз се чувствам удобно с друга JavaScript библиотека, наистина комбинирането на толкова ниско ниво на „уеб платформа“ (как е web framework на български) със JavaScript библиотека е наистина супер, и ще е много удобно за всички, които тепърва решават да „прохождат“ в тази област.

Друго интересно нещо е статистиката за проекта (въпреки, че някои неща изглеждат като стъкмистика):

  • над 7 милиона изтегляния
  • повече от 500 пешещи ентудиасти (ъъ, как е contributors на български)
  • над 1000 страници в Reference Guide
  • повече от 500 примера в Reference Guide
  • почти 2 милиона резултата в Google
  • над 750 резултата в момента на  Technorati
  • повече от 60 проекта базирани, или разширяващи Zend Framework на SourceForge
  • над 30 проекта базирани, или разширяващи Zend Framework на Google Code
  • повече от 3000 бъга оправени в тракера на проекта

И за финал – както виждате от снимката по-горе леееко са префасонирали и сайта на проекта, за да се види по-ясно „братската дружба“ с Dojo.

Propel 1.3 използва PDO вместо Creole

Схема на ORM връзките между Propel и Symfony[/snimka]

Eто нещо почти очаквано. В опит да подобри производителността и да се възползва от новите подобрения в PHP, новата версия Propel 1.3 използва PDO (който е native за PHP 5.1+) за абстркация на връзките с базите данни (как е на български database abstraction layer ?). Тази промяна има доста последствия, особенно за тези, които изпълняват SQL директно. API-то на PDO е подобно на Creole, така че тази промяна няма да предизвика някакви сериозни преструктуриряния в архитектурата на проекта.

Повече информация за новите подобрения може да прочетете тук:

Ние ползваме Creole, и обсъждахме дали да го сменим с PDO (и не само него, ами и сраслият с него демон „Rudolf“), обаче покрай многото работа, така и не стигнахме до заключение. Май това е поредния пирон в ковчега на Creole, както и другите подобни библиотеки.