Giter Club home page Giter Club logo

kirby3-boost's Introduction

🚀 Kirby3 Boost
⏱️ up to 3x faster content loading
🎣 fastest page lookup and resolution of relations

Release Downloads Build Status Maintainability Twitter

Boost the speed of Kirby by having content files of files/pages/users cached, with fast lookup based on uuid.

Commercial Usage


Support open source!

This plugin is free but if you use it in a commercial project please consider to sponsor me or make a donation.
If my work helped you to make some cash it seems fair to me that I might get a little reward as well, right?

Be kind. Share a little. Thanks.

‐ Bruno
 
M O N E Y
Github sponsor Patreon Buy Me a Coffee Paypal dontation Hire me

Installation

  • unzip master.zip as folder site/plugins/kirby3-boost or
  • git submodule add https://github.com/bnomei/kirby3-boost.git site/plugins/kirby3-boost or
  • composer require bnomei/kirby3-boost

Usecase

If you have to process within a single request a lot of page objects (1000+) or if you have a lot of relations between page objects to resolve then consider using this plugin. With less page objects you will probably not gain enough to justify the overhead.

How does this plugin work?

  • It caches all content files and keeps the cache up to date when you add or modify content. This cache will be used when constructing page objects making everything that involves page objects faster (even the Panel).
  • It provides a benchmark to help you decide which cachedriver to use.
  • It will use Kirby's uuid (unique id) for page objects to create relations that do not break even if the slug or directory of a page object changes.
  • It provides a very fast lookup for page objects via id, diruri or the uuid.

Setup

For each template you want to be cached you need to use a model to add the content cache logic using a trait.

site/models/default.php

class DefaultPage extends \Kirby\Cms\Page
{
    use \Bnomei\ModelHasBoost;
}

Since in most cases you will be using Kirbys autoloading for the pagemodels your classname needs to end in Page. Like site/models/article.php and ArticlePage or site/models/blogpost.php and BlogpostPage.

As a last step fill the boost cache in calling the following in a template or controller. You only have to do this once (not on every request).

// fill cache
$count = site()->boost();
echo $count . ' Pages have been boosted.';

Congratulations! Now your project is boosted.

User Models

Starting with version 1.9 you can also cache the content files of user models using the respective traits/extends in your custom models via a custom plugin.

class AdminUser extends \Kirby\Cms\User
{
    use \Bnomei\ModelHasBoost;
}

Kirby::plugin('myplugin/user', [
    'userModels' => [
        'admin' => AdminUser::class, // admin is default role
    ],
]);

File Models

Starting with version 2.0 the plugin to monkey patch the core Files class with content cache support. You can only turn this on or off for all files at once since Kirby does not allow custom File models. It would need to read content file first which would defeat the purpose for a content cache anyway.

site/config/config.php

<?php

