DROP TABLE events_2017_10. That is how you delete October now, and it is the reason I am reading PostgreSQL 10 release notes on a Thursday evening instead of waiting a year like usual.
Partitioning existed before, through inheritance, CHECK constraints and an insert trigger you wrote yourself and hoped was right. Now the database owns it:
CREATE TABLE events (
id bigserial NOT NULL,
created_at timestamptz NOT NULL,
payload jsonb
) PARTITION BY RANGE (created_at);
CREATE TABLE events_2017_10 PARTITION OF events
FOR VALUES FROM ('2017-10-01') TO ('2017-11-01');
The win is the data lifecycle. An events table grows forever, and deleting a year of history with DELETE is a night of I/O plus a bloated table in the morning. With partitions, retiring a month is one DROP. Instant. And a query that filters by created_at visits only the partitions in range, the planner skips the rest.
Logical replication is the second thing. Streaming replication copies the whole cluster byte by byte, same major version on both ends. Logical works per table: a publication on one server, a subscription on another, only the tables you named. So you can feed the two hot tables to a reporting server and let the analysts run their monster queries far from production. Or replicate across versions, which makes the next major upgrade less of a cliff.
Now the cold shower. Partitioning is not a speed switch. If your queries do not filter by the partition key, every query now visits every partition, and you made it slower and the schema stranger. A unique constraint across partitions does not exist in this version at all, and a row with no matching partition simply fails to insert, so somebody has to create November before November. The test I use: for each frequent query, can you say which partitions it touches? If the answer is “all of them”, you do not need partitions. You need an index.