Alphazed esegue l'intero backend — servendo più di 95.000 studenti in oltre 50 Paesi — su AWS Lambda con Serverless Framework. L'architettura utilizza Flask su Lambda dietro API Gateway, MySQL 8 su RDS, S3 per la distribuzione dei contenuti e un data lake di analisi personalizzato (SQS → Kinesis Firehose → S3 → Glue → Athena). Handler Lambda minimalisti ottimizzano la latenza di cold-start e il sistema gestisce più di 7 app da un singolo codice con configurazioni runtime.
Perché il Serverless per l'EdTech?
Le app educative hanno modelli di utilizzo imprevedibili:
- Mattine feriali: i genitori scaricano l’app prima di mandare i figli a scuola (picco di traffico)
- Pomeriggi feriali: sessioni di pratica post-scuola (carico sostenuto)
- Fine settimana: maratone intensive (2-3 volte il carico medio)
- Durante il Ramadan: forte aumento serale (sessioni familiari di Corano)
- Vacanze scolastiche: pattern totalmente diversi
Vantaggi del serverless:
- Prezzi pay-per-request: paghi solo per l’utilizzo effettivo, da 10 a 100.000 richieste scalando istantaneamente
- Zero cold start per endpoint ad alta frequenza: uso di Lambda layers "always warm" per richieste frequenti
- Auto-scaling automatico: da 10 a 10.000 utenti contemporanei senza modifiche all’infrastruttura
- Nessuna manutenzione server: il team si concentra su curriculum e AI, non su Kubernetes o bilanciatori
Dettagli sull'Architettura
API Gateway → Lambda → RDS
[Client App] (iOS, Android, Web)
↓
[API Gateway] (instradamento HTTP, limitazione richieste)
↓
[Lambda Handlers] (app Flask, 512MB RAM, timeout 28s)
├── Rotte app: /app/* (endpoint mobile)
├── Rotte utenti: /user/* (endpoint autenticati)
└── Rotte admin: /boss/* (dashboard amministrazione)
↓
[MySQL 8 su RDS] (dati persistenti)
↓
[Risposta] (JSON al client)
Handler Lambda minimalisti per velocità
La maggior parte dei Lambda sono volutamente leggeri:
# Handler leggero (~100KB)
import json
import pymysql
def get_user_progress(event, context):
user_id = event['pathParameters']['user_id']
# Connessione diretta al DB (senza overhead ORM)
conn = pymysql.connect(host='rds.aws.com', user='app', password='...', database='amal')
cursor = conn.cursor()
cursor.execute(
'SELECT concept_id, accuracy FROM user_memory WHERE user_id = %s',
(user_id,)
)
rows = cursor.fetchall()
conn.close()
return {
'statusCode': 200,
'body': json.dumps([{'concept': r[0], 'accuracy': r[1]} for r in rows])
}
Niente import di Flask, SQLAlchemy o middleware. Risultato: cold start ~500ms contro 5-10s per un’app Flask completa.
Endpoint più pesanti (creazione contenuti, analisi) usano Flask completo:
# Handler pesante (~30MB con Flask, SQLAlchemy, numpy)
from flask import Flask, jsonify
from models import UserMemory
import numpy as np
app = Flask(name)
@app.route('/content_duo/generate', methods=['POST'])
def generate_content_duo():
user = UserMemory.query.filter_by(user_id=request.json['user_id']).first()
# ... logica complessa per sessioni personalizzate ...
return jsonify(session_data)
Compromesso: cold start più lenti, ma meno richiesti.
Prefissi delle tabelle per app
Un’istanza RDS serve più di 7 app isolandole con prefissi a livello database:
-- App Amal
CREATE TABLE amal_users (...)
CREATE TABLE amal_content_bytes (...)
CREATE TABLE amal_user_memory (...)
-- App Thurayya
CREATE TABLE thurayya_users (...)
CREATE TABLE thurayya_content_bytes (...)
CREATE TABLE thurayya_user_memory (...)
-- Altre app: qais_, kidelite_, ecc.
A deploy, la variabile d’ambiente APP_NAME seleziona il prefisso:
app_name = os.getenv('APP_NAME', 'amal') # 'amal', 'thurayya', 'qais', ecc.
Query con prefisso dinamico
table_name = f'{app_name}_users'
cursor.execute(f'SELECT * FROM {table_name} WHERE id = %s', (user_id,))
Il Data Lake per l'Analisi
Problema: interrogazioni dirette al database rallentano la produzione e bloccano le tabelle.
Soluzione: pipeline asincrona per analytics
[App Mobile]
↓ (invia evento)
[API Endpoint] → [Coda SQS] (asincrona)
↓ (risposta immediata)
↓ (non blocca analytics)
[Kinesis Firehose] (raggruppa eventi ogni 5 min o 100MB)
↓
[S3] (partizionato in s3://analytics-lake/amal/2026/03/28/events.parquet)
↓
[AWS Glue] (analizza e inferisce schema su S3)
↓
[Athena] (query SQL via motore Presto)
↓
[Dashboard] (dati in tempo reale)
Pattern Dead Letter Queue (DLQ):
Se falla l’analisi:
SQS → [Firehose fallisce]
↓
[DLQ riceve messaggi falliti]
↓
[Alert al team operativo]
↓
[API di produzione non ne risente]
Gli analytics non bloccano mai l’esperienza utente. I bambini possono imparare anche se la pipeline è momentaneamente interrotta.
Strategie per ottimizzare i costi
Strategia 1: Lambdas leggere per endpoint ad alta frequenza
- Un’app mobile tipica fa 10-20 chiamate API per sessione
- 95.000 utenti attivi × 3 sessioni/giorno × 15 chiamate/sessione = 4,275 milioni chiamate/giorno
- Costo approssimativo per chiamata: $0.0000002, cioè circa 0,86$ al giorno
- Ridurre il cold start di 10s fa risparmiare circa $500 al mese
Strategia 2: RDS con Reserved Instances
- Impegno 3 anni con circa 60% di sconto rispetto on-demand
- Uso di db.r6i.xlarge (4 vCPU, 32GB RAM): $2.800 mese riservato vs $6.500 on-demand
- Risparmio annuo stimato: circa $50.000
Strategia 3: Caching
- Dati frequentemente richiesti (curriculum, contenuti) memorizzati in cache con ElastiCache (Redis)
- Riduce le query su RDS del 70%
- Costo della cache: $800 al mese, risparmio su RDS: $2.000/mese
7+ app da un unico codice backend
| App | Prefisso | Tabelle DB | Stack Lambda | Stato |
|---|---|---|---|---|
| Amal | amal_ | 40+ tabelle | Condiviso | Produzione |
| Thurayya | thurayya_ | 40+ tabelle | Condiviso | Produzione |
| Qais | qais_ | 35+ tabelle | Condiviso | Beta |
| KidElite | kidelite_ | 40+ tabelle | Condiviso | Produzione |
| Alphazed School | school_ | 50+ tabelle | Condiviso | Beta |
| Alphazed Montessori | montessori_ | 45+ tabelle | Condiviso | Interno |
Un backend, una pipeline di deploy, 6 app attive simultaneamente. Il lancio di una nuova app richiede settimane anziché mesi.
Domande Frequenti
D: Lambda ha un timeout massimo di 15 minuti?
A: Sì, ma noi raramente abbiamo richieste così lunghe. Carichi pesanti (generazione contenuti, esportazioni grandi) sono gestiti con job asincroni via SQS + Step Functions.
D: E se il database fallisce?
A: RDS usa failover Multi-AZ (replica primaria + standby). Il failover è automatico in circa 60 secondi. I client vedono timeout brevi ma il sistema si riprende velocemente.
D: Come gestite il pooling delle connessioni in Lambda senza stato?
A: Ogni istanza Lambda mantiene un pool di connessioni riutilizzato nelle invocazioni calde; alle cold start si crea una nuova connessione. RDS Proxy regola i limiti di connessioni tra Lambda e RDS.



