Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Will the server handle two or more at the same time?

Tags:

php

I have this code in PHP writing views to a text-file and just increasing the number. My question is here: What happens if two or more people are using my site and running the PHP script? Will the server handle it? Will the increasement be saved to the file?

Here is my code (if it helps):

<?php

    $clicks = file_get_contents("clicks.txt");
    $clicks++;

        $fp = fopen("clicks.txt", "w+");
        fwrite($fp, $clicks);
        fclose($fp);

//give the count to the user
echo "result: $clicks";

    ?>
like image 473
user1431627 Avatar asked Dec 09 '22 23:12

user1431627


1 Answers

First of all: This is not the way I would write a click counter.

That said, 100 users hitting your server at the same time (with initial clicks at 0) might result in a recorded number of 1..100 with low (=wrong) values being prominent.

  • If you want to count in a text file, lock as in @lanzz's answer and be prepared for a major performance hit - you are effectivly serializing the requests.
  • If you want to count in any file, consider SQlite and prepare for a manageable performance hit
  • If you just want to count, consider a real DB with a very small performance hit

EDIT: Implementations

I created the below implementations for a file counter in a text file, SQLite and MySQL

Please do not flame me for using the mysql_*() function family - as allways the code is meant to be instructive, not productive: instructive in the sense of concentrating on the issue at hand, not the surrounding layers.

counter-file.php:

<?php

//acquire file handle
$fd=fopen('counter.txt','c+b');
if (!$fd) die("Can't acquire file handle");

//lock the file - we must do this BEFORE reading, as not to read an outdated value
if (!flock($fd,LOCK_EX)) die("Can't lock file");

//read and sanitize the counter value
$counter=fgets($fd,10);
if ($counter===false) die("Can't read file");
if (!is_numeric($counter)) {
    flock($fd,LOCK_UN);
    die("Value in file '$counter' is not numeric");
}

//increase counter and reconvert to string
$counter++;
$counter="$counter";

//Write to file
if (!rewind($fd)) {
    flock($fd,LOCK_UN);
    die("Can't rewind file handle");
}
$num=fwrite($fd,$counter);
if ($num!=strlen($counter)) {
    flock($fd,LOCK_UN);
    die("Error writing file");
}

//Unlock the file and close file handle
flock($fd,LOCK_UN);
fclose($fd);

printf ("Counter is now %05d",$counter);
?>

counter-sqlite.php:

<?php

//counter.sqlite3 was created with 
//CREATE TABLE counter (counter NUMERIC)
//INSERT INTO counter VALUES (0)

//Open database
$dsn='sqlite:'.dirname(__FILE__).'/counter.sqlite3';
$db=new PDO($dsn);
if (!$db) die("Can't open SQlite database via DBO");

//Make exclusive
$sql="BEGIN EXCLUSIVE TRANSACTION";
if ($db->exec($sql)===false) die("Error starting exclusive transaction");

//Update counter
$sql="UPDATE counter SET counter=counter+1";
if (!$db->exec($sql)) die("Error inserting into database");

//Read value
$sql="SELECT counter FROM counter";
$result=$db->query($sql);
if (!$result) die("Error querying database");
foreach ($result as $row) $counter=$row['counter'];

//Commit
$sql="COMMIT TRANSACTION";
if (!$db->exec($sql)) die("Error committing to database");

//Print result
printf("Counter is now %05d",$counter);

?>

counter-mysql.php:

<?php

//mysql database was created with 
//CREATE TABLE counter (counter INT NOT NULL)
//INSERT INTO counter VALUES (0)

//Open database connection and select database 
$db=mysql_pconnect('127.0.0.1','redacted','redacted');
if (!$db) die("Can't open database");
if (!mysql_select_db('redacted', $db)) die("Can't select database");

//Update counter
$sql="UPDATE counter SET counter=counter+1";
$qry=mysql_query($sql,$db);
if (!$qry) die("Error updating database");

//Read value
$sql="SELECT counter FROM counter";
$qry=mysql_query($sql,$db);
if (!$qry) die("Error reading from database");
$counter=mysql_fetch_array($qry,MYSQL_ASSOC);
if (!$counter) die("Error reading result");

//Print result
printf("Counter is now %05d",$counter['counter']);

?>

As for the performance: I stand corrected. The SQLite implementation is 100 times slower than the two others - this is because I had to accept, that nothing else than START EXCLUSIVE TRANSACTION would end a test of ab -n 1000 -c 50 http://127.0.0.1/stackoverflow/counter/counter-sqlite.php with 1000 clicks counted.

My recommendation for the OP is to use the MySQL version - it is fast and will reliably save the counter over an OS crash. The file version has nearly the same performance characteristics, but it can quite easily be destroyed by an OS crash.

like image 107
Eugen Rieck Avatar answered Dec 27 '22 11:12

Eugen Rieck