3 ms·
This seems like a problem you can’t solve generically and you always end up making trade offs. The two main challenges I can think of: * State in old system is
by vlovich123 3y ago
This seems like a problem you can’t solve generically and you always end up making trade offs. The two main challenges I can think of:
* State in old system isn’t representable in new system (not a backwards compatible upgrade or more likely a bug exists in handling the new state)
* There’s state outside of the program that’s impossible to transition gracefully (e.g. dirtied IO socket where you don’t know what it’s state is & it’s a resource owned outside of your program)
* Transitioning state means there’s a possibility of failure because the program never reaches a graceful transition point to snapshot the state. So you either have to choose between running the old program forever or abandoning the graceful state transition anyway.
Distributed systems I’ve observed pick one of two strategies:
1. Using the load balancer strategy of migrating off the old version & then terminating it after some grace period.
2. Use a formal distributed state system like CockroachDB, Yuggabyte, DynamoDB, S3, etc etc.
This is probably a big reason why most programs use external storage solutions even if they’re less efficient - it centralizes maintenance of state onto a system that has well defined semantics and can handle repair transparently.
- naasking 3y ago> This is probably a big reason why most programs use external storage solutions even if they’re less efficient - it centralizes maintenance of state onto a system that has well defined semantics and can handle repair transparently. Indeed, now contrast with the article on HN discussing orthogonal persistence.
- mdaniel 3y agobecause the front page is a fickle thing: https://news.ycombinator.com/item?id=39615228 https://news.ycombinator.com/item?id=39615228
- stevan 3y ago> This seems like a problem you can’t solve generically and you always end up making trade offs. That shouldn't stop us from solving the problem in the cases where it's possible though? We can tackle the corner cases separately with manual overrides. > This is probably a big reason why most programs use external storage solutions even if they’re less efficient - it centralizes maintenance of state onto a system that has well defined semantics and can handle repair transparently. This is certainly the case today, what I'm asking is: does it always have to be like that in the future?
- vlovich123 3y agoI suspect that it’s impossible in the sense that the “possible” space will look like a distributed storage solution and the rest will look similar to graceful handoff of new connections to new version + shutdown of old version after some time (with forceful disconnect of sessions hanging around).
- stevan 3y agoI give two examples of a stateful upgrade in Erlang/OTP in the motivation, neither rely on distributed storage.
- vlovich123 3y agoUnfortunately the documentation for Erlang doesn’t really describe any pros/cons for anything and I’m not an expert in it so I don’t know what the limitations are for the Erlang approach but they certainly must be (e.g. if you have long running sessions and do several upgrades, are you running N versions of the code & eating up RAM because the old sessions aren’t complete?). As I understand it, Erlang/OTP captures the entire state of the program and it’s a feature of the language and VM to accomplish this. It’s not something you can retrofit into any arbitrary language. For example, your JS app or your Python app or your Rust app won’t be able to do the same easily which means it won’t be robust and it will be error prone. Thus I stand by that there’s no “generic” solution you can bolt onto an arbitrary language.
- stevan 3y ago> if you have long running sessions and do several upgrades, are you running N versions of the code & eating up RAM because the old sessions aren’t complete? I believe Erlang supports two versions running along each other. They capped it at two because back when this was developed there wasn't enough RAM. Joe Armstrong gave at least one talk where he says if he'd have liked to support arbitrary number of versions and garbage collect them as old sessions complete. > Thus I stand by that there’s no “generic” solution you can bolt onto an arbitrary language. The main point of the post is centered around Barbara Liskov saying "maybe we need languages that are a little bit more complete now". I'm not interested in the limitations of current languages, I'm interested in the future possibilities.
- Rygian 3y ago> * State in old system isn’t representable in new system (not a backwards compatible upgrade or more likely a bug exists in handling the new state) New system starts in a backwards compatible mode where it accepted all state that was representable in old system. Transition is achieved after the upgrade, with flag variables.
- vlovich123 3y agoYes, but I’ve seen people regularly struggle to write code that accepts all back compat state + handles it correctly. It’s a very hard problem and bugs are very real. Not convinced it’s a better strategy to go for seamless transition for every session vs other approaches.
- Rygian 3y agoWriting code without bugs is indeed a hard problem. Not only for upgrade paths. Sometimes the tradeoffs of the application make it worth to spend QA resources to validate a staggered upgrade path.
- stevan 3y ago> I’ve seen people regularly struggle to write code that accepts all back compat state + handles it correctly. From the post: > In a world where software systems are expected to evolve over time, wouldn’t it be neat if programming languages provided some notion of upgrade and could typecheck our code across versions, as opposed to merely typechecking a version of the code in isolation from the next?
- vlovich123 3y agoThat’s not an answer because type checking only protects against that class of errors. But you can have a logic bug in your upgrade code `if (old state) { buggy implementation of old compat } else { implementation for new state }` (or `convert(old state) -> new state` if the conversion is external). If you transparently just run the old code instead, then you’re not actually transferring state seamlessly and you run into the choice of “run N versions of code vs terminate sessions” when you have long running sessions. In any case, I think you start to run into real constraints and it’s not clear to me how Erlang solves these with it’s “2 simultaneous versions of the code only”.