| name | evernote-performance-tuning |
| description | 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".
|
| allowed-tools | Read, Write, Edit, Grep |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
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
const Redis = require('ioredis');
class EvernoteCacheService {
constructor(redisUrl) {
this.redis = new Redis(redisUrl);
this.defaultTTL = 300;
}
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');
}
async cacheNotebooks(userId, notebooks) {
const key = this.keys.notebooks(userId);
await this.redis.setex(key, 3600, JSON.stringify(notebooks));
}
async getNotebooks(userId) {
const key = this.keys.notebooks(userId);
const cached = await this.redis.get(key);
return cached ? JSON.parse(cached) : null;
}
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;
}
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;
}
async cacheSearch(userId, query, results) {
const key = this.keys.search(userId, query);
await this.redis.setex(key, 60, JSON.stringify(results));
}
async getSearch(userId, query) {
const key = this.keys.search(userId, query);
const cached = await this.redis.get(key);
return cached ? JSON.parse(cached) : null;
}
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
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;
}
async listNotebooks(forceRefresh = false) {
if (!forceRefresh) {
const cached = await this.cache.getNotebooks(this.userId);
if (cached) {
console.log('Cache HIT: notebooks');
cached;
}
}
.();
notebooks = ..();
..(., notebooks);
notebooks;
}
() {
{
withContent = ,
withResources = ,
forceRefresh =
} = options;
canCache = withContent && !withResources;
(canCache && !forceRefresh) {
cached = ..(guid);
(cached) {
.(, guid);
cached;
}
}
.(, guid);
note = ..(
guid,
withContent,
withResources,
,
);
(canCache) {
..(guid, note);
}
note;
}
() {
{ maxResults = , forceRefresh = } = options;
(!forceRefresh) {
cached = ..(., query);
(cached) {
.(, query);
cached;
}
}
.(, query);
filter = ..({ : query });
spec = ..({
: ,
: ,
: ,
: ,
:
});
results = ..(
filter,
,
maxResults,
spec
);
..(., query, results);
results;
}
}
. = ;
Step 3: Request Batching
class RequestBatcher {
constructor(options = {}) {
this.batchSize = options.batchSize || 10;
this.batchDelay = options.batchDelay || 100;
this.queue = [];
this.processing = false;
}
async add(operation) {
return new Promise((resolve, reject) => {
this.queue.push({ operation, resolve, reject });
if (!this.processing) {
this.processBatch();
}
});
}
async processBatch() {
if (this.queue.length === 0) {
this.processing = false;
return;
}
this. = ;
batch = ..(, .);
.(
batch.( ({ operation, resolve, reject }) => {
{
result = ();
(result);
} (error) {
(error);
}
})
);
(.. > ) {
( (r, .));
.();
} {
. = ;
}
}
}
. = ;
Step 4: Efficient Data Retrieval
class OptimizedNoteService {
constructor(noteStore, cache) {
this.noteStore = noteStore;
this.cache = cache;
}
async getNotePreview(guid) {
return this.noteStore.getNote(guid, false, false, false, false);
}
async getNotesWithContent(guids) {
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.(guid, , , , )
)
);
results.(...batchResults);
(i + batchSize < guids.) {
( (r, ));
}
}
results;
}
() {
note = ..(guid, , , , );
note. = (resourceGuid) => {
..(
resourceGuid,
,
,
,
);
};
note;
}
* () {
filter = ..({ : query });
spec = ..({
: ,
:
});
offset = ;
total = ;
(total === || offset < total) {
result = ..(
filter,
offset,
pageSize,
spec
);
total = result.;
( note result.) {
note;
}
offset += result..;
}
}
}
. = ;
Step 5: Connection Optimization
class ConnectionManager {
constructor() {
this.clients = new Map();
this.maxIdleTime = 5 * 60 * 1000;
}
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;
}
cleanup() {
const now = Date.();
( [userId, data] .) {
(now - data. > .) {
..(userId);
.();
}
}
}
() {
( .(), interval);
}
}
. = ;
Step 6: Performance Monitoring
class PerformanceMonitor {
constructor() {
this.metrics = {
apiCalls: 0,
cacheHits: 0,
cacheMisses: 0,
totalLatency: 0,
errors: 0
};
this.callDurations = [];
}
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++;
}
if (this.callDurations.length > 1000) {
this..();
}
}
() {
..++;
.(, error.);
}
() {
avgLatency = .. >
? .. / ..
: ;
cacheHitRate = .. >
? (.. / ..) *
: ;
sortedDurations = [....]
.( c.)
.( a - b);
p95Index = .(sortedDurations. * );
p95Latency = sortedDurations[p95Index] || ;
{
: ..,
: ..,
: ..,
: ,
: avgLatency.(),
: p95Latency.(),
: ..
};
}
() {
monitor = ;
(noteStore, {
() {
original = target[prop];
( original !== ) {
original;
}
(...args) => {
start = .();
{
result = original.(target, args);
duration = .() - start;
monitor.(prop, duration);
result;
} (error) {
duration = .() - start;
monitor.(prop, duration);
monitor.(prop, error);
error;
}
};
}
});
}
}
. = ;
Step 7: Usage Example
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
);
client.noteStore = monitor.instrument(client.noteStore);
console.log('\nFirst request (cache miss):');
console.time();
client.();
.();
.();
.();
client.();
.();
.();
.();
client.();
.();
.();
.();
client.();
.();
.(, monitor.());
redis.();
}
().(.);
Output
- Redis-based response caching
- Cache-aware client wrapper
- Request batching for bulk operations
- Efficient data retrieval patterns
- Connection pooling
- Performance monitoring
Performance Tips
| Optimization | Impact | When to Use |
|---|
| Cache notebooks | High | Always (rarely change) |
| Cache search results | Medium | Repeated searches |
| Lazy load resources | High | Large attachments |
| Request batching | Medium | Bulk operations |
| Skip content flag | High | Listing notes |
Resources
Next Steps
For cost optimization, see evernote-cost-tuning.