return [
    // other options
    'bnomei.boost.patch.files' => true, // default: true

Pages Field Alternative

This plugin provided a pages field alternative based on the multiselect field and optimized for performance.

site/blueprints/pages/default.yml

preset: page

fields:
  one_relation:
    extends: fields/boostidkv

  many_related:
    extends: fields/boostidkvs

You can create your own fields for related pages based on the fields and collections this plugins provides.

Easier loading of custom models, blueprints, ...

When you use boost your project you might end up with a couple of custom models in a plugin. You can use my autoloader helper to make registering these classes a bit easier. It can also load blueprints, classes, collections, controllers, blockModels, pageModels, routes, api/routes, userModels, snippets, templates and translation files. If you installed the Boost plugin via composer the autoloader helper was installed as a dependency, and you can start using it straight way.

Usage

Page from PageId

$page = page($somePageId); // slower
$page = boost($somePageId); // faster

Page from DirUri

$page = boost($somePageDirUri); // fastest

Page from uuid

$page = page($uuid); // slower
$page = boost($uuid); // will use fastest internally

Pages from uuids

$pages = pages([$uuid1, $uuid2, ...]); // slower
$pages = boost([$uuid1, $uuid2, ...]); // will use fastest internally

File from uuid

$file = site()->file($uuid); // slower
$file = boost($uuid1); // will use fastest internally

Resolving relations

Fields where defined in the example blueprint above.

// one
$pageOrNull = $page->one_relation()->toPage(); // slower
$pageOrNull = $page->one_relation()->toPageBoosted(); // faster

// many
$pagesCollectionOrNull = $page->many_related()->toPages(); // slower
$pagesCollectionOrNull = $page->many_related()->toPagesBoosted(); // faster

Modified timestamp from cache

This will try to get the modified timestamp from cache. If the page object content can be cached but currently was not, it will force a content cache write. It will return the modified timestamp of a page object or if it does not exist it will return null.

$pageModifiedTimestampOrNull = modified($someUuidOrPageId); // faster

Search for Template from cache

It will return a collection page object(s) and you can expect this to be a lot faster than calling site()->index()->template('myTemplateName')

 // in full site index
$allPagesWithTemplatePost = site()->searchForTemplate('post');

 // starting with blog as parent
$pagesWithTemplatePostInBlog = page('blog')->searchForTemplate('post');

Caches and Cache Drivers

A cache driver is a piece of code that defines where get/set commands for the key/value store of the cache are directed to. Kirby has built in support for File, Apcu, Memcached and Memory. I have created additional cache drivers for MySQL, Redis, SQLite and PHP.

Within Kirby caches can be used for:

  • Kirbys own Pages Cache to cache fully rendered HTML code
  • Plugin Caches for each individual plugin
  • The Content Cache provided by this plugin
  • Partial Caches like my helper plugin called Lapse
  • Configuration Caches are not supported yet

To optimize performance it would make sense to use the same cache driver for all but the Pages Cache. The Pages Cache is better of in a file cache than anywhere else.

TL;DR

If you have APCu cache available and your content fits into the defined memory limit use the apcu cache driver.

Debug = read from content file (not from cache)

If you set Kirbys global debug option to true the plugin will not read the content cache but from the content file on disk. But it will write to the content cache so you can get debug messages if anything goes wrong with that process.

Forcing a content cache update

You can force writing outdated values to the cache manually but doing that should not be necessary.

// write content cache of a single page
$cachedYesOrNoAsBoolean = $page->boost();

// write content cache of all pages in a Pages collection
$durationOfThisCacheIO = $page->children()->boost();

// write content cache of all pages in site index
$durationOfThisCacheIO = site()->boost();

Limitations

How much and if you gain anything regarding performance depends on the hardware. All your content files must fit within the memory limitation. If you run into errors consider increasing the server settings or choose a different cache driver.

Defaults for Memcached APCu Redis MySQL SQLite
max memory size 64MB 32MB 0 (none) 0 (none) 0 (none)
size of key/value pair 1MB 4MB 512MB 0 (none) 0 (none)

Benchmark

The included benchmark can help you make an educated guess which is the faster cache driver. The only way to make sure is measuring in production. Be aware that this will create and remove 1000 items cached. The benchmark will try to perform as many get operations within given timeframe (default 1 second per cache). The higher results are better.

// use helpers to generate caches to compare
// rough performance level is based on my tests
$caches = [
    // better
    // \Bnomei\BoostCache::null(),
    // \Bnomei\BoostCache::memory(),
    \Bnomei\BoostCache::php(),       // 142
    \Bnomei\BoostCache::apcu(),      // 118
    \Bnomei\BoostCache::sqlite(),    //  60
    \Bnomei\BoostCache::redis(),     //  57
    // \Bnomei\BoostCache::file(),   //  44
    \Bnomei\BoostCache::memcached(), //  11
    // \Bnomei\BoostCache::mysql(),  //  ??
    // worse
];

// run the cachediver benchmark
var_dump(\Bnomei\CacheBenchmark::run($caches, 1, 1000)); // a rough guess
var_dump(\Bnomei\CacheBenchmark::run($caches, 1, site()->index()->count())); // more realistic
  • Memory Cache Driver and Null Cache Driver would perform best but it either caches in memory only for current request or not at all and that is not really useful for this plugin.
  • PHP Cache Driver will be the fastest possible solution, but you might run out of php app memory. Use this driver if you need ultimate performance, have full control over your server php.ini configs and the size of your cached data fits within you application memory. But this driver is not suited well for concurrent writes from multiple requests with overlapping processing time.
  • APCu Cache can be expected to be very fast but one has to make sure all content fits into the memory limitations. You can also use my apcu cachedriver with garbage collection
  • SQLite Cache Driver will perform very well since everything will be in one file and I optimized the read/write with pragmas and wal journal mode. Content will be written using transactions.
  • My Redis Cache Driver has smart preloading using the very fast Redis pipeline and will write changes using transactions.
  • The MySQL Cache Driver is slightly slower than Redis and uses transactions as well.
  • The File Cache Driver will perform worse the more page objects you have. You are probably better of with no cache. This is the only driver with this flaw. Benchmarking this driver will also create a lot of file which in total might cause the script to exceed your php execution time.

But do not take my word for it. Download the plugin, set realistic benchmark options and run the benchmark on your production server.

Interactive Demo

I created an interactive demo to compare various cache drivers and prove how much your website can be boosted. It kind of ended up as a love-letter to the KQL Plugin as well. You can find the benchmark and interactive demos here:

Headless Demo

You can either use this interactive playground or a tool like HTTPie, Insomnia, PAW or Postman to connect to the public API of the demos. Queries are sent to the public API endpoint of the KQL Plugin. This means you can compare response times between cache drivers easily.

HTTPie examples

# get benchmark comparing the cachedrivers
http POST https://kirby3-boost.bnomei.com/benchmark --json

# get boostmark for a specific cache driver
http POST https://kirby3-boost-apcu.bnomei.com/boostmark --json

# compare apcu and sqlite
http POST https://kirby3-boost-apcu.bnomei.com/api/query -a [email protected]:kirby3boost < myquery.json
http POST https://kirby3-boost-sqlite.bnomei.com/api/query -a [email protected]:kirby3boost < myquery.json

Config

Once you know which driver you want to use you can set the plugin cache options.

site/config/config.php

<?php

return [
    // other options
    // like Pages or UUID Cache
    // cache type for each plugin you use like the Laspe plugin

    // default is file cache driver because it will always work
    // but performance is not great so set to something else please
    'bnomei.boost.cache' => [
        'type'     => 'file',
    ],

    // example php
    'bnomei.boost.cache' => [
        'type'     => 'php',
    ],
    'cache' => [
        'uuid' => [
            'type' => 'php',
        ],
    ],

    // example apcu
    'bnomei.boost.cache' => [
        'type'     => 'apcu',
    ],
    'cache' => [
        'uuid' => [
            'type' => 'apcu',
        ],
    ],
    
    // example apcu with garbage collection
    'bnomei.boost.cache' => [
        'type'     => 'apcugc',
    ],
    'cache' => [
        'uuid' => [
            'type' => 'apcugc',
        ],
    ],

    // example sqlite
    // https://github.com/bnomei/kirby3-sqlite-cachedriver
    'bnomei.boost.cache' => [
        'type'     => 'sqlite',
    ],
    'cache' => [
        'uuid' => [
            'type' => 'sqlite',
        ],
    ],

    // example redis
    // https://github.com/bnomei/kirby3-redis-cachedriver
    'bnomei.boost.cache' => [
        'type'     => 'redis',
        'host'     => function() { return env('REDIS_HOST'); },
        'port'     => function() { return env('REDIS_PORT'); },
        'database' => function() { return env('REDIS_DATABASE'); },
        'password' => function() { return env('REDIS_PASSWORD'); },
    ],
    'cache' => [
        'uuid' => [
            // do same as boost
        ],
    ],

    // example memcached
    'bnomei.boost.cache' => [
        'type'     => 'memcached',
        'host'     => '127.0.0.1',
        'port'     => 11211,
    ],
    'cache' => [
        'uuid' => [
            // do same as boost
        ],
    ],
];

Verify with Boostmark

First make sure all boosted pages are up-to-date in cache. Run this in a template or controller once. This will also add an unique id to boosted pages that do not have one yet (reindexing).

// this can be skipped on next benchmark
site()->boost();

Then comment out the forced cache update and run the benchmark that tracks how many and how fast your content is loaded.

// site()->boost();
var_dump(site()->boostmark());

If you are interested in how fast a certain pages collection loads you can do that as well.

// site()->boost();
var_dump(page('blog/2021')->children()->listed()->boostmark());

Site Index with lower memory footprint

Using site()->index() in Kirby will load all Pages into memory at the same time. This plugin provides a way to iterate over the index with having only one page loaded at a time.

$boostedCount = 0;
$indexCount = \Bnomei\Bolt::index(function ($page) use (&$boostedCount) {
    // do something with that $page like...
    $boostedCount += $page->boost() ? 1 : 0;
});
// or just
$boostedCount = site()->boost();

Settings

bnomei.boost. Default Description
hashalgo xxh3,crc32 used hash algorithm php8.1+/php8.0
expire 0 expire in minutes for all caches created
read true read from cache
write true write to cache
drafts true index drafts
patch.files true monkey patch Files Class to do content caching
fileModifiedCheck false expects file to not be altered outside of kirby
helper true allow usage of boost() helper

External changes to content files

If your content file are written to by any other means than using Kirbys page object methods you need to enable the bnomei.boost.fileModifiedCheck option or overwrite the checkModifiedTimestampForContentBoost(): bool method on a model basis. This will reduce performance by about 1/3 but still be faster than without using a cache at all.

Disclaimer

This plugin is provided "as is" with no guarantee. Use it at your own risk and always test it yourself before using it in a production environment. If you find any issues, please create a new issue.

License

MIT

It is discouraged to use this plugin in any project that promotes racism, sexism, homophobia, animal abuse, violence or any other form of hate speech.

kirby3-boost's People

Contributors

bnomei avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

kirby3-boost's Issues

Structure items with BoostID

Hello!

It's me again, after being able to transfer my project from autoID to BoostID, I wanted to know if BoostID is compatible with a structure object?

I'm trying to have a page where there is a structure called "Categories" with inner fields that contains a title and the BoostID, for exemple:

categories:
    type: structure
    translate: false
    columns:
      color:
        width: 1/4
      category:
        width: 3/4
    fields:
      category:
        label: Category
        type: text
      boostid: fields/boostid

This page is unlisted but still has a models page in order to use BoostID. Nevertheless, I'm not able to create the ID's via:

kirby()->impersonate('kirby');
site()->boost();
// or this
foreach(site()->index(true) as $page) {
   $page->boost();
}

Before I had autoID for each of the structure items and I could use the immutable ID's to retrieve the title of the category or change it without breaking all articles related to this category.

Would appreciate a hint or how to achieve that with structure object.

Issue with multi-languages and date

Hi,

I'm still exploring the plugin and I think there is an issue with how boostID is handling date in a multi-languages website.

I noticed that when playing with translation. In my case it was from FR to EN (where I have 'date.handler' => 'strftime' enabled in config.php).

This is the config of a clean installation with the minimal requirements to run boostID on my local machine:
Kirby: v.3.6.1.1
PHP: 8.0
Debug: off
Plugins: bnomei/boost & bnomei/sqlite-cachedriver

config.php:

<?php
return [
  'debug' => false,
  'languages' => true,
  'smartypants' => true,
  'date.handler' => 'strftime',
  'bnomei.boost.cache' => [
    'type'     => 'sqlite'
  ],
];

models/default.php:

<?php
class DefaultPage extends \Kirby\Cms\Page
{
    use \Bnomei\PageHasBoost;
}

blueprints/default.yml:

preset: page
fields:
  text:
    label: Text
    type: textarea
    size: large
  dateStart:
        label:
          fr: Date de début
          en: Start date
          de: Startdatum
        type: date
        width: 1/2
        default: today
        display: DD.MM.YYYY
        translate: false
  dateEnd:
    label:
      fr: Date de fin
      en: End date
      de: Enddatum
    type: date
    width: 1/2
    default: today + 1day
    display: DD.MM.YYYY
    translate: false
  boostid:
    type: boostid

How to reproduce the issue:

  1. Create a post in the default language (my case it was FR) that have a boostID field.
  2. Enter some content
  3. Save
  4. Change language (to EN)
  5. Edit the content
  6. Save
  7. Go back to the default language
  8. Switch back to EN, the date is not showing anymore.

But if you disable the boost function by commenting out the models/default.php, it works. Dates are shown.

Do you know why it doesn't work? Particularly with English, I tried with other languages like German, dates are shown.

Migration from Boost v1 -> v2 and kirby v3.7 -> kirby v3.8

Hello,

I was wondering if there is a specific steps to take in order to update the plugin in accordance with the latest Kirby release (+3.8.0), specifically related to the use of UUID, from my understanding, we don't need to provide the field:

boostid:
    type: boostid

on the page we want to use boostID with? There seems to have few changes I wonder if the changes might break current website if I blindly update Boost and Kirby?

Changes I meant are for the page models:
use \Bnomei\PageHasBoost; to use \Bnomei\ModelHasBoost;
or the ->fromBoostIDs() to ->toPagesBoosted()

Any recommendations would be appreciated.
Thank you!

translate false option hides non default language values

Hello,

I've been experimenting a weird behaviour likely related to this and #6

Basically, fields with the translate: false option stop displaying values in the translated page. This is an issue in the website as well as in the panel, where the fields with the translate: false options appear empty. Since these fields are also required, this basically blocks editors from saving pages in the translated versions...

When I set to debug: false;, the problem dissappear. Also, when I set 'bnomei.boost.cache' => false, the problem also stops. So I guess it must be from the cache?

Of course, not using the translate: false option could solve the issue, as well as making a page model that would overwrite the field values that should not be translated (since they would then be hard-coded within the content files). So far I set off the boost cache, but I'm unsure this should be the best long-term solution as I do see very pleasant performance gains by using the (sqlite) cache!

I've seen a wontfix tag on #6 and was curious about the issue, is there a lot of complexity into making this work with translate: false? I guess it's another layer of complexity on top?

Originally posted by @francois-gm in #11 (comment)

Bolt error

Hey,

I just tried the basic setup for the Default Template (Kirby 3.9.4 / PHP 8.1.13) and get this error:

Bnomei\Bolt::page(): Argument #1 ($id) must be of type string, null given, called in /site/plugins/kirby3-boost/classes/Bolt.php on line 233

Any ideas what I am doing wrong? Thanks!

PHP 8.1 and xxHash Hash issue

Hey there,

I'm running Boost on some server that, at the moment, and for some odd reasons, do not provide PHP 8.1. I'm wondering if there is an option to switch the hashing algo to something that PHP 8.0 can support?

It's a bit a drawback considering the performance advantage of the new hashing features, but at this stage, I can't migrate to another server.

All the demo pages throw errors

Hi there,
it seems I cannot run any of the demo pages right now, they all show an error message.

Would be cool if this could be fixed, without the demo pages I am somewhat lost on what to expect. :)

