Showing posts with label perl. Show all posts
Showing posts with label perl. Show all posts

Friday, April 27, 2018

Perl for DevOps: Mojo::UserAgent

There's no way anyone "doing devops" can work without needing to interact with products/systems remotely, and these days pretty much everything offers an API and that API is more than likely available over HTTP. The Mojolicious distribution provides a ton of useful modules for doing almost anything HTTP-related; both client- and server-side.

This post is going to mostly focus on writing client code with Mojo::UserAgent, and some utility modules that Mojolicious ships with. I will also touch on using Mojolicious::Lite for writing web interfaces and services, but deployment will be deferred until the next post, which will focus on Plack/PSGI, the various Plack servers available, and some of the web frameworks that support PSGI.

Mojo::UserAgent vs LWP::UserAgent

For the longest time, LWP::UserAgent has been the de-facto standard HTTP client library that the Perl community uses. But I've written before about why I prefer Mojo::UserAgent over LWP::UserAgent; it has both blocking and non-blocking interfaces, so you can write both kinds of applications and only need to know one API, should you get into writing non-blocking code in your devops journey.

But there's more.

Prototype with mojo

The mojo command is provided by the Mojolicious package, and is a great tool to query web services and pages. I wouldn't call it a curl or wget replacement (that's not its purpose), but it's a great tool for extracting data that is embedded in a response, for use in shell scripts, and for rapid prototyping before converting to Mojo::UserAgent in a Perl script.

As an example, if you wanted to use the MetaCPAN API to get the latest version of a package:


$ mojo get http://fastapi.metacpan.org/v1/package/Mojolicious
{
   "version" : "7.74",
   "module_name" : "Mojolicious",
   "dist_version" : "7.74",
   "file" : "S/SR/SRI/Mojolicious-7.74.tar.gz",
   "distribution" : "Mojolicious",
   "author" : "SRI"
}

This shows the JSON output of the API request. Mojolicious comes bundled with its own JSON decoding/encoding module, Mojo::JSON, which can be used directly in any application you want - perhaps as a replacement for the other JSON modules, if you so desired - but it's also integrated into Mojo::UserAgent, for decoding and easily extracting data from JSON responses.

The version is what I'm after. We can easily grab that with another parameter, utilising some simple notation.


$ mojo get http://fastapi.metacpan.org/v1/package/Mojolicious /version
7.74

But what if there is no nice JSON API, and we have to extract the same data from a web page? Well, we can do that too:


$ mojo get https://metacpan.org/pod/Mojolicious 'span[itemprop="softwareVersion"]' text
7.74

This fetches the Mojolicious documentation on MetaCPAN and looks for a span tag with an itemprop attribute value of softwareVersion and displays the text in the tag. In this case, we're pretty lucky that the MetaCPAN page gives us a friendly way to locate this data, but more complex queries can be used for less mojo-friendly websites.

The beauty of the mojo tool is that once you've prototyped how to extract the information that you want, you can either leave it in a bash script, or you can port the code to use the Mojo::UserAgent module and use it as part of a larger application.


#!/usr/bin/env perl

use v5.10;
use warnings;
use strict;

use Mojo::UserAgent;

my $ua = Mojo::UserAgent->new;
my $tx = $ua->get('http://fastapi.metacpan.org/v1/package/Mojolicious');

if ($tx->res->is_success) {
 say $tx->res->json->{version};
}

This is just a part of what the Mojolicious distribution has to offer; there's also an event loop and a promises implementation for writing non-blocking client and server code, and a whole lot more. Mojolicious wants to be a self-contained installation with as few external dependencies as possible, which makes it stable, and resilient to issues in the greater CPAN package ecosystem. Check out the other packages it provides.

Next in the series (whenever I get to it) I'll go through Mojolicious::Lite and Plack/PSGI, for when the time comes to write and deploy web sites and services.

Friday, January 19, 2018

Perl for DevOps: IO::All

A stupidly common task to perform is file and directory IO. In the Perl world, the IO::All module has wrapped up nearly every common IO task I can think of into a very expressive interface. With the many options available to perform the same task, it can fit into scripts in many different ways.

For example - as a sort of "hello world" of file IO - if I wanted to read the contents of a file, do some basic processing and then output to a new file, here is a very simple solution:

use IO::All;

my $contents < io("foo.txt");
$contents =~ s{foo}{bar}g;
$contents > io("bar.txt");

Or, if you're not a fan of operator overloading, that's cool too! Here's the same script, with a more explicit usage:

use IO::All;

my $contents = io("foo.txt")->slurp;
$contents =~ s{foo}{bar}g;
io("bar.txt")->print($contents);

And there are a bunch more options to do similar things in the documentation.

What about reading a file backwards? This is sometimes useful to look for the last instance of an event in a log file:

use v5.10;
use IO::All;

my $io = io("/var/log/maillog");
$io->backwards;
while (my $line = $io->getline) {
  if ($line =~ m{ dsn = 4\.5\.0 }xms) {
    say "last success: $line";
    last;
  }
}

What About Directories?

Perhaps we wanted to traverse /var/log recursively and list out anything that's gzip compressed:

use v5.10;
use IO::All;

my @files = io('/var/log')->deep->all_files;
foreach my $file (grep { $_->ext eq 'gz' } @files) {
  say $file->name;
}

Something I've had to do on more than one occasion - when bringing up and initialising a new VM - is create a directory structure and all of the parent directories with it:

use IO::All;

foreach my $a ('0' .. '9', 'a' .. 'f') {
  foreach my $b ('0' .. '9', 'a' .. 'f')
    io->dir("/var/my-application/tmp/$a/$b")->mkpath;
  }
}

So What?

The tendency is to just use bash scripts for a lot of these tasks. But bash scripts become unwieldy when the scope of a tiny script creeps, and it now needs to compress files, encrypt data, maybe upload stuff to S3, logging everything it does along the way to a centralised location, perhaps logging all errors to a Slack channel, or maybe just sending a notification to a Slack channel when the job is done. Perl is more than ready to handle those tasks.

I'll tackle some of these modules and tasks in more detail in future posts.

Worthy Mention: Path::Tiny

Although more can be accomplished with IO::All, the Path::Tiny module is also worth knowing about. There have been certain times where I've needed more specific control that IO::All doesn't provide. In those cases, Path::Tiny usually does what I want, so it's a handy backup tool and worth knowing about.

Between these two modules, pretty much all filesystem IO needs should be taken care of.

So Much More

I'd encourage anyone to look through the docs. There are tons of examples for all kinds of tasks that I haven't touched on at all in this post, even as far as being able to send emails via a plugin.

Unless - or until - you need to use the low-level IO functions for fine-grained control and/or better performance for a critical piece of functionality, the IO::All module (and Path::Tiny as its companion) should be more than enough almost all of the time.

Thursday, November 30, 2017

Perl for DevOps: perlbrew and carton

I'm sick of seeing the same, old, and very dated articles/books/whatever relating to Perl and systems administration. There are a ton of Perl modules and tools available to make life easy for developers, testers and operations staff in a DevOps environment, but unless you're already deep in the Perl world, many remain fairly hidden from the public eye and hard to come by.

