Paginator very slow even cached by filesystem after second visit

Hello Big Guns!

I use filesystem cache to store my patinator results. see the following codes.

public function getPaginator($class = 1,$page = 1,ApiModel $apiModel)
{
    if (!$page) $page = 1;

    $cache = $apiModel->fsCacheApdater('article-list-'.$class.'-'.$page,'data/cache/article','7200',7200);
    if ($cache)
    {
        //when visited the second time, I exit() here, it works, means the cache is ok.
        $paginator = unserialize($cache);
    }
    else
    {
        $contentExpression = new Expression('left(content,60)');

        $select = new Select('article');
        $select->columns(['id','class','title','cover','content' => $contentExpression,'addtime']);
        $whereArr = ['valid' => 1];
        if ($class) $whereArr['class'] = $class;
        $select->where($whereArr);
        $select->order('id desc');

        $selectAdapter = new DbSelect($select, $this->tableGateway->getAdapter());
        $paginator = new Paginator($selectAdapter);
        $paginator->setCurrentPageNumber($page);
        $paginator->setPageRange(5);
        $paginator->setCacheEnabled(true);

        $apiModel->fsCacheApdater('article-list-'.$class.'-'.$page,'data/cache/article','7200',7200,'set',serialize($paginator));
    }

    return $paginator;
}

The following code is the fsCacheAdapter in apiModel class:

public function fsCacheApdater($key,$path,$namespace,$ttl,$action = 'get',$value=null)
{
    $this->ensurePath($path);

    $cache = StorageFactory::factory([
        'adapter' => [
            'name'    => 'filesystem',
            'options' => [
                'ttl' => $ttl,
                'namespace' => $namespace,
                'cache_dir' => $path,
            ],
        ],
        'plugins' => [
            'exception_handler' => ['throw_exceptions' => false],
        ],
    ]);

    //$cache->clearExpired();

    if ($action == 'get')
    {
        if (!$cache->hasItem($key))
        {
            return false;
        }
        else
        {
            return $cache->getItem($key);
        }

    }
    elseif ($action == 'set')
    {
        return $cache->setItem($key, $value);
    }
    elseif ($action == 'remove')
    {
        if ($cache->hasItem($key)) $cache->removeItem($key);
    }
}

I think my exit(‘0000’) test the pagination results are cached in a local .dat file.
What I don’t understand is that why it is so slow when it was cached and no need to connect the database to acquire the data?

I checked it out in my mysql slow-log

The laminas-paginator already implements a caching features which simplifies the handling.

But I would suggest a different approach:

I see the problem somewhere else because the question is: why do you think a connection to the database is slow or do you mean the database query is slow? If this the case then you should optimize the query by testing the query independently with plain SQL without PHP.

This means that you should eliminate the cause, not the symptom.

hello @froschdesign, thanks for your reply! Always you! :laughing:

Ofcourse I will optimize my database query. But as well, I need to ensure that my method to cache the paginator work well.

From your reply, I guess I may use the paginator cache in the wrong way!!
To cache a paginator only in the following as the official document:

$cache = StorageFactory::adapterFactory('filesystem', [
    'cache_dir' => '/tmp',
    'ttl'       => 3600,
    'plugins'   => [ 'serializer' ],
]);
Paginator::setCache($cache);

It is very different from the way I used before, ordinary I used to create a function to pack all the cache logic into it. just like the function fsCacheApdater shows above. And I call it anywhere I need to cache a value or an object.

I changed code to the following, however, errors report:

public function getPaginator($class = 1,$page = 1)
{
    if (!$page) $page = 1;

    $cache = StorageFactory::adapterFactory('filesystem',[
        'cache_dir' => 'data/cache/article',
        'ttl'       => 7200,
        'plugins'   => [ 'serializer' ],
    ]);
    Paginator::setCache($cache);

    $contentExpression = new Expression('left(content,60)');

    $select = new Select('article');
    $select->columns(['id','class','title','cover','content' => $contentExpression,'addtime']);
    $whereArr = ['valid' => 1];
    if ($class) $whereArr['class'] = $class;
    $select->where($whereArr);
    $select->order('id desc');

    $selectAdapter = new DbSelect($select, $this->tableGateway->getAdapter());
    $paginator = new Paginator($selectAdapter);
    $paginator->setCurrentPageNumber($page);
    $paginator->setPageRange(5);
    //$paginator->setCacheEnabled(true);

    return $paginator;
}

The option “plugins” does not have a callable “setPlugins” (“setplugins”) setter method which must be defined

It is a little hard to use it!

Unfortunately, I can only help you with pseudo-code right now.


Why don’t you use your existing cache?

// Cache from your first post
$cache = $apiModel->fsCacheApdater('article-list-'.$class.'-'.$page,'data/cache/article','7200',7200);

// Set cache for paginator
Laminas\Paginator\Paginator::setCache($cache);

Thanks for your pseudo-code, @froschdesign

First of all, the following only can return a string, not a cache instance.

$cache = $apiModel->fsCacheApdater(‘article-list-’.$class.’-’.$page,‘data/cache/article’,‘7200’,7200);

Besides, there is no setCache() method for object $paginator

I read the official document page you provide above https://docs.laminas.dev/laminas-paginator/advanced/#caching-features
Never succeed even when I exactly the same as the documentation.

