House cleaning with Perl 6

2018-09-28 14:45:00

One of the standard features of content management systems is automatic scaling of images to the size required by the website layout. Our CMS does so on the first request for a certain size of a certain image. We have over 1400 customers and provide common content like tax related news. With so many shared images, having a centralized cache for scaled images becomes a really good idea and is easy to do using content addressing in the form of SHA hashes.

Of course there are probably as many cache implementations as there are CMS. Caches are simple beasts. You give them a key and they give you the data. Many implementations store the data in files in some directory structure which is appropriate for image data. Our use case is a bit special though, because we actually don't want the data. We would much rather have the file name to hand it off to the frontend webserver. That's why we implemented caching and fetching ourselves. But how do you keep the cache from growing indefinitely?

We just wrote a little daemon that watches the cache directory and limits its size. It's a simple "whenever a new file enters the cache, if the total size exceeds the configured limit, delete the least recently used files" algorithm. Reacting to events is inherently asynchronous and Perl 6 seemed the natural choice:

react {
    whenever $.cache_root.watch -> $change {
        my $path = $change.path.IO;
        if $path.e { # could be deleted after event was generated
            my $size = $path.s;
            if $size { # could get notified before any data is written
                %!files{$change.path} = CacheEntry.new(:$size, :age(now));
                self.clean-cache;
            }
        }
    }
    whenever signal(SIGINT) {
        done;
    }
}

The actual program of course needs some more scaffolding like reading the existing cache entries on startup and the 4 line CacheEntry class, so it comes at a total of 68 lines of code. 5 of those lines are for reading the configuration of our Perl 5 based content management system:

use lib:from<Perl5> $*PROGRAM.parent.parent.add('lib').Str; 
use ZMS::Configuration:from<Perl5>;
my $config = ZMS::Configuration.new;
my $cache_root = $config.get(|<Controller::Static cache_root>).IO;
my $cache_size = $config.get(|<Controller::Static cache_size>);
CacheCleaner.new(:$cache_root, :$cache_size).run;

Using a "best of both worlds" approach, we have Perl 5 code render our customers websites and Perl 6 code to clean up after it.

Managed by CiderCMS