oateck.com

Discovering the Programming World
Welcome to oateck.com Sign in | Join | Help
in Search

Programming Tips By Omar AlBadri

February 2008 - Posts

  • Better Php Caching

    I read many interesting articles on caching that all do the job. The problem I have with most of the techniques is that they do not show you how to implement caching for High Traffic sites. Let look at what caching is first

     

    What is Caching?


    Most web sites generate dynamically created content. To generate this content the web service usually has to query some data source(Database, XML, etc.) and output HTML to your web browser. Problem is that when you have thousands of users hitting your site, these dynamic request can cause serious overhead on your web server as it has to process many request which will cause you site to run slowly. See image:

     

     

     

     

     

     

     

     

    Enter Caching

    How can this problem be avoided? Consider this: Most web pages are updated periodically and perhaps need to be refreshed every half an hour or so. As in the dynamic content of the site really does not need to execute per every web request, but every X amounts of minutes. Lets say you run a News site and you receive a average new article every 10 minutes. So it would make sense to only allow the site to update every 10 minutes. This will really increase your performance and keep the site fresh.

    How Caching Works


    The concept of caching is simple. A user request your web page. The php code checks to see if the page needs to be updated. If yes then run the dynamic queries and refresh the content. If no then return the cached page. (see Image below):

     

    Implementing Simple caching


    Caching is fairly simple to implement lets look at a overview of what we want our script to do

    Sounds easy enough lets look at some code:

    $cfile = "cache/cachedpage.HTML";
    if (file_exists($cfile))
     {
    // the page has been cached from an earlier request
    //include and output the cached page

    include($cfile);

    // exit the script, so that the rest isn't executed

    exit;
    }
    ?>

    <HTML>
    .
    . Your HTML here
    .
    </HTML>

    <?php

    // open the cache file for writing
    $fp = fopen($cfile, 'w');
    // save the contents of output buffer to the file
    fwrite($fp, ob_get_contents());
    // close the file fclose($fp);
    // Send the output to the browser ob_end_flush();
    ?>

    A Fundamental Flaw


    While the above code will work for most of the sites out there, what will happen if we implement this on a high traffic site? Let consider the following scenario: You have a high traffic site that averages two users a second. Your dynamic content take around 3 seconds to regenerate and your cache page just expired.



    We can see that image that you allows 5 more request then needed. On a high traffic site you would site extremely slow downs during this moment. For even higher traffic sites(Digg.com, Fark.com, etc) where they average 10 users per second this could be exponentially a even bigger problem.



     

    Better Php caching

    In order to over come this issue, we are going to lock the file when the first request for a new cache file comes in. Then, if another users makes the request before the first cache file is written,  it will attempt to lock a file that is already locked. If this then we simply return the cache until the first user has unlocked the file

    <?php
          $cfile = "cache/cachedpage.HTML";

       
          $cfile ="cache/".$reqfilename.".html";
     
          $cachetime = 30*60; // 30 minutes
          // Serve from the cache if it is younger than $cachetime
      

    if (file_exists($cfile ) && (time() - $cachetime< filemtime($cfile ))) 
    {
      
         //if cache is not expired return cache files
         include($cfile );
          exit;
      
     }
     else
    {

    //open file and attempt to lock
     $fp = fopen($cfile , 'w');
     if( flock($fp, LOCK_EX))
     {
           ob_start(); // start the output buffer
     }
     else
     {
        //if cant lock then return the previously generated cache
        include($cachefile);
        exit;
     }
    }
      
    ?>

    <HTML>
    .
    . Your HTML here
    .
    </HTML>

    <?php  

         // open the cache file for writing 
          flock($fp, LOCK_UN); // unlock the file
         
           // save the contents of output buffer to the file 
          fwrite($fp, ob_get_contents());
      // close the file
          fclose($fp);
      // Send the output to the browser
           ob_end_flush();
      
      
    ?>

    Going over the code we see that we are now locking the file before we write to it. Any other users whom preform the cache request will attempt to lock a lock file and it will fail and they will return the old cache file. This ensures that only one cache file request is preformed in spite of how many users.

    Special thanks to Alex Gonzales(www.branchint.com) for some help on this. Please leave a comments/questions or improvements.

     

  • Data Integration using a MultiThreading Kernel

    Sometimes you have a problem where two incompatable system, (such as a POS/ Cash Register) , needs to integrate with a new software systems (such as a new website) data. The issue is that the POS system is the Master of Inventroy (MOI) and needs to update the websites database peridocally to sync up the inventories.

    There are three ways to accomplish this:

    1) Create a outbound xml channel to your database that updates the inventory periodacally. Problem: expensize as you would need a Biztalk server or something of the sort and complex as you will have to define and map the outbound and inbound structure
    2) Have the admin mannually update the products in stock.Problem: Inpracticall even if you created a upload page the  user would have to spend hours a day entering and verifing the data.
    3) Create a multithreading kernel that polls a specifc location and imports the data based on a export file.

    Most Inventory control systems have a export functionality, wither it is in XML or some propietary system. You simply need to create a kernal that will poll a particular location and pull in the data as soon as the Inventory control system spits it out.

    Here the outline of what we want to accomplish:
    1. A Multi-Threading
    2.  Some Feebback and Logging
    3. A Folder location where the export file will be continously polled.

    Program
    Start by opening Visual studio and create a Windows application in C#.

    Lets start by adding the basic elements that will contrust our kernel:

    1. Add a Start and Stop button and a browse button
    2. Add a text box of the File Directory to be inserted
    3. Add a Multiline TextBox that will display your messages
    4. Drop a OpenFileDialog Control
    5. Drop a Worker thread

    Your form should look something like this:

    Coding

    Double click the browse button and use the OpenFiledialog control which I named fbd to show and grab a file location. This will be the location were we will poll for a export file.

    Sample Code
    {
    fbd.ShowDialog();
    txtFolderLocation.Text = fbd.SelectedPath;
    }
     Next we will look at the start and stop buttonThinking about what we want to do: First the Start and Stop button we be associated with the ThreadWorker component that previously dropped on the form. Second we want to report all messages to the large Text box.
     

    Lets look a the properties of the Thread are:

    The only property that we are conserned with is the WorkerSupportsCancellation. We want to be able to stop the Thread gracefully, so this property needs to be set to true.Now lets look at the events of the Thread



     

     

     

     

    The only two things we are concerned win are the DoWork and the RunWorkerCompleted Event. The DoWork is is the function that is going to be called when you start the thread and RunWorkedCompleted is the event called when you push the stop button. Double click on these event to create the Events.DoWork The function of this event is to poll the folder selected previously the code is simple:

    Code:

    private void thread1_DoWork(object sender, DoWorkEventArgs e)
    {
    while
    (!thread1.CancellationPending)
    {
    if (File
    .Exists(path))
    {
    //do something with the file
    }
    }

    The only think to notice is that we are looping on the thread1.CancellationPending not being set to true. This allows us to Stop the thread by pressing the stop button.
    When the Stop button is pressed the
    private void thread1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {}
    is called. Use this function to do any clean up code and ideally write the log file.Next we will code the Start and Stop Buttons:Code:StartThe code for the start button is pretty simple. Basically we would like to disable the start button enable the stop button and call the thres to start
    Code:

    private void btnStart_Click(object sender, EventArgs e)
    {
    btnStart.Enabled = false
    ;
    btnStop.Enabled =
    true
    ;
    thread1.RunWorkerAsync();
    }


    Now your DoWork event will fire.

    Writing messages to the text box
    If you call the TextBox’s .Text property to send messages you will get the following error:
    Cross-thread operation not valid: Control “txtOutInfo” accessed from a thread other than the thread it was created on.
    In order to acess the textbpx Text Propety you need to use a delegate

    Code:
    private void SetText(string text)
    {
    if (this.OutInfo.InvokeRequired)
    {
    // InvokeRequired required compares the thread ID of the calling thread to the thread ID of the creating thread. If these threads are different, it returns true.
      SetTextCallback stc =
     new SetTextCallback(SetText);   this.Invoke(d, new object[] text });}
    else
    {
       this
    .txtOutInfo.Text += text;
       this
    .txtOutInfo.SelectionStart = OutInfo.Text.Length;
       OutInfo.ScrollToCaret();
    }
    }
    And you now have messages:

     

    Now that you set up your Kernel you can implement the import function to update the database

     

    Please leave comments or questions.

    O.