Skip to content

storage/gcp: skip schema DDL when the Spanner schema already exists - #1138

Draft
eperrine-ant wants to merge 2 commits into
transparency-dev:mainfrom
eperrine-ant:gcp-skip-schema-ddl-when-present
Draft

storage/gcp: skip schema DDL when the Spanner schema already exists#1138
eperrine-ant wants to merge 2 commits into
transparency-dev:mainfrom
eperrine-ant:gcp-skip-schema-ddl-when-present

Conversation

@eperrine-ant

Copy link
Copy Markdown
Contributor

What

storage/gcp's initDB and storage/gcp/antispam's NewAntispam now first check, with a few single-row reads through the existing Spanner data client, whether the schema they are about to create is already fully present, and skip the UpdateDatabaseDdl call and seed inserts when it is. If anything is missing, different, or unreadable, the existing create/migrate path runs unchanged. No API or schema change.

Why

Spanner runs DDL as schema-update operations — on the order of seconds each and serialised per database — even when IF NOT EXISTS makes them no-ops, and today every Appender/MigrationWriter/NewAntispam construction issues the full set. Re-opening an existing log therefore costs several schema-update operations before the appender is usable, and with SpannerTablePrefix (#1040) letting many logs share one database, N logs opening together queue N× that work behind the database's single schema-change pipeline. After this change a re-open is a handful of reads.

How "already present" is decided

schemaInitialised mirrors initDB item for item: a read of the id = 0 seed row of each seeded table naming exactly the columns initDB creates (so a missing table, column — e.g. PubCoord.size on an older schema — or row fails the read), a Limit: 1 read of the unseeded Seq / IDSeq, and Tessera.compatibilityVersion == SchemaCompatibilityVersion. Any error returns false, so it can only skip work that would have been a no-op; checkDataCompatibility still runs on every open. NewAntispam now resolves its Spanner client before the schema step so the same client serves the check.

Testing

TestSchemaInitialised (both packages) covers the initialised, empty, and each partially-initialised / mismatched case. TestInitDBExistingSchema and TestNewAntispamExistingSchema re-open an initialised database and check existing state is untouched; since the spannertest emulator rejects CREATE TABLE on an existing table regardless of IF NOT EXISTS, these also show no DDL is issued on re-open (and t.Skip should that emulator behaviour change). go test ./storage/gcp/..., gofmt, golangci-lint clean.

initDB (used by Appender and MigrationWriter) and antispam.NewAntispam
currently apply their CREATE TABLE IF NOT EXISTS / ALTER TABLE ... ADD
COLUMN IF NOT EXISTS statements through a DatabaseAdmin client, and then
insert the seed rows, every time they are called - not only when a log
is first created. Spanner executes DDL as schema-update operations,
which take on the order of seconds each and are serialised per database
even when IF NOT EXISTS means they end up changing nothing, so simply
restarting a log costs several schema-update operations. When many logs
share one database via SpannerTablePrefix this adds up quickly, and
concurrent restarts queue up behind one another.

Make initDB first do a cheap read-only check using the data client which
Appender/MigrationWriter have already created: if every table it would
create exists, PubCoord already has its size column, every seed row is
present, and the stored compatibilityVersion matches the library's, then
the DDL and seeding would be a no-op and are skipped. If anything is
missing, different, or cannot be read, initDB carries on and creates or
migrates the schema exactly as before, so the check can only ever elide
work which would have done nothing. The same client is now also used to
apply the seed mutations, rather than a temporary one.

The antispam storage gets the same treatment for its FollowCoord and
IDSeq tables. NewAntispam now resolves its Spanner client (the provided
one, or a new one) before initialising the schema, so that one client
serves the check, the seeding, and the returned storage.

There is no API or schema change, and behaviour against an empty or
partially initialised database is unchanged.

Tests cover the check against initialised, empty, and several partially
initialised or mismatched databases, and show that re-running initDB and
NewAntispam against an existing schema succeeds and leaves existing
state alone. The spannertest emulator rejects CREATE TABLE for an
existing table regardless of IF NOT EXISTS, so those re-runs would fail
if any DDL were still being applied.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant