dougmoscrop / serverless-http

Use your existing middleware framework (e.g. Express, Koa) in AWS Lambda 🎉
Other
1.72k stars 165 forks source link

I mean... But, why? #165

Closed kelvne closed 4 years ago

kelvne commented 4 years ago

940x450-Ryan-Reynold-Reaction-GIFs

Just trying to understand why this exists. I came here after analyzing a client's code that was using this and had 18 routes initialized on the same function. Profiling is horrible and paying for requests is terrible when it grows. (besides the other bad reasons)

jgcmarins commented 4 years ago

Nice question. What are your suggestions and/or recommendations?

kelvne commented 4 years ago

Nice question. What are your suggestions and/or recommendations?

The sole possibility is leading inexperienced people to ship gigantic applications on a single lambda function. Maybe it would be nice to append something on the README about this...

Currently, I was analyzing a client's platform: He hired me to evaluate their infrastructure because they were spending A LOT of money per day only with lambda (30USD+). He told me that all of their "backend" logic was serverless.

After checking it out, they had just one main function which was basically an express application, but a full-fledged one.

I just sum their routes. They have 57 routes. In. A. Single. Function.

This is exactly the case where choosing to manage a server process is more cost-efficient. Or at least have every route as a single function.

Every time a function is called a new express server is created and there is also the time to map the payload to the right "controller". And there is also profiling, which is really difficult this way. You need to use some sort of external integration to aggregate data.

I mean, I understand this is good for shipping small code directly from an old legacy node backend to a serverless structure, and I know no one is responsible for anybody but... 🤷‍♂️

dougmoscrop commented 4 years ago

haha oh boy

first, serverless is expensive. the whole value prop of scale to zero is a bit of a strawman (because you could be 'scaling on spot' or 'scaling to a pair of T3 nanos'), and you're paying a premium for the managed environment. you can work out the pricing per ms compared to EC2 and see just how high the premium is especially when you compare reserved.

to ship gigantic applications on a single lambda function

you can do this with or without this library. nothing stops you from having separate functions for /foo /bar and so on, and nothing stops someone from writing one big handler even without using a framework if (event.method === ...) else if / else if

shipping small code directly from an old legacy node backend to a serverless structure

Actually I wrote it for the opposite reason: to make "oh no, serverless = money-with-wings, let's dockerize it and throw it on elastic beanstalk" an escape hatch.

Profiling is horrible and paying for requests is terrible when it grows.

this part I find confusing - you're going to pay the same regardless of how many functions you have. 30 USD per day in Lambda is like what, 10,000,000 200ms-1gb invocations? That's either an insanely popular service, in which case 30 USD a day is peanuts, or they're doing something very wrong - waiting in the API tier maybe?

This is exactly the case where choosing to manage a server process is more cost-efficient.

That will almost always be the case

Or at least have every route as a single function.

I don't see how this would change anything?

Every time a function is called a new express server is created and there is also the time to map the payload to the right "controller".

No, now it's my turn for the memes!

image

Lambda functions are semi-persistent, the 'server' is created once per startup, and the time that takes is very, very little. The overhead of mapping the payload to the controller is less than a millisecond. Do you need some assistance with this consulting task? I don't need money, but feel free to ask any questions here. I have serverless APIs that are running right now using koa, and they respond fairly quickly:

Summary:
  Total:        1.1259 secs
  Slowest:      0.2220 secs
  Fastest:      0.0430 secs
  Average:      0.0556 secs
  Requests/sec: 888.1629

  Total data:   23000 bytes
  Size/request: 23 bytes

Response time histogram:
  0.043 [1]     |
  0.061 [949]   |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.079 [0]     |
  0.097 [0]     |
  0.115 [0]     |
  0.132 [0]     |
  0.150 [0]     |
  0.168 [0]     |
  0.186 [0]     |
  0.204 [0]     |
  0.222 [50]    |■■

I would make sure they've appropriately tuned their memory size (1.5GB is a good starting point for APIs that actually "do stuff" and don't just wait on other things), make sure they are using HTTP Keep-Alive in the AWS SDK (it is not enabled by default, so you get new TLS negotiations happening frequently):

const https = require('https');
const { S3 }= require('aws-sdk');
const s3 = new S3({
  httpOptions: {
    agent: new https.Agent({ keepAlive: true })
  }
});

Make sure they're not doing dumb sync stuff, or not using concurrency (Promise.all, if you're consulting I'm sure you're aware of all this).

In short, it's very unlikely that this library enabled them to do anything specifically or even tangentially that made their application so expensive.

