637 karma · joined February 8, 2012
We do use Go for almost all of our other services, and there are an increasing number of integrations written in Python. But we're still using and investing in our Node integrations code for the foreseeable future, and this was an important step for simplifying our infrastructure.
We certainly hope the tooling and rollout process in the post were instructive for anyone using Node, even if their stacks were pristine from day 1 and never need this sort of complex migration :)
With perfect hindsight, it's a fair point that all the pros and cons could net out to another language being best for our integrations. Integrations are the largest and most quickly-changing codebase at Plaid, so such a migration would be a massive undertaking. We definitely didn't want to block scalability improvements on doing a language migration.
In terms of what issues caused us to move away from parallelism in the first place, it was all the CPU-bound stuff that you might expect: ReDoS-style issues, post-processing arrays in very large edge cases, programmer error, etc.
The essential vs non-essential container designation is a little confusing. The standard use case for multi-container tasks seems to be that all containers are marked as essential, i.e. they essentially represent different services that are operating in concert on the same machine. This is definitely not the situation we're in, where each container is totally independent.
So it seems like we'd be a perfect use case for non-essential containers. However, (1) at least one container _must_ be marked as essential, and (2) non-essential containers which exit don't get restarted or replaced. This means we would still have a limited bleed-over effect (if the essential container exits, the other ones do too), and more importantly, we can't guarantee that our capacity will be robust to process exits.
"Never returning" is an interesting problem in Node. We have gRPC request timeouts, so the request fails on the client side after a set amount of time. Our Node server gets a cancellation event when this happens, but there's no guarantee that the in-flight request ever stops processing or gives up its resources, even if it's not blocking the event loop. E.g. our request handler could have a while(true) loop that continually does an async network request, and even though each gRPC request eventually times out, we would eventually have a swarm of zombie while(true) loops that are operating. To address this problem, we thread a lightweight context object into our framework which can essentially call an isCancelled() method. Before doing most low-level async operations (e.g. network requests), we check isCancelled() and throw if the gRPC request has timed out.
The integrations are all written in Typescript. As mentioned in another comment, we've attempted multiple processes per container and multiple containers per task. Right now, we see the bigger win as being able to fix "all of the above" and run multiple requests per Node process, but it'll take some legwork to get there :)
Note that there's an ECS_CONTAINER_STOP_TIMEOUT parameter in ECS that sets a hard upper limit on how long containers have to exit before getting SIGKILLed, and it defaults to 30 seconds. If you want to allow requests to drain for longer than that, you'll need to update that parameter.
(For services with fast request processing time, the draining process is often a lot simpler. You can just route traffic away from the server, then wait a short period of time before telling the process to exit. It's not quite as precise, but works well for many use cases and requires no bookkeeping.)
Node event loop blockages are the primary reason we have so many processes running. We have enough integrations and iterate on them quickly enough that our infrastructure essentially treats them as untrusted/breakable. We want to avoid ReDoS-style bugs from affecting more than the current request, so we handle one request per process. A little inelegant, but we've still been able to horizontally scale the system, and frankly the extra infrastructure cost hasn't been enough to be worth the effort to change it.
To get around the start-task rate limit, we've tried running multiple identical containers per ECS task. However, they need to be marked as "essential" in CloudFormation to make sure our capacity doesn't degrade on container exits, and this means that one container exiting will also exit other containers in the same task.
Multiple processes per container is another interesting approach. We've used Node subprocesses in the past, but we found them tricky for reasons that are unrelated to deploy speed.
One thing we've really liked about rolling our own approach is that we decide when to declare a deploy complete. ECS is pretty conservative about not declaring a deploy complete until the final container has finished draining, which can take minutes for some of our requests. With our fast deploys, we declare a deploy complete when the final container running old code stops accepting new requests, which is significantly sooner. This makes follow-on deploys and rollbacks much smoother.
It's inevitable that early systems are going to bend and break at various points of the scaling process, and the interesting question is how to navigate that – fight fires for awhile or fix things immediately, hack together a stopgap solution or do the full fix, etc. We thought ours might be an interesting data point to share.
I would disagree with your point about stock issuance suggesting belief in the company. Issuing stock is usually a negative signal that not only is your company struggling with cash flow, but it can't issue debt at a reasonable cost. (Startups issue equity because it is difficult to borrow at such a risky stage of the company, but more established companies like Tesla issuing equity is usually a sign of difficulties with cash flow.) Read up on cost of equity vs cost of debt for more info.
I never said building infrastructure would convince everyone to bike. Rather, the government can (and often does) move society as a whole in one direction using money as a lever. Do taxes on gas or alcohol prevent everyone from driving or drinking? No, but they move the needle, just like building cycling infrastructure does.
And I never cited the "10% increase" as an important metric for the success of cycling infrastructure, you did. None of my arguments is about the absolute number of people cycling, although others have pointed out that biking is likely underfunded relative to the number of people who use the infrastructure. I just cited the increasing number of people on bikes as a counterpoint to your claim that "no matter what people say, they don't want to cycle to work".
(Also incorrect is that people "don't want to cycle to work", I see literally hundreds of them every morning in SF, and it's growing every year.)
There are many arguments in favor of cycling infrastructure, but unfortunately I'm versed enough in anti-bike comment threads to recognize someone who won't be convinced :) But here goes...
* bikes do less damage to road than cars, and therefore the maintenance cost of bike lanes is lower than car lanes, which require frequent paving
* bikes are important as a "last mile" transportation option for public transit commuters. densely-placed bike share stations improve the feasibility of public transit for people whose homes or offices are 1-2 miles from a train station.
* bikes are far smaller than cars and so you can push more traffic through a given width of road than with cars. this is especially relevant if you ever get to a downtown stretch where one car turning right at a pedestrian crossing causes an entire green light to be wasted on the rest of the cars (basically every light along Market Street in SF). cars are just wide and clumsy when driving in dense downtowns.
* the government frequently subsidizes behaviors that are considered virtuous (or conversely, imposes "sin taxes"), and also subsidizes services that benefit lower-income citizens. both of those apply here. car parking in downtowns can run $40+ for a day. bike share and bus commutes are $2-5 and keep people active.
I could go on, but again, I don't think your mind will be changed much.
$4 doesn't get you far in SF, but if you do pay that rate to go across town, just know that you're not paying your fair share to support the driver, maintain the roads, and are probably inconveniencing other drivers and cyclists more than if you were using a bus. Which is fine — but if you then go and argue that this is a reason to never support public transportation, you're just buying into the Lyft/Uber marketing.
If you really wanted to measure how Olin's teaching compares to that of MIT/Stanford, vis-a-vis startup founding success, you could have the other schools "draft" a team of 100 CS students at the beginning of their freshman year, and measure the founding success rate of only those 100 students.
- knowledgeable at algorithms and data structures
- good at analyzing correctness and edge cases, even on simple non-algorithmic problems (e.g. FizzBuzz)
- accustomed to working hard and learning new concepts. this attribute is not specific to competitive programmer – for example, I'd expect similarly from an open source contributor – but it's higher than for a typical college student.
Some negatives I'd expect, which are fixable over the course of the course of their career:
- over-confidence in code, under-testing
- less skilled in OOP, coding style, version control systems, as well as web development or systems code (unless they have specific previous work in these areas)
- sometimes looking down on gruntwork/rote as beneath them (like the view that pure mathematicians have towards applied math or statistics)
I think that list of positive attributes often outweighs the potential negatives, especially during an internship or in the first year or two of someone's career. After that, I would expect many non-competitive programmers to have picked up on some of those advantages (code correctness, learning new concepts).
I've tried to steer my own interviews away from algorithms (especially DP) and focusing more on giving problems that are relatively straightforward, while still being complicated enough that someone has to write precise code and identify/fix a few edge cases.
When I tried Blue Apron, I spent an hour cooking a meal for two. That's 30 minutes of work per serving.
When I cook with my own ingredients, I spend an hour shopping and an hour cooking, then make 8 servings and refrigerate/freeze the leftovers. That's 15 minutes of work per serving, and it's significantly cheaper.
I agree that Blue Apron makes more sense if you don't have access to a good grocery store, or if you don't want to invest time in finding recipes.
I could see this being a valid argument if there were systemic risk to the industry that could have a simultaneous effect on your equity and salary compensation. e.g. if you're a truck driver and get equity in your company, then automated trucks could be disastrous and you'd want to diversify away from the equity.
This makes some assumptions about risk-aversion and rationality – it's possible that the latter is an optimistic assumption, given the amount of koolaid-drinking inherent in joining startups. But as long as you're telling your friend to discount his startup offers, and articles like this are being written, I think it's logical to think that the pendulum will swing towards mid- and late-stage startups building in such a premium to their offers, especially if they're serious about getting good talent.
Also, it's more precise to use the S&P dividend yield (2%) or 10-year Treasury yield (2.4%) to gauge how much someone with $1.5m could spend in rent, assuming they put all their interest into rent. They'd be renting something at more like $3k/month.
Now take the fact that home prices are, at the higher end, about 25x the yearly rent of the same place. So this person with $1.5m is essentially renting a place worth $900k, whereas the buyer (who perhaps only has $500k) is living in a nicer place that cost $1.5m. And that's not considering the fact that rents could rise further.
There are taxes and maintenance to consider, the risk of putting a lot of money into a volatile asset, and being tied to one place. Financial leverage in general is risky. But it's not a clear-cut decision like most people try to make it.
Candidate: "We're going to build a wall between us and Mexico. It'll cost us $7 billion."
Electorate: "Umm. That's a lot of money. Let's do something else with it."
If that's how people would react, they should react the same way even if we're getting the money from Mexico. Money is fungible, and as much as our hearts enjoy poetic justice, it shouldn't matter where it's coming from.
There are a couple of situations where we would still want it built, even if its cost exceeded what we were willing to pay ourselves. One is if this is a cooperative, non-zero-sum situation: if Mexico were getting some value from the wall, they might be willing to pay some of the cost, beyond what we can extract via tariffs and seized remittances. The other is if Mexico is terrible at negotiations, which is likely what Trump is assuming.