Skip to content
evernote-performance-tuning logo

Evernote Performance Tuning

evernote-performance-tuning

Optimize Evernote integration performance. Use when improving response times, reducing API calls, or scaling Evernote integrations. Trigger with phrases like "evernote performance", "optimize evernote", "evernote speed", "evernote caching".

SKILL.md

Full skill instructions

Evernote Performance Tuning

Overview

Optimize Evernote API integration performance through caching, efficient API usage, connection pooling, and smart data retrieval strategies.

Prerequisites

  • Working Evernote integration
  • Understanding of API rate limits
  • Caching infrastructure (Redis recommended)

Instructions

Step 1: Response Caching

// services/​cache-service.js
const Redis = require('ioredis');

class EvernoteCacheService {
  constructor(redisUrl) {
    this.redis = new Redis(redisUrl);
    this.defaultTTL = 300; // 5 minutes
  }

  /​**
   * Cache key generators
   */
  keys = {
    notebooks: (userId) => `evernote:${userId}:notebooks`,
    tags: (userId) => `evernote:${userId}:tags`,
    note: (guid) => `evernote:note:${guid}`,
    noteMetadata: (guid) => `evernote:note:${guid}:meta`,
    search: (userId, query) => `evernote:${userId}:search:${this.hashQuery(query)}`,
    syncState: (userId) => `evernote:${userId}:syncState`
  };

  hashQuery(query) {
    const crypto = require('crypto');
    return crypto.createHash('md5').update(query).digest('hex');
  }

  /​**
   * Cache notebooks (longer TTL - rarely change)
   */
  async cacheNotebooks(userId, notebooks) {
    const key = this.keys.notebooks(userId);
    await this.redis.setex(key, 3600, JSON.stringify(notebooks)); // 1 hour
  }

  async getNotebooks(userId) {
    const key = this.keys.notebooks(userId);
    const cached = await this.redis.get(key);
    return cached ? JSON.parse(cached) : null;
  }

  /​**
   * Cache note metadata (shorter TTL)
   */
  async cacheNoteMetadata(guid, metadata) {
    const key = this.keys.noteMetadata(guid);
    await this.redis.setex(key, this.defaultTTL, JSON.stringify(metadata));
  }

  async getNoteMetadata(guid) {
    const key = this.keys.noteMetadata(guid);
    const cached = await this.redis.get(key);
    return cached ? JSON.parse(cached) : null;
  }

  /​**
   * Cache full note content (configurable TTL)
   */
  async cacheNote(guid, note, ttl = 300) {
    const key = this.keys.note(guid);
    await this.redis.setex(key, ttl, JSON.stringify(note));
  }

  async getNote(guid) {
    const key = this.keys.note(guid);
    const cached = await this.redis.get(key);
    return cached ? JSON.parse(cached) : null;
  }

  /​**
   * Cache search results (short TTL)
   */
  async cacheSearch(userId, query, results) {
    const key = this.keys.search(userId, query);
    await this.redis.setex(key, 60, JSON.stringify(results)); // 1 minute
  }

  async getSearch(userId, query) {
    const key = this.keys.search(userId, query);
    const cached = await this.redis.get(key);
    return cached ? JSON.parse(cached) : null;
  }

  /​**
   * Invalidate cache on changes
   */
  async invalidateNote(guid) {
    await this.redis.del(this.keys.note(guid));
    await this.redis.del(this.keys.noteMetadata(guid));
  }

  async invalidateUserCache(userId) {
    const pattern = `evernote:${userId}:*`;
    const keys = await this.redis.keys(pattern);
    if (keys.length > 0) {
      await this.redis.del(...keys);
    }
  }
}

module.exports = EvernoteCacheService;

Step 2: Cached Client Wrapper

// services/​cached-evernote-client.js
const Evernote = require('evernote');
const EvernoteCacheService = require('./​cache-service');

