HNHacker News
TopNewBestAskShowJobs

wmanley

972 karma · joined June 18, 2018

submissionscomments
wmanley··on Show HN: Reduce SQLite database size by up to 80% with transparent compression
Thanks, but that wasn't quite what I meant. What I was thinking about was processing existing gzipped or zstd compressed JSON rather than having a new compression format.

I could see how customising the compressor could be helpful - a bit like how there's an `--rsyncable` option to `gzip`, but I'd like to keep compatibility with existing formats. I like to avoid coming up with new formats whenever possible because you might want to mine the data in 20 years time - and if it's a custom format it'll be much harder to deal with.

I don't have a current use-case for this, but I like thinking about it. In particular I like data stores to be as simple as possible. Managing persistent state is the hard bit of most systems as unlike with runtime state or code you have to manage forwards and backwards compatibility, migrations, etc. and you can't just "turn it off and on again" to fix issues.

wmanley··on Show HN: Reduce SQLite database size by up to 80% with transparent compression
You'd compress the JSON, and leave the data that you extract from the JSON uncompressed using Generated Columns[1]. That way you can save the original JSON data, but still have fast querying, etc.

It would look something like this:

    CREATE TABLE t1(
        json TEXT,
        data INT GENERATED ALWAYS AS (json_extract(body, '$.d')) STORED
    );
If all you need is fast querying you could make `data` a `VIRTUAL` column and set up an index on it.

You can read more about this kind of technique here: https://dgl.cx/2020/06/sqlite-json-support

[1]: https://www.sqlite.org/gencol.html

wmanley··on Show HN: Reduce SQLite database size by up to 80% with transparent compression
On that subject I've often thought of writing a combination decompressor/JSON parser that can parse the contents of the dictionary before decompression for faster JSON parsing and lower memory usage.
wmanley··on Show HN: Reduce SQLite database size by up to 80% with transparent compression
This is really neat. I like how the compression can be applied to specific columns rather than whole rows - or whole pages. I could imagine storing JSON from an HTTP request here and using Generated Columns[1] to extract the bits my application is interested in.

I also like how simple and powerful the custom partitioning is.

[1]: https://www.sqlite.org/gencol.html

wmanley··on Permissive forwarding rule leads to unintentional exposure of containers (2021)
With apt2ostree[1] we use lockfiles to allow us to version control the exact versions that were used to build a container. This makes updating the versions explicit and controlled, and building the containers functionally reproducible - albeit not byte-for-byte reproducible.

[1]: https://github.com/stb-tester/apt2ostree#lockfiles

wmanley··on Show HN: Top Links from Hacker News, Reddit, Techmeme, PH on a Single Page
I think "common culture" is precisely the problem with reddit. Before reddit there were many different separate forums. Each had to be signed up to separately and each had their own culture. With reddit there's little barrier between reddits, which has lead to a shared culture across the whole site. The culture is generally friendly, but is immature and superficial. It's great for small-talk, but useless and frustrating if you actually want to learn something.

Maybe I'm bitter, but I used to really enjoy r/rust up until 2 years ago or so. Now it's completely inane and it's no longer worth the time to sort through the noise to find the signal.

wmanley··on Simple declarative schema migration for SQLite
Wow, I hadn't come across that before. It's so close to being what we want, but not quite right. They've even considered this specific use case:

> 3. The sqldiff utility is not designed to support schema migrations and is forgiving with respect to differing column definitions. Normally, only the column names and their order are compared for like-named tables before content comparison proceeds. > > However, the single-table comparison option, with "sqlite_schema" named, can be used to show or detect detailed schema differences between a pair of databases. When doing this, the output should not be used directly to modify a database.

wmanley··on Simple declarative schema migration for SQLite
Disclosure: I'm the co-author of the blog post.

One nice thing about this approach is that because it works by inspecting a database that has been put in a desired state. This means it doesn't matter how you set up your database (with an ORM, a SQL file, etc.) the same function can be used to do the migration.

I'd really like if something like this were built into SQLite. That way we'd have more confidence that it would be/stay correct and complete. It seems generic enough that it could be a part of built-in functionality. I would have proposed it upstream, but AIUI the SQLite authors don't accept external patches.