Thanks!
trych

Cannot get Boost to generate boostIDs

Hi there,

I am trying to set up Boost for the first time and I am not sure, if I am doing something wrong, but I don't get it to work.

Initial situation
I have a site where I need to handle a lot (~5000) of front end form entries. Every entry ends up as its own page. Now in a first step, I want to allow to navigate those entries in the panel. As the page already slows down when I throw 1000 test entries at it, I thought that the Boost plugin could help in that case.

First question: Is that even the correct use case for the plugin? To speed up handling thousands of pages in the panel?

Now, I tried to set this up. I put this in my blueprint:

site/templates/application.yml

fields:
  boostid:
    type: boostid

Then I created a page model for the application page:

site/models/application.php

<?php

class Application extends \Kirby\Cms\Page {
    use \Bnomei\PageHasBoost;
}

Then I call this in some template (any template is good for that, right? I just would need to open the template once?):

site/models/someTemplate.php

<?php
  kirby()->impersonate('kirby');
  site()->boost();
 ?>

However, after calling the template, when I check the panel, the BoostID field is still empty. (I should see something there when it's working, right?)

I tried then setting the cache driver in the config file:

site/config/config.php

<?php
return [
   'bnomei.boost.cache' => [
       'type'     => 'apcu',
   ]
];

But I get this error message:
apcu

When I switch to memcached, I get this error message instead:
memcached

And when I set it explicitly to 'type' => 'file', just nothing happens.

I am running this on PHP 8.0 on a local Laravel Valet setup, as described here. I am using Kirby 3.6.2.

Any idea, what could be going wrong?

Thanks a lot!

Issue with cached version

Hi there,

Been enjoying a lot of your productions. I was wondering if there is a way to do the same with BoostID when it comes to re-index the ID or force that specific page cache to be purged?

I tried to execute this:
$cachedYesOrNoAsBoolean = $page->boost();
In the page frontend but it's still keep the cache and doesn't re-index the missing ID on the translated pages.

Indeed, sometimes I face a strange issue where translated pages don't get an ID… Still need to figure out what steps lead to that issue. But often case, it would nice to have a button in the panel to do that, mainly for the translated page I guess, because that's where that issue occurs.

Documentation - More Details & usage for boost with file / images

Currently, I am experimenting to implement boost with plenty of images e.g. as an alternative comparable to

if($file = $site->file('file://'.$uuid)){
   // do something
}

but using something like:

if($file = boost($fileuuid)){

}
// or
if($file = boost('file://'.$fileuuid)){

}

Didn't seem to work.
Also, my question would be, if I call an image via UUID - if the performance would be increased in a one by one image scenario.

Not sure where to start

Thanks for this new plugin.

I'm eager to make it work with a new project that would handle on a single request a lot of pages. But I'm having trouble to simply use BoostID.

Maybe I'm confused with your other plugin AutoID which was really straightforward and easy to use. With BoostID I seem not able to create and ID out of the box by following your instruction here.

I have installed:

  • kirby3-boost
  • kirby3-sqlite-cachedriver

I have the config.php with:

return [
    'smartypants' => true,
    'debug' => false,
    'bnomei.boost.cache' => ['type' => 'sqlite']
];

In my models folder I have the following file article.php:

class ArticlePage extends \Bnomei\BoostPage
{
}

and finally in the panel I have article.yml:

columns:
  - width: 2/3
    fields:
      boostid:
        type: boostid
      headline:
        type: writer
        marks:
          - italic
      text: fields/blocks
      source:
        type: textarea

  - width: 1/3
    sections:
      meta:
        type: fields
        fields:
          date:
            type: date
            time: false
            default: now
          category: tags/category

When in article.php I try to output the boostID nothing is return, also the article.txt doesn't register the boostID. Finally in the panel if I check the article, there is the following error:
SQLite3Stmt::execute(): Unable to execute statement: UNIQUE constraint failed: cache.id

Sorry for the long message, but maybe I don't understand how it differs from autoID where the ID was naturally register on the .txt file.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.