2,823 karma · joined October 6, 2014
For example:
DELETE FROM example WHERE id = (SELECT RANDOM(0, 100) FROM other_table LIMIT 1)
This could delete multiple rows in principle, since there may be multiple rows where the `=` expression is true.First, let's make sure we're talking about the same two examples.
A:
DELETE FROM task_queue
WHERE id = (
SELECT id FROM task_queue
WHERE queue_group_id = 15
LIMIT 1
FOR UPDATE SKIP LOCKED
)
B: DELETE FROM task_queue
WHERE id IN (
SELECT id FROM task_queue
WHERE queue_group_id = 15
LIMIT 1
FOR UPDATE SKIP LOCKED
)
You seem to be saying that B may exhibit the problem whilst A does not. (ie. there is a different between using `=` vs `IN`). I would like to see some documentation justifying that.Here's my logic:
- In both cases, the outer query is run once.
- In both cases the outer WHERE clause is evaluated for each row in `task_queue`.
- In both cases, it is up to the optimizer whether the result of the subquery is materialized or not.
- In both cases, if the subquery is not materialized, multiple rows in the outer query may match the condition.
In practice, it may be that the optimizer always materializes uncorrelated subqueries on the RHS of an `=` expression. My contention is whether that is a formal guarantee.
However, I think it's telling that almost every single comment here has a different understanding of how PostgreSQL works, and nobody can link to any documentation that conclusively addresses this case. I think that indicates that this is a failing if not of implementation, then of documentation.
For example: another comment says that `=` forces PostgreSQL to evaluate the subquery exactly once. I couldn't find any justification for that statement. Is it guaranteed? I don't know.
Another comment says "if only the subquery was ordered then the result would be consistent", but AFAIK, this is not true in the presence of "SKIP LOCKED".
I think the problem stems from the fact that the SQL standard is written from an academic point of view: the ordering of execution of subqueries is not defined because it is irrelevant within the relational algebra, so optimizations which change the execution order are just that: optimizations, with no observable side effects.
Real database systems do not actually have the nice algebraic properties of the relational algebra... The same subquery may give different results over multiple executions, etc. Given this, it's important that the database system give sufficient and sufficiently clear guarantees such that they can still be used correctly, and so that the optimizations implemented by the system can be justified.
Not remotely true. There are plenty of web apps that work just fine with a standard fixed set of API endpoints with minimal if any customization of responses. Not to mention the web apps that don't have any client-side logic at all...
GraphQL solves a problem that doesn't exist for most people, and creates a ton of new problems in its place.
The value of GraphQL is also its downfall. The flexibility it offers to the client greatly complicates the backend, and makes it next to impossible to protect against DoS attacks effectively, or even to understand app performanmce. Every major implementation of GraphQL I've seen has pretty serious flaws deriving from this complexity, to the point that GraphQL APIs are more buggy than simpler fixed APIs.
With most web apps having their front-end and back-end developed in concert, there's simply no need for this flexibility. Just have the backend provide the APIs that the front-end actually needs. If those needs change, also change the backend. When that kind of change is too hard or expensive to do, it's an organisational failing, not a technical one.
Sure, some use-cases might warrant the flexibility that GraphQL uses. A book tracking app does not.
I don't think you mean this, but elaborating on why a non-intrusive linked list is not very useful, that's because it takes linear time to find an object in a non-intrusive linked list, but constant time in an intrusive linked list.
For example:
struct Node {
Node* prev_by_last_update;
Node* next_by_last_update;
Node* prev_with_owner;
Node* next_with_owner;
Payload data;
}
This intrusive structure allows reassigning and updating nodes, whilst being able to efficiently find eg. the next most recently updated node, or to iterate over nodes with the same owner.Having said that, intrusive linked lists are quite an important data structure in code which is required to be allocation-free. Kernel code, signal handlers and code which requires consistent performance (eg. audio processing) need to be able to move objects between containers or have objects in multiple containers without allocating.
So the designers of the Cell processor made some mistakes and therefore the entire concept is bunk? Because you've seen a concept done badly, you can't imagine it done well?
To be clear, I'm not criticising those designers, they probably did a great job with what they had, but technology has moved on a long way from then... The theoretical foundations for memory models, etc. are much more advanced. We've figured out how to design languages to be memory safe without significantly compromising on performance or usability. We have decades of tooling for running and debugging programs on GPUs and we've figured out how to securely isolate "users" of the same GPU from each other. Programmers are as abstracted from the hardware as they've ever been with emulation of different architectures so fast that it's practical on most consumer hardware.
None of the things you mentioned are inherently at odds with more parallel computation. Whether something is a good idea can change. At one point in time electric cars were a bad idea. Decades of incremental improvements to battery and motor technology means they're now pretty practical. At one point landing and reusing a rocket was a bad idea. Then we had improvements to materials science, control systems, etc. that collectively changed the equation. You can't just apply the same old equation and come to the same conclusion.
The SAML spec itself is fairly reasonable, but is built upon XML signatures (and in turn, XML canonicalization) which are truly insane standards, if they can even be called such.
Only a committee could produce such a twisted and depraved specification, no single mind would be capable of holding and combining such contradictory ideas.
It would be so simple to just transmit signatures out-of-band and SAML would be a pleasure to implement.
People consistently misuse the Swiss cheese security metaphor to justify putting multiple ineffective security barriers in place.
The holes in the cheese are supposed to represent unknown or very difficult to exploit flaws in your security layers, and that's why you ideally want multiple layers.
You can't just stack up multiple known to be broken layers and call something secure. The extra layers are inconvenient to users and readily bypassed by attackers by simply tackling them one at a time.
Security by obscurity is one such layer.
Seems more like a question of degree. Dividends are also taxed as income so ~36% is already paid in tax depoending on the income of the shareholder. Increasing the corporate tax rate to 40% brings the effective tax rate to ~52%.
In my experience there's a more fundamental problem with large companies. In a small company, the best way to succeed as an individual (whatever position you have) is for the company as a whole to succeed. At a very large company, the best way to succeed is to be promoted up the ladder, whatever the cost. This effect is the worst at the levels just below the top: you have everything to lose and nothing to gain by the company being successful. It's far more effective to sabotage your peers and elevate yourself rather than work hard and increase the value of the company by a couple of percentage points.
The thing is, the people that have been there since the beginning still have the mindset of helping the company as a whole succeed, but after enough time and enough people have been rotated out, you're left with people at the top who only care about the politics. To them the company is simply a fixture - it existed before them and will continue to exist regardless of what they do.
As a software developer I want strong abstractions without bloat.
LLMs are so successful in part because they are a really strong abstraction. You feed in text and you get back text. Depending on the model and other parameters your results may be better or worse, but changing from eg. Claude to ChatGPT is as simple as swapping out one request with another.
If what I want is to run AI tasks, then GPUs are a poor abstraction. It's very complicated (as Fly have discovered) to share them securely. The amount of GPU you need could vary dramatically. You need to worry about drivers. You need to worry about all kinds of things. There is very little bloat to the ChatGPT-style abstraction, because the network overhead is a negligable part of the overall cost.
If I say I don't want magic, what I really mean is that I don't trust the strength of the abstraction that is being offered. For example, when a distributed SQL database claims to be PostgreSQL compatible, it might just mean it's wire compatible, so none of my existing queries will actually work. It might have all the same functions but be missing support for stored procedures. The transaction isolation might be a lie. It's not that these databases are bad, it's that "PostgreSQL as a whole" cannot serve as a strong abstraction boundary - the API surface is simply too large and complex, and too many implementation details are exposed.
It's the same reason people like containers: running your application on an existing system is a very poor abstraction. The API surface of a modern linux distro is huge, and includes everything from what libraries come pre-installed to the file-system layout. On the other hand the kernel API is (in comparison) small and stable, and so you can swap out either side without too much fear.
K8S can be a very good abstraction if you deploy a lot of services to multiple VMs and need a lot of control over how they are scaled up and down. If you're deploying a single container to a VM, it's massively bloated.
TLDR: Abstractions can be good and bad, both inherently, and depending on your use-case. Make the right choice based on your needs. Fly are probably correct that their GPU offering is a bad abstraction for many of their customer's needs.
It is the maintainers job to determine whether a project should be pursued at all.
If a maintainer says "yes I'd be happy to accept feature X", and then simply rejects all PRs implementing X without giving feedback then they're a bad maintainer!
That's essentially what's happening here due to the fact that the kernel maintainers have not come to a coherent decision regarding R4L.
Some maintainers are rejecting changes as a way to block a project they disagree with - ie. there is no path forward for the contributor. I wouldn't assume this normally, but they're not hiding this fact, it's self proclaimed.
On the other hand, Linus has been largely in favour of the R4L project, and gave the green light for it to go ahead.
The Linux kernel maintainers need to figure out among themselves whether they want to allow Rust to be introduced or not, and if so under what constraints. If they can't come to an agreement, they're wasting everyone's time.
Once that happens, either the project is canned, or people can stop arguing over if these changes should be upstreamed, and start arguing over how instead, and that's a lot more productive.
For example:
- Steps should be small but fallible operations - eg. sending a request to an external service. You generally want to tailor the retry logic on steps to the specific task they are doing. Doing too much in a step can increase failure rates or cause other problems due to the at-least-once behaviour of steps.
- The article makes a big deal of "Hello" being printed 5 times in the event of a crash, but durable execution doesn't guarantee this! You can never have exactly-once guarantees of side-effectful functions like this without cooperation from the other side. For example, if the external service supports idempotency via request IDs then you can generate an ID in a separate step and then use that in your request to get exactly-once behaviour. However, most services don't offer this. Crashes during a step will cause the step to re-run, so durable execution only gives you at least once behaviour.
- Triggering the workflow itself is a point of failure. In the example, the workflow decorator generates an ID for the workflow internally, but for triggering a workflow exactly once the workflow ID needs to be externally generated.
- The solution is light-weight in terms of infrastructure, but not at all lightweight in terms of performance.
The error rate needs to come down significantly from where it's currently at, and we'd also need AIs that cover each others weaknesses - at the moment you can't just run another AI to fix the 20% of errors the first one generated because they tend to have overlapping blind spots.
Having said all that, I think AI is hugely useful as a programming tool. Here are the places I've got most value:
- Better IDE auto-complete.
- Repetetive re-factors that are beyond what's possible to do with a regex. For example, replaing one function with another whose usage is subtly different. LLMs can crack out those changes across a codebase and it's easy to review.
- Summarizing and extracting intent from dense technical content. I'm often implementing some part of a web standard, and being able to quickly ask an AI questions, and have it give references back to specific parts of the standard is super useful.
- Bootstrapping tests. The hardest part of writing tests in my experience is writing the first one - there's always a bit of boilerplate that's annoying to write. The AI is pretty good at generating a smoke test and then you can use that a basis to do proper test coverage.
I'm sure there are more. The point is that it's currently incredibly valuable as a tool, but it's not yet capable of being an agent.
In principle the insurance company then dictates security requirements back to the company in order to keep the premiums manageable.
However, in practice the insurance company has no deep understanding of the company and so the security requirements are blunt and ineffective at preventing breaches. They are very effective at covering the asses of the decision makers though... "we tried: look we implemented all these policies and bought this security software and installed it on our machines! Nobody could possibly have prevented such an advanced attack that bypassed all these precautions!"
Another problem is that often the IT at large enterprises is functionally incompetent. Even when the individual people are smart and incentivised (which is no guarantee) the entire department is steeped in legacy ways of doing things and caught between petty power struggles of executives. You can't fix that with financial incentives because most of these companies would go bankrupt before figuring out how to change.
I don't see things improving unless someone spoon-feeds these companies solutions to these problems in a low risk (ie. nobody's going to get fired over implementing them) way.
What are you doing that requires dynamically building a query?
For example:
SELECT * FROM example
WHERE ($1 IS NULL OR field1 = $1)
AND ($2 IS NULL OR field2 = $2)
...
When you supply the parameters to this prepared statement, the constant conditions should be optimized away by the query planner if you're using a decent database.- Everyone knows at least some basic SQL, even non-technical users have often encountered it in some form.
- The documentation for eg. PostgreSQL is for SQL, so if you write queries in anything else you have to mentally translate back and forth, and so you need to know SQL anyway.
- Any external tools you use to interact with the database will use SQL.
- Changing the queries doesn't require an expensive compilation step - `sqlx` gets the best of both worlds in this respect, in that it is able to type-check the parameters and rely on the database itself to validate the query, so you don't end up with a ton of type-system shenanigans that increase compilation times.
Maybe for a brand new database, a better query language could win out, but having used sqlx I can't imagine going back to a query-builder style interface for existing SQL databases.
It's definitely getting more complex, but C++ has a huge lead haha. C++ is like a fractal in that you can look at almost any feature closer and closer to reveal more and more complexity, and there are a lot of features... Here's a page on just one dark corner of the language: https://isocpp.org/wiki/faq/pointers-to-members and it interacts in interesting ways with all the other corners (like virtual vs non-virtual inheritance) in fun and exciting ways...
Also, there are far more ways to cause UB in C++. Rust has a big lead on formalizing what constitutes UB, and even those rules you only need to learn if you are using "unsafe", whilst in C++ you don't have that luxury.
- Performance
- Stability
- Convenience
- Security
This differs from eg. Windows because on Windows the stable interface to the OS is in user-space, not tied to the syscall boundary. This has resulted in unfortunate compromises in the design of various pieces of OS functionality.
Thankfully things like futex and io-uring have dropped the "convenience" constraint from the syscall itself and moved it into user-space. Convenience is still important, but it doesn't need to be a constraint at the lowest level, and shouldn't compromise the other ideals.