I'm hoping that the next few posts will show off what's currently on offer in the Perl world. Whether it's a brand new startup launching a product from scratch, or an established organisation with an already mature product, I'm not trying to convince anyone to change their main product's stack, but for all of the glue required to support the application in a production environment, Perl is an excellent choice.

Perl's "There's More That One Way To Do It" attitude has inspired a variety of modules with expressive APIs and tools that make working with Perl in a production environment easy.

But before getting into specific modules and tools, the first thing to discuss, even if it's less exciting, is the management of multiple perl versions and the management of CPAN dependencies.

What I'm going to discuss here isn't new material; an article from 2016, A Perl toolchain for building micro-services at scale, summed up a great set of relevant tools for using Perl which can be extended from building microservices to doing almost any other development work with Perl. This first post will focus on the two tools that I think are the most important.

Perlbrew

Unfortunately, most Linux distributions still ship with perl 5.8, despite reaching end-of-life years ago. This often leads to people sticking with perl 5.8 and installing modules from CPAN to the system-level perl, sometimes even using their distribution's package manager instead of a CPAN client to do it. This is a terrible idea. Often, depending on which OS and distribution you're running, the system-level perl is used for internal tools, and breaking the system-level perl starts to break other important things.

This is where perlbrew is a no-brainer.

Perlbrew is just like pyenv for Python, or rbenv for Ruby; it's a tool for managing and using various perl versions without interfering with the system-level perl.

The added bonus of running a more recent version of perl out of perlbrew is the availability of some modules which require perl 5.10 or later, having left behind 5.8 long ago, e.g. Mojolicious.

Alternatively, plenv is another tool for managing Perl versions, although it's not a tool I have a lot of experience with.

Carton

There are a few options for managing Perl dependencies. I'm only going to describe Carton, but there are also distribution- or OS-specific options aswell that cover all or some of the functionality of Carton, e.g. Red Hat Software Collections.

While not strictly necessary, carton - comparable to using a combination of virtualenv + pip for Python or Bundler for Ruby - is an excellent tool to manage dependencies.

The cpanfile (used by carton) provides the ability to specify the direct dependencies of the script or system and, along with the generated cpanfile.snapshot file which contains the full dependency tree, can be checked into a source control system along with the code it supports. The carton utility then provides the ability to use this cpanfile to create a local repository containing only the modules and versions specified in the snapshot.

Multiple cpanfiles may be used to track the dependencies of multiple different systems or subsystems.

An example setup might be to only use a carton bundle for critical customer-facing services, as you would want that environment to be as static as possible and not be prone to failure just because someone updated a dependency for a utility script. Or perhaps use one carton bundle for critical production stuff, and another one for the less critical stuff. Or perhaps a more granular setup, depending on the situation.

The caveat with carton is that for any dependency on third party libraries (e.g. IO::Socket::SSL requiring openssl, or EV requiring libev), the third party library will not be bundled into your carton repository.

Kinda Boring But Important

I feel like this was a pretty boring introduction to using Perl as a language for your devops needs, but it's an important topic that - unfortunately, in my own personal experience - can be a real pain in the ass to deal with if it's not considered early on in the piece.

Perlbrew and Carton are powerful tools, are both worth knowing and, when used in tandem, they allow any development to be as isolated as possible, so as to interfere with as little as possible on a system.

Friday, October 27, 2017

Perl Hack: perlbrew libs

The libs feature of perlbrew is one I don't see used very often. At least, not by the developers I currently work with and have worked with in the past.

Sometimes I want to run a piece of code against the core libraries and only the core libraries. Sometimes I wrap a script up with Carton and want to verify that a base install + Carton can run my script. And sometimes I just want a place to install anything and everything from CPAN, play with new versions' features, etc...

This is where the libs feature comes in handy.

I have three sets of perl 5.20.3 libs:

$ perlbrew list
  perl-5.20.3
  perl-5.20.3@carton
* perl-5.20.3@dev

99% of my time is spent on the "dev" lib, where I install anything I want. The "perl-5.20.3" is just a base installation of 5.20.3. And the "carton" lib is just a base 5.20.3 installation with only Carton installed. And if I ever break the "carton" or "dev" libs, they're easily recreated from the base installation.

Tuesday, August 29, 2017

Serving the Current Directory over SSL

Recently at work, we needed to setup a dummy HTTPS server just as an endpoint that needed to do... something. Nothing specific. Just something that did SSL/TLS and returned a 200 response. Immediately I thought of python's builtin SimpleHTTPServer, which can be used to serve the current directory:

$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

And away it goes. But to put SSL into it, more code is needed, but there are examples around.

I wondered how easily (or not) I could do it with Perl and a Plack server.

First, I needed the following dependencies installed:

  1. Plack::App::Directory. This comes with the standard Plack distribution, but it's used to serve a directory listing.
  2. Starman. This is currently the only Plack server that supports SSL, without requiring something like nginx in front of it. A little disappointing, but not a big deal.
  3. IO::Socket::SSL. To do the SSL stuff. Requires OpenSSL.

These can either be managed by Carton, or you can just install them with cpanm.

$ cpanm Plack Starman IO::Socket::SSL

Next, I need to generate a dummy SSL certificate.

$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt

Now I can run the server:

$ plackup -s Starman --listen :8000 \
  --enable-ssl --ssl-cert server.crt --ssl-key server.key \
  -MPlack::App::Directory -e'Plack::App::Directory->new(root=>".")'
2017/08/29-12:27:01 Starman::Server (type Net::Server::PreFork) starting! pid(38015)
Resolved [*]:8000 to [0.0.0.0]:8000, IPv4
Binding to SSL port 8000 on host 0.0.0.0 with IPv4
Setting gid to "20 20 20 504 401 12 61 79 80 81 98 33 100 204 395 398 399"
Starman: Accepting connections at https://*:8000/

... and the directory listing is available via https://locahost:8000/

It's not as simple as Python's SimpleHTTPServer to get going, but it works!

Friday, July 28, 2017

Concurrency, Perl and Web Services, Oh My!

The majority of the systems I develop - both in my spare time and at work - are heavily IO-based; connecting various web services, databases and files on disk together in some fashion. In those particular systems, I'd be stupid not to promote a concurrency-based solution.

Lately I've spent a lot of time developing small web services (or microservices or whatever it's called this year), both as brand new systems and also as a way of shaving off parts of large monoliths into smaller chunks to auto-scale.

There are many ways to design the architecture for this kind of system and its subsystems, and often there are instances where pre-forking a bunch of worker processes to handle requests is either too resource-hungry or just not appropriate for the work it's doing.

Let's Write Something

I want to write an example web service, but I'm sick of seeing the same, "Hello World"-esque web services that can be written in a dozen lines of code that no way represent any web service that anyone ever has written. I want to write an application that can actually benefit from a concurrent solution and semi-resembles a real-world thing. So I've got an example:

  1. HTTP-based web service
  2. Runs in a single process
  3. Accepts a domain name, and returns the geographic locations of the domain's mail servers, in JSON format

To satisfy the first two criteria, a Plack/PSGI application running out of either Twiggy or Feersum should do just fine.

