Hoy en día, las aplicaciones que tienen un contacto directo con el usuario, por ejemplo una aplicación web o una aplicación movil requieren una latencia muy baja y un rendimiento muy alto, un usuario puede perder el interés si una aplicación tarda 2 o 3 segundos en responder.
Ejecutar una consulta en una base de datos con múltiples joins o consultar una API externa puede ser costoso, es entonces cuando es muy beneficioso usar caché, para responder de manera óptima al cliente.
Este es un ejemplo de como usar AWS ElastiCache a través de una Lambda en una red privada
Caso de uso
El caso de uso se compone de las siguientes características
- Cluster de memcached en una red privada
- Multi AZ deployment para garantizar alta disponilidad y tolerancia a fallos
- Ejemplo de una lambda que guarda y recupera datos del cluster de memcached
- La lambda con acceso a Internet para consultar una API externa
Componentes de AWS
Antes de comenzar con la solución es importante mencionar todos los servicios de AWS que se usarán en conjunto
- VPC: Red privada en AWS compuesta por 2 subnets privadas y 2 subnets públicas
- Internet Gateway: Gateway redundante y de alta dispobilidad que permite la comunicación entre Internet y la VPC
- NAT Gateway: Gateway que permite a subnets privadas conectarse a Internet, pero no permite que desde Internet se alcance la red privada.
- ElastiCache: Almacenamiento en memoria, para este caso usaremos el motor de memcached
- Lambda: Cómputo sin servidor ejecutándose en una red privada para poder acceder al cluster de memcached
Aquí unos detalles importantes a mencionar
- Se tienen dos subnets privadas y dos subnets públicas, dos de cada una por que una subnet no puede pertenecer a más de un availability zone y para garantizar la alta disponibilidad se necesitan al menos dos AZ
- Solo se tiene una Internet Gateway por que por default, AWS garantiza la alta disponiblidad de este servicio
- Se tiene una NAT Gateway por cada AZ por que este servicio solo tiene alta disponiblidad dentro de la AZ, si solo tuvieramos una NAT Gateway y hubiera algún problema con esa AZ, perderíamos totalmente acceso a Internet, en cambio como se tienen dos, se garantiza la alta disponibilidad de toda la solución
- La definición del cluster de memcached es MultiAZ con dos nodos, de esta manera se garantiza la alta disponiblidad
Implementación
Todos los recursos los podemos definir a través de Cloud Formation y para poder hacer deployment, para facilitar el ejemplo, podemos usar Serverless Framework
Todo el código está en GitHub, aquí algunos fragmentos que ayudan a explicar el ejemplo
La primera dependencia de un cluster de elasticache es un subnet group en donde se definen las subnets que se usarán, en este caso, para garantizar la seguridad, se usan las subnets privadas
MemcachedSubnetGroup:
Type: AWS::ElastiCache::SubnetGroup
Properties:
CacheSubnetGroupName: MemcachedSubnetGroup
Description: Memcached subnet group
SubnetIds:
- Ref: PrivateSubnetA
- Ref: PrivateSubnetB
La segunda dependencia de un cluster de elasticache es el security group que usará el cluster, como buena práctica debemos permitir únicamente acceso al cluster desde las ips privadas de la subnet. Es decir, solamente un recurso dentro de la red privada podrá acceder al cluster, en este caso, las instancias de las lambdas
SecurityGroupMemcached:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group Memcached
GroupName: SecurityGroupMemcached
VpcId:
Ref: VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 11211
ToPort: 11211
CidrIp: 10.0.0.128/26
Description: Private subnet A
- IpProtocol: tcp
FromPort: 11211
ToPort: 11211
CidrIp: 10.0.0.192/26
Description: Private subnet B
Finalmente, la definición del cluster
Memcached:
Type: AWS::ElastiCache::CacheCluster
Properties:
AZMode: cross-az
CacheNodeType: cache.t2.small
CacheSubnetGroupName:
Ref: MemcachedSubnetGroup
ClusterName: MemcachedCluster
Engine: memcached
NumCacheNodes: 2
Tags:
- Key: Name
Value: MemcachedCluster
VpcSecurityGroupIds:
- Ref: SecurityGroupMemcached
Es momento de hablar de la lambda, esta lambda se debe de instanciar dentro de la VPC en las subnets privadas, para lograr esto, lo definiremos a través de Serverlesss Framework
functions:
memcached:
handler: index.handler
vpc:
subnetIds:
- Ref: PrivateSubnetA
- Ref: PrivateSubnetB
securityGroupIds:
- Fn::GetAtt: [ VPC, DefaultSecurityGroup ]
environment:
memcachedUrl: !GetAtt 'Memcached.ConfigurationEndpoint.Address'
La variable de ambiente memcachedUrl se usa dentro de la lambda para poder conectarse al cluster, ahora veamos el código de la lambda, se usa Javascript
const memjs = require('memjs');
const axios = require('axios').default;
const getMemcachedURL = () => process.env.memcachedUrl;
const url = 'https://dummy.restapiexample.com/api/v1/employees';
const memcachedKey = 'employees';
module.exports.handler = async(event) => {
const client = memjs.Client.create(getMemcachedURL());
const employees = await client.get(memcachedKey);
let response;
if (employees.value !== null) {
response = JSON.parse(employees.value.toString());
} else {
const axiosResponse = await axios.get(url, {
headers: {
Accept: 'application/json'
}
});
await client.set(memcachedKey, JSON.stringify(axiosResponse.data));
response = axiosResponse.data;
}
client.quit();
return response;
};
Como estrategia de caching, se usa Lazy loading, es decir, como primer paso se verifica si los datos existen en caché, si existe, se devuelve el dato, si no existe, se obtiene de la fuente y se agrega a caché. A pesar de que la lambda está dentro de una red privada, puede acceder a internet a través de la NAT Gateway!
Despliegue
Ahora, es momento de instalar la solución en AWS, ejecutemos lo siguente
npm install
sls deploy
Esperemos algunos minutos debido a que la primera vez se crean todos los recursos, VPC, subnets, cluster, etc. y ahora, veamos que sucede si probamos la función
sls invoke --function memcached --log
La primera vez veremos una respuesta lenta, entre 0.5 y 1 segundo
START RequestId: ffba5783-0861-432a-8897-b506926a8e83 Version: $LATEST
END RequestId: ffba5783-0861-432a-8897-b506926a8e83
REPORT RequestId: ffba5783-0861-432a-8897-b506926a8e83 Duration: 690.68 ms
Segunda ejecución, entre 5 y 10 milisegundos! Magia
START RequestId: c37b5913-7b96-49d7-a721-88c35f164db3 Version: $LATEST
END RequestId: c37b5913-7b96-49d7-a721-88c35f164db3
REPORT RequestId: c37b5913-7b96-49d7-a721-88c35f164db3 Duration: 6.28 ms
Top comments (0)