Project challenges / verified progress
Beacon: turn a program into a service

The engineering notebook

Persist results in SQLite

How does Beacon remember checks after the process restarts?

Loading statusStage 4 of 10

  • Workspace not ready
  • Agent not ready
Focus25:00
A small focus ritual

0 focus sessions completed. Every fourth session offers a longer break. Start each phase when you are ready.

Study time never unlocks verified lesson progress.

Loading...

Loading verified progress...

Loading GitHub account...
Phase 2 — Memory and history

Step 01 of 06

Learn the concept

Memory is fast and forgetful. A service that forgets every outage on restart is a goldfish with a PID. SQLite gives Beacon durable history without asking the learner for a database server or a cloud account.

RESULT TO DURABLE ROW01Resulttyped check fact02Statementprepared insert03Transactionall rows or none04SQLite filesurvives restart
The prober result crosses one boundary: from Go value to database row. Preparing the statement and using a transaction make that boundary explicit instead of sprinkling SQL strings through the service.
Step 01

The ideas this is made of

Embedded does not mean toy

SQLite is a database library linked into the process, not a separate server. It is excellent for one service writing local state, which is exactly this course. The trade-off is topology: multiple machines do not magically share a file. That is fine here. Course four will give the file a persistent volume.

Migrations are source control for schema

A migration is an ordered change such as 001_create_results.sql. Startup applies missing migrations and records that they ran. This matters because databases outlive binaries. Without migrations, every deploy depends on someone remembering the right manual SQL at the worst possible time.

Indexes should match questions

Beacon will ask for recent results by target and time. An index on (target, checked_at) helps that query and has a cost on writes. Indexing every column is not thoroughness; it is tax collection. Name the query first, then add the index that serves it.

Pure Go matters when images arrive

The modernc.org/sqlite driver avoids cgo. That means later container builds do not need a C compiler, libc headers or platform-specific cross-compilation tricks. The import path is unusual, but the payoff is a smaller, simpler build pipeline in the packaging course.

Create one SQLite table
package main

import (
	"database/sql"
	"fmt"

	_ "modernc.org/sqlite"
)

func main() {
	db, err := sql.Open("sqlite", "file:example.db?mode=memory&cache=shared")
	if err != nil { panic(err) }
	defer db.Close()
	_, err = db.Exec("create table checks (target text, ok integer)")
	fmt.Println(err == nil)
}

The blank import registers the SQLite driver with database/sql. Beacon will use a real file path, but an in-memory database keeps the mechanism visible.

Persistence choices

StoreStrengthLimit

Memory

Fast snapshots

Lost on restart

SQLite

Local durable file

One-node storage

Postgres

Shared service

Needs server/account

JSON file

Readable

Hard to query safely

What these are called on the job

  • Migration — An ordered, repeatable database schema change.

  • Index — A data structure that speeds selected reads while adding write cost.

  • Transaction — A group of database operations committed together or rolled back together.