diff --git a/README.md b/README.md index dbb3deaa..a40cf26c 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ - [Advanced Settings](#advanced-settings) - [YAML Settings](#yaml-settings) - [Advanced Usage](#advanced-usage) - - [Keeping The Server Warm](#keeping-the-server-warm) + - [Keeping The Server Warm (Deprecated)](#keeping-the-server-warm-deprecated) - [Serving Static Files / Binary Uploads](#serving-static-files--binary-uploads) - [Enabling CORS](#enabling-cors) - [Large Projects](#large-projects) @@ -1239,8 +1239,8 @@ to change Zappa's behavior. Use these at your own risk! "token_header": "Authorization", // Optional. Default 'Authorization'. The name of a custom authorization header containing the token that clients submit as part of their requests. "validation_expression": "^Bearer \\w+$", // Optional. A validation expression for the incoming token, specify a regular expression. }, - "keep_warm": true, // Create CloudWatch events to keep the server warm. Default true. To remove, set to false and then `unschedule`. - "keep_warm_expression": "rate(4 minutes)", // How often to execute the keep-warm, in cron and rate format. Default 4 minutes. + "keep_warm": false, // Deprecated. Create CloudWatch events to keep the server warm. Default false. Does not reduce cold start duration. Consider snap_start or provisioned_concurrency instead. See #1451. + "keep_warm_expression": "rate(4 minutes)", // How often to execute the keep-warm, in cron and rate format. Default 4 minutes. Only used when keep_warm is true. "lambda_description": "Your Description", // However you want to describe your project for the AWS console. Default "Zappa Deployment". "lambda_handler": "your_custom_handler", // The name of Lambda handler. Default: handler.lambda_handler "layers": ["arn:aws:lambda:::layer::"], // optional lambda layers @@ -1317,9 +1317,11 @@ Similarly, you can supply a `zappa_settings.toml` file: ## Advanced Usage -### Keeping The Server Warm +### Keeping The Server Warm (Deprecated) -Zappa will automatically set up a regularly occurring execution of your application in order to keep the Lambda function warm. This can be disabled via the `keep_warm` setting. +> **Deprecated**: `keep_warm` is disabled by default and will be removed in a future version. Investigation ([#1445](https://github.com/zappa/Zappa/issues/1445)) found that `keep_warm` does not reduce cold start duration — only frequency for a single execution environment. Use `snap_start` (45-72% faster cold starts, ~$1.95/mo) or `provisioned_concurrency` (eliminates cold starts entirely, ~$5.40/mo) instead. See [#1451](https://github.com/zappa/Zappa/issues/1451). + +Zappa can set up a regularly occurring execution of your application in order to keep the Lambda function warm. This can be enabled via the `keep_warm` setting. #### Serving Static Files / Binary Uploads diff --git a/tests/test_core.py b/tests/test_core.py index 4faab0b2..1a8a1905 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -4557,5 +4557,78 @@ def test_upload_to_s3_reraises_unexpected_errors(self): z.s3_client.create_bucket.assert_not_called() +class TestKeepWarmDeprecation(unittest.TestCase): + def setUp(self): + self.users_current_region_name = os.environ.get("AWS_DEFAULT_REGION", None) + os.environ["AWS_DEFAULT_REGION"] = "us-east-1" + + def tearDown(self): + del os.environ["AWS_DEFAULT_REGION"] + if self.users_current_region_name is not None: + os.environ["AWS_DEFAULT_REGION"] = self.users_current_region_name + + def test_keep_warm_default_false(self): + """keep_warm should default to false after deprecation.""" + zappa_cli = ZappaCLI() + zappa_cli.api_stage = "ttt888" + zappa_cli.load_settings("test_settings.json") + # Default should be False when not explicitly set + self.assertFalse(zappa_cli.stage_config.get("keep_warm", False)) + + def test_keep_warm_deprecation_warning(self): + """schedule() should emit a deprecation warning when keep_warm is enabled.""" + zappa_cli = ZappaCLI() + zappa_cli.api_stage = "ttt888" + zappa_cli.load_settings("test_settings.json") + zappa_cli.override_stage_config_setting("keep_warm", True) + zappa_cli.override_stage_config_setting("events", []) + zappa_cli.lambda_name = "test-lambda" + + zappa_cli.zappa = mock.MagicMock() + zappa_cli.zappa.lambda_client.get_function.return_value = { + "Configuration": {"FunctionArn": "arn:aws:lambda:us-east-1:123:function:test"} + } + + with mock.patch("click.echo") as mock_echo: + zappa_cli.schedule() + + echo_calls = " ".join(str(c) for c in mock_echo.call_args_list) + self.assertIn("deprecated", echo_calls) + self.assertIn("snap_start", echo_calls) + self.assertIn("provisioned_concurrency", echo_calls) + + def test_keep_warm_no_warning_when_disabled(self): + """schedule() should not emit a deprecation warning when keep_warm is false.""" + zappa_cli = ZappaCLI() + zappa_cli.api_stage = "ttt888" + zappa_cli.load_settings("test_settings.json") + zappa_cli.override_stage_config_setting("keep_warm", False) + zappa_cli.override_stage_config_setting("events", []) + zappa_cli.lambda_name = "test-lambda" + + with mock.patch("click.echo") as mock_echo: + zappa_cli.schedule() + + echo_calls = " ".join(str(c) for c in mock_echo.call_args_list) + self.assertNotIn("deprecated", echo_calls) + + def test_keep_warm_callback_deprecation_warning(self): + """keep_warm_callback should emit a DeprecationWarning.""" + import warnings + + from zappa.handler import keep_warm_callback + + mock_context = mock.MagicMock() + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + with mock.patch("zappa.handler.lambda_handler"): + keep_warm_callback(event={}, context=mock_context) + + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[0].category, DeprecationWarning)) + self.assertIn("deprecated", str(w[0].message)) + + if __name__ == "__main__": unittest.main() diff --git a/zappa/cli.py b/zappa/cli.py index f13ecaa7..80465109 100755 --- a/zappa/cli.py +++ b/zappa/cli.py @@ -1452,7 +1452,21 @@ def schedule(self): for event in events: self.collision_warning(event.get("function")) - if self.stage_config.get("keep_warm", True): + keep_warm = self.stage_config.get("keep_warm", False) + if keep_warm: + click.echo( + click.style("Warning!", fg="yellow", bold=True) + + " keep_warm is " + + click.style("deprecated", bold=True) + + " and disabled by default." + + " It does not reduce cold start duration — only frequency for a single execution environment." + + " Consider using " + + click.style("snap_start", bold=True) + + " or " + + click.style("provisioned_concurrency", bold=True) + + " instead." + + " See https://github.com/zappa/Zappa/issues/1451" + ) if not events: events = [] diff --git a/zappa/handler.py b/zappa/handler.py index 11319dd2..12a4fecc 100644 --- a/zappa/handler.py +++ b/zappa/handler.py @@ -993,7 +993,21 @@ def lambda_handler(event, context): # pragma: no cover def keep_warm_callback(event, context): - """Method is triggered by the CloudWatch event scheduled when keep_warm setting is set to true.""" + """Deprecated: Method is triggered by the CloudWatch event scheduled when keep_warm setting is set to true. + + keep_warm does not reduce cold start duration — only frequency for a single execution environment. + Consider using snap_start or provisioned_concurrency instead. + See https://github.com/zappa/Zappa/issues/1451 + """ + import warnings + + warnings.warn( + "keep_warm is deprecated and will be removed in a future version. " + "Use snap_start or provisioned_concurrency instead. " + "See https://github.com/zappa/Zappa/issues/1451", + DeprecationWarning, + stacklevel=1, + ) lambda_handler(event={}, context=context) # overriding event with an empty one so that web app initialization will # be triggered.