From 9200e53b03b2d55d9613c52febfc8e591701f074 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Mon, 22 Mar 2021 16:13:41 +0800 Subject: [PATCH] Update README, deploy scripts --- CHANGELOG.markdown | 16 +- README.markdown | 58 +++--- cmd/gcbench/gcbench.go | 10 +- deploy/README.markdown | 3 + deploy/alpine/README.markdown | 24 +++ .../goatcounter-alpine.sh} | 45 ++-- docs/benchmark.markdown | 194 ++++++++++++++++++ 7 files changed, 291 insertions(+), 59 deletions(-) create mode 100644 deploy/README.markdown create mode 100644 deploy/alpine/README.markdown rename deploy/{StackScript => alpine/goatcounter-alpine.sh} (70%) create mode 100644 docs/benchmark.markdown diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index ce28452d1..7dc617956 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -5,24 +5,13 @@ This list is not comprehensive, and only lists new features and major changes, but not every minor bugfix. The goatcounter.com service generally runs the latest master. -Changes since rc1 +2021-03-29 v2.0.0 ----------------- -- MFA login was broken 🤦 -- Make sure GoatCounter works even when the `tzdata` package isn't available. -- Revamped integration documentation; what started as a single short page grew - in to a rather long chaotic page so clean that up a bit. - -2021-03-20 v2.0.0-rc1 ---------------------- The version is bumped to 2.0 because this contains a number of incompatible changes: several CLI commands got changed, and it includes some large database migrations – running them is a bit more complex than the standard migrations. -Because this includes so many changes I'll do a Release Candidate first. Please -report issues if you have any problems! Barring unexpected large issues I'll do -a 2.0 release next week or so. - An overview of **incompatible** changes: - There are some rather large changes to the database layout for better @@ -175,6 +164,9 @@ An overview of **incompatible** changes: - Many other minor changes and improvements. +- For changes since RC1 see: + https://github.com/zgoat/goatcounter/compare/v2.0.0-rc1...v2.0.0 + 2020-11-10, v1.4.2 ------------------ diff --git a/README.markdown b/README.markdown index 508f830d1..990e198c9 100644 --- a/README.markdown +++ b/README.markdown @@ -97,14 +97,14 @@ GoatCounter should run on any platform supported by Go, but there are no binaries for them (yet); you'll have to build from source if you want to run it on e.g. FreeBSD or macOS. -Note this README is for the latest master; use the [`release-1.4`][r-1.4] branch -for the 1.4 README. +Note this README is for the latest master; use the [`release-2.0`][r-2.0] branch +for the 2.0 README. Generally speaking only the latest release is supported, although critical fixes (security, data loss, etc.) may get backported to previous releases. [releases]: https://github.com/zgoat/goatcounter/releases -[r-1.4]: https://github.com/zgoat/goatcounter/tree/release-1.4 +[r-2.0]: https://github.com/zgoat/goatcounter/tree/release-2.0 ### Deploy scripts and such @@ -136,9 +136,12 @@ Generally speaking only the latest release is supported, although critical fixes ### Building from source +You need Go 1.16 or newer and a C compiler (for SQLite). If you compile it with +`CGO_ENABLED=0` you don't need a C compiler but can only use PostgreSQL. + Compile from source with: - $ git clone -b release-1.4 https://github.com/zgoat/goatcounter.git + $ git clone -b release-2.0 https://github.com/zgoat/goatcounter.git $ cd goatcounter $ go build -ldflags="-X zgo.at/goatcounter.Version=$(git log -n1 --format='%h_%cI')" ./cmd/goatcounter @@ -156,14 +159,21 @@ To build a fully statically linked binary: -ldflags="-X zgo.at/goatcounter.Version=$(git log -n1 --format='%h_%cI') -extldflags=-static" \ ./cmd/goatcounter -You need Go 1.16 or newer and a C compiler (for SQLite). If you compile it with -`CGO_ENABLED=0 go build` you don't need a C compiler, but can only use PostgreSQL. - It's recommended to use the latest release as in the above command. The master branch should be reasonably stable but no guarantees, and sometimes I don't write detailed release/upgrade notes until the actual release so you may run in to surprises. +You can compile goatcounter without cgo if you're planning to use PostgreSQL and +don't use SQLite: + + $ CGO_ENABLED=0 go build \ + -ldflags="-X zgo.at/goatcounter.Version=$(git log -n1 --format='%h_%cI')" \ + ./cmd/goatcounter + +Functionally it doesn't matter too much, but builds will be a bit easier and +faster as it won't require a C compiler. + ### Running You can start a server with: @@ -174,6 +184,11 @@ The default is to use an SQLite database at `./db/goatcounter.sqlite3`, which will be created if it doesn't exist yet. See the `-db` flag and `goatcounter help db` to customize this. +Both SQLite and PostgreSQL are supported. SQLite should work well for most +smaller sites, but PostgreSQL gives better performance. There are [some +benchmarks over here][bench] to give some indication of what performance to +expect from SQLite and PostgreSQL. + GoatCounter will listen on port `*:80` and `*:443` by default. You don't need to run it as root and can grant the appropriate permissions on Linux with: @@ -190,6 +205,8 @@ This will ask for a password for your new account; you can also add a password on the commandline with `-password`. You must also pass the `-db` flag here if you use something other than the default. +[bench]: https://github.com/zgoat/goatcounter/blob/master/docs/benchmark.markdown + ### Updating You may need to run the database migrations when updating. Use `goatcounter @@ -205,30 +222,17 @@ Use `goatcounter migrate pending` to get a list of pending migrations, or ### PostgreSQL -Both SQLite and PostgreSQL are supported. SQLite should work well for most -smaller sites, but PostgreSQL gives some better performance: - -1. Run with custom `-db` flag: - - $ goatcounter serve -db 'postgresql://dbname=goatcounter' - - Or use a socket: - - $ goatcounter serve \ - -db 'postgresql://host=/run/postgresql dbname=goatcounter sslmode=disable' - - You can also use the `PG*` environment variables: - - $ PGDATABASE=goatcounter DBHOST=/var/run goatcounter serve -db 'postgresql://' +To use PostgreSQL run GoatCounter with a custom `-db` flag; for example: - See `goatcounter help db` and the [pq docs][pq] for more details. + $ goatcounter serve -db 'postgresql://dbname=goatcounter' + $ goatcounter serve -db 'postgresql://host=/run/postgresql dbname=goatcounter sslmode=disable' -2. You can compile goatcounter without cgo if you don't use SQLite: +This follows the format in the `psql` CLI; you can also use the `PG*` +environment variables: - $ CGO_ENABLED=0 go build -ldflags="-X zgo.at/goatcounter.Version=$(git log -n1 --format='%h_%cI')" ./cmd/goatcounter + $ PGDATABASE=goatcounter DBHOST=/run/postgresql goatcounter serve -db 'postgresql://' - Functionally it doesn't matter too much, but builds will be a bit easier and - faster as it won't require a C compiler. +See `goatcounter help db` and the [pq docs][pq] for more details. [pq]: https://pkg.go.dev/github.com/lib/pq#hdr-Connection_String_Parameters diff --git a/cmd/gcbench/gcbench.go b/cmd/gcbench/gcbench.go index 2e1c63b0a..3ba167a0c 100644 --- a/cmd/gcbench/gcbench.go +++ b/cmd/gcbench/gcbench.go @@ -133,12 +133,14 @@ func main() { }) zli.F(err) + first := time.Now().Add(-time.Duration(r.nDays) * 24 * time.Hour) + r.ctx = goatcounter.NewContext(r.db) s := goatcounter.Site{ - Cname: zstring.NewPtr("gcbench.localhost").P, - Plan: goatcounter.PlanBusinessPlus, - Settings: goatcounter.SiteSettings{Public: true}, - // site.FirstHitAt = dates[len(dates)-1] + Cname: zstring.NewPtr("gcbench.localhost").P, + Plan: goatcounter.PlanBusinessPlus, + Settings: goatcounter.SiteSettings{Public: true}, + FirstHitAt: first, } zli.F(s.Insert(r.ctx)) r.ctx = goatcounter.WithSite(r.ctx, &s) diff --git a/deploy/README.markdown b/deploy/README.markdown new file mode 100644 index 000000000..c3f746eaf --- /dev/null +++ b/deploy/README.markdown @@ -0,0 +1,3 @@ +Various ways to deploy GoatCounter; see the subdirectories for details. + +- alpine – Set up an Alpine Linux machine. diff --git a/deploy/alpine/README.markdown b/deploy/alpine/README.markdown new file mode 100644 index 000000000..9b351d394 --- /dev/null +++ b/deploy/alpine/README.markdown @@ -0,0 +1,24 @@ +This sets up a basic GoatCounter installation on Alpine Linux, using SQLite. + +This is also available as a ["StackScript" for Linode][s]; If you don't have a +Linode account yet then [consider using my "referral URL"][r] and I'll get some +cash back from Linode :-) + +It should be fine to run this more than once; and can be used to upgrade to a +newer version. + +You can set the version to use with `GOATCOUNTER_VERSION`; this needs to be a +release on GitHub: + + $ GOATCOUNTER_VERSION=v2.0.0 ./goatcounter-alpine.sh + +You can create additional sites with: + + $ cd /home/goatcounter + $ ./bin/goatcounter db create site -domain example.com -email me@example.com + +Files are stored in `/home/goatcounter`; see `/var/log/goatcounter/current` for +logs; and you can configure the flags in `/etc/conf.d/goatcounter` + +[s]: https://cloud.linode.com/stackscripts/659823 +[r]: https://www.linode.com/?r=7acaf75737436d859e785dd5c9abe1ae99b4387e diff --git a/deploy/StackScript b/deploy/alpine/goatcounter-alpine.sh similarity index 70% rename from deploy/StackScript rename to deploy/alpine/goatcounter-alpine.sh index 7bf75c685..cad7d306d 100755 --- a/deploy/StackScript +++ b/deploy/alpine/goatcounter-alpine.sh @@ -2,38 +2,41 @@ # # # -# +# # -# This is a "StackScript" to deploy GoatCounter on a Linode VPS; it's available -# in Linode as: https://cloud.linode.com/stackscripts/659823 +# This will set up an Alpine Linux machine; environment variables: +# GOATCOUNTER_DOMAIN Domain you'll be hosting GoatCounter on +# GOATCOUNTER_EMAIL Your email address +# GOATCOUNTER_PASSWORD Password to access GoatCounter +# GOATCOUNTER_VERSION GoatCounter version (default: v2.0.0). # -# This script should also work fine outside of Linode; it does assume you're -# using Alpine Linux. +# This is available as a "StackScript" to deploy GoatCounter on a Linode VPS: +# https://cloud.linode.com/stackscripts/659823 # # If you don't have a Linode account yet then consider using my "referral URL" # and I'll get some cash back from Linode :-) # https://www.linode.com/?r=7acaf75737436d859e785dd5c9abe1ae99b4387e # # This script's source at the GoatCounter repo is: -# https://github.com/zgoat/goatcounter/blob/master/deploy/StackScript +# https://github.com/zgoat/goatcounter/blob/master/deploy/alpine # # It should be fine to run this more than once; and can be used to upgrade to a # newer version. # -# You can create additional sites with: +# Files are stored in /home/goatcounter; see /var/log/goatcounter for logs; and +# you can configure the flags in /etc/conf.d/goatcounter. # -# $ cd /home/goatcounter -# $ ./bin/goatcounter db create site -domain example.com -email me@example.com +# You can create additional sites with # -# Files are stored in /home/goatcounter; see /var/log/goatcounter/current for -# logs; and you can configure the flags it starts with in /etc/conf.d/goatcounter. +# $ cd /home/goatcounter +# $ ./bin/goatcounter db create site [..] # # Please report any bugs, problems, or other issues on the GoatCounter issue # tracker, or email me at martin@goatcounter.com # # GoatCounter version to set up. -v=${GOATCOUNTER_VERSION:-v1.4.1} +v=${GOATCOUNTER_VERSION:-"v2.0.0"} set -eu @@ -60,7 +63,7 @@ apk add tzdata grep -q '^goatcounter:' /etc/group || addgroup -S goatcounter grep -q '^goatcounter:' /etc/passwd || adduser -s /sbin/nologin -DS -G goatcounter goatcounter -# Get latest version. +# Get latest version if it doesn't exist yet. mkdir -p /home/goatcounter/bin dst="/home/goatcounter/bin/goatcounter-$v" if [ ! -f "$dst" ]; then @@ -74,7 +77,7 @@ ln -sf "$dst" "/home/goatcounter/bin/goatcounter" # Set up site; this may fail if the site already exists, which is fine. cd /home/goatcounter -./bin/goatcounter db create site -createdb -domain "$GOATCOUNTER_DOMAIN" -email "$GOATCOUNTER_EMAIL" -password "$GOATCOUNTER_PASSWORD" ||: +./bin/goatcounter db create site -createdb -vhost "$GOATCOUNTER_DOMAIN" -user.email "$GOATCOUNTER_EMAIL" -user.password "$GOATCOUNTER_PASSWORD" ||: chown -R goatcounter:goatcounter db # Set up log directory. @@ -97,6 +100,11 @@ pidfile="/run/\${RC_SVCNAME}.pid" output_log="/var/log/\${RC_SVCNAME}/current" error_log="/var/log/\${RC_SVCNAME}/current" +start_pre() { + # Make sure this is correct after updates etc. + setcap 'cap_net_bind_service=+ep cap_sys_chroot=+ep' "\$(readlink "\$command")" +} + depend() { use net use dns @@ -105,15 +113,20 @@ depend() { EOF cat << EOF > /etc/conf.d/goatcounter -# These are the defaults. +# The uncommented values are the defaults. -# Listen on all addressed. +# Listen on all addresses. #GOATCOUNTER_LISTEN=:443 # Location of SQLite3 database file or PostgreSQL connection. GoatCounter is # started from /home/goatcounter. #GOATCOUNTER_DB="sqlite://./db/goatcounter.sqlite3" +# If you use PostgreSQL then URI-type connector is recommended, as OpenRC can't +# deal well with spaces; for example: +#GOATCOUNTER_DB="postgresql:///run/postgresql/goatcounter?sslmode=disable" + + # Other flags to add. See "goatcounter help serve". #GOATCOUNTER_ARGS="-automigrate" EOF diff --git a/docs/benchmark.markdown b/docs/benchmark.markdown new file mode 100644 index 000000000..942c3eaa1 --- /dev/null +++ b/docs/benchmark.markdown @@ -0,0 +1,194 @@ +Here are some benchmarks of the GoatCounter dashboard, just to give an indication +of what to expect with the SQLite and PostgreSQL databases. + +These tests were all performed on Linode (SG) with Alpine Linux 3.13 (28 March +2021), PostgreSQL 13.2, and SQLite 3.34.0 using GoatCounter 2.0.0. Your exact +performance may differ based on VPS provider, location, or even individual VPS +in the same data centre. They're just intended as a rough comparison. I used +Linode because that's what I already use. + +GoatCounter was run with `goatcounter serve`; no extra flags or proxy. To +simulate real-world loading times I ran the benchmarks from my laptop, rather +than the VPS. + +Every configuration is run against SQLite and PostgreSQL with four different +databases: + +- 1M pageviews, evenly spread out over a year and 1,000 paths. +- 1M pageviews, evenly spread out over a year and 50,000 paths. +- 10M pageviews, evenly spread out over a year and 1,000 paths. +- 10M pageviews, evenly spread out over a year and 50,000 paths. + +The performance depends on two factors: the number of pageviews, and the number +of unique paths you have. For example a site with 10M pageviews spread out over +50 paths will be a lot faster than 1M pageviews spread out over 200,000 paths. + +The bencharks were run on the following configurations: + +- Nanode 1GB, 1 core, $5/month +- Linode 2GB, 1 core, $10/month +- Linode 4GB, 2 cores, $20/month +- Linode 8GB, 4 cores, $40/month + +The results aren't completely consistent; when resizing a Linode it may get +moved to a different physical machine, which may have better/worse performance. +It may also be affected by temporary performance spikes and the like. It is the +nature of these kind of cloud things. I didn't make a lot of effort to re-run +them a day later and/or on other locations and such as it's all a lot of work. +This is okay, as the overview is just intended as a rough rule-of-thumb +overview. + + +Results +------- + +The times are the average of 10 requests; after 60 seconds GoatCounter will kill +the query; `>60s` means it timed out. + + +### SQLite + +| | 1G/1c | 2G/1c | 4G/2c | 8G/4c | +| -------------- | ----: | ----: | ----: | ----: | +| 1M/1k week | 1.8s | 1.4s | 1.3s | 0.8s | +| 1M/1k month | 5s | 5.4s | 3.7s | 1.7s | +| 1M/1k quarter | 12.9s | 15.5s | 10.3s | 4s | +| 1M/1k year | >60s | >60s | >60s | 13.6s | +| | | | | | +| 1M/50k week | 1.9s | 1.6s | 1.5s | 0.9s | +| 1M/50k month | 5.4s | 5.5s | 4.2s | 1.9s | +| 1M/50k quarter | 15.8s | 16.4s | 11s | 5s | +| 1M/50k year | >60s | >60s | >60s | 15.3s | +| | | | | | +| 10M/1k week | 19s | 7s | 6.5s | 3.3s | +| 10M/1k month | >60s | >60s | 19.4s | 8.9s | +| 10M/1k quarter | >60s | >60s | >60s | >60s | +| 10M/1k year | >60s | >60s | >60s | >60s | +| | | | | | +| 10M/50k week | >60s | 19.3s | 11.1s | 5.9s | +| 10M/50k month | >60s | >60s | >60s | 16.8s | +| 10M/50k quarter | >60s | >60s | >60s | >60s | +| 10M/50k year | >60s | >60s | >60s | >60s | + + +### PostgreSQL + +| | 1G/1c | 2G/1c | 4G/2c | 8G/4c | +| -------------- | ----: | ----: | ----: | ----: | +| 1M/1k week | 0.2s | 0.1s | 0.6s | 0.1s | +| 1M/1k month | 1.8s | 0.8s | 1.2s | 0.3s | +| 1M/1k quarter | 4.2s | 2.4s | 3.4s | 0.9s | +| 1M/1k year | 5.3s | 3.2s | 3.6s | 1.5s | +| | | | | | +| 1M/50k week | 0.4s | 0.3s | 0.5s | 0.3s | +| 1M/50k month | 1.7s | 0.9s | 1.7s | 0.4s | +| 1M/50k quarter | 4.8s | 2.9s | 3.8s | 1.2s | +| 1M/50k year | 6.7s | 4.5s | 4.9s | 2s | +| | | | | | +| 10M/1k week | 12.4s | 2.4s | 2.1s | 1.9s | +| 10M/1k month | >60s | 9.3s | 2.7s | 1.6s | +| 10M/1k quarter | >60s | 10.9s | 2.4s | 0.9s | +| 10M/1k year | >60s | 10.6s | 2.4s | 0.9s | +| | | | | | +| 10M/50k week | >60s | >60s | 3.9s | 2s | +| 10M/50k month | >60s | >60s | 10.5s | 2.4s | +| 10M/50k quarter | >60s | >60s | 11s | 2s | +| 10M/50k year | >60s | >60s | 11s | 1.9s | + + +The take-away is that PostgreSQL is a lot faster; but I've also spent more time +optimizing PostgreSQL so there may be something to win here. + +Many smaller sites will have much fewer than 1 million pageview and 1,000 paths +though. If you're running GoatCounter on a small website then the cheapest +$5/month should be enough. + +The amount of pageviews it can record isn't benchmarked here, but should be 100/s +even on smaller machines. You'll likely hit in to performance problems on the +dashboard before you hit this. + +You can find the "raw" output from `hey` over here: +https://gist.github.com/arp242/ae9d409e47dfe1021ab0b4ff3e5faba7 + + +Benchmark details +----------------- + +You can use `./cmd/gcbench` to set up a database. The the help on that for some +details. + +I used [hey][hey] to run the actual benchmarks with a simple script: + + #!/bin/sh + # + # https://github.com/zgoat/goatcounter/blob/master/docs/benchmark.markdown + # Requires "hey": https://github.com/rakyll/hey + + url=https://gcbench.arp242.net + start=$(date -d @$(( $(date +%s) - 86400 * 5)) +%Y-%m-%d) # Created DB 5 days ago + run() { + send="$url?period-end=$start&period-start=$(date -d @$(( $(date +%s) - 86400 * $1)) +%Y-%m-%d)" + echo "$send" | tee "gcbench/$1" + ./hey -c 1 -n 10 "$send" | tee -a "gcbench/$1" + } + + mkdir -p gcbench + run 7 + run 30 + run 90 + run 182 + run 365 + + +[hey]: https://github.com/rakyll/hey + + +### PostgreSQL + +PostgreSQL 13.2 was run with the following configuration: + + max_wal_size = 4GB # Default: 1G + min_wal_size = 256MB # Default: 80M + random_page_cost = 0.5 # Better for SSDs. + effective_io_concurrency = 128 # Better for SSDs. + default_statistics_target = 1000 # Sample more in ANALYZE; Default: 100 + +And the following were tweaked based on the Linode instance size: + +Nanode 1GB/1 core $5/month + + max_parallel_workers_per_gather = 1 # Number of cores. + max_parallel_maintenance_workers = 1 # Half the cores. + shared_buffers = 256MB # ~25% of RAM + effective_cache_size = 768MB # ~75% of RAM + maintenance_work_mem = 52MB # ~5% of RAM + work_mem = 21MB # ~2% of RAM + + +Linode 2GB/1 core $10/month + + max_parallel_workers_per_gather = 1 # Number of cores. + max_parallel_maintenance_workers = 1 # Half the cores. + shared_buffers = 512MB # ~25% of RAM + effective_cache_size = 1536MB # ~75% of RAM + maintenance_work_mem = 100MB # ~5% of RAM + work_mem = 40MB # ~2% of RAM + + +Linode 4GB/2 cores $20/month + + max_parallel_workers_per_gather = 2 # Number of cores. + max_parallel_maintenance_workers = 1 # Half the cores. + shared_buffers = 1GB # ~25% of RAM + effective_cache_size = 3GB # ~75% of RAM + maintenance_work_mem = 205MB # ~5% of RAM + work_mem = 82MB # ~2% of RAM + +Linode 8GB/4 cores $40/month + + max_parallel_workers_per_gather = 4 # Number of cores. + max_parallel_maintenance_workers = 2 # Half the cores. + shared_buffers = 2GB # ~25% of RAM + effective_cache_size = 6GB # ~75% of RAM + maintenance_work_mem = 410MB # ~5% of RAM + work_mem = 164MB # ~2% of RAM