Edit: Another benefit of this being built-in: there are plenty of places where SQLite is applicable where you can't, or don't want to run Python.

wmanley··on Simple declarative schema migration for SQLite
Disclosure, I'm the co-author of the blog post.

> My preferred approach to database migrations is the one implemented by Django: migrations are a sequence of transformations stored in files on disk, and the database includes a table that says which of those migrations have been applied. This keeps everything in version control and means there's no chance of the wrong migration being applied in the wrong way.

Do the transformations have to be applied in sequence? If so how does this handle resolving git conflicts? With our system mostly conflicts in our database.sql script are resolved automatically with git merging. And CI works well because we downgrade the database to the version of database.sql found in $(git merge-base main HEAD) before running integration tests.

wmanley··on The money I saved as a child would buy one picogram of gold today
The lesson isn’t how to invest, it’s how to not spend.
wmanley··on Addressing Criticism of RISC-V Microprocessors
> Are you worried that the processor in your keyboard or mouse isn't backwards compatible with the processor you used in your previous mouse or keyboard?

No, but the keyboard manufacturer might be, as might the people who are writing and optimising compilers targetting that CPU.

wmanley··on Addressing Criticism of RISC-V Microprocessors
Things get locked in not by standards, but by usage. If your software depends on particular instructions being present you’re not going to buy a processor that has superseded those instructions, even if the new instructions conform to a theoretically cleaner design.

Everything being an extension (and thus removable) is a strength in some specific circumstances, but is a weakness in most.

wmanley··on The time might come when we add some JSON specific command line options
It seems the goal is to make it easier to craft JSON by having curl perform escaping, while the proposal would seem to require some sort of in-memory tree representation of the data.

One alternative would be to provide escaping more directly like this:

    curl --json '{
      "map": %s,
      "prime": [
        %i,
        %i
      ],
      "target": {
        "x": %i,
        "y": %i
      }
    }' "$continent" "$p1" "$p2" "$x" "$y" https://example.com
And then curl would do the substitution with the appropriate type-specific escaping for each variable. This has a few nice properties:

1. What's on the command line resembles what's actually going to be sent.

2. Curl doesn't actually need to parse (nor validate) the JSON, or to create a tree representation of the data within itself. %s is invalid JSON anyway, so you can do a string substitution - all you need to keep track of are matching quotes (including escape sequences).

I've used a printf style format string here, which could be expanded for extra convenience. For example the Python-style `%(env_var)s` sequences could be used which could expand environment variables directly. Or something could be added for convenient handling of bash arrays.

wmanley··on Systemd service sandboxing and security hardening (2020)
I have a bash alias for `make` and `ninja` to do something similar. Just having all the spawned processes in a cgroup helps with system interactivity while building. This works because the kernel will then schedule the whole build as a single unit against the other work on the system, rather than scheduling each process that the build spawns against every other process that I'm running.
wmanley··on Learn the workings of Git, not just the commands (2015)
For me it was "Git for Computer Scientists": https://eagain.net/articles/git-for-computer-scientists/
wmanley··on Systemd, 10 years later (2020)
To pick just a small piece of the pie:

Dependencies in make are equivalent to systemd's Requires= and After= combined. Systemd is far more general in this regard, both in that Requires= and After= are separate concepts, but also in that there is Wants=, BindsTo=, etc. and their inverses (RequiredBy=, Before=, etc.). Sometimes when I'm writing Makefiles I wish Make were capable of expressing some of these concepts.

wmanley··on Bitcoin: Addressing the Ponzi Scheme Characterization
I don't know why you're being downvoted: You've hit the nail on the head - Bitcoin is a Pyramid scheme, not a Ponzi scheme.

I've been seeing comparisons to Ponzi schemes a lot in the last few weeks. AFAICS "Bitcoin as Ponzi scheme" is a useful strawman for pro-bitcoin arguments because it's so easily defeated. Bitcoin as Pyramid scheme is a much stronger argument, with far fewer articles dedicated to it.

wmanley··on Go Replaces Interface{} with 'Any'
> the code is not "equivalent" when it comes to refactoring and certain changes.

