Serverless Ruby with Split
AWS lambdas were first launched way back in November of 2014. Today, you can create serverless functions in a variety of languages, including Ruby 2.7
I have a lot of familiarity with Java and node.js lambdas, but had never deployed a Ruby lambda until now. This blog catalogs the steps necessary to not just deploy a lambda, but include a high performance feature flag using Split.
Start working this blog with some time on your hands. I hope it will take you no more than a few hours, but I spent a lot more than that blazing the trail. If you don’t know Ruby, add some more time. If you don’t know Split, this isn’t the right place to start.
Background notes
I did not use the AWS command line client, and it might have been easier if I did. I went down the installation rabbit hole and lost my nerve several levels deep. The main consequence of doing things manually is the docker step for bundling gems and the manual upload process. If I were setting up for a production shop, I would figure out how to get the AWS client working. For this blog, we go without.
I work on an Apple laptop running OSX (Monterery). I have not tested these instructions on
Get started
Create a clean directory. Make sure you’re running the right version of Ruby. The AWS lambda printed out that it was using 2.7.5, so I used rvm to install exactly that version.
David-M-MacBook-Pro-NEW:lambda davidmartin$ ruby --version
ruby 2.7.5p203 (2021-11-24 revision f69aeb8314) [x86_64-darwin21]
Create Gemfile
source 'https://rubygems.org'
gem 'splitclient-rb', '~> 7.3.4'
That’s all I needed to get Split included. If your program needs more gems, include them.
Create ruby-split.rb
The script I used has some helpful debugging at the top.
print "CWD: " + Dir.pwd + "\n"
print "RUBY VERSION: " + RUBY_VERSION + "\n"
load_paths = Dir[
"/var/task/vendor/bundle/ruby/2.7.0/gems/**/lib"
]
$LOAD_PATH.unshift(*load_paths)
$LOAD_PATH.each { |x| puts x }
require 'json'
require 'splitclient-rb'
options = {
connection_timeout: 10,
read_timeout: 5,
cache_adapter: :redis,
mode: :consumer,
redis_url: 'redis://your-redis-host.amazonaws.com:6379/0',
redis_namespace: "demo-prod",
logger: Logger.new('/tmp/split.log'),
debug_enabled: true
}
split_factory = SplitIoClient::SplitFactory.new('your split sdk server key', options)
$split_client = split_factory.client
begin
$split_client.block_until_ready
rescue SplitIoClient::SDKBlockerTimeoutExpiredException
puts 'SDK is not ready. Decide whether to continue or abort execution'
end
def lambda_handler(event:, context:)
puts "Hello Split World!"
result = $split_client.get_treatment_with_config('dmartin-ruby', 'multivariant_demo')
configs = JSON.parse(result[:config])
treatment = result[:treatment]
configs[:treatment] = treatment
{ statusCode: 200, body: configs }
end
# CDN configuration
# options = {connection_timeout: 10,
# read_timeout: 5,
# features_refresh_rate: 5,
# segments_refresh_rate: 120,
# metrics_refresh_rate: 360,
# impressions_refresh_rate: 1,
# logger: Logger.new('/tmp/split.log'),
# debug_enabled: true }
The script expects that you have set up the Synchronizer/Redis optimization for peak performance of the lambda (that’s the redis host mentioned). For testing, you can swap the options commented out at the bottom with the redis options, but the performance isn’t good enough for most production uses.
You’ll also need to get the Split server SDK key, either from the Syntax generator for your split (menu next to KILL button), or in the Admin Settings.
Finally, create a split with a dynamic config. It doesn’t matter what JSON or key-value pairs you load. My multivariant split is called “multivariant_demo”
Install the Bundle using Docker
This step is necessary to put the third party code into the right format for AWS. I tried bundling dozens of times and didn’t get a good result until I used this approach.
I used this Ruby .bundle/config
---
BUNDLE_PATH: "vendor/bundle"
BUNDLE_DEPLOYMENT: "true"
BUNDLE_WITHOUT: "development:test"
And then I ran the following Docker command. Lambci is a sandboxed AWS Lambda environment that can be run locally. It finally put all the third party code into just the right spot.
docker run --rm -v "$PWD":/var/task lambci/lambda:build-ruby2.7 bundle install --deployment
Yep, you have to have Docker installed to run this command.
... many lines above ...
Fetching splitclient-rb 7.3.4
Installing splitclient-rb 7.3.4 with native extensions
Bundle complete! 1 Gemfile dependency, 25 gems now installed.
Gems in the groups development and test were not installed.
Bundled gems are installed into `./vendor/bundle`
Create a ZIP for Upload
zip -r ruby-split.zip ruby-split.rb vendor
Create a Ruby 2.7 lambda
Once created…
- upload your ZIP file
- update your configuration to use VPC that can see your Redis host (unless you decided to use the CDN)
- change your code runtime settings to hit the right handler
ruby-split.lambda_handler
… or whatever you named your Ruby script.
Test for Success
{
"statusCode": 200,
"body": {
"text": "Adopt a Dog",
"image": "http://www.cortazar-split.com/dog_origin.jpeg",
"image_width": "100%"
}
}
### Summary
Code SHA-256
9YUyQR/lSkO8v003jjb0pfDsz+iXkgZSnoqwtuehG04=
Request ID
ed590a7b-d4d9-4f9a-a5ed-856bde47a4d8
Duration
29.20 ms
Billed duration
30 ms
Resources configured
128 MB
Max memory used
65 MB
Troubleshooting
This was an advanced move for me, so I can easily understand if you have trouble along the way.
- Use the Cloudwatch log
Add logging of your own as necessary.
- Stackoverflow does have good advice, sometimes buried
The docker command to get the right bundle install was off a Stackoverflow answer.
- Contact me, david.martin@split.io
I am happy to help any way I can.
Cheers!
David
Leave a comment