class CachedEvernoteClient {
  constructor(accessToken, userId, cacheService) {
    this.client = new Evernote.Client({
      token: accessToken,
      sandbox: process.env.EVERNOTE_SANDBOX === 'true'
    });
    this.noteStore = this.client.getNoteStore();
    this.userId = userId;
    this.cache = cacheService;
  }

  /​**
   * List notebooks with caching
   */
  async listNotebooks(forceRefresh = false) {
    if (!forceRefresh) {
      const cached = await this.cache.getNotebooks(this.userId);
      if (cached) {
        console.log('Cache HIT: notebooks');
        return cached;
      }
    }

    console.log('Cache MISS: notebooks');
    const notebooks = await this.noteStore.listNotebooks();
    await this.cache.cacheNotebooks(this.userId, notebooks);
    return notebooks;
  }

  /​**
   * Get note with caching
   */
  async getNote(guid, options = {}) {
    const {
      withContent = true,
      withResources = false,
      forceRefresh = false
    } = options;

    // Only cache if requesting content without resources
    const canCache = withContent && !withResources;

    if (canCache && !forceRefresh) {
      const cached = await this.cache.getNote(guid);
      if (cached) {
        console.log('Cache HIT: note', guid);
        return cached;
      }
    }

    console.log('Cache MISS: note', guid);
    const note = await this.noteStore.getNote(
      guid,
      withContent,
      withResources,
      false,
      false
    );

    if (canCache) {
      await this.cache.cacheNote(guid, note);
    }

    return note;
  }

  /​**
   * Search with caching
   */
  async search(query, options = {}) {
    const { maxResults = 50, forceRefresh = false } = options;

    if (!forceRefresh) {
      const cached = await this.cache.getSearch(this.userId, query);
      if (cached) {
        console.log('Cache HIT: search', query);
        return cached;
      }
    }

    console.log('Cache MISS: search', query);
    const filter = new Evernote.NoteStore.NoteFilter({ words: query });
    const spec = new Evernote.NoteStore.NotesMetadataResultSpec({
      includeTitle: true,
      includeCreated: true,
      includeUpdated: true,
      includeTagGuids: true,
      includeNotebookGuid: true
    });

    const results = await this.noteStore.findNotesMetadata(
      filter,
      0,
      maxResults,
      spec
    );

    await this.cache.cacheSearch(this.userId, query, results);
    return results;
  }
}

module.exports = CachedEvernoteClient;

Step 3: Request Batching

// utils/​request-batcher.js

class RequestBatcher {
  constructor(options = {}) {
    this.batchSize = options.batchSize || 10;
    this.batchDelay = options.batchDelay || 100;
    this.queue = [];
    this.processing = false;
  }

  /​**
   * Add request to batch
   */
  async add(operation) {
    return new Promise((resolve, reject) => {
      this.queue.push({ operation, resolve, reject });

      if (!this.processing) {
        this.processBatch();
      }
    });
  }

  /​**
   * Process queued requests
   */
  async processBatch() {
    if (this.queue.length === 0) {
      this.processing = false;
      return;
    }

    this.processing = true;

    // Take batch from queue
    const batch = this.queue.splice(0, this.batchSize);

    // Execute in parallel
    await Promise.all(
      batch.map(async ({ operation, resolve, reject }) => {
        try {
          const result = await operation();
          resolve(result);
        } catch (error) {
          reject(error);
        }
      })
    );

    // Small delay before next batch
    if (this.queue.length > 0) {
      await new Promise(r => setTimeout(r, this.batchDelay));
      this.processBatch();
    } else {
      this.processing = false;
    }
  }
}

module.exports = RequestBatcher;

Step 4: Efficient Data Retrieval

// services/​optimized-note-service.js

class OptimizedNoteService {
  constructor(noteStore, cache) {
    this.noteStore = noteStore;
    this.cache = cache;
  }

