Journal
Sections
Reading settings
No voice for this language in this browser.
More voices
Code and English terms
Off: the page’s voice reads everything, with no pause between two voices.
No voice for this language in this browser.
More voices
More voices: Microsoft Edge has natural online voices, and Windows (Settings › Time & language › Speech), macOS and Android install others.
Progress
Section titled “Progress”- Mission and a 13-lesson outline
- Code: SQL scripts run by
psql, C# and Java programs, all compared with their expected output bycheck.sh - CI:
postgresql-aurora-examples.ymlruns everything against PostgreSQL on Linux and builds the programs on Windows and macOS; first run green on 2026-09-16 - Lesson 1: a container,
psql, databases, schemas and roles - Lesson 2: types and modeling
- Lesson 3: CTEs, windows,
LATERAL, upserts andMERGE - Lesson 4: PostgreSQL from C# and Java
- Lesson 5: indexes and plans
- Lesson 6: transactions and MVCC
- Lesson 7: JSON and search
- Lesson 8: functions, extensions and pgvector
- Lesson 9: partitioning
- Lesson 10: replication, on three clusters inside the course container
- Lesson 11: backups, point-in-time recovery and
pg_upgrade - Lesson 12: Aurora, from the documentation, and a failover from C# and Java
- Lesson 13: migrating from SQL Server, by hand, by program and with pgloader; DMS, Babelfish and costs from the documentation
- The course is complete
- Anything on AWS: every Aurora section is still to verify
PostgreSQL 18.6 in a container, Npgsql 10.0.3, pgjdbc 42.7.13, pgvector 0.8.6, pgloader from source, and SQL Server 2025 for the migration. Most rows are a driver, a tool or a default doing something the documentation or the other driver does not; two are GuitarAlchemist/ga and AWS’s own documentation. Nothing here has been reported upstream.
There is no Experiments table. The journal records no hypothesis before any of its measurements: the five HNSW rebuilds, the statistics-target threshold and the driver timings are measurements whose explanation was written after the number, and turning them into experiments now would mean inventing the prior backwards.
| Expected | What happens | Where | Measure | Status |
|---|---|---|---|---|
CREATE FUNCTION … BEGIN ATOMIC …; END is one command, since its semicolons are inside the body |
Npgsql splits the command text at the semicolon inside the body, as it does between statements, and the server rejects the truncated command | Npgsql 10.0.3, NpgsqlCommand without parameters |
42601: syntax error at end of input. It works in an NpgsqlBatch, with Npgsql.EnableSqlRewriting off, or with positional parameters. pgjdbc 42.7.13 recognises BEGIN ATOMIC and does not split |
Reproduced, not reported 2026-09-16 |
pgloader migrates a SQL Server money column without losing digits, or warns |
It keeps two decimals, silently: it reads money through convert(varchar(40), [col], 0), and style 0 truncates |
pgloader built from 231ab86, mssql-schema.lisp, lines 212-217 |
0.0125 became 0.01, with no error or warning. The same run dropped the CHECK constraint and turned INCLUDE (Conclusion) into a key column |
Reproduced, not reported 2026-09-16 |
Target Session Attributes=standby is accepted with a single host, as pgjdbc accepts targetServerType=secondary |
Npgsql refuses the connection string outright; pgjdbc accepts it and fails later, at connection time | Npgsql 10.0.3, pgjdbc 42.7.13 | NotSupportedException: Target Session Attributes other then Any is only supported with multiple hosts |
By design in both, differently 2026-09-15 |
PowerShell passes -Duser.timezone=Asia/Tokyo to java as one argument |
PowerShell 7.6 still splits it at the dot | PowerShell 7.6 | Could not find or load main class .timezone=Asia.Tokyo |
Reproduced; quoted, it works 2026-09-15 |
An unspecified-kind DateTime gives the same rows whatever the session’s time zone |
It does not throw, as expected, but it is sent as timestamp and converted in the session’s time zone |
Npgsql 10.0.3 | 82 runs or 59, depending on Timezone. A DateTimeOffset with a −4 hour offset throws ArgumentException instead |
By design 2026-09-15 |
SET statement_timeout = '2s' ends an INSERT waiting for a stopped synchronous standby |
The wait is not interrupted; the statement hangs until it is cancelled from outside | PostgreSQL 18.6, synchronous replication between the course container’s clusters | Ten minutes, until pg_cancel_backend; the warning says the transaction “has already committed locally” |
By design: the commit is already local and only the wait remains. The script now cancels the wait from another session 2026-09-16 |
pg_rewind rewinds the demoted primary against the new one |
The old primary had already recycled the WAL segment pg_rewind needs |
pg_rewind 18, the course container’s node1 |
could not open file "node1/pg_wal/000000010000000000000002" |
Reproduced; wal_keep_size = 128MB keeps the segment 2026-09-16 |
pg_upgrade --check from a default 17 cluster to a default 18 cluster passes |
initdb 18 enables data checksums by default, so the two defaults no longer match |
pg_upgrade 18, initdb 18 |
Stopped at “old cluster does not use data checksums but the new one does” | By design, a changed default; --no-data-checksums on the new cluster 2026-09-16 |
A jsonb_path_ops GIN index is smaller than a jsonb_ops one, as the documentation says is usual |
On these documents it is slightly larger | PostgreSQL 18.6, the copied CI documents | 1,752 kB for jsonb_path_ops against 1,688 kB for jsonb_ops |
Reproduced; “usual” is not “always” 2026-09-16 |
| A filtered nearest-neighbour query returns the ten rows asked for | The HNSW scan collects its candidates before the filter, none of them passes it, and the query returns nothing | pgvector 0.8.6, enable_seqscan = off |
No rows where ten were asked for: 40 candidates, none a triad. Rebuilding the index five times gave the same 40 distances each time | By design; hnsw.iterative_scan = strict_order returns the ten, and the lesson sets it 2026-09-16 |
A script run with sqlcmd -i prints what the same statements print with -Q |
With -i, every result after an uncaught error is dropped, when the error is the first statement of a batch following a batch with results |
sqlcmd 18.6 against SQL Server 2025 CU9 |
-Q printed the results; -i dropped them |
Reproduced, not reported; the scripts catch their errors with TRY … CATCH 2026-09-16 |
Microsoft.Data.SqlClient opens a connection from the course’s C# project |
It refuses, because the project sets InvariantGlobalization |
Microsoft.Data.SqlClient 7.0.3, OpenAsync |
Globalization Invariant Mode is not supported. |
By design; the migration program has its own project 2026-09-16 |
| A converter that joins a list into one string reads back every element it saved | StringSplitOptions.RemoveEmptyEntries on the read side drops an empty alternate name |
GuitarAlchemist/ga, the value converters of MusicalKnowledgeDbContext.cs, in three copies |
Three names saved, two read back | Fixed upstream by #734, merged 2026-09-24, in the three copies 2026-09-15, 2026-09-24 |
| AWS’s pages agree with each other on an end-of-life date | Two guides give two dates, and disagree on a default mode too | the CloudWatch guide and the Aurora User Guide’s document history | Performance Insights ends on July 31, 2026 in one and November 30, 2025 in the other; Database Insights defaults to Standard in one and Advanced in the other | Reproduced in the documentation; not reported 2026-09-16 |
2026-09-15 — Versions
Section titled “2026-09-15 — Versions”- PostgreSQL’s versioning page lists 18.6 as the current minor release of PostgreSQL 18, supported until November 14, 2030. PostgreSQL 19 is in beta. The course pins the image tag
postgres:18.6-trixieand records its digest in the mission page. - The newest Aurora PostgreSQL release in AWS’s release notes is 18.4.1, from August 21, 2026. Its extension table gives
btree_gist1.6; PostgreSQL 18.6 in the image offers 1.8. - Clients: Npgsql 10.0.3 and its EF Core provider 10.0.3, which requires EF Core 10.0.4 or later; pgjdbc 42.7.13, HikariCP 7.1.0.
pgvectoris not in the official image: lesson 8 will need another image or a build.
2026-09-15 — Starting the server
Section titled “2026-09-15 — Starting the server”- The first start of the image runs a temporary server to create the cluster, listening on the Unix socket only.
pg_isreadythrough the socket said “accepting connections” during that window, and the next command hit a server that was shutting down.pg_isready -h 127.0.0.1goes through TCP.server.shand the CI service’s health check use it. psqlerrors came out before the results of earlier statements in the saved outputs:psqlbuffers standard output, not standard error.server.shrunspsqlunderstdbuf -o0, inside the container, with both streams merged there.- Every script runs with
TimeZone=UTC,DateStyle=ISO, MDYandlc_messages=C, set inPGOPTIONS, and every query has anORDER BY.\conninfoprints the server process ID, which changes at every run: the scripts don’t use it.
2026-09-15 — Modeling the CI history
Section titled “2026-09-15 — Modeling the CI history”- A closed range
[started_at, completed_at]for the steps made 2,798 pairs of steps of the same job overlap: GitHub’s times are to the second, and a step starts the second the previous one ends. Half-open[)ranges fix that, but 857 of the 2,204 steps become empty ranges, which lose their bounds. The table keeps both timestamps and derives the range as a stored generated column. - A
CHECKwhose error message includednow()gave a different output at each run; the test rows use fixed dates. WITH TIESreturned tied rows in a different order from one run to the next: an outerORDER BYfixes the order.
2026-09-15 — Findings in Guitar Alchemist
Section titled “2026-09-15 — Findings in Guitar Alchemist”Nothing here was reported to the project; these are notes, with the queries that reproduce them in the lessons.
GA.Knowledge.Service.csprojreferencesGA.Business.Config.fsprojtwice, on lines 26 and 31, at commit32f143c. The primary key ofga.project_refsrejects the second one (lesson 2).MusicalKnowledgeDbContext.csexists in three copies that differ only by their namespace, inCommon/GA.Data.EntityFramework/,Common/GA.Data.EntityFramework/Data/andCommon/GA.Infrastructure/Persistence/EntityFramework/. I found noAddDbContextorUseSqlitecall for it at that commit, while services inject it.- Its value converters store lists as joined strings and read them back with
StringSplitOptions.RemoveEmptyEntries: an empty alternate name is lost. Three names saved, two read back (lesson 4). - In
IconicChords.yaml, three voicings don’t play their declared pitch classes: Blackbird plays no B and adds C♯, E and A; the Hendrix voicing has no B; the Mu Major voicing,Cadd9(no3), plays E (lesson 3, exercise 1). - Package versions:
Microsoft.Extensions.Hostingis referenced in 5 versions andMongoDB.Driverin 5; 16 packages have a text maximum that isn’t their highest version; 13 distinct versions aren’t plain numbers, including the floating0.*and8.*-*(lesson 3). - Two projects are named
GaApi.Tests.csproj, inTests/Apps/GaApi.TestsandTests/GaApi.Tests.
2026-09-15 — The drivers
Section titled “2026-09-15 — The drivers”- Npgsql: a
DateTimewithKind=Unspecifieddidn’t throw, as I expected, but was sent astimestampand converted in the session’s time zone: 82 runs or 59 depending onTimezone. ADateTimeOffsetwith an offset of −4 hours throwsArgumentException. pg_prepared_statementscounted the counting query itself once it was prepared; a regular expression that doesn’t match its own text fixed the count.- EF Core builds a model once per context type: two configurations of the same entity in one context class, chosen by a constructor argument, gave the first model twice. One subclass per configuration.
- pgjdbc sends the JVM’s time zone as the session’s
TimeZone, sogetStringon atimestamptzdepended on the machine.check.shruns Java with-Duser.timezone=America/Toronto. - PowerShell 7.6 still splits
-Duser.timezone=Asia/Tokyoat the dot: Java answered “Could not find or load main class .timezone=Asia.Tokyo”. Quoted, it works. - Npgsql refuses
Target Session Attributes=standbywith a single host:NotSupportedException: Target Session Attributes other then Any is only supported with multiple hosts. pgjdbc acceptstargetServerType=secondarywith one host and fails at connection time. - One run of
l04-timings: 478INSERTcommands in about a second, oneNpgsqlBatchin 5 to 7 ms, a binaryCOPYin about 50 ms. Earlier runs the same day took up to 2.2 seconds for theINSERTloop.
2026-09-16 — Plans that give the same output at every run
Section titled “2026-09-16 — Plans that give the same output at every run”EXPLAIN ANALYZEon the 440,800-row step history gave different rows per node from one run to the next: parallel workers split the rows differently each time.max_parallel_workers_per_gather = 0for the lessons that show plans.- The planner’s estimates changed after each
ANALYZE, which reads a random sample of 30,000 rows with the default statistics target. At 1,500, the sample is 450,000 rows, more than the table, and the estimates are exact and stable. - The
Buffersline split pages betweenhitandreaddepending on what the previous statements left in memory. The plan helper adds them into one number; the total didn’t change across a container restart. - PostgreSQL 18 prints
BufferswithoutBUFFERS, row counts with two decimals, andIndex Searches. xminprinted-1for the first row version: the transaction ID saved with\gsetwas taken after theINSERT. It is taken just before now.pg_stat_user_tablesshowed zero HOT updates right after the updates: statistics are sent at most once a second.pg_stat_force_next_flush()fixes the count.
2026-09-16 — Two sessions in one program
Section titled “2026-09-16 — Two sessions in one program”- A program can’t wait for B’s blocked statement to return. The C# and Java programs start it, and a third connection polls
pg_stat_activityuntil B waits on a lock, withpg_blocking_pidsnaming A. - In the deadlock example, either session could be the one cancelled, depending on which waited
deadlock_timeoutfirst. A has 10 seconds and B 100 ms: B always detects the cycle, and is cancelled.
2026-09-16 — JSON and search
Section titled “2026-09-16 — JSON and search”- A stored generated
tsvectorwhose configuration came from another generated column was refused: a generated column can’t refer to another one. The configuration is a plain column, set by theINSERT. - A trigram index on the 478 rows of
ga.package_refswas never used, even withenable_seqscan = off: the primary key’s index was cheaper. The example uses the 88,160 step names of the copied documents. - On these documents,
jsonb_path_opscame out slightly larger thanjsonb_ops, 1,752 kB against 1,688 kB, the opposite of what the documentation says is usual. - The phrase
"prepared statements"matchedpg_prepared_statementsin this journal: the text search parser splits at underscores.
2026-09-16 — Functions and pgvector
Section titled “2026-09-16 — Functions and pgvector”- Finding, Npgsql 10.0.3. A single
CREATE FUNCTION … BEGIN ATOMIC SELECT …; ENDin anNpgsqlCommandwithout parameters fails with42601: syntax error at end of input. Npgsql splits the command text at the semicolon inside the body, as it does for several statements. The same text works in anNpgsqlBatch, with theNpgsql.EnableSqlRewritingswitch set tofalse, or in a command that has positional parameters. pgjdbc 42.7.13 detectsBEGIN ATOMICand doesn’t split; aBEGIN ATOMICfunction followed by another statement in oneexecutethen fails as several commands in a prepared statement. Not reported to either project. - The official image has no pgvector. The pgvector scripts run on
pgvector/pgvector:0.8.6-pg18-trixie, one server at a time on port 5432; CI runs it as a second service, without a port. - With
enable_seqscan = off, a nearest-neighbour query filtered on triads returned no row at all, where ten were asked for: the HNSW scan returns 40 candidates, none of them a triad.hnsw.iterative_scan = strict_orderreturns the ten. I rebuilt the index five times: the 40 candidates’ distances were the same each time. - The interval-class vectors of the iconic chords show that the Elektra chord is the Petrushka chord transposed up a major third, and that the Foxy Lady chord is the inversion of the Joni Mitchell chord, whose transposition is the Debussy chord.
2026-09-16 — Aurora, from the documentation
Section titled “2026-09-16 — Aurora, from the documentation”- Aurora PostgreSQL 18.4 ships pgvector 0.8.2, where the course runs 0.8.6.
pageinspectisn’t in the Aurora PostgreSQL 18 extension table. - AWS’s pages disagree on Performance Insights’ end of life: July 31, 2026 in the CloudWatch guide, November 30, 2025 in the Aurora User Guide’s history. They also disagree on whether Database Insights defaults to Standard or Advanced mode.
max_standby_streaming_delay: 30 seconds on theLock:Relationpage, 14,000 ms in the parameter table for Aurora PostgreSQL 14.
2026-09-16 — Partitioning
Section titled “2026-09-16 — Partitioning”- A primary key on
idalone is refused on a table partitioned bystarted_at; with(id, started_at)it’s accepted, and checked partition by partition. DETACH PARTITION … CONCURRENTLYisn’t allowed while the table has a default partition, and a default partition that holds a row for October blocks creating October’s partition.- Hash partitioning on 64 job names gave one partition 2.1 times the rows of another.
- A procedure that printed a
regclassafterDROP TABLEprinted the bare OID: the notice has to come before the drop.
2026-09-16 — Several servers in one container
Section titled “2026-09-16 — Several servers in one container”- The course runs one container at a time. Lessons 10 and 11 start small clusters inside it with
initdbandpg_ctl, on ports 5433 to 5436, withshared_buffers = 32MB; CI runs the same scripts inside its service container. - A script rewritten on Windows through Python’s text mode got CRLF line endings, and
bash -sin the container failed on its first lines. Files written withnewline=''keep LF. - The image’s clusters put their Unix socket in
/var/run/postgresql, not/tmp: withPGHOST=/tmp, everypsqlfailed, and each wait loop timed out after 30 seconds. SET statement_timeout = '2s'didn’t end anINSERTwaiting for a synchronous standby that was stopped: it waited ten minutes, until I cancelled it withpg_cancel_backend. The script now cancels the wait from another session; the warning says the transaction “has already committed locally”.pg_rewindfirst failed withcould not open file "node1/pg_wal/000000010000000000000002": the old primary had recycled the segment it needed.wal_keep_size = 128MBkeeps it.- PostgreSQL 18’s conflict statistics count the
insert_existsconflict of a subscriber that had its own row; the apply worker retries until the row is deleted.
2026-09-16 — Backups and pg_upgrade
Section titled “2026-09-16 — Backups and pg_upgrade”pg_upgrade --checkfrom a 17 cluster to a new 18 cluster stopped at “old cluster does not use data checksums but the new one does”:initdb18 enables checksums by default.--no-data-checksumson the new cluster fixes it.pg_upgrade18 kept the six column statistics ofci.runs, not its extended statistics;vacuumdb --analyze-only --missing-stats-onlyrebuilt them.- The upgrade needs PostgreSQL 17’s binaries:
check.shinstallspostgresql-17from the apt repository the image already uses, so the old side’s minor version isn’t pinned. The outputs show the major version only.
2026-09-16 — A failover from the drivers
Section titled “2026-09-16 — A failover from the drivers”ops/12-cluster.shstarts a primary and a standby in the course container, published on ports 5433 and 5434. The programs stop the primary throughCOPY … TO PROGRAM 'pg_ctl … -W stop'and promote the standby withpg_promote().- On the connection opened before the failure, pgjdbc reports
57P01, “terminating connection due to administrator command”, and Npgsql “Exception while reading from stream” on Windows through Docker Desktop, but aPostgresExceptionwith57P01in CI on Linux: the first CI run failed on that line. The C# program now prints the connection’sFullState,Brokenon both. - Both drivers cache host states for 10 seconds by default, yet a write right after the promotion found the new primary in both. I haven’t traced why in their source.
2026-09-16 — Aurora, lessons 9 to 12
Section titled “2026-09-16 — Aurora, lessons 9 to 12”- The maximum cluster volume: 256 TiB for Aurora PostgreSQL 15.13, 16.9, 17.5 and higher in the quotas page’s version table, 128 TiB in the same page’s quota table, 256 TiB without condition in the overview.
- The release calendar gives PostgreSQL 18 a “community release date” of February 26, 2026, the date of 18.3; PostgreSQL 18.0 came out on September 25, 2025.
- The major version upgrade page says optimizer statistics aren’t transferred, while
pg_upgrade18 transfers most of them. - Blue/green deployments and zero-ETL have no Aurora PostgreSQL 18 column in their version tables yet; RDS Proxy has one, from 18.3.
- The AWS Advanced .NET Data Provider Wrapper exists, 2.2.0 on GitHub, with an Npgsql dialect, but the Aurora User Guide’s list of AWS drivers doesn’t mention it.
2026-09-16 — Migrating from SQL Server
Section titled “2026-09-16 — Migrating from SQL Server”- The course is complete: 13 lessons. SQL Server 2025 CU9 started in a few seconds in its container and used about 450 MB at rest; CI runs it as a service container next to PostgreSQL.
sqlcmd18.6 (mssql-tools18in the image) dropped every result that followed an uncaught error in a script run with-i, when the error was the first statement of a batch that came after a batch with results;-Qwith the same statements printed them. The scripts catch their errors withTRY … CATCH.Microsoft.Data.SqlClient7.0.3 threwGlobalization Invariant Mode is not supported.onOpenAsyncin the course’s C# project, which setsInvariantGlobalization. The migration program has its own project.- Npgsql truncated
datetimeoffset(7)ticks to microseconds (.9999999to.999999); pgloader rounded them (10:06:00.9999999 -04:00to14:06:01). - pgloader, image built from
231ab86, turned amoneyvalue of0.0125into0.01, without error or warning: it readsmoneywithconvert(varchar(40), [col], 0), and style 0 keeps two decimals (mssql-schema.lisp, lines 212-217). It also left out theCHECKconstraint and turnedINCLUDE (Conclusion)into a key column. - SQL Server’s
IDENT_CURRENTwas 6 with three rows: two failed inserts and a deleted row had used values. - AWS documentation read the same day: DMS doesn’t list SQL Server 2025 as a source; Babelfish’s parameter table and collations page give two different default server collations, and the collations page still says PostgreSQL doesn’t support
LIKEon nondeterministic collations; the open-source Babelfish stops at PostgreSQL 17.7 and has no container image. - Microsoft’s SQL Server 2025 pricing web page refused scripted requests; the prices come from its PDF pricing sheet.
2026-09-24 — Upstream fixes
Section titled “2026-09-24 — Upstream fixes”- #734, merged 2026-09-24, fixes the list converters: the 11 string-list converters in the three copies of
MusicalKnowledgeDbContextsplit withoutRemoveEmptyEntries, and an empty column reads back as an empty list. Its test fails on all 11 with the old code. One limit remains, and the PR says so: a list holding a single empty string still reads back as an empty list, because both store the same column value.
To verify
Section titled “To verify”- The Linux and macOS commands of lessons 1 and 4, on those systems.
- Every “On Aurora” section:
pg_read_fileand theCONNECTrequirement forrds_superuser,btree_gist1.6, the read-only error on a replica, TLS by default, IAM tokens with Npgsql’s periodic password provider, RDS Proxy pinning with Npgsql’sDISCARD ALLand with protocol-level prepared statements, andpg_is_in_recovery()on an Aurora Replica. - Lessons 5 to 8 on Aurora: the
shared_buffersdefault for Aurora PostgreSQL 18 and the reason it’s larger, query plan management on 18.4,hot_standby_feedbackholding backVACUUMon the writer, themax_standby_streaming_delaydefault, trusted extensions underrds.allowed_extensions, and pgvector 0.8.2’s iterative scans. - Lessons 9 to 12 on Aurora: pg_partman 5.4.3 on 18.4, logical replication after
rds.logical_replication, statistics after a major upgrade to 18, blue/green deployments and zero-ETL on 18, Aurora Serverless ranges and auto-pause on 18, whether a pool’s idle connections prevent auto-pause, RDS Proxy with cancel requests, and a failover’s duration as seen from Npgsql and pgjdbc. - Why Npgsql and pgjdbc found the promoted standby right away despite their 10-second host state caches.
- Lesson 13 on AWS: DMS with a SQL Server 2025 source, whether DMS validation reports
datetimeoffset(7)values truncated to microseconds, Babelfish’s default server collation and its handling of this lesson’s seven behaviours, the prices of the AWS Price List published on 2026-09-11, and whether RDS bills a 2-vCPU SQL Server instance for 4 license vCPUs.