My project is build when the framework named zendframework 3, now it is Laminas.
I don’t known whether the framework logic is the same. I didn’t migrate it from zf3 to laminas. But I will use Laminas in our new project. I am not very familar to migration.

The method name fsCacheApdater led me astray. I’m sorry, then my suggestion obviously won’t work.

There is but a static method. I have corrected this in my previous post.

It is the same for paginator component and most of the other components.

If you are interested in this topic then you can read the migration guide.


I will give you later a working solution in another post for the cache but have you checked my first suggestion? If the database itself is the problem, then you need to find a solution for it, and the cache is not necessary and irrelevant at all.

First install laminas-cache and laminas-serializer:

$ composer require laminas/laminas-cache laminas/laminas-serializer

Then use a cache for paginator:

require_once __DIR__ . '/vendor/autoload.php';

// Create cache
$cache = Laminas\Cache\StorageFactory::factory(
    [
        'adapter' => [
            'name'    => Laminas\Cache\Storage\Adapter\Filesystem::class,
            'options' => [
                'cache_dir' => __DIR__ . '/tmp',
                'ttl'       => 3600,
            ],
        ],
        'plugins' => [
            Laminas\Cache\Storage\Plugin\Serializer::class,
        ],
    ]
);

// Set cache for each paginator
Laminas\Paginator\Paginator::setCache($cache);

// Create paginator
$data      = range(1, 100);
$paginator = new Laminas\Paginator\Paginator(
    new Laminas\Paginator\Adapter\ArrayAdapter($data)
);

// Fetch items and store items in cache
$items = $paginator->getCurrentItems();

https://docs.laminas.dev/laminas-cache/storage/adapter/#quick-start
https://docs.laminas.dev/laminas-cache/storage/plugin/#the-serializer-plugin

I guess I didn’t install laminas-serializer.

It is possible that I donot make the migration to laminas and install the laminas-series within ZF3?

If you change the namespace in my code example from Laminas to Zend then everything works in your current project without any installation of components from Laminas.

Use this in your project:

Zend\Paginator\Paginator::setCache($cache);

instead of:

Laminas\Paginator\Paginator::setCache($cache);

The same for all other classes.

So don’t worry, because no special efforts are necessary.

Hi @froschdesign, sorry for troubling again!

not work! Maybe I use the wrong way, I show my steps as follow:

Finally I got the error output as debug:

I run the composer command: php composer.phar require zendframework/zend-serializer

Yes, this is wrong and I’ve given you an alternative. Use the factory method and not the adapterFactory. See my example from above:

$cache = Zend\Cache\StorageFactory::factory(
    [
        'adapter' => [
            'name'    => Zend\Cache\Storage\Adapter\Filesystem::class,
            'options' => [
                'cache_dir' => __DIR__ . '/tmp',
                'ttl'       => 3600,
            ],
        ],
        'plugins' => [
            Zend\Cache\Storage\Plugin\Serializer::class,
        ],
    ]
);

@froschdesign

Hi Frank, It works now. Thank you so much!!

The key point is that.
use StorageFactory::factory not StorageFactory::adapterFactory
And use ‘plugins’ => [‘serializer’] not ‘plugins’ => [Zend\Cache\Storage\Plugin\Serializer::class]

Thank you again!

Great!

I will recheck the related code examples in documentation because I think there is a bug.

I pasted the worked one above.

If I wrote ‘plugins’ => [Zend\Cache\Storage\Plugin\Serializer::class] instead ‘plugins’ => [‘serializer’], It raise an error class Zend\Cache\Storage\Plugin\Serializer::class not found.

The above is my experience, thank you!

Hello @froschdesign

Sorry for troubling! I found a very big problem, when I use the above solution! All the page are the same!!!

In my application, I have two parameters for the paginator, $class and $page
Pls see the shotscreen as follow:

It seems all the paginator cached togather!! it seems the parameter $class totall ignored!!

Besides article, I also collection some poems too. and use paginator cache also in the poems list. They mixed togather after I use the paginator cache.

Do you have any idea why?

https://www.wendangs.com/article/index/list/1
https://www.wendangs.com/article/index/list/2
https://www.wendangs.com/article/index/list/3
https://www.wendangs.com/article/gushiwen/list

The above links are all the same!!

You can check the paginator which page items are cached:

var_dump($paginator->getPageItemCache());

Last code sample on this page:

https://docs.laminas.dev/laminas-paginator/advanced/#caching-features

Another option is to disabled the cache and to check if you get the same results:

$paginator->setCacheEnabled(false);

Then you know if the cache is the problem or something different.

Before I set $paginator->setCacheEnabled(false);

After I set $paginator->setCacheEnabled(false);, the following picture shows the correct page!

After I set this to false, The page obviously not cached because it is very slow since I have a big database of more than 150,000 records.

I follow your suggestion to print var_dump($paginator->getPageItemCache());


I look throught the webserver, and found a file named zfcache-Zend_Paginator_1_7de66ce1c4d2fe3c7bd565e5c59eb795.dat
I just can find a “1” in the file name, I guess it stands for the first page, page number.

Please check the value of your $page variable in the method getPaginator.

I think the paginator only considered the page parameter when the cachedir was set.
So it confict when different class of article with the same page.