  /​**
   * Get minimal data needed (don't over-fetch)
   */
  async getNotePreview(guid) {
    // Only get metadata, not full content
    return this.noteStore.getNote(guid, false, false, false, false);
  }

  /​**
   * Get notes with content in single request
   */
  async getNotesWithContent(guids) {
    // Evernote doesn't support batch note retrieval
    // Optimize by parallelizing with rate limit awareness
    const results = [];
    const batchSize = 5;

    for (let i = 0; i < guids.length; i += batchSize) {
      const batch = guids.slice(i, i + batchSize);
      const batchResults = await Promise.all(
        batch.map(guid =>
          this.noteStore.getNote(guid, true, false, false, false)
        )
      );
      results.push(...batchResults);

      // Small delay between batches
      if (i + batchSize < guids.length) {
        await new Promise(r => setTimeout(r, 100));
      }
    }

    return results;
  }

  /​**
   * Get resources separately (more efficient for large files)
   */
  async getNoteWithLazyResources(guid) {
    // Get note without resource data
    const note = await this.noteStore.getNote(guid, true, false, false, false);

    // Create lazy loader for resources
    note.loadResource = async (resourceGuid) => {
      return this.noteStore.getResource(
        resourceGuid,
        true,  // withData
        false, // withRecognition
        false, // withAttributes
        false  // withAlternateData
      );
    };

    return note;
  }

  /​**
   * Efficient search with pagination
   */
  async* searchPaginated(query, pageSize = 50) {
    const filter = new Evernote.NoteStore.NoteFilter({ words: query });
    const spec = new Evernote.NoteStore.NotesMetadataResultSpec({
      includeTitle: true,
      includeUpdated: true
    });

    let offset = 0;
    let total = null;

    while (total === null || offset < total) {
      const result = await this.noteStore.findNotesMetadata(
        filter,
        offset,
        pageSize,
        spec
      );

      total = result.totalNotes;

      for (const note of result.notes) {
        yield note;
      }

      offset += result.notes.length;
    }
  }
}

module.exports = OptimizedNoteService;

Step 5: Connection Optimization

// utils/​connection-manager.js

class ConnectionManager {
  constructor() {
    this.clients = new Map();
    this.maxIdleTime = 5 * 60 * 1000; // 5 minutes
  }

  /​**
   * Get or create client for user
   */
  getClient(userId, accessToken) {
    const existing = this.clients.get(userId);

    if (existing) {
      existing.lastUsed = Date.now();
      return existing.client;
    }

    const client = new Evernote.Client({
      token: accessToken,
      sandbox: process.env.EVERNOTE_SANDBOX === 'true'
    });

    this.clients.set(userId, {
      client,
      lastUsed: Date.now()
    });

    return client;
  }

  /​**
   * Clean up idle connections
   */
  cleanup() {
    const now = Date.now();

    for (const [userId, data] of this.clients) {
      if (now - data.lastUsed > this.maxIdleTime) {
        this.clients.delete(userId);
        console.log(`Cleaned up idle connection for user ${userId}`);
      }
    }
  }

  /​**
   * Start cleanup interval
   */
  startCleanup(interval = 60000) {
    setInterval(() => this.cleanup(), interval);
  }
}

module.exports = ConnectionManager;

Step 6: Performance Monitoring

// utils/​performance-monitor.js

class PerformanceMonitor {
  constructor() {
    this.metrics = {
      apiCalls: 0,
      cacheHits: 0,
      cacheMisses: 0,
      totalLatency: 0,
      errors: 0
    };
    this.callDurations = [];
  }

  /​**
   * Track API call
   */
  trackCall(operation, duration, fromCache = false) {
    this.metrics.apiCalls++;
    this.metrics.totalLatency += duration;
    this.callDurations.push({ operation, duration, fromCache, timestamp: Date.now() });

    if (fromCache) {
      this.metrics.cacheHits++;
    } else {
      this.metrics.cacheMisses++;
    }

    // Keep last 1000 calls
    if (this.callDurations.length > 1000) {
      this.callDurations.shift();
    }
  }

  /​**
   * Track error
   */
  trackError(operation, error) {
    this.metrics.errors++;
    console.error(`[PERF] Error in ${operation}:`, error.message);
  }