In order to satisfy the last point (i.e. the actual functionality), the app needs to perform a few steps:

  1. Retrieve the mail servers of the domain via a DNS lookup. AnyEvent::DNS can do this.
  2. For each of the mail servers, resolve the IP addresses via another DNS lookup. AnyEvent::DNS to the rescue again.
  3. For each of the IP addresses, I'm going to use the IP Vigilante API to retrieve the geographic location data. There are no modules on CPAN for the IP Vigilante service, so I'll need to write something. AnyEvent::HTTP would work just fine here, but lately I prefer to use Mojo::UserAgent where possible, because it's much more versatile, e.g. providing a proper request/response object for us, and handling JSON responses.

There's a fairly straight-forward sequence of operations to perform, so I'm going to use a Promises-based approach. I've found that this makes concurrent Perl code much easier to follow, especially when other developers need to jump in and understand and maintain it. There's no real reason for why I've settled on Promises over Futures (or any other implementation of these two patterns); either will do just fine.

Firstly, I need a function that can lookup the MX records for a single domain and return an arrayref of addresses (via a promise).


sub lookup_mx {
    my ($domain) = @_;
    AE::log trace => "lookup_mx($domain)";

    my $d = Promises::deferred;

    AnyEvent::DNS::mx $domain, sub {
        my (@addrs) = @_;

        if (@addrs) {
            $d->resolve(\@addrs);
            return;
        }

        $d->reject("unable to perform MX lookup of $domain");
    };

    return $d->promise;
}

This is actually a pretty boring function to look at.

Next, I need a function that can resolve a domain name to an IP address (or addresses).


sub resolve_addr {
    my ($domain) = @_;
    AE::log trace => "resolve_addr($domain)";

    my $d = Promises::deferred;

    AnyEvent::DNS::a $domain, sub {
        my (@addrs) = @_;

        if (@addrs) {
            $d->resolve(\@addrs);
            return;
        }

        $d->reject("unable to resolve $domain");
    };

    return $d->promise;
}

This is also a pretty boring function.

Now I need a function that can perform a lookup to the IP Vigilante service for a single IP address and return an arrayref containing the continent, country and city for which it resides.


my $ua = Mojo::UserAgent->new->max_redirects(5);

sub ipvigilante {
    my ($address) = @_;
    AE::log trace => "ipvigilante($address)";

    my $d = Promises::deferred;
    my $url = sprintf "https://ipvigilante.com/json/%s", $address;

    $ua->get($url, sub {
        my ($ua, $tx) = @_;
        if ($tx->res->is_success) {
            my $json = $tx->res->json;
            my $rv = [
                $json->{data}->{continent_name},
                $json->{data}->{country_name},
                $json->{data}->{city_name},
            ];
            $d->resolve($rv);
            return;
        }
        $d->reject( $tx->res->error );
    } );

    return $d->promise;
}

This function is slightly more interesting - it receives a JSON response from IP Vigilante - but, in the end, is still fairly boring, since Mojo::UserAgent handles all of it for us.

The next function will need to take an arrayref of IP addresses, and collate the IP Vigilante data into a hashref, for which the keys will be the IP addresses and the values will be the IP Vigilante information from the previous function.


sub get_ip_informations {
    my ($ips) = @_;

    my $d = Promises::deferred;

    my %rv;
    Promises::collect( map {
            my $ip = $_;
            ipvigilante($ip)
                ->then( sub {
                    my ($ip_info) = @_;
                    $rv{$ip} = $ip_info;
                } )
            } @$ips )
        ->then( sub { $d->resolve(\%rv) } )
        ->catch( sub { $d->reject(@_) } );

    return $d->promise;
}

This is the first note-worthy function, and it's still not that big of a function. The call to the ipvigilante()->then() chain will return a new promise, and we have used map and the Promises::collect() function to collate the results of multiple promises. This means that if we are trying to get the IP information for 10 addresses, the map will return 10 promises, and for this function to return a result, we need the response from all 10 promises. The entire batch executes concurrently and only runs as slow as the slowest IP Vigilante lookup. Yay concurrency!

Lastly, I need a function that will take an arrayref of domain names, resolve each domain to its IP address(es) and get the IP Vigilante information for each IP address (via the previous function) and return it as a hashref.


sub get_mx_informations {
    my ($addrs) = @_;

    my $d = Promises::deferred;

    my %rv;
    Promises::collect( map {
                my $mx = $_;
                resolve_addr($mx)
                    ->then( sub { get_ip_informations($_[0]) } )
                    ->then( sub { $rv{$mx} = $_[0] } );
            } @$addrs )
        ->then( sub { $d->resolve(\%rv) } )
        ->catch( sub { $d->reject(@_) } );


    return $d->promise;
}

This function is basically the bulk of the application.

I feel like these last two functions shouldn't be necessary and they rub me the wrong way a little, as they're essentially just for-loops where the inside of the loop has already been put into another function, but for the purposes of maintainability and testability, I kept them.

The beauty about all of the code written so far is that because Promises, AnyEvent and Mojo all integrate with the lower-level EV event loop, and in some cases with each other, everything works together. This makes it simple to mix and match your favorite libraries that were originally written for different frameworks.

The whole thing just needs to be wrapped in a Plack/PSGI application.


my $app = sub {
    my ($env) = @_;
    my $request = Plack::Request->new($env);

    if ($request->method ne 'GET') {
        return [ 400, [], [] ];
    }

    (my $domain = $request->path_info) =~ s{^/}{};

    if (not $domain) {
        return [
            400,
            [ 'Content-Type' => 'application/json' ],
            [ Mojo::JSON::encode_json( { error => 'domain required' } ) ]
        ];
    }

    return sub {
        my ($responder) = @_;
        my $response = Plack::Response->new;

        lookup_mx($domain)
            ->then( sub { get_mx_informations($_[0]) } )
            ->then( sub {
                    my ($mx_informations) = @_;
                    $response->status(200);
                    return { $domain => $mx_informations };
                } )
            ->catch( sub {
                    my ($error) = @_;
                    $response->status(400);
                    return { error => $error };
                } )
            ->finally( sub {
                    my ($json) = @_;
                    $response->headers( [
                        'Content-Type' => 'application/json'
                    ] );
                    $response->body( Mojo::JSON::encode_json($json) );
                    $responder->( $response->finalize )
                } );
    }
};

I'm going to use Carton to handle and bundle the dependencies. This step isn't absolutely necessary, but when deploying Perl applications across many machines in a production environment, it's a solid tool for keeping things consistent across the board. Not having a solution for this is a massive headache once many different pieces of code have been deployed again and again for a few years. The Carton FAQ has a good rundown of its use-cases. I now need to declare my immediate dependencies in a new file - for Carton to consume - cpanfile.


requires 'Plack';
requires 'Feersum';
requires 'AnyEvent';
requires 'IO::Socket::SSL';
requires 'Mojolicious';
requires 'Promises';

I'm not tied down to specific versions of any of these modules.

The last step is to - with the help of carton - install the dependencies, which will also generate a snapshot file with all my dependencies' dependicies, and then run the server.

$ carton install
$ carton exec -- feersum --listen :5000 -a mx.psgi

... and in another shell ...