dougmoscrop commented 4 years ago

oh, and assuming that it's not a case of tens of millions of API calls per day, and it's execution time that is the problem, that's an architectural thing that needs to be sorted out - use materialized views (you can stream a csv file of a million or so records from S3 really, really fast, especially if it's Avro encoded) for calls that are slow, and use SQS/Kinesis for things that need time...

edit:

just also realized/remembered, you should actually confirm that the amount of invocations you're seeing are valid. it's very easy to 'denial of money attack' serverless applications -- normally a DDoS requires a pretty significant botnet or whatever to coordinate the attack, and before the breaking point response times will suffer and grow, but the cost will remain the same (until autoscaling kicks in) - serverless is different... if you say "woohoo I just launched this new serverless app using API Gateway at ihatemoney.interweb/api/v1" I can send 1000 req/sec to it with a low powered computer, even if they all just return 401 or 403 because I've set the Authorization header to some (maybe very large?) junk value... and 1000 60 60 * 24 = 86 million requests a day, or you know, $160/day just for Lambda plus another $86-301 for API Gateway depending on whether or not you're using a HTTP or REST API.

So yeah, anyone who dislikes your Serverless API can burn $400/day with a Raspberry Pi. When I talked to Corey Quinn about this, he said AWS has refunded fees in these cases, so if you audit their load and do find that someone was hitting them constantly, there might be some losses that can be recovered there.

Serverless isn't a replacement for architecture or planning/estimation, the way you mitigate this stuff is by paying attention to traffic and setting billing alarms accordingly..

kelvne commented 4 years ago

I totally agree with 95% of what you just said. It was a bad choice because the developers were not experts in software architecture. (and as I said, no one is responsible for other individuals)

Btw, I audit it no signals of attacks. There are just a lot of requests.

I state that it was a bad choice because the client required that it should be at most cost-efficient possible. Because of NDA, I can't really share my proposals but it is an online game in which the execution time is actually a problem. (and there are 2000+ DAU)

I can advance here that, of course, I'll probably suggest (as this is a solution consulting) to maintain a lot of functions on lambda, the ones that execution time is not a problem or the team has not the expertise to maintain or keep optimizing the "server version" of it. Mostly async stuff that doesn't require fast response. In this scenario, using plain functions with the less overhead as possible.

But in the current scenario, the execution time is being ridiculously high, and as you said, it is not only paying by request but there is the price for execution time.

kelvne commented 4 years ago

Oh, and yes, the code is overall really bad. A lot of concurrency and bad practices for lambda functions (Like a lot of console.logs and stuff like that). I wonder how the game got so successful. Marketing is the key, I guess 🤷‍♂️

Anyway, my suggestion is to, I don't know. Maybe to have more "educational" information on the README. I can help with that if you think it's ok... I can open a pull request and we can discuss topics there. Serverless is awesome, I just think that people should understand the tradeoffs... and beginners could be discovering about the serverless world from this repository.

kelvne commented 4 years ago

Closing the issue, either way. Thanks for the replies.

dougmoscrop commented 4 years ago

I'm always up for knowledge sharing and best practices, I've also been very very slowly working on a kind of reference implementation of how we've been building fully serverless services for a few years now but something always gets in the way :(

I would say that video games are probably the least suited for this type of environment, certainly 'in-game' stuff (as opposed to like shop/leaderboard/matchmaking - even the last one depends) because of how frequently that they send requests. Pay per request is great until it isn't!

The thing I push for most is what I call the game of 0s - can I send a batch of 10 or 100 instead of individual requests? Maybe, maybe not - again, in a game, if it's ticking at 30-60 times per second.. oof. That is not a good thing for any kind of serverless system.

tehnrd commented 4 years ago

Actually I wrote it for the opposite reason: to make "oh no, serverless = money-with-wings, let's dockerize it and throw it on elastic beanstalk" an escape hatch.

I wanted to add some perspectives on this as well. ☝️, this is exactly the reason we are using this module. In a new project/app in which you don't know what traffic volume will be, or you know that it will be low, serverless will be cheaper and easier to deliver something of value sooner. If the service grows there will be a tipping point in which running on dedicated servers makes sense and this module should minimize the pain of migrating the code and business logic to Elasticbeanstalk/Docker/EC2/ECS/EKS/WhateverIsNewAndCool if/when that time comes.

It also abstracts the AWS API Gateway Lamda event object and reduces vendor lock-in.

It also allows us to take advantage of the huge community of Express modules, middleware, and resources that exist instead of having to roll our own or finding a library that tries to apply similar functionality to API Gateway.