There's also a bunch from Google at various stages of QUIC development.
1,103 karma · joined August 22, 2011
[ my public key: https://keybase.io/cakoose; my proof: https://keybase.io/cakoose/sigs/gNz7dqjDBzs5anzP30jhCtr5bcogtlGjKLP2yghDN9I ]
There's also a bunch from Google at various stages of QUIC development.
How exactly does widespread adoption of QUIC destroy decentralization?
Is it any different from widespread adoption of HTTPS, e.g. with browsers becoming increasingly discouraging towards unencrypted HTTP?
> What if you used one of the managed RDBMS services offered by the big cloud providers?
Yeah, AWS RDS "multi-AZ" does a good job of taking care of HA for you. (Google Cloud SQL's HA setup is extremely similar.) But you still get 1-2 minutes of full unavailability when hardware fails.
I haven't operated etcd in production myself, but I assume it does better because it's designed specifically for HA. You can't even run less than three nodes. (The etcd docs talk about election timeouts on the order of 1s, which is encouraging.)
For many use cases, 1-2 minutes of downtime is tolerable. But I can imagine situations where availability is paramount and you're willing to give up scale/performance/features to gain another 9.
There's also the Einstein line: "Everything should be made as simple as possible, but no simpler."
I have no idea if PeerJS is actually too simple, but the "shape" of KaoruAoiShiho's statement seems fine.
For example, compilers already model the CPU's pipeline and ILP capacity to inform instruction selection. VLIW designs are an attempt to make that cooperation more explicit, similar in spirit to branch/prefetch hints. Maybe there's still a way to do that well?
And re: microarchitectural changes in the next hardware rev: https://news.ycombinator.com/item?id=25237056
These ideas might ultimately fail, but they seem worth looking in to, at least.
Without non-repudiation, you don't automatically get to frame someone for whatever. You need to provide the usual (non-DKIM) evidence of whatever you're claiming.
And even with non-repudiation, you can still try and frame someone. Not having the DKIM signature might be suspicious in some circumstances, but it doesn't eliminate the possibility.
Second, "innocent" is not that simple.
I don't want my private communication to become public, or publicly verifiable. That doesn't mean I'm not "innocent". This is not a fringe concept: https://en.wikipedia.org/wiki/Nothing_to_hide_argument
"Give me six lines written by the most honest man in the world, and I will find enough in them to hang him." - Cardinal Richelieu
Not on it's own, but it's a critical step in this chain:
1. DKIM verifies that a message was sent by Gmail.
2. We assume Gmail is careful with its keys.
3. We assume Gmail doesn't forge addresses.
4. Find evidence that links me to that address.
Most people will readily grant #2 and #3. Now we just need #4, which can be easy.
No, it's not cryptographically verified end-to-end, but it's good enough to convince a court or to convince a respectable news organization to run a story.
A union type "A | B" means "a value of type A or a value of type B". Example:
function f1(): String | Integer {
if (rand()) {
return "hello"
} else {
return 12
}
}
function f2(x: String | Integer) {
switch (typeof x) {
case String: return "string: " + x
case Integer: return "integer: " + x
}
}
The type "String | String" is exactly equivalent to "String".A tagged union (aka sum type) "A + B" means "either a left value or a right value; if it's the left, it has type A, if it's the right it has type B".
function g1(): String + Integer {
if (rand()) {
return Inl("hello")
} else {
return Inr("bye")
}
}
function g2(x: String + String) {
switch (x) {
case Inl(s): return "left value: " + x
case Inr(s): return "right value: " + x
}
}
The type "String + String" has one bit of additional information than just "String".> And ultimately, adding quality tests is the best you can do to improve your software.
Tests are great, but that's not "the best you can do". You can redesign things to be simpler, and there's even an example of that earlier in the article:
> By re-architecting the design, the code could be hugely simplified. So, I had rewritten the whole thing from scratch over the past few months.
> The rewritten, simplified codebase did fix a variety of Jepsen issues. The last major stronghold was the bank test.
A better design can make it easier to informally "prove" the absence of certain issues.
(But even "just" end-to-end encryption is more difficult than many realize.)
> To mitigate this problem, a pause() method was added. This could solve the problem, but it required extra code; and since the flooding issue only presents itself when the process is very busy, many Node programs can be flooded with data. The result is a system with bad tail latency.
Are they saying that even with correct use of pause() there are still issues?
Bloom filters allow adding members, but not removing them.
Cuckoo filters allow removing members: https://www.cs.cmu.edu/~dga/papers/cuckoo-conext2014.pdf
And just for completeness, storing everything losslessly in a btree, radix tree, or hash table would probably be under 300 kB. (80 kB just for the IDs plus, say, an additional ~2x overhead.)
> If you're looking for concurrent code that doesn't use `unsafe` you won't find any.
The Rust standard library provides a bunch of concurrency primitives. You can write 100% safe Rust code use these primitives to do things concurrently, and Rust guarantees that your resulting program is free of a specific category of bugs: memory errors, data races, etc. No other mainstream language gives you this combination of concurrency safety and efficiency.
The implementation of the concurrency primitives, however, often includes assembly, or C, or unsafe Rust code, which the Rust compiler can't provide guarantees for. We have to rely on humans for that. This is the norm for almost all programming languages.
Ideally, yes, it would be nice if the safe subset of Rust were powerful enough to implement efficient concurrency primitives, but that would add a TON of complexity to the type system.
People are generally ok with the Rust standard library using assembly, C, or unsafe Rust, because even though it can be tricky to write this kind of code correctly, the standard library is maintained by Rust experts who take every change seriously.
People tend to worry more about third party libraries because, on average, the authors are not Rust language experts and may not understand the subtleties.
Yes, the post starts out describing an issue with how Gunicorn listens for connections. Like you said, there are better libraries than Gunicorn so that's not a reason to jump to Ruby.
However, the article goes on to talk about other things, like the lack of real multithreading, import-time code execution, and overall efficiency, all of which also apply to Ruby.
I'm not saying Python or Ruby are a bad choice. It's just that MapleWalnut was soliciting an alternative that doesn't have the problems described in the original post, so Ruby doesn't qualify.
Node.js is much faster because V8 is much faster, but it's still basically single threaded, so you need to run process-per-CPU, which is what the original blog post (to which this post is a follow-up) was complaining about.
Let's say your server has 4 CPUs. The conservative option is to limit yourself to 4 requests at a time. But for most web applications, requests use tiny bursts of CPU in between longer spans of I/O, so your CPUs will be mostly idle.
Let's say we want to make better use of our CPUs and accept 40 requests at a time. Some environments (Java, Go, etc) allow any of the 40 requests to run on any of the CPUs. A request will have to wait only if 4+ of the 40 requests currently need to do CPU work.
Some environments (Node, Python, Ruby) allow a process to only use a single CPU at a time (roughly). You could run 40 processes, but that uses a lot of memory. The standard alternative is to do process-per-CPU; for this example we might run 4 processes and give each process 10 concurrent requests.
But now requests will have to wait if more than 1 of the 10 requests in its process needs to do CPU work. This has a higher probability of happening than "4+ out of 40". That's why this setup will result in higher latency.
And there's a bunch more to it. For example, it's slightly more expensive (for cache/NUMA reasons) for a request to switch from one CPU to another, so some high-performance frameworks intentionally pin requests to CPUs, e.g. Nginx, Seastar. A "work-stealing" scheduler tries to strike a balance: requests are pinned to CPUs, but if a CPU is idle it can "steal" a request from another CPU.
The starvation/timeout problem described in the post is strictly more likely to happen in process-per-CPU, sure. But for a ton of web app workloads, the odds of it happening are low, and there are things you can do to improve the situation.
The post also talks about Gunicorn accepting connections inefficiently and that should probably be fixed, but that space has very similar tradeoffs <https://blog.cloudflare.com/the-sad-state-of-linux-socket-ba....
Every time you access an object, its reference count is mutated, which will the memory page to be copied.
There are workarounds if you're willing to mess with the Python interpreter: https://instagram-engineering.com/copy-on-write-friendly-pyt...
After I changed the "--bus" CSS variable from 30px to 40px, I got the stepping effect.
You'd want a probability more like 1-in-a-million (or 1-in-a-billion). That's 2^39 (or 2^34), which is 1000 messages a second for 17 years (or 7 months).
Again, probably still safe for many use cases. But you do have to think about it for a second. XChaCha20's 192-bit nonce capacity lets you totally ignore that factor, which is a nice property for generic crypto recommendations.
With AES-GCM, if you reuse a nonce with two different plaintexts, you reveal the XOR of the two plaintexts!
> This code avoids some of the numerous pitfalls that you can encounter when constructing encryption schemes yourself. For example, it ensures that you use a randomly selected nonce, and that you authenticate your ciphertext.
The AES-GCM nonce is only 96 bits, which might be enough in many contexts, but is still a little short for comfort when selecting nonces randomly: https://www.imperialviolet.org/2017/05/14/aesgcmsiv.html
It's surprising that the blog post just declared success without bringing this up at all.
(It looks like AES.GCM.seal does let you specify the nonce, though, in cases where you can maintain a counter yourself.)
when you do `foo()` it foos the bar.
It does the right thing for me. This was the case yesterday and it still is today.It's possible that the author got a different release. It's also possible that the author was trying other very similar scenarios that behave differently, e.g. backquotes behaves differently at word boundaries and inside a span that has already been converted to the code-formatted rendering.
Overall it's pretty confusing, but the simple case that the original article points out works fine.
Without the initializer, factorial(0) doesn't work.
Nah, lots of other languages can do this.
Python:
def factorial(i):
return reduce(operator.mul, range(2, i+1), 1)
Ruby: def factorial(i)
(2..i).reduce(1, :*)
end
Haskell: factorial n = foldl (*) 1 [2..n]But if I were describing Git to others, I wouldn't say "Git is a distributed, blockchain-based VCS" because that would give the wrong idea.