2015年10月28日水曜日

PHP with Amazon DynamoDB

This blog entry aim to construct the good connectivity performance from PHP to Amazon DynamoDB.

Make DynamoDB table
Table Name: ProductCatalog
Primary Hash Key: Id (String)
Provisioned Read Capacity Units: 10000
Provisioned Write Capacity Units: 10000
Region: Asia Pacific (Tokyo)

Launch EC2 Instance
AMI ID: Amazon Linux AMI 2015.09 (HVM), SSD Volume Type - ami-9a2fb89a
Instance type: c4.xlarge

Install PHP and AWS SDK for PHP
Install PHP5.6
# yum -y install php56 php56-devel php56-fpm php56-opcache gcc
Make app dir and install new version of aws-sdk-php by Composer.
# mkdir ~/sample_app; cd ~/sample_app
# curl -sS https://getcomposer.org/installer | php
# php composer.phar require aws/aws-sdk-php
# php composer.phar require doctrine/cache
# php composer.phar install --optimize-autoloader
# yum -y install php-pear
# pecl install uri_template
# echo "extension=uri_template.so" >> /etc/php.ini
# php composer.phar show --installed | grep aws/aws-sdk-php
aws/aws-sdk-php        3.9.1 AWS SDK for PHP - Use Amazon Web Services in your PHP project
Make AWS credentials file.
# mkdir ~/.aws/
# emacs ~/.aws/credentials
[default]
aws_access_key_id = YOUR_AWS_ACCESS_KEY_ID
aws_secret_access_key = YOUR_AWS_SECRET_ACCESS_KEY
Make sample put app
# emacs put.php
<?php
require 'vendor/autoload.php';
use Aws\DynamoDb\DynamoDbClient;
$client = DynamoDbClient::factory(array(
    'profile' => 'default',
    'region' => 'ap-northeast-1',
    'version' => '2012-08-10',
    'credentials.cache' => true,
    'validation' => false,
    'scheme' => 'http'
));
$response = $client->putItem(array(
    'TableName' => 'ProductCatalog',
    'Item' => array(
        'Id'       => array('S'      => '104'      ), // Primary Key
        'Title'    => array('S'      => 'Book 104 Title' ),
        'ISBN'     => array('S'      => '111-1111111111' ),
        'Price'    => array('N'      => '25' ),
        'Authors'  => array('SS'  => array('Author1', 'Author2') )
    )
));
Run.
# php put.php

Make sample get app
# emacs get.php
<?php
require 'vendor/autoload.php';
use Aws\DynamoDb\DynamoDbClient;
$client = DynamoDbClient::factory(array(
    'profile' => 'default',
    'region' => 'ap-northeast-1',
    'version' => '2012-08-10',
    'credentials.cache' => true,
    'validation' => false,
    'scheme' => 'http'
));
$response = $client->getItem(array(
    'TableName' => 'ProductCatalog',
    'Key' => array(
 'Id' => array( 'S' => '104' )
    )
));
print_r ($response['Item']);
Run
# php get.php
Array
(
    [ISBN] => Array
        (
            [S] => 111-1111111111
        )

    [Id] => Array
        (
            [S] => 104
        )

    [Authors] => Array
        (
            [SS] => Array
                (
                    [0] => Author1
                    [1] => Author2
                )

        )

    [Price] => Array
        (
            [N] => 25
        )

    [Title] => Array
        (
            [S] => Book 104 Title
        )

)


Benchmark
Setup Nginx.
# yum -y install nginx
# mkdir /var/lib/nginx/.aws/
# cp ~/.aws/credentials /var/lib/nginx/.aws/credentials
# cp -r ~/sample_app/* /usr/share/nginx/html/
# emacs /etc/php-fpm.d/www.conf
user = apache
group = apache
->
user = nginx
group = nginx

# emacs /etc/nginx/nginx.conf
    server {
        include /etc/nginx/default.d/*.conf;
 
# /etc/init.d/nginx start
# /etc/init.d/php-fpm start
# curl 127.0.0.1/get.php -v
Run benchmark.
# ab -c 100 -n 10000 http://127.0.0.1/put.php
Requests per second:    763.44 [#/sec] (mean)

# ab -c 100 -n 10000 http://127.0.0.1/get.php
Requests per second:    832.62 [#/sec] (mean)
Maybe. If I install AWS SDK with ClassPreloader, that will be more faster.
But, ClassPreloader does not work with AWS SDK for PHP version 3.
Please let me know, if you know how to use ClassPreloader.
Next section. I tried a other way.

Benchmark with Go DynamoDB Proxy
Use bbpd.
http://takeshiyako.blogspot.jp/2015/10/go-lang-bbpd-godynamo-http-proxy.html

Make sample apps.
# emacs /usr/share/nginx/html/get_go.php
<?php
$url = "http://localhost:12333/GetItemJSON";
$postdata = "{\"Key\": {\"Id\": {\"S\": \"104\"}}, \"TableName\": \"ProductCatalog\"}";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
$result = curl_exec($ch);
curl_close($ch);
echo $result;

# emacs /usr/share/nginx/html/put_go.php
<?php
$random = substr(str_shuffle('1234567890abcdefghijklmnopqrstuvwxyz'), 0, 36);
$url = "http://localhost:12333/PutItemJSON";
$postdata = "{\"Item\": {\"Id\": \"$random\",\"Title\":\"Book 107 Title\",\"ISBN\":\"111-1111111111\",\"Price\":\"33\",\"Authors\":[\"Author1\", \"Author2\"]},\"TableName\": \"ProductCatalog\"}";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
$result = curl_exec($ch);
curl_close($ch);
echo $result;
Run benchmark.
# ab -c 100 -n 10000 http://127.0.0.1/get_go.php
Requests per second:    4260.64 [#/sec] (mean)

# ab -c 100 -n 10000 http://127.0.0.1/put_go.php
Requests per second:    3753.05 [#/sec] (mean)
It seems this solution is better than AWS SDK for PHP.
NOTE: When update same Id, many RETRY occurs with authreq. result of benchmark is 750 req/s.
if you update many data, please carefully.
https://github.com/smugmug/godynamo/blob/master/authreq/authreq.go#L91

See also
AWS SDK for PHP 3.x
http://docs.aws.amazon.com/aws-sdk-php/v3/api/index.html
Requirements — AWS SDK for PHP documentation
http://docs.aws.amazon.com/aws-sdk-php/v3/guide/getting-started/requirements.html
Performance Guide — AWS SDK for PHP 2.8.22 documentation
http://docs.aws.amazon.com/aws-sdk-php/v2/guide/performance.html
Top image from Felix De Vliegher Enjoying the view