$ curl -s http://localhost:5000/nab.com.au
{
   "nab.com.au" : {
      "cust23659-2-in.mailcontrol.com" : {
         "116.50.58.190" : [
            "Oceania",
            "Australia",
            null
         ]
      },
      "cust23659-1-in.mailcontrol.com" : {
         "116.50.59.190" : [
            "Asia",
            "India",
            null
         ]
      }
   }
}

The full code is available on github.

Just Release It Now, Right?

This is beyond the original scope of this post, but there's still a lot more to do. The application is just barely in an acceptable state. There are a number of extra steps before this can/should be deployed to production, for which I may write follow-up posts:

  1. Unit tests. The application and functions should be moved into its own package in order to have unit tests written against it. I've had great success using Plack::Test to test Plack applications and Test::MockObject::Extends to mock functions that would perform network calls, so that I don't require a working internet connection to run unit tests.
  2. Logging. Self-explanatory (I hope).
  3. Rate limiting the ipvigilante.com API requests. I don't want the service to inundate IP Vigilante with tons of connections/requests at the same time.
  4. Dealing with ipvigilante.com failures. The circuitbreaker pattern will help the service remain stable and not constantly hit a remote service which is having an outage.
  5. Caching. IP addresses aren't likely to move geographic locations very often (if at all), so caching the IP Vigilante responses will be of great benefit. Either a simple local cache with Cache::FastMmap, or perhaps with a remote cache in Cache::Memcached, if I end up with a cluster of servers - which are an auto-scaling group - and I want a centralised cache for all hosts to use.
  6. Monitoring. How long do DNS lookups take? How long do ipvigilante.com API requests take? How often do they fail? When they fail, do they fail fast or do they timeout after 5 minutes of waiting?
  7. There's probably more...

Friday, June 30, 2017

Mojo::UserAgent is Best User Agent

For the longest time, I used LWP::UserAgent, because it was always there and it was reliable and there were tons of examples on the internet and almost every other Perl programmer I'd interacted with had used it before. So it was just easier to use it.

But then my requirements for a HTTP client changed.

A few years ago when I started tackling a lot of problems with concurrent/non-blocking solutions, I needed a HTTP client. Initially, because I was using AnyEvent, I just used AnyEvent::HTTP. And then I came across a really annoying issue: all headers are lower-cased and then the first letter is upper-cased. For the particular problem I was solving, this wasn't gonna work.

The next step was to combine AnyEvent::Handle with HTTP::Request and HTTP::Parser::XS. But that quickly introduced new problems; handling 302 responses, keep-alive, chunked responses and everything in between. I also had to package responses into robust objects myself. This was way more work than it was worth.

Finally, I ended up at Mojo::UserAgent. Because it has a blocking interface, it works great as a replacement for LWP::UserAgent. And because everything in Mojolicious was built to be non-blocking and because Mojo supports EV, it ties in well with all of the other libraries I use for my non-blocking code and it replaces my hand-rolled AnyEvent/HTTP::Request/HTTP::Parser::XS approach.

And for all of this, I only need to remember one API and manage one dependency.

Friday, April 7, 2017

Performance: Pick Better Libraries/Frameworks

I both love and hate CPAN; it has a module for every need, but often, for one reason or another, those modules aren't written by someone who cares about performance.

For example, Crypt::Blowfish is a XS module, which is great, but it's meant to be used, for example, via Crypt::CBC, which is a pure-Perl library, and it runs terribly slow if you encrypt a lot of data (and we do). On the flipside, Crypt::Mode::CBC is a XS module and performs brilliantly (as does the rest of CryptX). Swapping from Crypt::CBC/Crypt::Blowfish to Crypt::Mode::CBC/Crypt::Cipher::Blowfish, we noticed a 7x performance gain in our system.

In the Perl world, when utilising microservices is the right tool for the job, Plack/PSGI is the winning platform for writing RESTful webservices.

An old service that I was maintaining used HTTP::Server::Simple::CGI, which was originally used for the sake of simplicity and getting something up and running with what seemed like a lightweight framework. It was also able to plug in a Net::Server subclass to leverage Net::Server's features, which was desirable from an operational point-of-view given we'd used Net::Server successfully for other purposes. However, HTTP::Server::Simple is terribly written from a performance point-of-view.

Simply porting the service over to Plack/PSGI and serving it out of Starman resulted in a 6x performance gain.

Popular web frameworks, like Mojolicious, support PSGI out of the box, if you'd prefer the abstraction they give you. However, in my experience, even a "Hello World" will run significantly slower. But, depending on the complexity of your service, the performance hit may be worth it.

Friday, March 10, 2017

Performance: Compress Your Payloads

... unless the CPU is the source of your latency.

Compressing payloads requires a little more CPU, but it can save on network IO, i.e. bandwidth, which can also have cost-saving benefits if your bandwidth costs are high.

The beauty is, if you're serving content out of a HTTP server, the popular ones can handle this for you with a small amount of configuration.

See:

Friday, February 10, 2017

Performance: Use Persistent Connections

Small Changes First

The first few points of this series are going to be around moderately small changes for the greatest gain.

It's easy to recommend the complete overhaul and re-engineering of a system using all the latest and greatest technologies and platforms available, but for many organisations, especially if you're having to maintain and slowly migrate a monolith to something more granular, that kind of change takes many months (and longer) to achieve once you factor in costs (both in time and dollars), training, testing, competing business priorities, etc...

As I run out of quick wins, I'll get into more architectural changes because they're pretty much inevitable as a system grows.

Be Persistent

If a single process is making multiple network calls to the same service, it makes sense that maintaining a persistent connection instead of creating a new connection for each individual call will lead to a performance gain; it reduces the number of connect() syscalls, the number of TLS handshakes and any sort of authentication/authorisation that happens once a connection is established.

See:

The question then becomes: can the endpoint you're connecting to (if you control it) handle the number of persistent connections you'll have open in the worst case?

Monday, January 30, 2017

Performance: Profile Your Damn Code First

I started jotting down notes for this post without much of an idea of how I was going to present them; I was just working on a bunch of performance issues and investigating potential future improvements and noticed a number of recurring ideas across different systems, and I thought they were worth sharing with the world.

Originally I wanted to post all of these ideas in one large post, but with the number of things to discuss, it felt like I was never gonna get the damn thing finished, so I decided to split it up instead.

This isn't for the developer who is already well-seasoned in tackling performance issues; it's for the developer who may need to do it soon, but doesn't know exactly where to start.

Although the points in this series will be on the Perl-ish side, the ideas should easily transfer.

Latency

Mostly what I'm going to be talking about in this series relates to latency.

With the various infrastructure providers around today, if a system is running slow, it's pretty easy to add more machines to the mix to pick up the slack. But all you've done is increased the capacity of the system. The latency - the time it takes to serve a single request - still sucks, no matter how many more machines you add.

Until you address the latency issues, you won't be utilising all of the resources of the individual machines you're paying for and your cost-to-serve (which your CFO cares about) will be unnecessarily high. That's bad.

Stretching the hardware and infrastructure you've already got by managing and minimising latency lowers cost-to-serve and increases capacity and throughput.

Use a Profiler

