diff --git a/README.md b/README.md new file mode 100644 index 0000000..e2a2481 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# serverless-python +Application performance comparison Django vs Flask vs Native lambda using [serverless framework](https://serverless.com/framework/) in [AWS Lambda](https://aws.amazon.com/lambda/). + +## Results: + +### Framework comparison +| Framework | Pack size | Memory usage | Cold start | After warm-up | +|---------------|-----------|--------------|------------|---------------| +| Native lambda | 3.84 MB | 34 MB | 1.60 ms | 1.19 - 3 ms | +| Flask | 6.91 MB | 67 MB | 290.83 ms | 3.97 - 20 ms | +| Django | 14.47 MB | 71 MB | 675.96 ms | 11.33 - 70 ms | + +### ORM comparison +| ORM | Memory usage | Cold start | After warm-up | +|-------------|--------------|------------|----------------| +| Psycopg2 | 59 MB | 2.96 ms | 1.59 - 1.6 ms | +| SQL Alchemy | 58 MB | 37.60 ms | 2.31 - 2.49 ms | +| Peewee | 58 MB | 16.65 ms | 3.89 - 4.2 ms | +| Django ORM | 54 MB | 297.40 ms | 2.15 - 2.26 ms | diff --git a/sls_native/conf/__init__.py b/sls_native/conf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sls_native/conf/apps.py b/sls_native/conf/apps.py new file mode 100644 index 0000000..f70fa93 --- /dev/null +++ b/sls_native/conf/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ConfConfig(AppConfig): + name = 'conf' diff --git a/sls_native/conf/handler.py b/sls_native/conf/handler.py new file mode 100644 index 0000000..3c0843f --- /dev/null +++ b/sls_native/conf/handler.py @@ -0,0 +1,19 @@ +import json +import os + +import django + +os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' +django.setup() + +from conf.models import BookDjango + + +def hello_django_orm(event, context): + book = BookDjango.objects.first() + response = { + "statusCode": 200, + "body": json.dumps({'id': book.id, 'name': book.name}) + } + + return response diff --git a/sls_native/conf/models.py b/sls_native/conf/models.py new file mode 100644 index 0000000..f6101e9 --- /dev/null +++ b/sls_native/conf/models.py @@ -0,0 +1,8 @@ +from django.db import models + + +class BookDjango(models.Model): + name = models.CharField('name', max_length=20) + + class Meta: + db_table = 'book_book' diff --git a/sls_native/event.json b/sls_native/event.json new file mode 100644 index 0000000..0a6e876 --- /dev/null +++ b/sls_native/event.json @@ -0,0 +1,3 @@ +{ + "answer": 42 +} diff --git a/sls_native/handler.py b/sls_native/handler.py index d0b01ac..ea60501 100644 --- a/sls_native/handler.py +++ b/sls_native/handler.py @@ -4,6 +4,10 @@ import psycopg2 from dotenv import load_dotenv from psycopg2.extras import DictCursor +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +from models import Book, BookPeewee load_dotenv() @@ -25,3 +29,28 @@ def hello(event, context): } return response + + +engine = create_engine(os.getenv('DATABASE_URL')) +Session = sessionmaker(bind=engine) +session = Session() + + +def hello_alchemy(event, context): + book = session.query(Book).first() + response = { + "statusCode": 200, + "body": json.dumps({'id': book.id, 'name': book.name}) + } + + return response + + +def hello_peewee(event, context): + book = BookPeewee.get() + response = { + "statusCode": 200, + "body": json.dumps({'id': book.id, 'name': book.name}) + } + + return response diff --git a/sls_native/models.py b/sls_native/models.py new file mode 100644 index 0000000..27630b9 --- /dev/null +++ b/sls_native/models.py @@ -0,0 +1,37 @@ +import os + +from dotenv import load_dotenv +from peewee import Model, IntegerField, CharField +from playhouse.db_url import connect +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import Column, Integer, String + +load_dotenv() +Base = declarative_base() +peewee_db = connect(os.getenv('DATABASE_URL')) + + +class Book(Base): + __tablename__ = 'book_book' + + id = Column(Integer, primary_key=True) + name = Column(String()) + + def __repr__(self): + return ''.format(self.id) + + +def make_table_name(model_class): + return 'book_book' + + +class BookPeewee(Model): + id = IntegerField(primary_key=True) + name = CharField() + + class Meta: + database = peewee_db + table_function = make_table_name + + def __repr__(self): + return ''.format(self.id) diff --git a/sls_native/python-lambda-local.py b/sls_native/python-lambda-local.py new file mode 120000 index 0000000..085f005 --- /dev/null +++ b/sls_native/python-lambda-local.py @@ -0,0 +1 @@ +/home/listener/.pyenv/versions/sls-native/bin/python-lambda-local \ No newline at end of file diff --git a/sls_native/requirements.txt b/sls_native/requirements.txt index 0bf8205..4ab404f 100644 --- a/sls_native/requirements.txt +++ b/sls_native/requirements.txt @@ -1,4 +1,8 @@ psycopg2-binary python-dotenv raven -werkzeug \ No newline at end of file +werkzeug +SQLAlchemy +python-lambda-local +peewee +django \ No newline at end of file diff --git a/sls_native/serverless.yml b/sls_native/serverless.yml index b17fb76..db9f40d 100644 --- a/sls_native/serverless.yml +++ b/sls_native/serverless.yml @@ -1,4 +1,4 @@ -service: sls-native +service: sls-native-2 # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details @@ -63,7 +63,24 @@ functions: - http: path: / method: GET - + hello_alchemy: + handler: handler.hello_alchemy + events: + - http: + path: /alchemy + method: GET + hello_peewee: + handler: handler.hello_peewee + events: + - http: + path: /peewee + method: GET + hello_django_orm: + handler: conf.handler.hello_django_orm + events: + - http: + path: /django_orm + method: GET # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details diff --git a/sls_native/settings.py b/sls_native/settings.py new file mode 100644 index 0000000..5045051 --- /dev/null +++ b/sls_native/settings.py @@ -0,0 +1,136 @@ +""" +Django settings for serverless_django project. + +Generated by 'django-admin startproject' using Django 2.1. + +For more information on this file, see +https://docs.djangoproject.com/en/2.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/2.1/ref/settings/ +""" + +import os +import raven +from dotenv import load_dotenv + + +load_dotenv() +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = os.getenv('SECRET_KEY') + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = os.getenv('DEBUG', False) + +ALLOWED_HOSTS = ['*'] + + +# Application definition + +INSTALLED_APPS = [ + 'conf.apps.ConfConfig', +] + +MIDDLEWARE = [ + # 'django.middleware.security.SecurityMiddleware', + # 'django.contrib.sessions.middleware.SessionMiddleware', + # 'django.middleware.common.CommonMiddleware', + # 'django.middleware.csrf.CsrfViewMiddleware', + # 'django.contrib.auth.middleware.AuthenticationMiddleware', + # 'django.contrib.messages.middleware.MessageMiddleware', + # 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'serverless_django.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +# WSGI_APPLICATION = 'serverless_django.wsgi.application' +SQLITE_BUCKET = os.environ.get('SQLITE_BUCKET', "serverless-django") +IS_OFFLINE = os.environ.get('LAMBDA_TASK_ROOT') is None + +# RAVEN_CONFIG = { +# 'dsn': os.getenv('RAVEN_DNS'), +# # If you are using git, you can also automatically configure the +# # release based on the git info. +# # 'release': raven.fetch_git_sha(os.path.abspath(os.pardir)), +# } + + +SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' + +# Database +# https://docs.djangoproject.com/en/2.1/ref/settings/#databases +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': os.getenv('DB_NAME', 'slstest'), + 'USER': os.getenv('DB_USER_NAME'), + 'PASSWORD': os.getenv('DB_PASSWORD'), + 'HOST': os.getenv('DB_HOST'), + 'PORT': os.getenv('DB_PORT', '5432'), + 'OPTIONS': { + 'connect_timeout': 5, + } + } +} + + +# Password validation +# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/2.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.1/howto/static-files/ + +STATIC_URL = '/static/'