Agent Skills: Notion Performance Tuning

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/notion-performance-tuning

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/notion-pack/skills/notion-performance-tuning

Skill Files

Browse the full folder contents for notion-performance-tuning.

Download Skill

Loading file tree…

plugins/saas-packs/notion-pack/skills/notion-performance-tuning/SKILL.md

Skill Metadata

Name
notion-performance-tuning
Description
'Optimize Notion API performance with caching, batching, parallel requests,

Notion Performance Tuning

Overview

Optimize Notion API performance by minimizing API calls, caching responses with TTL-based invalidation, batching block appends, parallelizing requests within rate limits, selecting only needed properties, and implementing incremental sync patterns. Target latency benchmarks: Database Query p50=150ms, Page Create p50=200ms, Search p50=300ms.

Prerequisites

  • @notionhq/client installed (npm install @notionhq/client)
  • p-queue for rate-limited parallelism (npm install p-queue)
  • lru-cache for TTL-based caching (npm install lru-cache)
  • Authentication: a Notion integration token in NOTION_TOKEN (internal integration secret from https://www.notion.so/my-integrations), passed as new Client({ auth: process.env.NOTION_TOKEN })
  • Understanding of your access patterns (read-heavy vs write-heavy)
  • Optional: Redis or ioredis for distributed caching across instances

Instructions

Apply the three techniques in order — each builds on the previous one. The lean skeletons below are enough to follow the workflow; drill into the linked reference files for the complete, copy-paste-ready code.

Step 1: Minimize API Calls and Reduce Payload

Avoid N+1 patterns, page with page_size: 100 (the maximum), select only the properties you need with filter_properties, and batch block appends in chunks of 100.

// Batch block appends — up to 100 blocks per request (API maximum)
for (let i = 0; i < blocks.length; i += 100) {
  await notion.blocks.children.append({
    block_id: pageId,
    children: blocks.slice(i, i + 100),
  });
}

See full implementation walkthrough for the N+1-vs-batched query comparison, filter_properties usage, and selective block-tree expansion.

Step 2: Cache Responses with TTL-Based Invalidation

Use an LRU cache with per-operation TTLs (short for volatile data like search, longer for stable data like schemas) and invalidate entries on every write to keep reads consistent.

import { LRUCache } from 'lru-cache';
const cache = new LRUCache<string, any>({ max: 1000, ttl: 60_000, allowStale: false });
// Invalidate on write: for (const key of cache.keys())
//   if (key.startsWith(`db:${dbId}:`)) cache.delete(key);

See full implementation walkthrough for tiered TTL configuration, cursor-based cached pagination, write-through invalidation, and cache-stats monitoring.

Step 3: Parallel Requests with Rate-Limited Queue and Latency Monitoring

See parallel requests and latency monitoring for p-queue rate-limited parallelism, latency tracking with p50/p95 benchmarks, incremental sync, and memory-efficient streaming via async generators.

Output

  • Reduced API call count through property selection, filtering, and batched block appends
  • TTL-based caching with write-through invalidation for data consistency
  • Parallel requests within Notion's 3 req/sec rate limit using p-queue
  • Incremental sync fetching only changed pages since last sync timestamp
  • Latency monitoring with p50/p95 tracking against target benchmarks
  • Memory-efficient streaming for large datasets via async generators

Error Handling

| Issue | Cause | Solution | | ------- | ------- | ---------- | | Stale cache data | TTL too long for volatile data | Use shorter TTL (30s for search, 60s for queries) | | Rate limit despite queue | Other code paths making unqueued calls | Use a single shared p-queue instance across your app | | Memory pressure from cache | Too many entries or large payloads | Set max on LRU cache; use filter_properties to shrink payloads | | Pagination never ends | Circular cursor or API bug | Add max-iteration guard (if (requestCount > 50) break) | | Incremental sync misses | Clock skew between client and API | Subtract a 5-second buffer from lastSyncTime | | p50 latency above target | Cold cache or large responses | Pre-fetch critical pages; use filter_properties to reduce response size |

Examples

A complete NotionPerf class combining caching, rate limiting, and incremental sync, plus a before/after latency benchmark, lives in the examples reference. The essential surface:

const perf = new NotionPerf(process.env.NOTION_TOKEN!);
const results = await perf.query('db-id-here');  // cached + rate-limited
const updates = await perf.sync('db-id-here');   // fetches only changed pages

See the examples reference for the full class definition and the latency-comparison harness.

Resources

Next Steps

After tuning request patterns, wire up event-driven invalidation instead of relying purely on TTL expiry: the notion-webhooks-events skill covers receiving Notion webhook events and invalidating exactly the cache keys that changed, which keeps caches fresh without shortening TTLs.