perf(posts): read pre-rendered content_html/toc_html from DB instead of re-rendering

Previously row_to_post_full() ignored the stored content_html and
re-rendered markdown with render_markdown_enhanced() on every read,
triggering expensive ammonia sanitization (aho_corasick accounted for
87% CPU in flame graph).

Now reads content_html and toc_html directly from the database row.
Falls back to render_markdown_enhanced() only for legacy posts where
content_html is NULL.
This commit is contained in:
xfy 2026-06-12 10:35:39 +08:00
parent 7244c39a47
commit acd85d18ce

View File

@ -91,9 +91,26 @@ pub(super) async fn row_to_post_full(
None None
}; };
let content_html: Option<String> = row.get("content_html");
let toc_html_row: Option<String> = row.get("toc_html");
let (content_html, toc_html) = if let Some(html) = content_html {
(html, toc_html_row)
} else {
let content_md: String = row.get("content_md");
let rendered = crate::api::markdown::render_markdown_enhanced(&content_md);
(
rendered.html,
if rendered.toc_html.is_empty() {
None
} else {
Some(rendered.toc_html)
},
)
};
let content_md: String = row.get("content_md"); let content_md: String = row.get("content_md");
let word_count = count_words(&content_md); let word_count = count_words(&content_md);
let rendered = crate::api::markdown::render_markdown_enhanced(&content_md);
Post { Post {
id, id,
@ -102,7 +119,7 @@ pub(super) async fn row_to_post_full(
slug: row.get("slug"), slug: row.get("slug"),
summary: row.get("summary"), summary: row.get("summary"),
content_md, content_md,
content_html: Some(rendered.html), content_html: Some(content_html),
status, status,
published_at: row.get("published_at"), published_at: row.get("published_at"),
created_at: row.get("created_at"), created_at: row.get("created_at"),
@ -111,11 +128,7 @@ pub(super) async fn row_to_post_full(
cover_image: row.get("cover_image"), cover_image: row.get("cover_image"),
reading_time: (word_count / 200).max(1), reading_time: (word_count / 200).max(1),
word_count, word_count,
toc_html: if rendered.toc_html.is_empty() { toc_html,
None
} else {
Some(rendered.toc_html)
},
prev_post, prev_post,
next_post, next_post,
} }