> there is always more wisdom in what we throw away than what we innovate
i have found this to be true throughout my career. not that things cannot improve, they can always improve. but assuming the original work wasn't grossly incompetent, anything "new" tend to make the same [undocumented] mistakes
An interesting mathematical caveat is that regardless of whether the majority of the work thrown out is correct, throwing the baby with the bathwater may still be optimal, as it might be the only way to affect change. It's not like decreasing the rate of change would decrease the amount of false positives discarded.
1. Beyond just querying, the stored proc spent a lot of time processing data. As in, looping through cursors, making business logic decisions, calculating things, etc.
2. Having the business logic (not just loading the data) inside the stored procs meant that a change in business logic that would normally only need to update application code, now invalidates the stored procedure's cached execution plan.
If you’re going to process a lot of data, doing that in the database is usually faster, as you avoid moving all the data into a separate server process and then moving it back again. For many queries the round trip to the database server from the application server takes longer that the query itself.
> looping through cursors, making business logic decisions, calculating things, etc.
Interesting. Can you share more details about the "non-data" processing that was done? Were they doing heavy mathematical calculations and such?
> change in business logic that would normally only need to update application code, now invalidates the stored procedure's cached execution plan
As for plan cache invalidation - the most extreme case I saw was on the order of 5-10s. Basically, it depends on the size of that one stored procedure - not on all other stored procedures that may call it or be called by it. What was the actual time that they got?
The KV store had etag support for conditional writes. Etags are only useful to make sure the data didn't change underneath between your read and your write.
Storing the checkpoints along with the mutation was for idempotency. If the checkpoint was in the document, that meant the mutation had succeeded and a retry should be no-op
Hm, I misunderstood, I thought the checkpoint system was also concurrency control to make sure nobody else had changed it from underneath you between read and write, since you had to read and write the whole (mega) document even though you only wanted to change a part (sub-document).
Doesn't the KV provide idempotency on it's own -- so long as you're checking that no changes have happened between read and write, why wouldn't doing the same write twice produce an idempotent result? A change happenening between read and write seems the only reason that would be a problem.
But clearly it's complicated and we don't have the whole picture as to business needs. Definitely sounds awful.
If the thing was mutated between your retries, then wasn't the etag changed by that mutation? So if you know the etag you started with, your conditional update on etag fails due to changed etag. So you fetch it again and start over. Which is the general optimistic locking algorithm.
let's say you have an object like this when you started: {count: 10, etag: 1}. then for some reason, something failed.
when you retry and load the object, you get {count: 12, etag: 3}. how do you know if your previous attempt had successfully persisted or not, or if the updates to the object came from other processes/requests?
you're mixing up conflict handling vs. idempotency
Ah, the system is not capable of giving you a confirmation that your update succeeded, you don't really have a way to know without external "checkpoint" system?
Anyway, okay, I get that I don't get it, and I get that it does sound terrible, agreed!
Look up the two general problem on youtube. Unless the entire end to end operation is wrapped inside a giant transaction, no system in the world can give you the confirmation.
Imagine this: you issued a write, a few things can happen:
1. The callsite crashed, maybe due to an out of memory issue or whatever. You don't know if it succeeded
2. The database returned an acknowledgement, but then the callsite crashed before storing the acknowlwedgement for the next step.
You are under no obligation to continue this conversation of course.
> Unless the entire end to end operation is wrapped inside a giant transaction, no system in the world can give you the confirmation.
But how do people use, say, redis, without an external "checkpoint" system, how do you do, say, an INCR operation, and know if it succeeded or not?
Or are most uses of redis actually dangerous and subject to these error conditions, perhaps it's tolerable to risk that error for most redis use cases, but wasn't for the system under discussion? Most developers using redis definitely aren't using external "checkpointing" systems, or considering if they should be or not -- should they be?
But of course you've convinced me that the system under discussion would have been better off using an rdbms, something I never doubted.
Where our conversation is at, it's not about RDBMS vs. NoSQL.
On the Redis INCR operation example (or an equivalent operation in any database, even in an RDBMS). In short, you don't always have the guarantee to know the result of the operation. This is part of what the 99.9% means when services advertise their reliability. 99.9% of the time you'll get an acknowledgement back, but not 100%.
The question comes to, what happens if I miss an INCR, or if I accidentally double INCR when I retry? For some use cases, it's acceptable: like counting the number of views on a Youtube video. For others, say a financial system counting money or making a payment, that's not OK. That's where idempotency really matters (btw this is why many redis operations have the NX option).
Let's go completely off track for a little bit, say you're Strava. You let devices and users upload workouts to your service. What happens when a phone sent you the workout, then disconnected due to bad receptions? In this case, the phone doesn't know if the operation succeeded so it's forced to retry. How do we make sure the workout isn't created and counted twice on Strava? Well, each workout has a unique UUID generated by the device. Strava uses this UUID to dedup when it receives an upload. The UUID is the idempotency "checkpoint".
In this case, it doesn't matter what kind of database Strava is using. The idempotency problem is the same.
I sometimes wonder what it would be like to be named something like Madonna Tucci or Madonna Bianchi and be born in the late 60s, slowly coming to the realization, as you grew up, that your name would never be fully yours again; that, if you were to write a book or perform in a play as just "Madonna", half the audience would assume you were that damned blonde from New York.
Sometimes, the goal isn’t actually to switch - it’s to have a credible threat of switching. That alone can bring Microsoft to the table with a whole new attitude toward pricing.
Munich pulled off a version of this around 2010: announce a bold move to Linux and open source, let Microsoft panic, enjoy the sudden price cuts, and quietly stay put.
Personally, I think cost is just one part of the equation. The real value is being in a position where you’re not locked in—and where Microsoft knows it. That leverage is worth more than any licensing discount.
For workstation or laptops?? Non-factor for a business.
It is included in Office 365 E3/E5 that also does Intune device management, apps, Defender, the whole shebang. Nobody cares about individual licence costs.
Windows Server? Yea, that costs for sure, but that's not running on laptops.
- Go (the language) is missing the ability to declare something immutable. I don't mean making fields in a struct private then exposing getters. I mean the equivalent of an immutable reference in Rust or `const ref` in C++.
- Go mod and go workspace still can't get a cohesive story together [1]:
- - If my module wants to depend on another module in a workspace, `go mod tidy` would freak out.
- - If I use replace in my go mod file to point to the other modules in the same workspace, `go mod vendor` will copy these workspace modules to the vendor folder as well.
- Go (the language) is missing the ability to declare something immutable. I don't mean making fields in a struct private then exposing getters. I mean the equivalent of an immutable reference in Rust or `const ref` in C++.
- Go mod and go workspace still can't get a cohesive story together [1]:
- - If my module wants to depend on another module in a workspace, `go mod tidy` would freak out.
- - If I use replace in my go mod file to point to the other modules in the same workspace, `go mod vendor` will copy these workspace modules to the vendor folder as well.
i have found this to be true throughout my career. not that things cannot improve, they can always improve. but assuming the original work wasn't grossly incompetent, anything "new" tend to make the same [undocumented] mistakes