I feel like this goes without saying, but evidently, I've found that having a vested interest in this kind of work makes me the exception to the rule, so: profile your code first. If you have a slow application, there's no point guessing which bit is the slow bit.

In the past, I've seen slow page-load times and memory spikes, and everyone was sure what the cause was going to be, only to hook up NYTProf and quickly see from the flame graph that a Class::DBI relationship inflating timestamps into DateTime objects was the cause.

Don't guess! Profile the damn thing!

Friday, September 9, 2016

Return Promises, not Condition Variables

Eventually it'll happen; you'll be writing a library that's responsible for making a bunch of network calls, and because you've worked in IO-heavy applications before, you've already been sold on asynchronous programming patterns and because you've hated having to turn away so many modules on CPAN that do the exact job you want because they completely ignore the needs of the asynchronous crowd, you're going to write your library (or a version of the library) so that it can integrate easily with an event-loop framework by providing an asynchronous API.

One of the biggest rookie mistakes I made in the beginning, was writing functions that return AnyEvent::CondVar objects. It can work just fine if your entire application uses and expects other condition variables and you only need a small handful of them, but when the application grows and you perhaps start integrating with libraries whose functions return Promises or Futures, condition variables only get in the way. And when you start calling many functions that all return condition variables, you wind up in an ugly spaghetti of callbacks.

Return promises or futures and, in your application code, utilise the chaining/sequencing/pipelining features so it doesn't look like spaghetti. You'll end up with cleaner-looking code that's more easily maintainable, reads like synchronous code, and is easier for other developers to dive into.

Friday, August 5, 2016

Writing an Asynchronous Echo Server

Echo servers are basically the "Hello, World!" of network programming, so I'm going to step through building an asynchronous echo server using AnyEvent, although any bare-bones event loop, like EV, could be used.

This is less to do with building an echo server and more about thinking asynchronously and what complications arise from writing asynchronous code from the ground up. Asynchronous solutions in IO-heavy applications will result in much better performance than if you, for example, went with a forking model.

The forking model is often very tempting as it has a much lower barrier to entry, but is also very memory hungry as each forked process ends up with a copy of the parent processes' memory. The other problem with the forking model in an IO-heavy application, is that when disk and/or network IO is the bottleneck, adding more processes, which will only attempt more IO operations, is more likely to compound the problem rather than fix it, so it's often the wrong tool for the job, despite many people using it as such.

Knowing when your code is IO-bound is something most likely discovered with some kind of profiling tool. In the Perl world, Devel::NYTProf is the defacto standard weapon of choice for profiling and I can't count the number of times it's helped me out with identifying performance bottlenecks.

Before I get into any code, I just want to mention that all of what I'm about to discuss could be managed by AnyEvent::Handle (or your favourite event loop framework's equivalent), but, as a learning exercise, I'm doing it the hard way, to appreciate what AnyEvent::Handle abstracts away for us. The take-home message should be: use AnyEvent::Handle. If you don't use it, hopefully this will give you a taste of what you're in for.

Gotta Start Somewhere

Let's start with a pretty basic implementation of a non-blocking echo server.


#!/usr/bin/env perl

use warnings;
use strict;

use AnyEvent;
use IO::Socket;
use Socket qw/SOMAXCONN/;
use POSIX qw/EAGAIN EWOULDBLOCK EPIPE/;

use constant {
  SYSREAD_MAX => 8192,
};

my $listen = IO::Socket::INET->new(
  Listen    => SOMAXCONN,
  LocalAddr => 'localhost',
  LocalPort => 5000,
  ReuseAddr => 1,
  Blocking  => 0
) or die $!;

print "Listening on port 5000...\n";

my %clients;

my $w = AnyEvent->io(
  fh   => $listen,
  poll => 'r',
  cb   => sub {
    my $client = $listen->accept;
    $client->blocking(0);

    printf "Client connection from %s\n", $client->peerhost;

    $clients{$client}->{r} = AnyEvent->io(
      fh   => $client,
      poll => 'r',
      cb   => sub { read_data($client) }
    );
  }
);

AE::cv->recv;

sub read_data {
  my ($client) = @_;

  my $bytes = sysread $client, my $buf, SYSREAD_MAX;

  if ( not defined $bytes ) {
    if ( ( $! == EAGAIN ) or ( $! == EWOULDBLOCK ) ) {
      return;
    }
  }
  elsif ( $bytes == 0 ) {
    disconnect($client);
    return;
  }

  chomp( my $chomped = $buf );
  printf "Read %d bytes from %s: %s\n", $bytes, $client->peerhost, $chomped;

  my $w; $w = AnyEvent->io(
    fh   => $client,
    poll => 'w',
    cb   => sub {
      write_data( $client, $buf );
      undef $w;
    }
  );
}

sub write_data {
  my ( $client, $buf ) = @_;

  my $bytes = syswrite $client, $buf, length($buf);

  if ( not defined $bytes ) {
    if ( ( $! == EAGAIN ) or ( $! == EWOULDBLOCK ) ) {
      return;
    }
    elsif ( $! == EPIPE ) {
      disconnect($client);
      return;
    }
  }
  else {
    printf "Wrote %d bytes to client %s\n", $bytes, $client->peerhost;
  }
}

sub disconnect {
  my ($client) = @_;

  printf "Client %s disconnected\n", $client->peerhost;
  delete $clients{$client};
  $client->close;
}

The big issues are:

  1. The read_data() function, after reading data from the client, will blindly create more and more watchers to write data back to the client. These watchers aren't guaranteed to be run in the order they were spawned, which means we run the risk of writing data in the wrong order. The number of watchers we create will be non-deterministic, which means memory usage may also go up. In the same way that we only have one read watcher, it'd be great to only have one write watcher.
  2. The write_data() function presumes that because we asked syswrite() to write X bytes to the socket, that X bytes were actually written. Because this is a non-blocking socket, we're not guaranteed that to be the case, and if we end up in this situation, and there are many scheduled write_data() events to happen, we need to finish sending what's left of the current buffer first, otherwise we risk writing data in the wrong order.

Another big issue, which isn't the case for an echo server, but would be in the case of, for example, a HTTP server, is that the read_data() function will sysread() some data, presume it's read the entire input and then act on that input. At the moment the code reads, at most, 8192 bytes of data from the client, but a full HTTP request (e.g. a file upload) may easily exceed that.

Buffers

A way to solve these issues is with read and write buffers and one watcher to act on each buffer, so that, at most, each client connection results in two watchers being created, one to act on the read buffer, and one to act on the write buffer.


#!/usr/bin/env perl

use warnings;
use strict;

use AnyEvent;
use IO::Socket;
use Socket qw/SOMAXCONN/;
use POSIX qw/EAGAIN EWOULDBLOCK EPIPE/;

use constant {
  SYSREAD_MAX  => 8192,
  SYSWRITE_MAX => 8192,
};

my $listen = IO::Socket::INET->new(
  Listen    => SOMAXCONN,
  LocalAddr => 'localhost',
  LocalPort => 5000,
  ReuseAddr => 1,
  Blocking  => 0
) or die $!;

print "Listening on port 5000...\n";

my %clients;

