donderdag, november 25, 2010

Hebben banners hun langste tijd gehad?

Banners beginnen hun effect te verliezen. Bezoekers gebruiken ad-blockers en zien de advertenties niet. De banners zien er altijd hetzelfde uit en vallen niet meer op. En de banners die wel getoond worden hebben vaak een lage relevantie voor de bezoeker. Door de afnemende resultaten staan de prijzen die voor banners betaald worden onder druk. Een webmaster verdient minder aan zijn banner en moet op zoek naar nieuwe verdienmodellen.

De reactie van de markt is voorspelbaar: meer en grotere banners. Animaties om beter op te vallen en hele site takeovers waarbij banners over de content vallen. Hoe groter de advertenties hoe irritanter mensen ze vaak vinden. En als mensen al klikken is het maar de vraag of ze ook doorgaan naar conversie. Wellicht is zo’n advertentie goed voor de naamsbekendheid van je merk maar het resultaat daarvan blijft moeilijk meetbaar. Terwijl online-marketing bij uitstek het platform is voor meetbare, resultaatgerichte campagnes.

Aan de ene kant zijn adverteerders altijd op zoek naar goede kwalitatieve leads, aan de andere kant willen webmasters hun traffic verzilveren zonder hun bezoekers af te schrikken. En adverteerders willen hun klanten ook niet overdonderen met hun boodschap door op 100-en sites dezelfde advertentie te plaatsen. Daarom wordt het steeds belangrijker om bezoekers relevante advertenties te tonen en de barrière voor conversie zo laag mogelijk te houden. Met banners gaat dat niet lukken. Widgetmarketing maakt het wel mogelijk.

Bij widgetmarketing plaatsen we een klein, interactief, herbruikbaar blokje: de widget op de websites van partners. Deze widget kan zich aanpassen aan de context van de website en het (klik)gedrag van de bezoeker. De interactie met de gebruiker vindt in de widget plaats. De bezoeker gaat niet naar een andere website en schrikt dus ook niet van ineens een andere site.

Succesvolle widgetmarketing staat of valt bij een goede widget, een goed platform wat de widget snel en stabiel host en uitgebreide statistieken voor de adverteerder en webmaster. En laat dat nu net zijn waar ik de afgelopen maanden hard aan gewerkt heb: widgeteer, het platform voor widgetmarketing van Colours

maandag, februari 08, 2010

Klein en ondernemend, een jaar later

Een jaar geleden schreef ik het volgende:

Het succes van jufmelis.nl is ons naar het hoofd gestegen. We zien continue stijgende bezoekersaantallen (bijna 1000 per dag), veel nieuwe links naar ons en steeds hogere ratings bij google.

http://wasigh.blogspot.com/2009_01_01_archive.html

We zijn nu een jaar later en er is veel gebeurd. Sinds vorig jaar zijn de bezoekersaantallen verdubbeld. En afgelopen januari was wederom een recordmaand. Bijna 1 miljoen pagina's van Juf Melis zijn er opgevraagd! Er waren duidelijke pieken rondom de CITO toets.

Ook hebben we in een aantal bladen gestaan zoals de COS, en blijven de rankings van google stijgen. Wat grappis is om te zien is dat veel mensen de site binnenkomen via zoekwoorden als: "juf melis", "juffrouw melis", "jufmelis" etc. Veel mond- tot mond reclame waarschijnlijk.

Sinds eind oktober verkopen we ook abonnementen. Iets later als dan gepland, maar met echt klanten ben je natuurlijk pas echt een bedrijf. En bij een echt bedrijf hoort een echte administratie en een echte begroting. Dus zijn we een tijd geleden bij elkaar gaan zitten om de plannen en doelstellingen voor 2010 vast te stellen.

Ik wil nog niet teveel over de plannen van 2010 vertellen. Maar met een beetje geluk hebben we binnenkort ook een tastbaar product! Voor de rest blijft het een leuke hobby. Het succes geeft veel energie, de vele leuke reacties helpen daar zeker bij. Ik ben benieuwd wat ik volgend jaar kan melden!

zaterdag, februari 06, 2010

Distributed version control for Databases (technical story)

I'm using Mercurial for some time now and I really enjoy using it. I only code by myself but I want my code to be versioned. One of the biggest advantages for me is the build in pull/push mechanism which can update repositories over the network.

I have a development repository on my computer. And with a simple click I can upload my code changes to the beta environment. Also: if I need to quickly fix a bug, I can clone the live environment to a new folder on my development laptop, make some changes and push it right back to the production server. And be sure that my change is tracked.

There is however one more problem: my web applications consist of both code and data. And the data is located in a database which is described by a data model. For some changes I need a data model change or certain data in the database. And this is not yet manageable by Mercurial. But there has to be a solution. And this is what kept me busy for weeks already.

If we take Mercurial as an example we have three things: The repository, the current working dir and the buffer.

The repository holds all the checked-in versions of the code. It also holds the history. It also holds all the information to recreate a certain snapshot.