  /​**
   * Get performance stats
   */
  getStats() {
    const avgLatency = this.metrics.apiCalls > 0
      ? this.metrics.totalLatency / this.metrics.apiCalls
      : 0;

    const cacheHitRate = this.metrics.apiCalls > 0
      ? (this.metrics.cacheHits / this.metrics.apiCalls) * 100
      : 0;

    // Calculate p95 latency
    const sortedDurations = [...this.callDurations]
      .map(c => c.duration)
      .sort((a, b) => a - b);
    const p95Index = Math.floor(sortedDurations.length * 0.95);
    const p95Latency = sortedDurations[p95Index] || 0;

    return {
      totalCalls: this.metrics.apiCalls,
      cacheHits: this.metrics.cacheHits,
      cacheMisses: this.metrics.cacheMisses,
      cacheHitRate: `${cacheHitRate.toFixed(1)}%`,
      avgLatencyMs: avgLatency.toFixed(2),
      p95LatencyMs: p95Latency.toFixed(2),
      errors: this.metrics.errors
    };
  }

  /​**
   * Create instrumented wrapper
   */
  instrument(noteStore) {
    const monitor = this;

    return new Proxy(noteStore, {
      get(target, prop) {
        const original = target[prop];

        if (typeof original !== 'function') {
          return original;
        }

        return async (...args) => {
          const start = Date.now();

          try {
            const result = await original.apply(target, args);
            const duration = Date.now() - start;
            monitor.trackCall(prop, duration);
            return result;
          } catch (error) {
            const duration = Date.now() - start;
            monitor.trackCall(prop, duration);
            monitor.trackError(prop, error);
            throw error;
          }
        };
      }
    });
  }
}

module.exports = PerformanceMonitor;

Step 7: Usage Example

// example-optimized.js
const Redis = require('ioredis');
const EvernoteCacheService = require('./​services/​cache-service');
const CachedEvernoteClient = require('./​services/​cached-evernote-client');
const PerformanceMonitor = require('./​utils/​performance-monitor');

async function main() {
  const redis = new Redis(process.env.REDIS_URL);
  const cache = new EvernoteCacheService(redis);
  const monitor = new PerformanceMonitor();

  const userId = 'user-123';
  const client = new CachedEvernoteClient(
    process.env.EVERNOTE_ACCESS_TOKEN,
    userId,
    cache
  );

  // Instrument for monitoring
  client.noteStore = monitor.instrument(client.noteStore);

  // First call - cache miss
  console.log('\nFirst request (cache miss):');
  console.time('First notebooks call');
  await client.listNotebooks();
  console.timeEnd('First notebooks call');

  // Second call - cache hit
  console.log('\nSecond request (cache hit):');
  console.time('Second notebooks call');
  await client.listNotebooks();
  console.timeEnd('Second notebooks call');

  // Search with caching
  console.log('\nSearch (cache miss):');
  console.time('First search');
  await client.search('meeting notes');
  console.timeEnd('First search');

  console.log('\nSearch (cache hit):');
  console.time('Second search');
  await client.search('meeting notes');
  console.timeEnd('Second search');

  // Print stats
  console.log('\nPerformance Stats:', monitor.getStats());

  redis.quit();
}

main().catch(console.error);

Output

