6 tips to build
Fast Web Applications
-
Luciano Mammino (@loige)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/2372814/logo-meetup.png)
PHP Dublin 22/03/2016
About me
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/2372824/mario_rebuilt_by_nickmarino-d6bg1ya.png)
Integrations Engineer at Smartbox
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/2372828/cropped-Logo_Smartbox_Posi_RVB.png)
Php developer since *.php3
I Tweet as @loige
I Blog on loige.co
Preamble
- Only 6 tips (not exhaustive)
- Focused on backend
- Many generic advices (not just Php)
- ... Tools and examples (mostly) for Php :)
Tip 1.
Avoid premature optimisation!
"Premature optimisation is the root of all evil"
— Donald Knuth
(not an excuse to write 💩 code!)
Do you really have a problem?
How big the problem is?
Measure before questioning
Set (realistic) goals
Improve only where needed!
The right mindset
The performance lifecycle
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/2375218/performance-lifecycle.png)
Benchmarking
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/2375260/Screen_Shot_2016-03-21_at_21.04.03.png)
Profiling
Active or Passive?
Xdebug (Active)
- Gives a lot of data
- Slows down the execution
- Good in development
- Easy to integrate with IDEs
- Also a debugger
Libraries
Symfony stopwatch component
<?php
use Symfony\Component\Stopwatch\Stopwatch;
$stopwatch = new Stopwatch();
// Start event named 'eventName'
$stopwatch->start('eventName');
// ... some code goes here
$event = $stopwatch->stop('eventName');
echo $event->getDuration(); // xxx milliseconds
Cloud tools
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/2375346/comparison.png)
Tip 2.
Do just what you need to
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/2375361/i-just-do-things-joker-meme.gif)
A.k.a.
"Don't do things that you don't need to"
E.g.
- Load classes that you are not using
- Open a connection to a database for every request
- Parse a huge XML config file for every request
<?php
require __DIR__ . '/vendor/autoload.php'; // generated by Composer
use Mario\Characters\Mario;
use Mario\Powerups\Mushroom;
// Mario & Mushroom classes not loaded yet...
$mario = new Mario();
// Mario class is loaded!
$mushroom = new Mushroom();
// Mushroom class is loaded!
$mario->eat($mushroom); // Tasty!
Other patterns
Dependency Injection
(& Dependency Injection Container)
Simplifies the loading of classes and the resolution of dependencies
Lazy loading & Proxy
Access resources only when you are about to use them
Tip 3.
If you really need to do it,
do it tomorrow!
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/2375516/i-ll-do-it-tomorrow-meme.jpg)
Defer tasks when possible
E.g.
- Send an email to a user to confirm an order
- Resize an image after the upload
- Aggregate metrics
(Serve the response as soon as it's ready!)
Queues to the rescue
A PHP library (Redis as data store).
Laravel/Lumen built-in solution (multiple data storages).
Generic job server that supports many languages.
Work queue originally written to speed up Facebook
Tip 4.
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/2375577/cache-me.png)
Use cache to avoid repetitive computations and roundtrips
Different Caching layers:
- Byte code Cache (APC, OpCache)
- Application Cache (Redis, Memcache, Gibson)
- HTTP Cache (E-tag, Cache-control headers)
- Proxy Cache (Varnish, Squid, Nginx)
"There are only two hard things in Computer Science: cache invalidation and naming things"
— Phil Karlton
Tip 5.
Beware of the N+1 query problem!
<?php
function getUsers() {
//... retrieve the users from the database (1 query)
return $users;
}
function loadLastLoginsForUsers($users) {
foreach ($users as $user) {
$lastLogins = ... // load the last logins
// for the user (1 query, executed n times)
$user->setLastLogins($lastLogins);
}
return $users;
}
$users = getUsers();
loadLastLoginsForUsers($users);
SELECT id FROM Users; -- ids: 1, 2, 3, 4, 5, 6...
SELECT * FROM Logins WHERE user_id = 1;
SELECT * FROM Logins WHERE user_id = 2;
SELECT * FROM Logins WHERE user_id = 3;
SELECT * FROM Logins WHERE user_id = 4;
SELECT * FROM Logins WHERE user_id = 5;
SELECT * FROM Logins WHERE user_id = 6;
-- ...
N+1 Queries! 😓
Better solution
or use JOINS...
SELECT id FROM Users; -- ids: 1, 2, 3, 4, 5, 6...
SELECT * FROM Logins
WHERE user_id IN (1, 2, 3, 4, 5, 6, ...);
Don't trust the ORM
Check the queries generated by your code!
Tip 6.
Plan for horizontal scalability
Build your app to be replicated across many servers
Sessions:
- Don't use the default file based engine
- Be as much stateless as possible
User files:
- CDN, Cloud Storage (S3, Cloudfiles), Sync (NFS, Gluster)
Consider Microservices...
BONUS Tip.
Update to PHP7
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/2375567/2326296360_426b2d3726.jpg)
(Rasmus approves)
Let's Recap
-
Avoid premature optimisation!
-
Do just what you need to
-
If you really need to do it, do it tomorrow!
-
Cache me if you can
-
Beware of the N+1 query problem!
-
Plan for horizontal scalability
-
Bonus: Update to Php 7
Thank you!
Let's keep in touch:
![](https://s3.amazonaws.com/media-p.slid.es/uploads/472695/images/2375712/piq_9212_400x400.png)