The current working dir holds the so called "HEAD" version of the repository and the changes that have not yet been committed to the repository.

The buffer is used to track changes in the current working dir and how to apply them to the repository. For example if two files are modified and then committed. They are written to the buffer first. And from there a transaction is started if for example a change can not be committed because of a merge conflict the corresponding file will be marked and the other will be written to the buffer.

How does this apply to databases?

It took a lot of thinking to come up with the simplest idea. In a database the current working dir is the database itself.

The repository should be an exact copy of the database but then in a different format because we need a different kind of metadata.

There are however some problems:

Tracking changes to data:

Where changes to files are checked by generating and comparing hashed versions of files. This also can be done with database-tables or records. However this could be very costly for large recordsets. I would propose to use two fields: "created" & "modified". When a record is inserted into the database the "created" field is set to the current timestamp.  When a record is updated the "modified" field is set to the current timestamp.

Getting changes since the last "commit" is simple querying the database records that have created or modified fields later than the last commit time.

(This may introduce a problem when times are inconsistent between different database servers, however I guess this will workout as long as time is consistent on the server by itself)

Tracking changes to the data model:

It can be possible for the data model to change, maybe a table field will be changed, added or deleted. During a commit the system must check the data model against the repository and save changes accordingly.

Updating a live data on a database:

In mercurial we execute the "pull" command to get the latest changes from another repository. Updating the working dir is a different command "update". With databases there may be a lot of data to change, taking a long time. Also we don't want to get the database into an inconsistent state. The key here seems to be transactions. However it might be better to create a shadow database, change the data model and data there and when all changes are applied correctly the whole database is copied to the live database.

Unique identifiers:

One mayor problem with databases are primary keys, they need to be unique. The problem is here is that we have separate servers inserting records into the database. We could use GUID's for primary and foreign keys . But I really dislike GUID's for keys. Especially from a performance point (in mySQL at least). My solution would be to use GUIDs in conjunction with integer primary keys. Every record would have a primary key an auto increment int and a GUID. The GUID is created with a server unique key and a unique hash over the record. The GUID for the record will never change.

When a record is saved to the repository all primary keys and foreign keys will be replaced by GUIDs. When merging records also the GUIDs will be used.

Not tracking all data:

Just as with code you may not need some data not to be in your repository like compiled DLL's for example. This can also be true for databases. For cached data, logs, sessiondata etc you may want to track the datamodel but not the data. So there must be a way to not track the data. (or stop saving this data in the database. The data is just filling up your backups!)

Keeping the repository small:

A repository will be at least the size of the original database. But was we track more metadata and delta's the repository can become quite large. Smart use of compression should make it possible to keep the repository quite small.

What's next?

This solution depends heavily on a certain database structure. I would really like to have a solution that works on any database, but that would mean I had to get a HASH on every record in a database. I imagine this will become very costly quite fast

The best way to implement this would probably be a mercurial commit hook. So the database changes get committed at the same time as the code changes.

I'm going to start to implement this as PHP files, as this is the environment I am really needing it for at the moment. I might even learn python so I'll be able to implement the solution as a proper mercurial extension.

I'm not convinced this will be a solution for all possible scenarios, but it seems to be a quite elegant solution for my scenarios at my scale. It would really solve a great annoyance I have with working on and deploying webapplications. It there are better alternatives please let me know!

(Or perhaps should I use a noSQL solution for my data storage needs?)

maandag, februari 01, 2010

Fixing a Drupal Cron error

I took me a couple of hours the find and fix a very annoying Drupal cron error. To be sure that I don't get bitten by it again I'm writing this post. Hopefully somebody will find it useful in the future :). ANyway: the rest of this blog is quite technical. So be warned!

Situation:

We developed a website for a customer using the open source CMS drupal.

Symptoms:

We created a module with a cron hook to update content from an external source into our website. This cron hook didn't run well when used by crontab. When requested from the browser it displayed a "acces denied" page: rendered twice…

Analysis:

If we take a look at cron.php we see that it does two things:

drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
drupal_cron_run();

As the bootstrap runs normally for the rest op the pages, the drupal_cron_run() must be the problem. In any advanced other development platform (i.e. .NET) we would be able to connect a debugger and step into the code to find the problem.