my $w = AnyEvent->io(
  fh   => $listen,
  poll => 'r',
  cb   => sub {
    my $client = $listen->accept;
    $client->blocking(0);

    printf "Client connection from %s\n", $client->peerhost;

    # Each connection gets a read buffer, a write buffer, and read/write
    # watchers.
    $clients{$client}->{rbuf} = '';
    $clients{$client}->{r}    = AnyEvent->io(
      fh   => $client,
      poll => 'r',
      cb   => sub { read_data($client) }
    );

    $clients{$client}->{wbuf} = '';
    $clients{$client}->{w}    = AnyEvent->io(
      fh   => $client,
      poll => 'w',
      cb   => sub { write_data($client) }
    );
  }
);

AE::cv->recv;

What's changed is that each client socket gets exactly one read watcher, one write watcher and read and write buffers. Otherwise, everything's the same.


sub read_data {
  my ($client) = @_;

  # Read data from the client and append it to the read buffer
  my $bytes = sysread $client, $clients{$client}->{rbuf}, SYSREAD_MAX,
    length( $clients{$client}->{rbuf} );

  if ( not defined $bytes ) {
    if ( ( $! == EAGAIN ) or ( $! == EWOULDBLOCK ) ) {
      return;
    }
  }
  elsif ( $bytes == 0 ) {
    disconnect($client);
    return;
  }

  printf "Read %d bytes from %s. Read buffer: %s\n", $bytes,
    $client->peerhost, $clients{$client}->{rbuf};

  while ( ( my $i = index( $clients{$client}->{rbuf}, "\n" ) ) >= 0 ) {
    my $msg = substr( $clients{$client}->{rbuf}, 0, $i + 1, '' );
    push_write( $client, $msg );
  }
}

The main changes here are that we sysread right onto the end of the read buffer, and then we process what's in the read buffer. For an echo server, we presume that one "message" is any data that has been terminated by a newline character. So when we have received a full message, we queue it to be sent back to the client with the push_write function.


sub push_write {
  my ( $client, $msg ) = @_;

  $clients{$client}->{wbuf} .= $msg;
}

All this does is append to the write buffer. There is already a write watcher associated with this client socket, which will consume the write buffer when it's scheduled to run by the event loop.


sub write_data {
  my ($client) = @_;

  # Nothing in the write buffer?
  return unless $clients{$client}->{wbuf};

  my $bytes = syswrite $client, $clients{$client}->{wbuf}, SYSWRITE_MAX;

  if ( not defined $bytes ) {
    if ( ( $! == EAGAIN ) or ( $! == EWOULDBLOCK ) ) {
      return;
    }
    elsif ( $! == EPIPE ) {
      disconnect($client);
      return;
    }
  }
  else {
    # $bytes were successfully sent to the client, so we can remove it from
    # the write buffer.
    substr( $clients{$client}->{wbuf}, 0, $bytes ) = '';
    printf "Wrote %d bytes to client %s\n", $bytes, $client->peerhost;
  }
}

All this code does is attempt to write the contents of the write buffer to the client socket. When a chunk of data has been written successfully to the socket, the write buffer is trimmed of that data.


sub disconnect {
  my ($client) = @_;

  printf "Client %s disconnected\n", $client->peerhost;
  delete $clients{$client};
  $client->close;
}

And this function hasn't changed at all.

That works pretty damn well and we've kept the memory usage per connection as consistent as possible.

More Issues to Consider

