Live data from Hacker News

Practices of Reliable Software Design

entropicthoughts.com

51–58 of 58 posts

Re: Practices of Reliable Software Design

#51

Earlier quoted context omitted.

Fail early, and fail noisily . Don't fail silently.

Yes, .NET. I really love how an uncaught exception in a secondary thread simply causes a silent termination of the thread. In the development environment (C#) things work normally but a release version silently eats them.

Perhaps you meant Task?

    var thread = new Thread(() => {
        Thread.Sleep(10);
        throw new Exception("Uh oh!");
    });
    thread.Start();

    Thread.Sleep(100);
    Console.WriteLine("Done!");
fails as expected.

Re: Practices of Reliable Software Design

#52

The first point is one that resonates strongly with me. Counter-intuitivly, the first instinct of a programmer should be "buy that, don't write it" Of course, as a programmer, this is by far not my first instinct. I am a programmer, my function is programming, not purchasing. Of course buying something is always cheaper (compared to the cost of my time) and will be orders of magnitude cheaper once the costs to mainta…

> Counter-intuitivly, the first instinct of a programmer should be "buy that, don't write it" I don't think this is counter intuitive at all... this is the whole premise behind free software. Why write it yourself when someone else already has and there is a community around using and updating it. We all buy the vast majority of our software and it is usually our go to move, unless there is an itch.

In the context of you, at home, wanting to get stuff done, I agree.

But in the context of "you're at work paid to be a programmer" the instinct is to look for things to program.

Re: Practices of Reliable Software Design

#53

The first point is one that resonates strongly with me. Counter-intuitivly, the first instinct of a programmer should be "buy that, don't write it" Of course, as a programmer, this is by far not my first instinct. I am a programmer, my function is programming, not purchasing. Of course buying something is always cheaper (compared to the cost of my time) and will be orders of magnitude cheaper once the costs to mainta…

I'm not sure I 100% agree. I've been thinking a lot lately about the cost of off-the-shelf solutions from the perspective of sustainability, and there is a cost beyond money. The performance of software almost always degrades over time. By buying Foo off-the-shelf, you are saying, "I am ok with getting on the same bloat-dictated hardware upgrade cycle as Foo." Of course you have the option of buying Foo and never upg…

Certainly nothing is free (not even Free Software.) So there will surely be times when building is better than buying.

I suppose the key is to understand the hidden costs with both approaches. The salary vs subscription cost is part of it, but there also subtle things, like flexibility (or lack thereof in bought systems), security (or lack thereof in homegrown systems) and so on.

Re: Practices of Reliable Software Design

#54

My first thought upon seeing the prompt: If you would build an in-memory cache, how would you do it? It should have good performance and be able to hold many entries. Reads are more common than writes. I know how I would do it already, but I’m curious about your approach. Was to add this requirement since it comes up so often: Let's assume that keys accessed follow a power law, so some keys get accessed very frequent…

In a typical LRU cache every read is a write in order to maintain access order. If this is a concurrent cache then those mutations would cause contention, as the skewed access distribution leads to serializing threads on atomic operations trying to maintain this ordering. The way concurrent caches work is by avoiding this work because popular items will be reordered more often, e.g. sample the requests into lossy ring buffers to replay those reorderings under a try-lock. This is what Java's Caffeine cache does for 940M reads/s using 16 thread (vs 2.3B/s for an unbounded map). At that point other system overhead, like network I/O, will dominate the profile so trying to rearrange the hash table to dynamically optimize the data layout for hot items seems unnecessary. As you suggest, one would probably be better served by using a SwissTable style approach to optimize the hash table data layout and instruction mix rather than muck with recency-aware structural adjustments.

The fastest retrieval will be a cache hit, so really once the data structures are not the bottleneck then the focus should switch to the hit rates. That's where the Count-Min sketch, hill climbing, etc. come into play in the Java case. There's also memoization to avoid cache stampedes, efficient expiration (e.g. timing wheels), async reloads, and so on that can become important. Or if a dedicated cache server like memcached, one has to worry about fragmentation, minimizing wasted space (to maximizing usable capacity), efficient I/O, etc. because every cache server can saturating the network these days so the goals shifts towards reducing the operational cost with stable tail latencies. What "good performance" means is actually on a spectrum because one should optimize for overall system performance rather than any individual, narrow metric.

Re: Practices of Reliable Software Design

#55

My first thought upon seeing the prompt: If you would build an in-memory cache, how would you do it? It should have good performance and be able to hold many entries. Reads are more common than writes. I know how I would do it already, but I’m curious about your approach. Was to add this requirement since it comes up so often: Let's assume that keys accessed follow a power law, so some keys get accessed very frequent…

You should check out the FASTER paper from Microsoft. It specifically covers how to create a K/V log that spills to disk for older keys, but keeps recent keys in memory.

Re: Practices of Reliable Software Design

#56

Earlier quoted context omitted.

Yes, .NET. I really love how an uncaught exception in a secondary thread simply causes a silent termination of the thread. In the development environment (C#) things work normally but a release version silently eats them.

Perhaps you meant Task ? var thread = new Thread(() => { Thread.Sleep(10); throw new Exception("Uh oh!"); }); thread.Start(); Thread.Sleep(100); Console.WriteLine("Done!"); fails as expected.

Yeah, I forgot exactly where the evil was.

Re: Practices of Reliable Software Design

#57

Earlier quoted context omitted.

Perhaps you meant Task ? var thread = new Thread(() => { Thread.Sleep(10); throw new Exception("Uh oh!"); }); thread.Start(); Thread.Sleep(100); Console.WriteLine("Done!"); fails as expected.

Yeah, I forgot exactly where the evil was.

The "evil" is what makes .NET scale with project complexity and dependency graph size - tasks are cheap and easy to spawn. You do not want to be beholden to a third party dependency that spawns a task that ends up throwing an exception somewhere crashing your entire application even if you don't care about it in the slightest.

You can opt into unobserved task exceptions terminating the application if that's what you are looking for, and maybe subscribe to TaskScheduler.UnobservedTaskException event too: https://learn.microsoft.com/en-us/dotnet/api/system.threadin...

Notably, this is an issue in Go where a package might spawn a goroutine with uncaught panic, like dereferencing a nil which is common, and you have no recourse to this at all. Perhaps it did historically make sense in Go, but it continues to bite people and requires more careful vetting of the dependencies. Moreover, in type-safe memory-safe languages uncaught exception might be a perfectly fine thing to ignore.

When you fire and forget a task and it ends up throwing, GC will simply collect all the objects that no longer have GC roots, and the finally blocks will be ran, and finalizers will be called eventually on Gen2 GC if there are any - the standard library and most community abstractions that interact with manual memory management through interop or otherwise end up being watertight as a result of that.

Re: Practices of Reliable Software Design

#58
It seems like the author had some very specific read and write pattern in mind when they designed for performance, but it's never explicitly stated. The problem setting only stated that "reads are more common than writes", but that's not really saying much when discussing performance. For example, a HTML server commonly has a small set of items that are most frequently read, and successive reads are not very strongly dependent. On the other hand, a PIM system may often get iterative reads correlated on some fuzzy search filter, which will be slow and thrash cache pretty badly if the system is optimized for different access patterns.

When designing software, you first need to nail down the requirements, which I didn't really find in TFA.

Post reply on HN