How to Properly Use Serverless Technologies and AWS Lambda

I recently read, and shared with the team, an article on using serverless technologies for a web API.
In short, the author researched whether to move his API from its existing setup to serverless. He found that Lambda was a little slower than an EC2 instance, but the main problem was the price, and specifically the price of API Gateway rather than Lambda itself.
I liked his research and his approach. It also reminded us of a case where Lambda worked well for us.
Lambda functions are a good fit for small, independent functions, as the name suggests, and especially for calling many different APIs or fetching data from many web pages. You can invoke as many Lambda functions in parallel as you need, and they scale without changes to the infrastructure code, up to your account’s concurrency limit. By default, AWS sets that limit at 1,000 concurrent executions across all functions in a Region.
Lambda does not start a separate server with its own IP address for each call: execution environments are reused between invocations (warm starts). AWS also publishes its IP address ranges, so websites can easily identify and block traffic that comes from AWS.
Example
In this example, we fetch the latest exchange rate between two cryptocurrencies from several exchanges in parallel.
We’ll use CCXT for that. It supports more than 100 cryptocurrency exchanges and has a Python implementation.
First, create a simple Lambda function that retrieves data for the specified tickers from the specified exchange.
import json
import ccxt
def lambda_handler(event, context):
id = event.get('exchanges')
exchange_found = id in ccxt.exchanges
output = []
if exchange_found:
exchange = getattr(ccxt, id)()
for symbol in event.get('symbols'):
ticker = exchange.fetch_ticker(symbol.upper())
output.append({'exchange': id, 'symbol': symbol, 'ticker': ticker})
return {'statusCode': 200, 'body': json.dumps(output)}
return {
'statusCode': 400,
'body': json.dumps(f'Exchange {id} not found')
}Once the Lambda function is ready, the next step is to run it. We retrieve data from many sources, and it has to be fast because rates change quickly, so we make the code asynchronous and invoke the Lambda functions concurrently.
To invoke AWS Lambda from Python, you use Boto3, the AWS SDK for Python. Here we use aioboto3, a third-party async wrapper around boto3 and aiobotocore.
client = aioboto3.client(
'lambda',
region_name='[YOUR_LAMBDA_REGION]',
aws_access_key_id='[YOUR_AWS_KEY_ID]',
aws_secret_access_key='[YOUR_AWS_KEY]'
)
response = await client.invoke(
FunctionName='[YOUR_LAMBDA_FUNCTION_NAME_OR_ARN]',
InvocationType='RequestResponse',
LogType='Tail',
Payload=json.dumps([PAYLOAD]),
)Here is a simple example of invoking a Lambda function; see the Boto3 docs for more details.
And here is the runner I ended up with:
import ast
import asyncio
import json
import aioboto3
class LambdaInvoker:
def __init__(self):
self.client = aioboto3.client(
'lambda',
region_name='[YOUR_LAMBDA_REGION]',
aws_access_key_id='[YOUR_AWS_KEY_ID]',
aws_secret_access_key='[YOUR_AWS_KEY]'
)
async def invoke(self, payloads):
results = await asyncio.gather(*map(self.__invoke_lambda, payloads))
return results
async def __invoke_lambda(self, payload):
print(f'Invoking {payload["exchanges"]}')
response = await self.client.invoke(
FunctionName='[YOUR_LAMBDA_FUNCTION_NAME_OR_ARN]',
InvocationType='RequestResponse',
LogType='Tail',
Payload=json.dumps(payload),
)
result = await response['Payload'].read()
print(f'Finished {payload["exchanges"]}')
return result
async def close(self):
await self.client.close()
@staticmethod
def print_result(result):
data_dict = ast.literal_eval(result.decode('utf-8'))
body_json = data_dict.get("body")
if body_json:
body = json.loads(body_json)
print(json.dumps(body, indent=4))
else:
print(json.dumps(data_dict, indent=4))
if __name__ == '__main__':
payloads = [
{'exchanges': 'binance', 'symbols': ['BTC/USDT', 'ETH/BTC']},
{'exchanges': 'cex', 'symbols': ['BTC/USD', 'ETH/BTC']},
{'exchanges': 'bitfinex', 'symbols': ['BTC/USDT', 'ETH/BTC']},
{'exchanges': 'kraken', 'symbols': ['BTC/USD', 'ETH/BTC']},
{'exchanges': 'poloniex', 'symbols': ['BTC/USDT', 'ETH/BTC']}
]
lambda_invoker = LambdaInvoker()
loop = asyncio.get_event_loop()
results = loop.run_until_complete(lambda_invoker.invoke(payloads))
loop.run_until_complete(lambda_invoker.close())
for result in results:
LambdaInvoker.print_result(result)The main logic is in the __invoke_lambda and invoke methods.
__invoke_lambda uses the same code as above to run a Lambda function and get its response. It is an asynchronous method.
invoke maps the __invoke_lambda coroutine to each payload, and asyncio.gather runs them concurrently in one thread, which works well for network calls like these.
Bonus
If you don’t want to write asynchronous code, here is the same using Python’s multiprocessing module:
import json
import multiprocessing as mp
import boto3
def run(payloads):
pool = mp.Pool(len(payloads))
return pool.map(invoke_lambda, payloads)
def invoke_lambda(payload):
print(f'Invoking {payload["exchanges"]}')
response = client.invoke(
FunctionName='arn:aws:lambda:eu-central-1:557458395923:function:ccxt-exchange',
InvocationType='RequestResponse',
LogType='Tail',
Payload=json.dumps(payload),
)
result = response['Payload'].read()
print(f'Finished {payload["exchanges"]}')
return resultShare and subscribe to our blog
How can we help you ?