  • Redis-based response caching
  • Cache-aware client wrapper
  • Request batching for bulk operations
  • Efficient data retrieval patterns
  • Connection pooling
  • Performance monitoring

Performance Tips

OptimizationImpactWhen to Use
Cache notebooksHighAlways (rarely change)
Cache search resultsMediumRepeated searches
Lazy load resourcesHighLarge attachments
Request batchingMediumBulk operations
Skip content flagHighListing notes

Resources

Next Steps

For cost optimization, see evernote-cost-tuning.

More skills from Dicklesworthstone

environment-setup-guide logo
Dicklesworthstone/pi_agent_rust

environment-setup-guide

Guide developers through setting up development environments with proper tools, dependencies, and configurations

1.8K 0
View
exa-policy-guardrails logo
Dicklesworthstone/pi_agent_rust

exa-policy-guardrails

Implement Exa lint rules, policy enforcement, and automated guardrails. Use when setting up code quality rules for Exa integrations, implementing pre-commit hooks, or configuring CI policy checks for Exa best practices. Trigger with phrases like "exa policy", "exa lint", "exa guardrails", "exa be...

1.8K 0
View
documenso-ci-integration logo
Dicklesworthstone/pi_agent_rust

documenso-ci-integration

Configure CI/CD pipelines for Documenso integrations. Use when setting up automated testing, deployment pipelines, or continuous integration for Documenso projects. Trigger with phrases like "documenso CI", "documenso GitHub Actions", "documenso pipeline", "documenso automated testing".

1.8K 0
View
openrouter-model-availability logo
Dicklesworthstone/pi_agent_rust

openrouter-model-availability

Build check model availability and implement fallback chains. Use when building resilient systems or handling model outages. Trigger with phrases like 'openrouter availability', 'openrouter fallback', 'openrouter model down', 'openrouter health check'.

1.8K 0
View
langfuse-enterprise-rbac logo
Dicklesworthstone/pi_agent_rust

langfuse-enterprise-rbac

Configure Langfuse enterprise organization management and access control. Use when implementing team access controls, configuring organization settings, or setting up role-based permissions for Langfuse projects. Trigger with phrases like "langfuse RBAC", "langfuse teams", "langfuse organization"...

1.8K 0
View
cursor-compliance-audit logo
Dicklesworthstone/pi_agent_rust

cursor-compliance-audit

Execute compliance and security auditing for Cursor usage. Triggers on "cursor compliance", "cursor audit", "cursor security review", "cursor soc2", "cursor gdpr". Use when analyzing or auditing cursor compliance audit. Trigger with phrases like "cursor compliance audit", "cursor audit", "cursor".

1.8K 0
View
apollo-ci-integration logo
Dicklesworthstone/pi_agent_rust

apollo-ci-integration

Configure Apollo.io CI/CD integration. Use when setting up automated testing, continuous integration, or deployment pipelines for Apollo integrations. Trigger with phrases like "apollo ci", "apollo github actions", "apollo pipeline", "apollo ci/cd", "apollo automated tests".

1.8K 0
View
python-script logo
Dicklesworthstone/pi_agent_rust

python-script

Create robust Python automation with full logging and safety checks. Use when tasks need complex data processing, authenticated API work, conditional file operations, or error handling beyond simple shell commands.

1.8K 0
View
posthog-multi-env-setup logo
Dicklesworthstone/pi_agent_rust

posthog-multi-env-setup

Configure PostHog across development, staging, and production environments. Use when setting up multi-environment deployments, configuring per-environment secrets, or implementing environment-specific PostHog configurations. Trigger with phrases like "posthog environments", "posthog staging", "po...

1.8K 0
View
ai-agents-architect logo
Dicklesworthstone/pi_agent_rust

ai-agents-architect

Expert in designing and building autonomous AI agents. Masters tool use, memory systems, planning strategies, and multi-agent orchestration. Use when: build agent, AI agent, autonomous agent, tool use, function calling.

1.8K 0
View
insecure-deserialization-checker logo
Dicklesworthstone/pi_agent_rust

insecure-deserialization-checker

Validate insecure deserialization checker operations. Auto-activating skill for Security Fundamentals. Triggers on: insecure deserialization checker, insecure deserialization checker Part of the Security Fundamentals skill category. Use when working with insecure deserialization checker functiona...

1.8K 0
View
path-traversal-finder logo
Dicklesworthstone/pi_agent_rust

path-traversal-finder

Manage path traversal finder operations. Auto-activating skill for Security Fundamentals. Triggers on: path traversal finder, path traversal finder Part of the Security Fundamentals skill category. Use when working with path traversal finder functionality. Trigger with phrases like "path traversa...

1.8K 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View