1. 69
    1. 15

      It feels easier to me to come back to my Go projects that are full of a lot of repetitive boilerplate, because at least I can read the code and figure out how it works.

      I 100% feel this with my toy Go servers - it’s usually a lot easier for me to dip in and make a couple of small changes because it is locally simple, and everything I need to understand what’s going on is usually in the same file. For a hobby project where I often don’t have (or want to commit) a lot of time it’s a godsend. Honestly, that’s part of why I’m not updating them to use the new Go 1.22 router; I like having everything in that function, I can just see it to know everything.

      1. 3

        This is also why I appreciate Go. Quoting another bit of the article:

        In general everything about it feels like it makes projects easy to work on for 5 days, abandon for 2 years, and then get back into writing code without a lot of problems.

        This is what a lot of my development workflows look like. 😅 Having spent most of my career in sysadmin-like roles, I write a lot of small one-off tools, and rarely end up in a situation where I’m continually working on a larger codebase for a long time. Go makes these kinds of low-activity projects very easy to work with.

      2. 11

        if I want to make reads faster, I could have 2 separate db objects, one for writing and one for reading

        It’s really necessary if you use the previous recommendation (which you very much should), otherwise you serialise all access to the database which is a shame especially in WAL mode.

        After a while I also found it was useful because it makes the endpoint’s requirements clearer upfront, and that can become useful information to then scale with things like readonly replicas.

        Also sometimes if I have two tables where I know I’ll never need to do a JOIN between them, I’ll just put them in separate databases so that I can connect to them independently.

        You can do cross-database joins in sqlite, when connecting to multiple dbs they basically act as schemas. Though obviously FKs don’t work cross database (which might be a factor in them having to be enabled per connection). Neither do triggers.

        Go’s garbage collector will let the application allocate memory up to 2x the current heap size.

        AFAIK that’s pretty normal for non-refcounting GCs, it’s also what GOGC controls in Go (it’s the ratio — in percent — of new allocations to previous heap). So GOGC=100 (default) means performing a GC when the heap has grown 100% since the last GC.

        GOMEMLIMIT adds the ability to cap memory, but can cause the opposite problem (which is a java classic): if the limit is too close to the working set size (or the working set size grows too close to it), your application starts spending all its time garbage-collecting because it can only ever free up a small amount of memory, and so has to GC every other allocation (Go actually has a GC CPU cap to avoid this, so GOMEMLIMIT is soft and in this scenario memory would simply start running away above limit until the process returns under control or is OOM-killed). That means you need to start keeping a close eye on your working set size and allocation rate.

        And from the linked article,

        SQLite doesn’t keep statistics about its indexes, unlike PostgreSQL, so COUNT queries are slow, even when using a WHERE clause on an indexed field: SQLite has to scan for all the matching records.

        COUNT queries are famously a slog in postgres: COUNT requires an exact count, postgres only keeps sampled statistics and the statistics are only updated during an ANALYZE (and VACUUM, and a few DDL operations like creating a new index), so a COUNT also requires a full table scan. Fast counts in postgres likewise requires maintaining a separate count via triggers.

        Also sqlite does keep index statistics, that’s necessary for the planner to do its job, those stats are stored in sqlite_stat1 and (if support is compiled in) sqlite_stat4.

        1. 1

          those stats are stored in sqlite_stat1 and (if support is compiled in) sqlite_stat4.

          How do I know if that’s compiled in? How is it used? What do I do if it’s not compiled in?

          1. 3

            See if pragma compile_options; contains SQLITE_ENABLE_STAT4, and you shouldn’t have to do anything. https://www.sqlite.org/optoverview.html talks a bit about how it works.

            If it’s not compiled in then you can compile sqlite yourself, which is very easy because it’s one file.

            1. 1

              Okay, I guess a more direct question would have been - how do I work out where the sqlite library Go is relying on at any one time is coming from?

              1. 3

                This is always such a pain when working with SQLite in Python.

                I’ve found a few recipes for influencing this - LD_PRELOAD on Linux works for a Python, so it might also work for Go: https://til.simonwillison.net/sqlite/ld-preload

                1. 2

                  I don’t know of any golang sqlite binding that will link to the sqlite on your system.

                  Some of them compile the C code to go (modernc.org/sqlite, zombiezen.com/go/sqlite), some of them contain a WASM build of sqlite (ncruces/go-sqlite3) and others link the C amalgamation file (mattn, crawshaw)

        2. 8

          Work are in the process of starting on some Go webapp development, in a team that has mostly .NET experience, and I’m a bit apprehensive about the bias towards frameworks. The prototype is built in gin, and there’s been some discussion of test frameworks, but I would prefer to use an approach like this where we mostly just use the standard library…

          1. 5

            The Go community’s starting to buckle under eternal September, with so many new devs (especially from Java) bringing nonsensical design patterns and concepts like “clean” and SOLID, which Go’s primitives almost entirely obviate.

            1. 4

              Need a test framework for golang HTTP APIs? Just use Hurl and do some end-to-end testing :)

              1. 3

                I suggest you read Let’s go and Let’s go further from Alex Edwards if you are interested in seeing how to fully test production code using solely the standard library. The books will show you how to do that in an “idiomatic” way.

              2. 8

                Go still remains one of the most readable languages, though I wonder how much that will change going forward with generics and iterators being introduced - I do like their being added to the language btw, just feeling that might make the Go code a bit “harder” to understand on a quick glance

                1. 3

                  It’s fun to read this article while developing a web app with Go + sqlite myself :)

                  I am already using Go 1.22 routing, but sqlite tuning part was kinda useful for me.

                  1. 3

                    I’ve never felt motivated to learn any of the Go routing libraries

                    That’s a weird one. Using Gin or Chi was infinitely easier that using the standard router pre-1.22. Exhibit A: the awful snippet she shows below. Even after 1.22 I’ve encountered weird behaviors with the standard router (handlers called when the HTTP verb didn’t match, or unclear handling of trailing slashes or lack thereof) and would still recommend using Gin or Chi for any serious work.

                    1. 6

                      Can you reproduce the router matching the wrong verb? That’s a pretty serious bug and should be fixed if you can.

                      1. 3

                        I think I have it in one of my repos, I’ll check if I can find it again, and notify you if I do.

                        But to be clear I don’t think it’s a bug with Go, my guess at the time was that I used {$} incorrectly somehow but didn’t have time to look into it. So if anything, it’s probably a documentation issue, or a skill issue on my side.

                        1. 3

                          FWIW, I saw an article on the Go blog the other day that incorrectly used a trailing slash (which captures all sub-routes) instead of a final dollar sign, so clearly the API isn’t as intuitive as it could be.

                          1. 3

                            I got hit by this when I didn’t update the go version in my go.mod file. I was baffled for hours.

                            1. 6

                              Stringly typed APIs 😅

                              1. 2

                                After being hit by this a couple of times I ended up blogging about it - I’ve had quite a few hits from Google by other folks seeing similar 😅

                                  1. 1

                                    Yes it is, I probably should’ve linked it :)

                          2. 2

                            Using Gin or Chi was infinitely easier that using the standard router pre-1.22

                            It was really not that much work (extra couple lines per route plus minimal to average complexity regex helper function for path match logic depth to add the functionality of http request type matching and request parameter parsing pre 1.22. It also provided a way to learn a bit more about net/http and http requests in go than just using a third party library without having tried it in the standard library even if only for a early implementation of your web backend code that you for whichever reason swapped in chi or gin for later in development. https://benhoyt.com/writings/go-routing/

                          3. 3

                            for sqlc:

                            UPDATE subathons
                            SET enabled                  = COALESCE(sqlc.narg('enabled'), enabled),
                                started_ms               = COALESCE(sqlc.narg('started_ms'), started_ms),
                                ends_ms                  = COALESCE(sqlc.narg('ends_ms'), ends_ms),
                                overlay                  = COALESCE(sqlc.narg('overlay'), overlay),
                            ...
                            

                            you’re welcome, it’s life saving

                            1. 2

                              Another amazing aspect of Go for web services is that context is a first-class object. Your server can cancel heavy work (e.g., reading files, reading databases, or making further web requests) as soon as the calling client cancels the request.

                              1. 1

                                I run all of my Go projects in VMs with relatively little memory, like 256MB or 512MB. I ran into an issue where my application kept getting OOM killed and it was confusing – did I have a memory leak? What?

                                I mostly have experience running Go backends in production in VMs with at least 1GB of memory. Nonetheless, I am interested on deploying some personal projects in smaller (therefore also much cheaper) VMs. Does anyone have experience with that, or any suggestions on to how to avoid OOM?

                                1. 2

                                  I think that if you want to go extreme on that ruote, tinygo may help.

                                  1. [Comment removed by author]