Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.32.0] - Not released
### Changed
- Reduced PostgreSQL WAL generation by storing transient `local_bumps` feed-ordering
data in an unlogged table. Its contents can be cleared after an unclean PostgreSQL
shutdown and will repopulate through new activity.
- Skipped physical `posts.feed_ids` updates when all requested feed IDs are already
present, avoiding unnecessary heap and index writes.

## [2.31.1] - 2026-08-03
### Added
Expand Down
12 changes: 8 additions & 4 deletions app/support/DbAdapter/posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,14 @@ const postsTrait = (superClass) =>
return null;
}

return this.database.raw('UPDATE posts SET feed_ids = (feed_ids | ?) WHERE uid = ?', [
feedIntIds,
postId,
]);
// Updating feed_ids prevents HOT and rewrites every posts index.
// Skip physical no-op updates.
return this.database.raw(
`UPDATE posts
SET feed_ids = (feed_ids | ?)
WHERE uid = ? AND NOT (feed_ids @> ?::int[])`,
[feedIntIds, postId, feedIntIds],
);
}

withdrawPostFromFeeds(feedIntIds, postUUID) {
Expand Down
7 changes: 7 additions & 0 deletions migrations/20260810123325_local_bumps_unlogged.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type Knex } from 'knex';

// local_bumps is disposable feed-ordering state that naturally repopulates after a crash.
// Keep it unlogged to avoid WAL for its high-churn heap and indexes.
export const up = (knex: Knex) => knex.schema.raw(`alter table local_bumps set unlogged`);

export const down = (knex: Knex) => knex.schema.raw(`alter table local_bumps set logged`);
33 changes: 33 additions & 0 deletions test/integration/support/DbAdapter/posts/feed-ids.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* global $pg_database */
import expect from 'unexpected';

import cleanDB from '../../../../dbCleaner';
import { dbAdapter, User } from '../../../../../app/models';

describe('Post feed IDs', () => {
beforeEach(() => cleanDB($pg_database));

it('updates a post only when at least one feed ID is missing', async () => {
const luna = new User({ username: 'luna', password: 'password' });
await luna.create();

const post = await luna.newPost({ body: 'Post body' });
await post.create();

const likesFeedId = await luna.getLikesTimelineIntId();
const firstResult = await dbAdapter.insertPostIntoFeeds([likesFeedId], post.id);
const secondResult = await dbAdapter.insertPostIntoFeeds([likesFeedId], post.id);
const commentsFeedId = await luna.getCommentsTimelineIntId();
const partialResult = await dbAdapter.insertPostIntoFeeds(
[likesFeedId, commentsFeedId],
post.id,
);
const updatedPost = await dbAdapter.getPostById(post.id);

expect(firstResult.rowCount, 'to equal', 1);
expect(secondResult.rowCount, 'to equal', 0);
expect(partialResult.rowCount, 'to equal', 1);
expect(updatedPost.feedIntIds, 'to contain', likesFeedId);
expect(updatedPost.feedIntIds, 'to contain', commentsFeedId);
});
});
Loading