batch-processing

DataLoader pattern for batch processing to solve N+1 query problems. Reduces database/API calls from N+1 to 2 by batching and caching.

Safety Notice

This listing is from the official public ClawHub registry. Review SKILL.md and referenced scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "batch-processing" with this command: npx skills add qwe123sddfsdfs/batch-processing

Batch Processing with DataLoader

Solve N+1 query problems using the DataLoader pattern. This skill provides utilities for batching database queries, API calls, or any expensive operations.

Problem: N+1 Query

// BAD: N+1 queries (1 for list + N for each item)
const users = await db.query('SELECT * FROM users');
for (const user of users) {
  const posts = await db.query('SELECT * FROM posts WHERE user_id = ?', [user.id]);
  user.posts = posts;
}
// Total queries: 1 + N (where N = number of users)

Solution: DataLoader Pattern

// GOOD: 2 queries total using batching
const DataLoader = require('./batch-processing/dataloader.js');

const userLoader = new DataLoader(async (userIds) => {
  // Batch load all users in one query
  const users = await db.query('SELECT * FROM users WHERE id IN (?)', [userIds]);
  return userIds.map(id => users.find(u => u.id === id));
});

const postLoader = new DataLoader(async (userIdss) => {
  // Batch load posts for multiple users
  const allPosts = await db.query('SELECT * FROM posts WHERE user_id IN (?)', [userIdss.flat()]);
  return userIdss.map(ids => allPosts.filter(p => ids.includes(p.user_id)));
});

// Usage - automatically batches concurrent requests
const users = await Promise.all(userIds.map(id => userLoader.load(id)));
const posts = await Promise.all(userIds.map(id => postLoader.load(id)));
// Total queries: 2 (regardless of N)

Core API

DataLoader Constructor

const loader = new DataLoader(batchLoadFn, options);

batchLoadFn: async (keys) => values

  • Receives array of keys
  • Must return array of values in same order
  • Can return null/undefined for missing keys

options (optional):

  • maxBatchSize: Maximum keys per batch (default: 100)
  • batchScheduleMs: Delay to collect batch (default: 0, immediate)
  • cache: Enable caching (default: true)
  • cacheKeyFn: Custom cache key function

Methods

  • load(key): Load a single key (returns Promise)
  • loadMany(keys): Load multiple keys (returns Promise<Array>)
  • prime(key, value): Manually prime cache
  • clear(key): Clear cache for key
  • clearAll(): Clear entire cache

Usage Examples

Database Batching

const userLoader = new DataLoader(async (ids) => {
  const rows = await db.query(
    'SELECT * FROM users WHERE id IN (?)',
    [ids]
  );
  return ids.map(id => rows.find(r => r.id === id) || null);
});

// Concurrent loads are automatically batched
const [user1, user2, user3] = await Promise.all([
  userLoader.load(1),
  userLoader.load(2),
  userLoader.load(3)
]);

API Batching

const apiLoader = new DataLoader(async (urls) => {
  const responses = await Promise.all(
    urls.map(url => fetch(url).then(r => r.json()))
  );
  return responses;
});

// Batch multiple API calls
const [data1, data2] = await Promise.all([
  apiLoader.load('https://api.example.com/users/1'),
  apiLoader.load('https://api.example.com/users/2')
]);

Nested Batching (GraphQL-style)

const postLoader = new DataLoader(async (userIds) => {
  const posts = await db.query(
    'SELECT * FROM posts WHERE user_id IN (?)',
    [userIds]
  );
  return userIds.map(id => posts.filter(p => p.user_id === id));
});

const commentLoader = new DataLoader(async (postIds) => {
  const comments = await db.query(
    'SELECT * FROM comments WHERE post_id IN (?)',
    [postIds.flat()]
  );
  return postIds.map(ids => comments.filter(c => ids.includes(c.post_id)));
});

// Resolve nested data efficiently
const users = await userLoader.loadMany(userIds);
for (const user of users) {
  user.posts = await postLoader.load(user.id);
  for (const post of user.posts) {
    post.comments = await commentLoader.load(post.id);
  }
}

Performance Comparison

ScenarioWithout DataLoaderWith DataLoader
Load 100 users101 queries1 query
Load 100 users + posts201 queries2 queries
Load 100 users + posts + comments301 queries3 queries

Best Practices

  1. Create loaders per request: Don't share loaders across requests to avoid cache pollution
  2. Batch size matters: Tune maxBatchSize based on your database/API limits
  3. Handle errors gracefully: Return null/undefined for missing keys, don't throw
  4. Use with Promise.all: Concurrent loads trigger batching
  5. Clear cache when needed: Use clear() for mutations that affect cached data

Files

  • dataloader.js - Core DataLoader implementation
  • examples/ - Usage examples for different scenarios
  • benchmarks/ - Performance comparison scripts

Installation

This skill is self-contained. Import the DataLoader:

const DataLoader = require('./batch-processing/dataloader.js');

When to Use

✅ Multiple database queries in a loop ✅ Fetching related data for multiple items ✅ API calls that can be batched ✅ GraphQL resolvers ✅ Any N+1 query pattern

❌ Single queries ❌ Real-time streaming data ❌ When order matters and can't be preserved

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

Charging Ledger

充电记录账本 - 从截图提取充电信息并记录,支持按周、月查询汇总。**快速暗号**: 充电记录、充电账本、充电汇总。**自然触发**: 记录充电、查询充电费用、充电统计。

Registry SourceRecently Updated
General

qg-skill-sync

从团队 Git 仓库同步最新技能到本机 OpenClaw。支持首次设置、定时自动更新、手动同步和卸载。当用户需要同步技能、设置技能同步、安装或更新团队技能,或提到「技能同步」「同步技能」时使用。

Registry SourceRecently Updated
General

Ad Manager

广告投放管理 - 自动管理广告投放、优化ROI、生成报告。适合:营销人员、电商运营。

Registry SourceRecently Updated