We've got the basics down, but there's more (there's always more).

Lingering

All we've dealt with here is an asynchronous echo server. Asynchronous client code has its own issues.

In the code above, a call to push_write() simply appends data to the write buffer, but that doesn't mean that it's been successfully written to the socket yet. So, if in a client application, we wanted to now disconnect from the server after a bunch of calls to push_write, we don't want to close the connection to the server until the write buffer has been completely flushed. One solution to this is to introduce lingering, as it's called in the TCP world (see the SO_LINGER socket option).

Lingering in this case means that, for a number of seconds, the connection will hang around attempting to flush the write buffer before closing the connection. It's a simple idea that adds more complexity to our code.

Buffer Sizes

Another potential issue, when on a very slow network for example, is that the buffer sizes may grow out of control, so they may need to be capped.

TLS, Corking, Delays and More

The list goes on...

There are many socket options and behaviours that you may want to utilise depending on the nature of your application, so support for these options would need to be included.

Factoring Everything Out

The thing that's clear from the code above is that most of it is handling generic asynchronous programming issues, and only a tiny amount of it is actually specific to the functionality of an echo server.

The first time I used AnyEvent::Handle, I didn't understand why it was designed the way it was, but when the time came to implement many of these features in a proprietary event loop framework where I couldn't use AnyEvent, I soon realised I was reinventing the wheel and was finally able to appreciate AnyEvent::Handle's design.

Just for reference, here's what the echo server looks like when written with AnyEvent::Socket and AnyEvent::Handle.


#!/usr/bin/env perl

use warnings;
use strict;

use AnyEvent;
use AnyEvent::Socket;
use AnyEvent::Handle;

print "Listening on port 5000...\n";

tcp_server undef, 5000, sub {
  my ( $fh, $host, $port ) = @_;

  printf "Client connection from %s\n", $host;

  my $hdl;
  my $disconnect_f = sub {
    printf "Client %s disconnected\n", $host;
    $hdl->destroy;
  };

  $hdl = AnyEvent::Handle->new(
    fh       => $fh,
    on_eof   => $disconnect_f,
    on_error => $disconnect_f,
    on_read  => sub {
      $hdl->push_read( line => sub {
        my ( $hdl, $line ) = @_;
        printf "Read from %s: %s\n", $host, $line;
        $hdl->push_write("$line\n");
      } );
    }
  );
};

AE::cv->recv;

So, like most things, I guess the takeaway here is: don't reinvent the wheel if you don't have to, and if you do, learn from those who came before you.

Friday, May 13, 2016

Perl/XS Hello World

The number one thing in Perl I've always found confusing is writing an XS extension. I don't write them very often, but when I do, I completely forget how to get started and I end up copying and pasting something I wrote for a previous project, and then I've got a bunch of extra files that I'm not sure I need, and if it turns out that I do need them, I'm not even sure what they're for. So I'm writing this as much as a future reference for myself and as something to help others.

For a bare-bones "hello world" XS extension, we'll need four files:

  • HelloWorld.xs (contains the XS code)
  • lib/HelloWorld.pm (the package, which ends up being the glue between the driver script and the XS code)
  • Makefile.PL (to build the module)
  • bin/driver.pl (a test driver script)

An older method for generating these files (and more) was to use the h2xs utility. I prefer to not use h2xs if possible, purely because it generates a lot more cruft than we need at the moment and also because doing it without h2xs means we know precisely what files we're creating and, more importantly, why. Having said that, later on, testing the various h2xs options can help solve problems in our XS stuff, if we get stuck and can't find any documentation for our problem.

HelloWorld.xs will contain one function (referred to as an XSUB) that simply prints some text to stdout.


#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <stdio.h>

MODULE = HelloWorld    PACKAGE = HelloWorld::handle

void
hello()
  CODE:
    printf("Hello, world!\n");

In this simple example, we're exposing one XSUB, hello(), to the Perl world, which will be available via the HelloWorld::handle package.

The resulting C code can be generated by running xsubpp over the file. Running xsubpp will generate a ton of code that won't make sense, but it can be interesting to see just how much code is generated for such a simple module.

Looking at the code, it looks like C with some extra stuff tacked on. That extra stuff is the XS stuff. Any code that precedes the MODULE directive is purely C code. In this top section, we can write whatever C functions we want, and they can be referenced below in the "XS stuff". It's important to realise that any C functions you write at the top of the file are not automatically exposed as XSUBs. To do that, you'd have to write a corresponding XSUB further down (and there's some nice shorthand for that).

A common question at this point is "what's the difference between MODULE and PACKAGE?" A MODULE is a way to group multiple XS extensions together under different PACKAGE names. For example, we may write a ton of HTTP XS libraries under the MODULE HTTP::XS but split up code into a PACKAGE named HTTP::XS::HTTP1_0 and another named HTTP::XS::HTTP1_1 and some other packages to deal with TLS, proxies, authentication, etc...

The name of your Perl package doesn't need to be the same as the name of your XS module either, so, if we really wanted to, we could have the Perl package FooBar, in lib/FooBar.pm, load the HelloWorld.xs extension.

Moving on, now HelloWorld.pm needs to tell Perl how to load the extension.


package HelloWorld;

use warnings;
use strict;

our $VERSION = '0.01';

require XSLoader;
XSLoader::load('HelloWorld');

sub say_hello {
  my ($self) = @_;
  HelloWorld::handle::hello();
}

1;

As per the XSLoader docs, XSLoader is a simplified version of DynaLoader. Use XSLoader. XSLoader works well.

The say_hello() function wraps the hello() XSUB from our XS module. The benefit of adding this extra layer (as opposed to having the client code directly call our XSUB), is so the module developer (us) can add something extra (like checking argument values/types with a type system like Type::Tiny) without changing the interface to the XS extension and without adding any unnecessary complexity to the XSUB.

The next step is to build the extension. We use Makefile.PL for this (or Build.PL if you prefer Module::Build).


use 5.008009;
use ExtUtils::MakeMaker;

WriteMakefile(
  NAME         => "HelloWorld",
  VERSION_FROM => "lib/HelloWorld.pm",
);

There's nothing magical going on; it's a pretty stock-standard Makefile.PL. If we wanted to reference any external libraries or if we wanted to use g++, llvm or clang to build our extension, the docs give a few hints how to do that.

We now have enough pieces in place to build the module.


$ perl Makefile.PL
Generating a Unix-style Makefile
Writing Makefile for HelloWorld
Writing MYMETA.yml and MYMETA.json
$ make
cp lib/HelloWorld.pm blib/lib/HelloWorld.pm

 ... removed for brevity ...

At this point a ton of extra files have been generated, in particular the blib directory. This is the "build library" directory and it's the staging area for everything that is to be tested and, finally, installed onto the machine.

Since we don't want to install the module yet, and we just want the driver script to use what's in the blib directory, the driver scripts needs to make sure it retrieves its definition of the HelloWorld package from this directory and not a version that may already be pre-installed on the machine...


#!/usr/bin/env perl

use warnings;
use strict;

use ExtUtils::testlib;
use HelloWorld;

HelloWorld->say_hello();

... and that's what ExtUtils::testlib handles for us, by manipulating @INC to include the blib directory. Once our module is installed, using ExtUtils::testlib would be unnecessary. Apart from that, the driver script is insanely simple.


$ perl bin/driver.pl
Hello, world!

Huzzah!

So what's next? What if I want to write my XSUBs in C++? What if I want to interface with some other C/C++ library? How do I return a list or a hash from my XSUB? How do I pass a list or a hash into my XSUB? That all kinda goes beyond the scope of this post, and I may follow up with another post eventually, but until then here's some useful links:

  1. XS Fun. Sawyer X's XS tutorial. I actually found this after I'd pretty much finished writing this blog post. Definitely the most useful resource to read next before really getting into the perldocs.
  2. perlxs. Includes documentation on all of the XS keywords.
  3. perlguts. The section on variables is very useful.
  4. perlapi. The Perl API. Contains a bunch of functions and macros.

Friday, October 30, 2015

Perl Hack: Modifying a List

One of my favorite Perl hacks, shown to me by a work colleague a few years ago, is for modifying a list. The hack is actually fairly pretty:

s/^ +//g, s/ +$//g for @list;

For very simple substitutions, I think this is more expressive and easier to understand than the usual foreach loop, or a map.

Friday, May 22, 2015

First Steps with AnyEvent

Nearly 10 years ago, while I was at university, I was first introduced to event-driven programming with Twisted, an event-driven framework for Python. At the time, I was thoroughly confused by the whole thing and didn't really get it (Not allowed to block? But I want my user input now, dammit!). I persisted for a while, but ultimately gave up.

The second time around it made a lot more sense and, since then, I've been exposed to libev (via the EV Perl module) and POE. It's a powerful programming paradigm, and super worthy of being a part of any programmer's toolbox. And this is where AnyEvent can be of service.

AnyEvent isn't exactly an event framework itself, but it can sit on top of other event frameworks; the self-described "DBI of event loop programming". So, because we have a ton of legacy EV code at work, I can write AnyEvent code alongside it, and AnyEvent will integrate with EV's event loop and everything will just work. This a huge benefit and an obvious reason why adopting AnyEvent into an existing system can be a good idea.

Experimenting at Home

I wrote some code to hit my wireless internet router's web server a bunch of times and it wasn't long before I came across my first issue with AnyEvent::HTTP; my router (by Huawei, so take what you will from that) only accepts the content length header in exactly the form 'Content-Length'. However, AnyEvent::HTTP issues 'Content-length' (note lowercased 'L') and my router issues a 404 response for this. Diving into the AnyEvent::HTTP source, I found that there's no way to override this behaviour, as AnyEvent::HTTP lowercases all headers you pass in (plus a couple of default ones) and then ucfirst's them on output.

So what was my workaround for this? To use AnyEvent::Handle and HTTP::Request to talk HTTP. I could have used AnyEvent::Socket alongside AnyEvent::Handle, but AnyEvent::Handle can make TCP connections aswell, which is handy.

That worked! Making many many many HTTP requests? AnyEvent handles it like a champ. That's great!

But the server didn't handle the number of connections very well, so there were lots of failed and closed connections. That's bad.

My first instinct was to write code to retry a connection if it failed for any reason. This worked for a while, but eventually EV (the event backend that AnyEvent had chosen to use, since it was installed) would die with a critical error. That's also bad.

In the end, the solution was to throttle the number of connections I was making, and everything started working again. Great!

How do you throttle? Something like this:


use AnyEvent;
use AnyEvent::HTTP;

my $cv = AE::cv;
my $num_connections = 0;

my $w; $w = AE::io \*DATA, 0, sub {
    return if $num_connections >= 2;

    my $url = <DATA>;

    unless (defined $url) {
        AE::log info => "Finished reading DATA";
        undef $w;
        return;
    }

    chomp $url;
    AE::log info => "Trying $url";

    $num_connections += 1;
    $cv->begin;

    my $hw; $hw = http_head $url, sub {
        my ($data, $headers) = @_;
        AE::log info => "Result from $url: $headers->{Status}";

        $num_connections -= 1;
        $cv->end;
        undef $hw;
    };

};

$cv->recv;

__DATA__
http://google.com
http://reddit.com
http://facebook.com
http://twitter.com
http://gmail.com

Closing Thoughts

I've been wanting to use AnyEvent for something work-related recently, but I want to experiment a little more before I go down that rabbit hole, or perhaps just use it in something non-critical.

Condition variables are great!

I've heard that IO::Async (with futures) is another great way to go as far as Perl asynchronous programming frameworks go, so I might experiment with that some time soon, but for now I'll stick with AnyEvent.

Wednesday, August 27, 2014

Moo and Types

Wow. I haven't posted anything on here about programming in what seems like forever. Well, to be honest, I haven't been doing that much programming outside of work that's been worth blogging about, but now here we are!

For a number of years now, the post-modern object systems have been making a lot of head-way in the Perl community. We use Mouse at work, and in my own little projects, however small they may be, I've been using Moo. Why? Quicker startup than Mouse (and of course Moose), has the features that I really care about and use most in Mouse and doesn't install half of CPAN along with it.

For a first, testing script, I generally write classes to model people. And of course, people have a date of birth, which can be neatly represented with a DateTime object. But no Moo-compatible DateTime type existed out of the box (that may have changed now, but not at the time). Brilliant! So now I got to write my own Moo type, which would be nice as an introduction to the library, as writing your own types is something you're inevitably going to do.

First I found MooX::Types::MooseLike, which pretty much did what I wanted. So I wrote MooX::Types::MooseLike::DateTime (and released it only recently despite the fact that I'm blogging about a better method). The backend code was a little verbose and coercions had to be done in the type declaration. But whatever, it worked. So my test script continued on.

And then I found Type::Tiny. And it's compatible with Moose and Mouse aswell as Moo! The backend code is quite compact and coercions can be easily implemented.

Here's my type declaration.

package MyTypes;

use Type::Library -base, -declare => qw/DateTime/;
use Types::Standard qw/Str Int Object/;
use Type::Utils qw/class_type coerce from via/;

use DateTime::Format::Strptime;

class_type DateTime => { class => 'DateTime' };

coerce 'DateTime',
 from Int, via { DateTime->class->from_epoch(epoch => $_) },
 from Str, via { DateTime::Format::Strptime->new(pattern => '%F %T')->parse_datetime($_) };

1;

Something I really like here is that I can declare a DateTime type and access the underlying DateTime class with 'DateTime->class', so there's no need to import the original DateTime class, or alias or quote it so it doesn't clash with my type. And the coercions read very easily.

Here's the Person class that utilises the type.

package Person;

use Moo;
use MyTypes qw/DateTime/;

has dob => (
 is      => 'rw',
 isa     => DateTime,
 coerce  => DateTime->coercion,
 default => sub { DateTime->class->today }
);

1;

If I wanted to use Moose or Mouse instead, I could change the use statement at the top, and then change the coerce parameter to a value of '1'. EDIT: With Moo 1.006000 you can do the same with Moo now ;)

And here's the driver script.

#!/usr/bin/env perl

use warnings;
use strict;

use Person;
use DateTime;

print Person->new(dob => time)->dob->iso8601, "\n";
print Person->new(dob => '2013-01-01 13:37:00')->dob->iso8601, "\n";
print Person->new(dob => DateTime->today)->dob->iso8601, "\n";
print Person->new->dob->iso8601, "\n";

Simple and versatile.

Monday, January 2, 2012

How I Deal with Lots of Data

Just for some context, by "lots of data", I mean a couple hundred million rows in a table joined to other tables of similar size. By no means is it a lot compared to what other people deal with, but it's certainly enough to warrant some forethought instead of just diving right in.

Late last year, I was tasked with a few one-off reports that required me to summarise data that was stored over a couple hundred million rows in a database. Knowing that it was going to be a long process to retrieve the data for these reports, I had to come up with a game plan to do this as quickly and as efficiently as possible. In the process of doing this, as always when handling relatively large quantities of data, a few things were learned. None of these lessons/ideas are new or original, but I wanted to write them down somewhere, and here feels like as good a place as any.

Extracting the data that I needed (which, in one instance, was a subset of a couple of ~300 million row tables) into a local database meant I was able to modify the data (like cleaning up dirty, inconsistent spellings of suburbs, states and countries) and modify the schema (like adding new indexes which, because of the odd nature of the reports, the production databases didn't have or ever need previously).

Assuming that you don't require something like schema changes, that local database doesn't even need to be a relational database. CSV files work perfectly fine a lot of the time. For a couple of reports I wrote a handful of scripts, the first of which was to pull the data out of the MySQL database, perform some simple operations on the data and output it into a CSV file. The other scripts that needed to operate on the same data could then easily (thanks to Text::CSV_XS) read the CSV data, which was a lot quicker than reading it from a relational database.

Why do CSVs lend themselves nicely to this kind of task? Because with reports like these, in my experience, you very rarely perform complicated operations on the data after extracting it from the original data source(s); you just want to suck the data up, summarise the data, output the summary, and then output the nitty gritty details on subsequent pages or into a separate file.

An obvious advantage to storing the data like this is the speed in which you can retrieve and process the data. That improvement made a huge difference for me because I like to run my scripts very often throughout the development process, no matter how small the change.

Of course, depending on the size of the data (in bytes), extracting the data into a local database of some sort may not always be possible.

The last big win I had was not using object-relational mappers (Class::DBI in this case). They're great a lot of the time and save on code and development time, but when dealing with millions of rows, they just add bloat and everything runs much slower than it should.

That's all I can think of now, a few months later.

Tuesday, June 28, 2011

Crappy IRC and Unicode

I use MacIrssi as my IRC client at work and at home. It's mostly great. By mostly, I mean, I wish when people sent smart-ass messages on IRC filled with unicode characters, I could actually appreciate how much of a smart-ass they are being. Instead, all I see is this:

< ganeshanator> gonna go to the \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
    and \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
    getting those \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
    and the \u2588\u2588\u2588\u2588\u2588
    on \u2588\u2588\u2588\u2588\u2588

I always forget how to convert it, so I wrote this, and now I never have to remember again:

#!/usr/bin/env perl

use warnings;
use strict;

use Encode;

sub unicode_plz { encode( 'UTF-8', pack( 'U', hex shift ) ) }

( my $message = shift ) =~ s{\\u([a-fA-F0-9]+)}{unicode_plz($1)}ge;
print "$message\n";

And, BAM!

Crappy was a harsh word. MacIrssi is pretty great, except for this.

Thursday, March 31, 2011

DateTime Woes

This morning, I hate DateTime for this.

$ perl -MDateTime -wle'$a = DateTime->today( time_zone => "Australia/Melbourne" ); $b = DateTime->today; $b->set_time_zone("Australia/Melbourne"); print $a->iso8601; print $b->iso8601'
2011-03-31T00:00:00
2011-03-30T11:00:00

I understand why it happens, but it's still an annoying, pissy, little bug to find. sigh...