But not with PHP. In this case I had to login via SSH and edit the files via VIM. Inserting debug statements as I went. (Poor men's debugging avant la lettre).

If we have a look at drupal_cron_run() we see that it calls:

module_invoke_all('cron');

I.E. all modules which define a hook_cron() will be called.


via the module_invoke_all. I was able to find the misbehaving module: search. When I removed the call to search_cron from the list, the problem was gone. So let's have a look at: search_cron. The main work of this function is done by:

foreach (module_list() as $module) {
module_invoke($module, 'update_index');
}

It tries to invoke the "update_index" hook on all modules. And as the API docs says: "Update Drupal's full-text index for this module."


With some more var_dumps and prints I came to the conclusion that the node module was the problem.


Here is the code for node_update_index:

$result = db_query_range("SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, $limit);

while ($node = db_fetch_object($result)) {
_node_index_node($node);
}
More var_dumps and print statements brougt me to the NID's of the faulting nodes. There where two nodes of the type: "multichoice" of the "quiz" module.  

The problem is within the call to the

$node = node_build_content($node, FALSE, FALSE);

In _node_index_node. As we can see it 2nd & 3th parameter are FALSE. node_build_content will call node_invoke and this will call HOOK_view() which in the case of the multichoice has this code:


function multichoice_view($node, $teaser = FALSE, $page = FALSE) {
  if (!$teaser && $page) {
    // This is not necessary:
    //$mynode = node_prepare($node, $teaser);
    $node->content['body'] = array('#value' => multichoice_render_question($node));
    return $node;
  }
  elseif ($teaser) {
    $node = node_prepare($node, $teaser);
    return $node;
  }
  else {
    drupal_access_denied();
    }
}

As we remember: $teaser and $page are set to false. So we fall trough to the last else: "drupal_acces_denied()";

As we have two content nodes of this type the acces denied message is displayed twice.

In the HEAD version of the module it seems to be fixed:

http://drupalcode.org/viewvc/drupal/contributions/modules/quiz/question_types/multichoice/multichoice.module?annotate=1.4#l687

Lessons learned:



  • Always keep up to date with modules (the module was updated Jan-5-2010) as it may bite you in strange ways…

  • Drupal debugging is hard

woensdag, januari 20, 2010

Hallo 2010!

Als je mee een beetje kent dan weet je dat ik graag even de kat uit de boom kijk. Dat ik nu pas kom met een "Hallo 2010" blog zal je daarom niet verbazen.

Er is veel gebeurd in 2009: de dakkapel en de zolder zijn klaar en ik heb nu dus een prachtige wekrtuimte aan huis. JufMelis is een echt bedrijf met een echt logo, echte klanten en echte facturen. En op mijn werk ben ik nu lid van flexteam. Een chique naam voor: "de losse eindjes oplossen". De vrijheid die daarbij hoort om losse projectjes te doen staat me wel aan. Helaas hebben we net niet de postcode-straat-prijs gewonnen, we zaten er maar 7 km vanaf!

En nu 2010 drie weken onderweg is kunnen we de eerste observaties al maken. Wat opvalt: ik schrijf weer een blog (maar dat heeft vooral te maken met deze blog van mijn vrouw), ik ben eindelijk begonnen aan echt gitaar spelen, IMAGE_102

JufMelis blijft records breken,  ook twittert ze nu! Mijn vrouw heeft zelfs een 101 doelen lijst opgesteld en op Internet gepubliceerd. Dat durf ik niet aan. Maar hoog op mijn lijst staat het afronden van de LOI cursus administratie. De eerste belastingaangifte voor Jufmelis zit eraan te komen ;)

Ik heb geen idee wat 2010 verder zal brengen, genoeg dromen, wensen en plannen. Maar eerst de kat maar eens eten geven.

dinsdag, november 03, 2009

Gezocht: een nieuw CMS!

Ik werk nu ruim 5 jaar voor een internetbureau en heb met verschillende CMS-en gewerkt. Elk systeem heeft zo zijn sterke en zwakke punten, maar 1 ding hebben ze gemeen:

De ondersteuning voor OTAP is slecht.

Nu is dat voor een eerste release van een website/webapplicatie niet zo'n probleem, maar op het moment dat je door gaat ontwikkelen en bugs gaat fixen loop je snel tegen grenzen aan.

Het is dan zaak om de content vanuit de productie omgeving terug te zetten naar de acceptatie omgeving. Deployments moeten gedaan worden op de acceptatieomgeving en daarna nog een keer op de productieomgeving etc. Twee wijzigingen worden tegelijkertijd ontwikkeld, de eerste wordt wel geaccepteerd, de tweede niet.. Hoe kun je wel de 1e live zetten en de 2e niet?

Volgens mij is er ruimte in de CMS markt voor een nieuw CMS met de OTAP straat als uitgangspunt:

  • ondersteuning van changesets (code + content)
  • versiebeheer op changesets (commit & rollback!)
  • eenvoudig automatisch migreren van changesets binnen de OTAP
  • uitvoeren van webtests

Mis ik iets? bestaat een dergelijk systeem al? Zie ik het allemaal te simpel? Vertel het me ;)

donderdag, oktober 08, 2009

Chinese collega's op bezoek

De afgelopen weken zijn mijn Chinese collega's op bezoek. Jack & Julian waar ik 2 jaar terug in China met heel veel plezier mee gewerkt heb. Morgen ga ik nog samen met collega N en Jack & Julian nog een mooie avond op pad.

Daarna gaan ze weer terug. Vandaag kwamen ze met een mooi cadeau van een andere collega: Coofucoo. Een mooie gelukshanger. Ik heb hem een prominente plek gegeven op mijn kantoor!

IMAGE_071 IMAGE_072