Yes, there are a very specific and limited set of changes you could make to the types here and not have to change this code. You can't replace `Result<>` with `Option<>` because of `map_err`. You could replace `Result<>` with something else that is very `Result<>`y, but your flexibility would be very limited if you didn't want to change the signatures of `with_some_error_handling_that_can_rescue` or `some_error_processing`.

I'm sure it's possible to contrive an example where this would help, but I don't believe that it would be that much of a help very often in practice. I think it's just a bit more monady and people who take the time to learn monads then want to apply that wherever they can.

I'm not saying that the combinators should never be used, but that each additional one you use increases the cognitive burden of reading your code. So the question becomes: which of the combinators are worth it.

I would argue that `.map_err()` is useful as it compliments the `?` operator. Hopefully with (and often without) `try!` blocks many of the other ones can go away. In particular I think that language constructs are almost always better than `.and_then()`.

wmanley··on Go Replaces Interface{} with 'Any'
This is equivalent to:

    let x = match may_fail_who_knows() {
        Ok(y) => Ok(another_computation_which_can_fail(use_value(x))),
        Err(e) => with_some_error_handling_that_can_rescue(
            some_error_processing(e)),
    };
    match x {
        Ok(y) => y,
        _ => a_default_value,
    }
It's a bit more verbose than using the combinators, but someone coming across it for the first time will understand it immediately because there's less to remember to understand it (this is where go really shines).

Also: by avoiding functors there are fewer subtle lifetime issues and `move ||` stuff to deal with and you can return from the containing function and use the `?` operator.

During the discussions of how `.await` was going to work for rust async there was the proposal to add other suffix keywords. So this would look like:

    may_fail_who_knows()
    .match {
        Ok(y) => Ok(another_computation_which_can_fail(use_value(x))),
        Err(e) => with_some_error_handling_that_can_rescue(
            some_error_processing(e)),
    }
    .match {
        Ok(y) => y,
        _ => a_default_value,
    }
Maybe not that different.
wmanley··on There are over one trillion SQLite databases in active use
> SQLite doesn't support functional indexing

SQLite has "Indexes On Expressions"[1] since 2015. I'm assuming functional indexing in Postgres is the same. So I think the syntax would be:

    CREATE INDEX idx_t_bar ON json_extract(data, '$.bar');
[1]: https://www.sqlite.org/expridx.html

> Also PostgreSQL has many operators[2] which makes using JSON fields easier.

This does look a little more convenient to write than SQLite's JSON syntax, but I don't see any fundamental difference here.

wmanley··on There are over one trillion SQLite databases in active use
Unless I'm misunderstanding SQLite can do this as well, but with less nice syntax. The above could be written something like:

    SELECT id, json_extract(data, '$.foo')
    FROM   t
    WHERE  json_extract(data, '$.bar') > 5
