Skip to content

Commit

Permalink
Cache image generation at HTTP level. Add cache warmup command.
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbaldessin committed Nov 3, 2018
1 parent f38298e commit 44beb3e
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 14 deletions.
131 changes: 131 additions & 0 deletions app/Console/Commands/WarmCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Pool;

use GuzzleHttp\Exception\RequestException;


use Illuminate\Support\Facades\DB;
use \App\Models\Product;

class WarmCache extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'gobelins:warmcache';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Warm the nginx cache';


/**
* HTTP client instance.
*/
protected $client;

/**
* UI: a Symfony Progress Bar instance.
*/
protected $progress_bar;

/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}

/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->initHttpClient();
$this->fetchImages();
}

private function initHttpClient()
{
$this->client = new Client([
'base_uri' => env('APP_URL'),
'timeout' => 15.0,
]);
}

private function fetchImages()
{
$http_options = env('HTTP_AUTH_USERNAME') ? ['auth' => [env('HTTP_AUTH_USERNAME'), env('HTTP_AUTH_PASSWORD')]] : null;
$glide_params = '?q=40&fm=pjpg&w=600';

$this->progress_bar = $this->output->createProgressBar(Product::count());


/////////////////////////////////
// Consecutive single requests //
/////////////////////////////////

// Product::chunk(200, function ($prods) use ($glide_params, $http_options) {
// foreach ($prods as $prod) {
// $img = $prod->images->first();
// if ($img) {
// $response = $this->client->request('GET', '/image/' . $img->path . $glide_params, $http_options);
// }
// $this->progress_bar->advance();
// }
// });


///////////////////////////////////
// Pool of 4 concurrent requests //
///////////////////////////////////

Product::chunk(200, function ($prods) use ($glide_params, $http_options) {
$requests = function ($prods) use ($glide_params, $http_options) {
foreach ($prods as $prod) {
$img = $prod->images->first();
if ($img) {
yield new Request('GET', '/image/' . $img->path . $glide_params, $http_options);
}
}
};

$pool = new Pool($this->client, $requests($prods), [
'concurrency' => 4,
'fulfilled' => function () {
$this->progress_bar->advance();
},
'rejected' => function () {
$this->progress_bar->advance();
},
]);

$promise = $pool->promise();
// Force the pool of requests to complete.
$promise->wait();
});





$this->progress_bar->finish();
}
}
7 changes: 5 additions & 2 deletions app/Http/Controllers/ImageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use Illuminate\Contracts\Filesystem\Filesystem;
use League\Glide\Responses\LaravelResponseFactory;
use League\Glide\ServerFactory;
use League\Flysystem\Filesystem as FlyFileSystem;
use League\Flysystem\Memory\MemoryAdapter;

class ImageController extends Controller
{
Expand All @@ -15,8 +17,9 @@ public function show($path)
'response' => new LaravelResponseFactory(app('request')),
'source' => '/', # Local filesystem for now.
'source_path_prefix' => storage_path(env('MEDIA_STORAGE_PATH')),
'cache' => '/', # Local filesystem for now.
'cache_path_prefix' => storage_path('cache/media'),
// 'cache' => '/', # Local filesystem for now.
// 'cache_path_prefix' => storage_path('cache/media'),
'cache' => new FlyFileSystem(new MemoryAdapter()), // Don't write files to disk. Nginx handles caching.
'base_url' => 'image',
]);

Expand Down
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"laravel/framework": "5.6.*",
"laravel/scout": "^4.0",
"laravel/tinker": "^1.0",
"league/flysystem": "^1.0",
"league/flysystem-memory": "^1.0",
"league/glide": "^1.3",
"league/glide-laravel": "^1.0",
"nothingworks/blade-svg": "^0.2.2",
Expand Down
69 changes: 60 additions & 9 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,20 @@ php artisan scout:flush "App\Models\Product"
php artisan scout:import "App\Models\Product"
```

#### Credits
### Cache stategy

#### Image caching

The images should hardely ever change, so we set up a request cache at the HTTP server level,
so we only need to generate images once.
Nginx will cache whatever result is outputted from the Image controller.
For more configuration information, see the [gobelins-devops](https://github.com/entrepreneur-interet-general/gobelins-devops) repository.

#### Response cache

…TODO…

### Credits

- Laurie Chapotte, design
- Ned Baldessin, development
Expand Down
15 changes: 13 additions & 2 deletions resources/assets/js/Collection/CollectionGrid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ class CollectionGrid extends Component {
return this.props.hits.map((datum, index) => {
let hasImages = datum.images && datum.images.length > 0;
let imgRoot = hasImages
? "/image/" + encodeURIComponent(datum.images[0].path) + "?w="
? "/image/" +
encodeURIComponent(datum.images[0].path) +
"?q=40&fm=pjpg&w="
: "";
let display_name =
datum.title_or_designation ||
Expand All @@ -49,6 +51,15 @@ class CollectionGrid extends Component {
}}
>
<img
sizes="(min-width: 1800px) calc((100vw - 288px - (40px * 6)) / 6),
(min-width: 1600px) and (max-width: 1799px) calc((100vw - 288px - (40px * 5)) / 5),
(min-width: 1440px) and (max-width: 1599px) calc((100vw - 288px - (40px * 4)) / 4),
(min-width: 1024px) and (max-width: 1439px) calc((100vw - 288px - (40px * 3)) / 3),
(min-width: 800px) and (max-width: 1023px) calc((100vw - (40px * 4)) / 3),
calc(100vw - (3 * 15px) / 2)"
srcSet={imgRoot + "330 330w,\n" + imgRoot + "600 600w"}
/>
{/* <img
sizes="(min-width: 1800px) calc((100vw - 288px - (40px * 6)) / 6),
(min-width: 1600px) and (max-width: 1799px) calc((100vw - 288px - (40px * 5)) / 5),
(min-width: 1440px) and (max-width: 1599px) calc((100vw - 288px - (40px * 4)) / 4),
Expand All @@ -65,7 +76,7 @@ class CollectionGrid extends Component {
imgRoot +
"760 760w"
}
/>
/> */}
</div>
) : (
<div className="Collection__image-container--empty" />
Expand Down

0 comments on commit 44beb3e

Please sign in to comment.