Category Archives: PHP

Surviving Reddit Front Page on A Digital Ocean Droplet

I hope this post helps someone else out. On Monday, I had page on another website go viral via a Reddit post, and it briefly hit the front page (whooo #19 on r/all at around noon).  It sounds great right?  Well, I noticed this as I was sitting in an airport ready to fly back home after Christmas. My website is hosted on a Digital Ocean droplet, and I had kept it at the 2GB plan which was generous as it usually only has about 700-2000 users a day. At 9AM all was well, and my thinking was that I could resize to a larger droplet if I needed to.

At 10am, I noticed that my position on Reddit kept climbing and the number of active users was flat. I thought that was weird. So, I tried to access my website. It. was. soo. slow. But, this should be fine, all I had to do was resize the droplet. I shutdown my droplet and resized to 8GB / 4 CPU’s.  I waited a few minutes and fired up Google Analytics again, I was still at ~900 users and my website was still borderline non-responsive.

I was in a panic.  My flight was getting close to boarding. In a rash decision, I decided to try resizing again. Bigger is better, right? A couple clicks later, the droplet was racing around at the 64GB / 20 CPU plan.  It did not help. The post kept rising on Reddit, and my little droplet couldn’t keep up.

I enlisted a tech friend to help, while I was on the plane.  We tried increasing the memory limits on MySQL, PHP, and some obscure WordPress memory limit.  Nothing worked. Hours passed by, I knew there was a bottleneck somewhere, but I couldn’t figure it out.  My droplet basically had unlimited CPU and RAM for all intents and purposes, yet it was barely chugging a long.

The Solution

Finally after some hundreds of google searches, I came across this post on increasing the number of concurrent users in Apache. My server is running Ubuntu 14.0.4 LTS and the setup was a little bit different, but these changes finally helped. I basically doubled the max number of Request Workers that my Apache was allowed to generate:

/etc/apache2/mods-available/

mpm_prefork.conf
Added: ServerLimit 300
Change to: MaxRequestWorkers 300

<IfModule mpm_prefork_module>
	StartServers			 5
	MinSpareServers		  5
	MaxSpareServers		 10
	ServerLimit		500
	MaxRequestWorkers	  500
	MaxConnectionsPerChild   0
</IfModule>

mpm_event.conf
Change to: MaxRequestWorkers 300

mpm_worker.conf
Change to: MaxRequestWorkers 300

Then restart your Apache server for the changes to take place: service apache2 restart

If you need to roll back your settings, you may need to clear your cache by running:
sync; sudo echo 3 > /proc/sys/vm/drop_caches

Finally I saw the number of users climb past ~900 well over 1000, and my site was as responsive as normal. By this time though, my site was dropping off of r/all and r/rising’s frontpages, so I can only imagine how many people I missed during the 5 hours that my server was throttled. I hope that if you’re reading this, and you are facing a similar problem that this will help you. I know I tried googling and people mentioned CDN’s and mirroring databases, but no quick fixes like this.

Trigger VBS Script via PHP

You probably won’t come into this situation very often where you have a Windows computer/server running PHP. Basically this is a web-service that allows the user to trigger a VBS script bu posting a variable to a web service. The web service then takes that information and runs the VBS script after updating the database with the appropriate information. The VBS script uses information in the database, which is why the first part of this code block is needed in the example.

This is also an example of a PHP script connecting to an Access database over ODBC as it’s data source.

header('Content-type: application/json');
require('session.php');
/* Get POST Parameters */
if(isset( $_POST['fmreq']) == True)
{
	$FM_REQ = $_POST['fmreq'];
/* connect to Database */
	if (!($con = odbc_connect("DRIVER={Microsoft Access Driver (*.mdb, *.accdb)}; DBQ=" . str_replace("/", "\\", $_SERVER["DOCUMENT_ROOT"]) . "\data_source.accdb", "", "")))
			echo "<p>Connection to CollectOh failed.</p>\n";
		else
		{
/* Updating database so that when the script runs on the Access database it will have the correct information */
			$sql = "update frm_date_select set FMREQ = '" . $FM_REQ . "' where ID = 1";
			odbc_exec($con, $sql);
 
		}
/* This is the meat of where you set the path to your VBS script */
	$command = 'wscript.exe "' . str_replace("/", "\\", $_SERVER["DOCUMENT_ROOT"]) . '"\FMREQ.vbs"'; 
 
	/* 
	 * wait for command to return a exit code? 
	 * 
	 * true = waits for the command to complete, before continuing this script 
	 * false = executes command then continues this script without waiting for command to exit 
	 * 
	*/ 
 
	$wait = false; 
 
	// run it 
 
	$obj = new COM ( 'WScript.Shell' ); 
/* Feedback via JSON */
	if ( is_object ( $obj ) ) 
	{ 
		$obj->Run ( 'cmd /C ' . $command, 0, $wait ); 
		echo json_encode(array('Saved at '.date('H:i').''));
	} 
	else 
	{ 
		echo json_encode(array('Failed at '.date('H:i')));
	} 
 
	$obj = null; 
}

Basic Regular Expression Patterns for Beginners

This is super simplified, but it’s enough to get started with. I remember being confused by the gigantic tables with all the special clauses, so when you’re beginning keep it simple.

Remember if you want to match a literal character you can just type it out, unless there’s a special character. Special characters have meanings in regex patterns, so you need to type a \ in front of them if you want to specifically match them in the pattern.

Common special characters are: + . \ – [] {} $ ? |

Example: If I wanted to match taste, taster, tasters, I would type a pattern like taste\w{0,2}

If I wanted to match taste!, I would write a pattern like taste\!

Code What does it Match?
\d Numbers
\w Word characters like letters of any case
\s Spaces
\b Boundary of a word character
\D Not a number
\W Not a word character
\S Not a space
\n Line break
\r Line break
\t Tab
? Previous code is optional
* Repeat previous code 0 to infinity
+ Repeat previous code once or more
{1} Repeat previous code once (can use any number)
{1,2} Repeat previous code 1 to 2 times (can use any numbers)

[Demo] Use a PHP & MySQL database to load markers on a Google Map

Setting up a Google Maps to use PHP and a MySQL back-end isn’t the most straight forward thing to do, especially in comparison to using Google Map’s JavaScript API V3.  I wanted to load a map from a MySQL database with lat, longs, the name of the marker, and a description to put in an information window (it’s the little text bubble that pops up when you click on a marker).  I didn’t want to learn how to use the Maps API through PHP, so what did I do?

  1. Loaded the name, description, latitude, and longitude into a MySQL table
  2. Had a PHP query output a hidden input with the name, description, latitude and longitude in a single string with each field and marker separated by some sort of delimiter that you split the string with.
  3. When the page loads, jQuery will take that hidden input and split the string by the delimiter
  4. Use jQuery and Google Maps JavaScript API V3 to loop through the arrays of markers and plop them to the map and also add the info windows.

Here’s a link to the finished version demo, it should work in Chrome, Firefox and IE: http://www.macrostash.com/wp-content/plugins/big-ten-demo.php

There’s a lot going on behind the scenes, so this is a pretty long demo. Features this demo shows:

  1. Multiple Info-windows
  2. Multiple Markers
  3. Auto-sizing the map
  4. jQuery
  5. MySQL
  6. PHP
  7. Javascript, especially string splitting and arrays

The MySQL Database

I’m using the locations of the 12 colleges/universities in the Big Ten Conference. The XLS of the data that I used is available: Big Ten Schools Demo Data

Basically, what I’ve got there is the latitude, longitude, name of the location, address of the location, and a random fact. In this case the random fact is which division the school is in. Legends or Leaders. Here’s the first school, so that you get an idea of what the data looks like.

Id Lat Long Name Returned Address Division
1 40.01264 -83.0308 Ohio State University – Columbus Campus The Ohio State University, 1739 N High St, Columbus, OH 43210, USA Leaders

The PHP

//Connect to the MySQL database that is holding your data, replace the x's with your data
mysql_connect("localhost", "xxxxx_xxx", "xxxx") or
 die("Could not connect: " . mysql_error());
mysql_select_db("xxxxx_xxxx");
 
//Initialize your first couple variables
$encodedString = ""; //This is the string that will hold all your location data
$x = 0; //This is a trigger to keep the string tidy
 
//Now we do a simple query to the database
$result = mysql_query("SELECT * FROM `big-ten`");
 
//Multiple rows are returned
while ($row = mysql_fetch_array($result, MYSQL_NUM))
{
    //This is to keep an empty first or last line from forming, when the string is split
    if ( $x == 0 )
    {
         $separator = "";
    }
    else
    {
         //Each row in the database is separated in the string by four *'s
         $separator = "****";
    }
    //Saving to the String, each variable is separated by three &'s
    $encodedString = $encodedString.$separator.
    "<p class='content'><b>Lat:</b> ".$row[1].
    "<br><b>Long:</b> ".$row[2].
    "<br><b>Name: </b>".$row[3].
    "<br><b>Address: </b>".$row[4].
    "<br><b>Division: </b>".$row[5].
    "</p>&&&".$row[1]."&&&".$row[2];
    $x = $x + 1;
}

The HTML (Body)

We have 2 parts to the body.

The input part has the encoded string, and it’s hidden.

The map_canvas is where the Google map will be loaded into.

<body>
    <div id='input'>
        <input type="hidden" id="encodedString" name="encodedString" value="<?php echo $encodedString; ?>" />
    </div>
    <div id="map_canvas"></div>
</body>

 

The JavaScript

//This javascript will load when the page loads.
jQuery(document).ready( function($){
 
        //Initialize the Google Maps
        var geocoder;
        var map;
        var markersArray = [];
        var infos = [];
 
        geocoder = new google.maps.Geocoder();
        var myOptions = {
              zoom: 9,
              mapTypeId: google.maps.MapTypeId.ROADMAP
            }
        //Load the Map into the map_canvas div
        var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
        map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
 
        //Initialize a variable that the auto-size the map to whatever you are plotting
        var bounds = new google.maps.LatLngBounds();
        //Initialize the encoded string
        var encodedString;
        //Initialize the array that will hold the contents of the split string
        var stringArray = [];
        //Get the value of the encoded string from the hidden input
        encodedString = document.getElementById("encodedString").value;
        //Split the encoded string into an array the separates each location
        stringArray = encodedString.split("****");
 
        var x;
        for (x = 0; x < stringArray.length; x = x + 1)
        {
            var addressDetails = [];
            var marker;
            //Separate each field
            addressDetails = stringArray[x].split("&&&");
            //Load the lat, long data
            var lat = new google.maps.LatLng(addressDetails[1], addressDetails[2]);
            //Create a new marker and info window
            marker = new google.maps.Marker({
                map: map,
                position: lat,
                //Content is what will show up in the info window
                content: addressDetails[0]
            });
            //Pushing the markers into an array so that it's easier to manage them
            markersArray.push(marker);
            google.maps.event.addListener( marker, 'click', function () {
                closeInfos();
                var info = new google.maps.InfoWindow({content: this.content});
                //On click the map will load the info window
                info.open(map,this);
                infos[0]=info;
            });
           //Extends the boundaries of the map to include this new location
           bounds.extend(lat);
        }
        //Takes all the lat, longs in the bounds variable and autosizes the map
        map.fitBounds(bounds);
 
        //Manages the info windows
        function closeInfos(){
       if(infos.length > 0){
          infos[0].set("marker",null);
          infos[0].close();
          infos.length = 0;
       }
        }
 
});

 

All Together With the HTML

*The comments are getting highlighted a little bit weird (I set it to HTML syntax), but the code should copy paste properly into a new file.

<html>
    <head>
    <script type='text/javascript' src='jquery-1.6.2.min.js'></script>
    <script type='text/javascript' src='jquery-ui-1.8.14.custom.min.js'></script>
    <style>
 
        BODY {font-family : Verdana,Arial,Helvetica,sans-serif; color: #000000; font-size : 13px ; }
 
        #map_canvas { width:100%; height: 100%; z-index: 0; }
    </style>
    <script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false" /></script>
    <script type='text/javascript'>
 
    //This javascript will load when the page loads.
    jQuery(document).ready( function($){
 
            //Initialize the Google Maps
            var geocoder;
            var map;
            var markersArray = [];
            var infos = [];
 
            geocoder = new google.maps.Geocoder();
            var myOptions = {
                  zoom: 9,
                  mapTypeId: google.maps.MapTypeId.ROADMAP
                }
            //Load the Map into the map_canvas div
            var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
            map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
 
            //Initialize a variable that the auto-size the map to whatever you are plotting
            var bounds = new google.maps.LatLngBounds();
            //Initialize the encoded string       
            var encodedString;
            //Initialize the array that will hold the contents of the split string
            var stringArray = [];
            //Get the value of the encoded string from the hidden input
            encodedString = document.getElementById("encodedString").value;
            //Split the encoded string into an array the separates each location
            stringArray = encodedString.split("****");
 
            var x;
            for (x = 0; x < stringArray.length; x = x + 1)
            {
                var addressDetails = [];
                var marker;
                //Separate each field
                addressDetails = stringArray[x].split("&&&");
                //Load the lat, long data
                var lat = new google.maps.LatLng(addressDetails[1], addressDetails[2]);
                //Create a new marker and info window
                marker = new google.maps.Marker({
                    map: map,
                    position: lat,
                    //Content is what will show up in the info window
                    content: addressDetails[0]
                });
                //Pushing the markers into an array so that it's easier to manage them
                markersArray.push(marker);
                google.maps.event.addListener( marker, 'click', function () {
                    closeInfos();
                    var info = new google.maps.InfoWindow({content: this.content});
                    //On click the map will load the info window
                    info.open(map,this);
                    infos[0]=info;
                });
               //Extends the boundaries of the map to include this new location
               bounds.extend(lat);
            }
            //Takes all the lat, longs in the bounds variable and autosizes the map
            map.fitBounds(bounds);
 
            //Manages the info windows
            function closeInfos(){
           if(infos.length > 0){
              infos[0].set("marker",null);
              infos[0].close();
              infos.length = 0;
           }
            }
 
    });
    </script>
 
    </head>
    <body>
    <div id='input'>
 
        <?php
 
        //Connect to the MySQL database that is holding your data, replace the x's with your data
        mysql_connect("localhost", "xxxxx_xxx", "xxxx") or
         die("Could not connect: " . mysql_error());
        mysql_select_db("xxxxx_xxxx");
 
        //Initialize your first couple variables
        $encodedString = ""; //This is the string that will hold all your location data
        $x = 0; //This is a trigger to keep the string tidy
 
        //Now we do a simple query to the database
        $result = mysql_query("SELECT * FROM `big-ten`");
 
        //Multiple rows are returned
        while ($row = mysql_fetch_array($result, MYSQL_NUM))
        {
            //This is to keep an empty first or last line from forming, when the string is split
            if ( $x == 0 )
            {
                 $separator = "";
            }
            else
            {
                 //Each row in the database is separated in the string by four *'s
                 $separator = "****";
            }
            //Saving to the String, each variable is separated by three &'s
            $encodedString = $encodedString.$separator.
            "<p class='content'><b>Lat:</b> ".$row[1].
            "<br><b>Long:</b> ".$row[2].
            "<br><b>Name: </b>".$row[3].
            "<br><b>Address: </b>".$row[4].
            "<br><b>Division: </b>".$row[5].
            "</p>&&&".$row[1]."&&&".$row[2];
            $x = $x + 1;
        }        
        ?>
 
        <input type="hidden" id="encodedString" name="encodedString" value="<?php echo $encodedString; ?>" />
    </div>
    <div id="map_canvas"></div>
    </body>
</html>