in SQLite. You can also add an index on one of these expressions: https://dgl.cx/2020/06/sqlite-json-support
wmanley··on There are over one trillion SQLite databases in active use
Could you expand on what Postgres offers that SQLite doesn't in this regard? I'm curious to know what I'm missing out on.
wmanley··on There are over one trillion SQLite databases in active use
That's exactly what's being counted. A database is a SQLite document.
wmanley··on I built a $5k Raspberry Pi server (yes, it's ridiculous)
I think it's because the thumbnails are small, so if the faces aren't exaggerated you won't perceive any particular emotion at all. It's a bit like the difference between theatre and film acting.
wmanley··on Builder Pattern in Rust
I would quite like to see some syntax sugar around this. If we could elide the type and made `...` equivalent to `..Default::default()` it would be a lot closer to kwargs without having to introduce any new "real" features. So we could write:

     f(arg1, arg2, _ {name: "Hello", address, ...})
instead of

     f(arg1, arg2, MyKwargs {name: "Hello", address, ..Default::default()}
Using a struct for this feels natural as you get forwarding and non-exhaustiveness for adding additional fields for free.
wmanley··on Lockfiles for packages in a Debian/Ubuntu rootfs
Author here. I thought I'd post this since "Reproducible builds for Debian"[1] is being discussed on the front page.

I wrote apt2ostree. It applies the cargo/npm lockfile idea to debian rootfs images. From a list of packages we perform dependency resolution and generate a "lockfile" that contains the complete list of all packages, their versions and their SHAs. You can commit this lockfile to git.

You can then install Debian or Ubuntu into a chroot just based on this lockfile and end up with a functionally reproducible result. It won't be completely byte identical as your SSH keys, machine-id, etc. will be different between installations, but you'll always end up with the same packages and package versions installed for a given lockfile.

This has saved us on a few occasions where an apt upgrade had broken the workflow of some of our customers. We could see exactly which package versions changed in git history and roll-back the problematic package before working on fixing it properly. This is vastly better than the traditional `RUN apt-get install -y blah blah` you see in `Dockerfile`s. You know exactly what was installed before an update and exactly what is installed after and you can rebuild old versions.

IMO it's also more convenient than debootstrap as you don't need to worry about gpg keys, etc. when building the image. Dependency resolution and gpg key stuff is done at lockfile generation time, so the installation process can be much simpler. In theory it could be made such that only dpkg is required to do the install, rather than the whole of apt, but that's by-the-by.

apt2ostree itself is probably not interesting to most people as it depends on ostree and ninja but I think the lockfile concept as applied to debian repos could be of much broader interest. The lockfile generation could be easily separated from apt2ostree. Most of it is implemented in a custom fork of aptly anyway. Actually applying the lockfiles and creating a rootfs from it is much more apt2ostree specific and would probably need to be reimplemented separately to be more generally useful. Ideally that would be built in to dpkg, but I don't think I'm the man to drive that consensus.

[1]: https://news.ycombinator.com/item?id=28837690

[2]: https://ostreedev.github.io/ostree/

wmanley··on Reproducible builds for Debian: a big step forward
On the subject of reproducible debian-based environments I wrote apt2ostree[1]. It applies the cargo/npm lockfile idea to debian rootfs images. From a list of packages we perform dependency resolution and generate a "lockfile" that contains the complete list of all packages, their versions and their SHAs. You can commit this lockfile to git.

You can then install Debian or Ubuntu into a chroot just based on this lockfile and end up with a functionally reproducible result. It won't be completely byte identical as your SSH keys, machine-id, etc. will be different between installations, but you'll always end up with the same packages and package versions installed for a given lockfile.

This has saved us on a few occasions where an apt upgrade had broken the workflow of some of our customers. We could see exactly which package versions changed in git history and roll-back the problematic package before working on fixing it properly. This is vastly better than the traditional `RUN apt-get install -y blah blah` you see in `Dockerfile`s. You know exactly what was installed before an update and exactly what is installed after and you can rebuild old versions.

IMO it's also more convenient than debootstrap as you don't need to worry about gpg keys, etc. when building the image. Dependency resolution and gpg key stuff is done at lockfile generation time, so the installation process can be much simpler. In theory it could be made such that only dpkg is required to do the install, rather than the whole of apt, but that's by-the-by.

apt2ostree itself is probably not interesting to most people as it depends on ostree and ninja but I think the lockfile concept as applied to debian repos could be of much broader interest.

[1]: https://github.com/stb-tester/apt2ostree#lockfiles

[2]: https://ostreedev.github.io/ostree/

wmanley··on Worst Case
There's a sentence in here that really gets to the value of AWS for businesses:

> So while your customers and employees are going to be mad, they’re also going to be distracted from worrying about your downtime.

This is incredibly important for business. If we go with AWS and it goes down, then it's big news and our customers blame AWS. If we choose a different provider, even one which is more reliable than AWS, then our customers blame us.

This reasoning obviously doesn't apply to all systems e.g. air-traffic control or stock exchanges, but it does for the vast majority of businesses, and certainly any business that would host their systems in the cloud in the first place.

wmanley··on Understanding Python through its builtins

    $ python2 -c 'False = None = True'
      File "<string>", line 1
    SyntaxError: cannot assign to None

    $ python3 -c 'False = None = True'
      File "<string>", line 1
    SyntaxError: cannot assign to False
wmanley··on JSON5 Data Interchange Format
> Trailing commas are also nice to have - although we have linters for that, unfortunately some people think they're too good to need a linter.

IMO the real value in trailing commas is that it's easier to produce simple JSON programmatically.

← PreviousPage 5 of 6Next →