[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-trigger-dev-staff-engineering-skills-sharding":3,"mdc--g0j2ek-key":37,"related-repo-trigger-dev-staff-engineering-skills-sharding":2344,"related-org-trigger-dev-staff-engineering-skills-sharding":2424},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":12,"stars":26,"repoUrl":27,"updatedAt":28,"license":29,"forks":30,"topics":31,"repo":32,"sourceUrl":35,"mdContent":36},"staff-engineering-skills-sharding","design database sharding strategies","Make correct decisions about database sharding -- when to shard, when not to, and what breaks when you do. Use when designing database architecture, choosing scaling strategies, writing queries that assume a single database, selecting shard keys, or when someone says \"we need to shard.\" Activates on patterns like cross-table JOINs in potentially sharded systems, global unique constraints, multi-entity transactions, or premature scaling discussions.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},"trigger-dev","Trigger.dev","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Ftrigger-dev.jpg","triggerdotdev",[13,17,20,23],{"name":14,"slug":15,"type":16},"Performance","performance","tag",{"name":18,"slug":19,"type":16},"Architecture","architecture",{"name":21,"slug":22,"type":16},"Database","database",{"name":24,"slug":25,"type":16},"Engineering","engineering",3,"https:\u002F\u002Fgithub.com\u002Ftriggerdotdev\u002Fstaff-engineering-skills","2026-06-17T08:40:48.870771",null,1,[],{"repoUrl":27,"stars":26,"forks":30,"topics":33,"description":34},[],"Skills that give AI   coding agents staff-engineer instincts: recognizing and avoiding production failure modes like cardinality, idempotency, and race conditions.","https:\u002F\u002Fgithub.com\u002Ftriggerdotdev\u002Fstaff-engineering-skills\u002Ftree\u002FHEAD\u002Fskills\u002Fstaff-engineering-skills-sharding","---\nname: staff-engineering-skills-sharding\ndescription: Make correct decisions about database sharding -- when to shard, when not to, and what breaks when you do. Use when designing database architecture, choosing scaling strategies, writing queries that assume a single database, selecting shard keys, or when someone says \"we need to shard.\" Activates on patterns like cross-table JOINs in potentially sharded systems, global unique constraints, multi-entity transactions, or premature scaling discussions.\n---\n\n# Sharding Trap\n\nSharding is nearly impossible to undo and fundamentally changes what your code can do. Before sharding, ask: **have you exhausted every simpler alternative?**\n\n## Do You Actually Need to Shard?\n\nAlmost certainly not. Work through this checklist first:\n\n| Step | Solution | Handles |\n|------|----------|---------|\n| 1 | **Indexes** -- run `EXPLAIN ANALYZE` on slow queries | Missing indexes cause 90% of \"database is slow\" |\n| 2 | **Query optimization** -- fix N+1 queries, unnecessary JOINs, full scans | Bad queries, not database limits |\n| 3 | **Connection pooling** -- PgBouncer or built-in pooling | Connection exhaustion |\n| 4 | **Caching** -- Redis for hot data, query result caches | Read throughput |\n| 5 | **Read replicas** -- route reads to replicas | Read scaling |\n| 6 | **Table partitioning** -- partition by date range within one database | Large table performance, transparent to app code |\n| 7 | **Vertical scaling** -- bigger machine (64-core, 256GB RAM) | Everything, up to a point |\n\n**Only if ALL of these are insufficient should you consider sharding.** A single PostgreSQL instance with proper indexing and read replicas handles far more than most people expect. If you have less than 1TB of data or fewer than 10,000 writes per second, you almost certainly don't need to shard.\n\n## What Sharding Breaks\n\nOnce data is distributed across shards, these operations become expensive or impossible:\n\n| Before sharding (trivial) | After sharding (painful) |\n|--------------------------|-------------------------|\n| `JOIN users ON orders.user_id = users.id` | Cross-shard join: fetch from both shards, join in memory |\n| `BEGIN; UPDATE orders; UPDATE inventory; COMMIT;` | Cross-shard transaction: 2PC or saga pattern |\n| `SELECT COUNT(*) FROM orders` | Scatter to all shards, aggregate results |\n| `CREATE UNIQUE INDEX ON users(email)` | Cross-shard uniqueness: separate lookup table |\n| `SELECT * FROM orders ORDER BY created_at LIMIT 20` | Fetch 20 from each shard, merge-sort |\n\n**Every feature you build must now answer: \"which shard is this data on?\"**\n\n## Detection: Sharding-Unsafe Code\n\nIf the system is sharded or may be sharded, **stop and reassess if you see:**\n\n1. **JOINs across entity types** -- `orders JOIN products JOIN users`. These tables may be on different shards. Each join may need to become a separate query + application-level join.\n\n2. **Multi-entity transactions** -- `BEGIN; update order; update inventory; update user; COMMIT;`. If these entities are on different shards, this transaction cannot work.\n\n3. **Global unique constraints** -- `UNIQUE(email)` only works within a single database. Cross-shard uniqueness requires a coordination layer.\n\n4. **Unscoped aggregations** -- `SELECT COUNT(*) FROM orders`. Without a shard key in the WHERE clause, this hits every shard.\n\n5. **`ORDER BY ... LIMIT` without shard key** -- requires fetching from all shards and merge-sorting.\n\n## Shard Key Selection\n\nThe shard key determines everything. Get it wrong and sharding makes things worse.\n\n**The shard key must be in your most common query's WHERE clause.** If 80% of queries are scoped to a tenant, shard by tenant. If 80% are scoped to a user, shard by user.\n\n| Shard key choice | Good when | Bad when |\n|-----------------|-----------|----------|\n| **Tenant\u002Forg ID** | Multi-tenant SaaS, queries scoped to tenant | One tenant is 1000x bigger than others (hot shard) |\n| **User ID** | User-scoped apps (social, messaging) | Queries need cross-user views (analytics, search) |\n| **Hash of primary key** | Even distribution needed | Range queries on the key (date ranges, alphabetical) |\n| **Geographic region** | Latency-sensitive, data residency requirements | Uneven population distribution |\n\n**Red flags in shard key selection:**\n- Low cardinality (country code: US gets 50% of traffic)\n- Skewed distribution (first letter of name: \"S\" has 4x more than \"Q\")\n- Doesn't appear in the most frequent query's WHERE clause\n- Changes over time (user can move between tenants)\n\n## Patterns\n\n### Tenant-based sharding (most common for SaaS)\n\n```typescript\nfunction getShardForTenant(tenantId: string): DatabaseConnection {\n  const shardIndex = consistentHash(tenantId, shardCount);\n  return shardConnections[shardIndex];\n}\n\nasync function getOrders(tenantId: string, filters: OrderFilters) {\n  const shard = getShardForTenant(tenantId);\n  \u002F\u002F Single-shard query -- no scatter-gather\n  return shard.query(\n    `SELECT * FROM orders WHERE tenant_id = $1 AND status = $2\n     ORDER BY created_at DESC LIMIT $3`,\n    [tenantId, filters.status, filters.limit]\n  );\n}\n```\n\nWorks because almost all SaaS operations are scoped to one tenant. Cross-tenant analytics go to a separate data warehouse fed by event streaming.\n\n### Handling cross-shard queries with a read store\n\n```typescript\n\u002F\u002F Writes go to the shard\nasync function createOrder(tenantId: string, data: OrderInput) {\n  const shard = getShardForTenant(tenantId);\n  const order = await shard.insert(\"orders\", { tenantId, ...data });\n\n  \u002F\u002F Async: replicate to a global read store for cross-shard queries\n  await eventBus.publish(\"order.created\", { orderId: order.id, tenantId, ...data });\n  return order;\n}\n\n\u002F\u002F Cross-shard queries hit the read store (eventually consistent)\nasync function globalOrderStats() {\n  return analyticsDb.query(`\n    SELECT DATE_TRUNC('day', created_at) as day, COUNT(*), SUM(amount)\n    FROM orders_read_model GROUP BY day ORDER BY day DESC\n  `);\n}\n```\n\nAccept that cross-shard queries need a different store. The read store is eventually consistent but avoids scatter-gather.\n\n### Hot tenant isolation\n\n```typescript\nconst DEDICATED_TENANTS = new Map([\n  [\"acme-corp\", dedicatedShardConnection],\n  [\"megacorp\", dedicatedShardConnection2],\n]);\n\nfunction getShardForTenant(tenantId: string): DatabaseConnection {\n  \u002F\u002F Large tenants get their own shard\n  const dedicated = DEDICATED_TENANTS.get(tenantId);\n  if (dedicated) return dedicated;\n\n  \u002F\u002F Everyone else shares the pool\n  const shardIndex = consistentHash(tenantId, sharedShardCount);\n  return sharedShardConnections[shardIndex];\n}\n```\n\nWhen one tenant generates 40% of traffic, hash-based distribution doesn't help. Move them to dedicated infrastructure.\n\n## Anti-Patterns\n\n```typescript\n\u002F\u002F Premature: \"We might have millions of users someday\"\n\u002F\u002F You have 50,000 users and 5GB of data. A single PostgreSQL instance\n\u002F\u002F on a $600\u002Fmonth machine handles 100x this. Sharding infrastructure\n\u002F\u002F will cost more in engineering time than 5 years of vertical scaling.\n\n\u002F\u002F Wrong shard key: sharded by user_id but dashboard queries by org_id\n\u002F\u002F Every dashboard query must scatter to ALL shards and aggregate\nconst results = await Promise.all(\n  shards.map(shard =>\n    shard.query(`SELECT count(*), sum(amount) FROM orders WHERE org_id = $1`, [orgId])\n  )\n);\n\u002F\u002F If the primary access pattern is per-org, shard by org, not user.\n\n\u002F\u002F Assumes single database: JOIN across entity types\nconst orderDetails = await db.query(`\n  SELECT o.*, p.name, u.email\n  FROM orders o\n  JOIN products p ON o.product_id = p.id\n  JOIN users u ON o.user_id = u.id\n  WHERE o.id = $1\n`, [orderId]);\n\u002F\u002F If orders, products, and users are on different shards, this doesn't work.\n```\n\n## Related Traps\n\n- **Race Conditions** -- `SELECT FOR UPDATE` doesn't work across shards. Distributed locking adds latency and failure modes. Cross-shard operations need saga patterns or optimistic concurrency.\n- **Idempotency** -- the idempotency key and the data it protects must be on the same shard. Otherwise you can't atomically check for duplicates and perform the operation.\n- **Hot Partitions** -- even with sharding, one shard can receive disproportionate traffic. Shard key distribution determines whether you've solved the problem or just renamed it.\n- **Consistency Models** -- within a shard: strong consistency. Across shards: eventual consistency at best. Cross-shard queries see data at different points in time.\n- **Denormalization** -- cross-shard queries often require maintaining denormalized read stores. This creates consistency obligations (see denormalization trap).\n",{"data":38,"body":39},{"name":4,"description":6},{"type":40,"children":41},"root",[42,51,63,70,75,278,288,294,299,406,414,420,430,519,525,530,540,651,659,683,689,696,1110,1115,1121,1571,1576,1582,1931,1936,1942,2272,2278,2338],{"type":43,"tag":44,"props":45,"children":47},"element","h1",{"id":46},"sharding-trap",[48],{"type":49,"value":50},"text","Sharding Trap",{"type":43,"tag":52,"props":53,"children":54},"p",{},[55,57],{"type":49,"value":56},"Sharding is nearly impossible to undo and fundamentally changes what your code can do. Before sharding, ask: ",{"type":43,"tag":58,"props":59,"children":60},"strong",{},[61],{"type":49,"value":62},"have you exhausted every simpler alternative?",{"type":43,"tag":64,"props":65,"children":67},"h2",{"id":66},"do-you-actually-need-to-shard",[68],{"type":49,"value":69},"Do You Actually Need to Shard?",{"type":43,"tag":52,"props":71,"children":72},{},[73],{"type":49,"value":74},"Almost certainly not. Work through this checklist first:",{"type":43,"tag":76,"props":77,"children":78},"table",{},[79,103],{"type":43,"tag":80,"props":81,"children":82},"thead",{},[83],{"type":43,"tag":84,"props":85,"children":86},"tr",{},[87,93,98],{"type":43,"tag":88,"props":89,"children":90},"th",{},[91],{"type":49,"value":92},"Step",{"type":43,"tag":88,"props":94,"children":95},{},[96],{"type":49,"value":97},"Solution",{"type":43,"tag":88,"props":99,"children":100},{},[101],{"type":49,"value":102},"Handles",{"type":43,"tag":104,"props":105,"children":106},"tbody",{},[107,140,163,186,209,232,255],{"type":43,"tag":84,"props":108,"children":109},{},[110,116,135],{"type":43,"tag":111,"props":112,"children":113},"td",{},[114],{"type":49,"value":115},"1",{"type":43,"tag":111,"props":117,"children":118},{},[119,124,126,133],{"type":43,"tag":58,"props":120,"children":121},{},[122],{"type":49,"value":123},"Indexes",{"type":49,"value":125}," -- run ",{"type":43,"tag":127,"props":128,"children":130},"code",{"className":129},[],[131],{"type":49,"value":132},"EXPLAIN ANALYZE",{"type":49,"value":134}," on slow queries",{"type":43,"tag":111,"props":136,"children":137},{},[138],{"type":49,"value":139},"Missing indexes cause 90% of \"database is slow\"",{"type":43,"tag":84,"props":141,"children":142},{},[143,148,158],{"type":43,"tag":111,"props":144,"children":145},{},[146],{"type":49,"value":147},"2",{"type":43,"tag":111,"props":149,"children":150},{},[151,156],{"type":43,"tag":58,"props":152,"children":153},{},[154],{"type":49,"value":155},"Query optimization",{"type":49,"value":157}," -- fix N+1 queries, unnecessary JOINs, full scans",{"type":43,"tag":111,"props":159,"children":160},{},[161],{"type":49,"value":162},"Bad queries, not database limits",{"type":43,"tag":84,"props":164,"children":165},{},[166,171,181],{"type":43,"tag":111,"props":167,"children":168},{},[169],{"type":49,"value":170},"3",{"type":43,"tag":111,"props":172,"children":173},{},[174,179],{"type":43,"tag":58,"props":175,"children":176},{},[177],{"type":49,"value":178},"Connection pooling",{"type":49,"value":180}," -- PgBouncer or built-in pooling",{"type":43,"tag":111,"props":182,"children":183},{},[184],{"type":49,"value":185},"Connection exhaustion",{"type":43,"tag":84,"props":187,"children":188},{},[189,194,204],{"type":43,"tag":111,"props":190,"children":191},{},[192],{"type":49,"value":193},"4",{"type":43,"tag":111,"props":195,"children":196},{},[197,202],{"type":43,"tag":58,"props":198,"children":199},{},[200],{"type":49,"value":201},"Caching",{"type":49,"value":203}," -- Redis for hot data, query result caches",{"type":43,"tag":111,"props":205,"children":206},{},[207],{"type":49,"value":208},"Read throughput",{"type":43,"tag":84,"props":210,"children":211},{},[212,217,227],{"type":43,"tag":111,"props":213,"children":214},{},[215],{"type":49,"value":216},"5",{"type":43,"tag":111,"props":218,"children":219},{},[220,225],{"type":43,"tag":58,"props":221,"children":222},{},[223],{"type":49,"value":224},"Read replicas",{"type":49,"value":226}," -- route reads to replicas",{"type":43,"tag":111,"props":228,"children":229},{},[230],{"type":49,"value":231},"Read scaling",{"type":43,"tag":84,"props":233,"children":234},{},[235,240,250],{"type":43,"tag":111,"props":236,"children":237},{},[238],{"type":49,"value":239},"6",{"type":43,"tag":111,"props":241,"children":242},{},[243,248],{"type":43,"tag":58,"props":244,"children":245},{},[246],{"type":49,"value":247},"Table partitioning",{"type":49,"value":249}," -- partition by date range within one database",{"type":43,"tag":111,"props":251,"children":252},{},[253],{"type":49,"value":254},"Large table performance, transparent to app code",{"type":43,"tag":84,"props":256,"children":257},{},[258,263,273],{"type":43,"tag":111,"props":259,"children":260},{},[261],{"type":49,"value":262},"7",{"type":43,"tag":111,"props":264,"children":265},{},[266,271],{"type":43,"tag":58,"props":267,"children":268},{},[269],{"type":49,"value":270},"Vertical scaling",{"type":49,"value":272}," -- bigger machine (64-core, 256GB RAM)",{"type":43,"tag":111,"props":274,"children":275},{},[276],{"type":49,"value":277},"Everything, up to a point",{"type":43,"tag":52,"props":279,"children":280},{},[281,286],{"type":43,"tag":58,"props":282,"children":283},{},[284],{"type":49,"value":285},"Only if ALL of these are insufficient should you consider sharding.",{"type":49,"value":287}," A single PostgreSQL instance with proper indexing and read replicas handles far more than most people expect. If you have less than 1TB of data or fewer than 10,000 writes per second, you almost certainly don't need to shard.",{"type":43,"tag":64,"props":289,"children":291},{"id":290},"what-sharding-breaks",[292],{"type":49,"value":293},"What Sharding Breaks",{"type":43,"tag":52,"props":295,"children":296},{},[297],{"type":49,"value":298},"Once data is distributed across shards, these operations become expensive or impossible:",{"type":43,"tag":76,"props":300,"children":301},{},[302,318],{"type":43,"tag":80,"props":303,"children":304},{},[305],{"type":43,"tag":84,"props":306,"children":307},{},[308,313],{"type":43,"tag":88,"props":309,"children":310},{},[311],{"type":49,"value":312},"Before sharding (trivial)",{"type":43,"tag":88,"props":314,"children":315},{},[316],{"type":49,"value":317},"After sharding (painful)",{"type":43,"tag":104,"props":319,"children":320},{},[321,338,355,372,389],{"type":43,"tag":84,"props":322,"children":323},{},[324,333],{"type":43,"tag":111,"props":325,"children":326},{},[327],{"type":43,"tag":127,"props":328,"children":330},{"className":329},[],[331],{"type":49,"value":332},"JOIN users ON orders.user_id = users.id",{"type":43,"tag":111,"props":334,"children":335},{},[336],{"type":49,"value":337},"Cross-shard join: fetch from both shards, join in memory",{"type":43,"tag":84,"props":339,"children":340},{},[341,350],{"type":43,"tag":111,"props":342,"children":343},{},[344],{"type":43,"tag":127,"props":345,"children":347},{"className":346},[],[348],{"type":49,"value":349},"BEGIN; UPDATE orders; UPDATE inventory; COMMIT;",{"type":43,"tag":111,"props":351,"children":352},{},[353],{"type":49,"value":354},"Cross-shard transaction: 2PC or saga pattern",{"type":43,"tag":84,"props":356,"children":357},{},[358,367],{"type":43,"tag":111,"props":359,"children":360},{},[361],{"type":43,"tag":127,"props":362,"children":364},{"className":363},[],[365],{"type":49,"value":366},"SELECT COUNT(*) FROM orders",{"type":43,"tag":111,"props":368,"children":369},{},[370],{"type":49,"value":371},"Scatter to all shards, aggregate results",{"type":43,"tag":84,"props":373,"children":374},{},[375,384],{"type":43,"tag":111,"props":376,"children":377},{},[378],{"type":43,"tag":127,"props":379,"children":381},{"className":380},[],[382],{"type":49,"value":383},"CREATE UNIQUE INDEX ON users(email)",{"type":43,"tag":111,"props":385,"children":386},{},[387],{"type":49,"value":388},"Cross-shard uniqueness: separate lookup table",{"type":43,"tag":84,"props":390,"children":391},{},[392,401],{"type":43,"tag":111,"props":393,"children":394},{},[395],{"type":43,"tag":127,"props":396,"children":398},{"className":397},[],[399],{"type":49,"value":400},"SELECT * FROM orders ORDER BY created_at LIMIT 20",{"type":43,"tag":111,"props":402,"children":403},{},[404],{"type":49,"value":405},"Fetch 20 from each shard, merge-sort",{"type":43,"tag":52,"props":407,"children":408},{},[409],{"type":43,"tag":58,"props":410,"children":411},{},[412],{"type":49,"value":413},"Every feature you build must now answer: \"which shard is this data on?\"",{"type":43,"tag":64,"props":415,"children":417},{"id":416},"detection-sharding-unsafe-code",[418],{"type":49,"value":419},"Detection: Sharding-Unsafe Code",{"type":43,"tag":52,"props":421,"children":422},{},[423,425],{"type":49,"value":424},"If the system is sharded or may be sharded, ",{"type":43,"tag":58,"props":426,"children":427},{},[428],{"type":49,"value":429},"stop and reassess if you see:",{"type":43,"tag":431,"props":432,"children":433},"ol",{},[434,453,470,487,503],{"type":43,"tag":435,"props":436,"children":437},"li",{},[438,443,445,451],{"type":43,"tag":58,"props":439,"children":440},{},[441],{"type":49,"value":442},"JOINs across entity types",{"type":49,"value":444}," -- ",{"type":43,"tag":127,"props":446,"children":448},{"className":447},[],[449],{"type":49,"value":450},"orders JOIN products JOIN users",{"type":49,"value":452},". These tables may be on different shards. Each join may need to become a separate query + application-level join.",{"type":43,"tag":435,"props":454,"children":455},{},[456,461,462,468],{"type":43,"tag":58,"props":457,"children":458},{},[459],{"type":49,"value":460},"Multi-entity transactions",{"type":49,"value":444},{"type":43,"tag":127,"props":463,"children":465},{"className":464},[],[466],{"type":49,"value":467},"BEGIN; update order; update inventory; update user; COMMIT;",{"type":49,"value":469},". If these entities are on different shards, this transaction cannot work.",{"type":43,"tag":435,"props":471,"children":472},{},[473,478,479,485],{"type":43,"tag":58,"props":474,"children":475},{},[476],{"type":49,"value":477},"Global unique constraints",{"type":49,"value":444},{"type":43,"tag":127,"props":480,"children":482},{"className":481},[],[483],{"type":49,"value":484},"UNIQUE(email)",{"type":49,"value":486}," only works within a single database. Cross-shard uniqueness requires a coordination layer.",{"type":43,"tag":435,"props":488,"children":489},{},[490,495,496,501],{"type":43,"tag":58,"props":491,"children":492},{},[493],{"type":49,"value":494},"Unscoped aggregations",{"type":49,"value":444},{"type":43,"tag":127,"props":497,"children":499},{"className":498},[],[500],{"type":49,"value":366},{"type":49,"value":502},". Without a shard key in the WHERE clause, this hits every shard.",{"type":43,"tag":435,"props":504,"children":505},{},[506,517],{"type":43,"tag":58,"props":507,"children":508},{},[509,515],{"type":43,"tag":127,"props":510,"children":512},{"className":511},[],[513],{"type":49,"value":514},"ORDER BY ... LIMIT",{"type":49,"value":516}," without shard key",{"type":49,"value":518}," -- requires fetching from all shards and merge-sorting.",{"type":43,"tag":64,"props":520,"children":522},{"id":521},"shard-key-selection",[523],{"type":49,"value":524},"Shard Key Selection",{"type":43,"tag":52,"props":526,"children":527},{},[528],{"type":49,"value":529},"The shard key determines everything. Get it wrong and sharding makes things worse.",{"type":43,"tag":52,"props":531,"children":532},{},[533,538],{"type":43,"tag":58,"props":534,"children":535},{},[536],{"type":49,"value":537},"The shard key must be in your most common query's WHERE clause.",{"type":49,"value":539}," If 80% of queries are scoped to a tenant, shard by tenant. If 80% are scoped to a user, shard by user.",{"type":43,"tag":76,"props":541,"children":542},{},[543,564],{"type":43,"tag":80,"props":544,"children":545},{},[546],{"type":43,"tag":84,"props":547,"children":548},{},[549,554,559],{"type":43,"tag":88,"props":550,"children":551},{},[552],{"type":49,"value":553},"Shard key choice",{"type":43,"tag":88,"props":555,"children":556},{},[557],{"type":49,"value":558},"Good when",{"type":43,"tag":88,"props":560,"children":561},{},[562],{"type":49,"value":563},"Bad when",{"type":43,"tag":104,"props":565,"children":566},{},[567,588,609,630],{"type":43,"tag":84,"props":568,"children":569},{},[570,578,583],{"type":43,"tag":111,"props":571,"children":572},{},[573],{"type":43,"tag":58,"props":574,"children":575},{},[576],{"type":49,"value":577},"Tenant\u002Forg ID",{"type":43,"tag":111,"props":579,"children":580},{},[581],{"type":49,"value":582},"Multi-tenant SaaS, queries scoped to tenant",{"type":43,"tag":111,"props":584,"children":585},{},[586],{"type":49,"value":587},"One tenant is 1000x bigger than others (hot shard)",{"type":43,"tag":84,"props":589,"children":590},{},[591,599,604],{"type":43,"tag":111,"props":592,"children":593},{},[594],{"type":43,"tag":58,"props":595,"children":596},{},[597],{"type":49,"value":598},"User ID",{"type":43,"tag":111,"props":600,"children":601},{},[602],{"type":49,"value":603},"User-scoped apps (social, messaging)",{"type":43,"tag":111,"props":605,"children":606},{},[607],{"type":49,"value":608},"Queries need cross-user views (analytics, search)",{"type":43,"tag":84,"props":610,"children":611},{},[612,620,625],{"type":43,"tag":111,"props":613,"children":614},{},[615],{"type":43,"tag":58,"props":616,"children":617},{},[618],{"type":49,"value":619},"Hash of primary key",{"type":43,"tag":111,"props":621,"children":622},{},[623],{"type":49,"value":624},"Even distribution needed",{"type":43,"tag":111,"props":626,"children":627},{},[628],{"type":49,"value":629},"Range queries on the key (date ranges, alphabetical)",{"type":43,"tag":84,"props":631,"children":632},{},[633,641,646],{"type":43,"tag":111,"props":634,"children":635},{},[636],{"type":43,"tag":58,"props":637,"children":638},{},[639],{"type":49,"value":640},"Geographic region",{"type":43,"tag":111,"props":642,"children":643},{},[644],{"type":49,"value":645},"Latency-sensitive, data residency requirements",{"type":43,"tag":111,"props":647,"children":648},{},[649],{"type":49,"value":650},"Uneven population distribution",{"type":43,"tag":52,"props":652,"children":653},{},[654],{"type":43,"tag":58,"props":655,"children":656},{},[657],{"type":49,"value":658},"Red flags in shard key selection:",{"type":43,"tag":660,"props":661,"children":662},"ul",{},[663,668,673,678],{"type":43,"tag":435,"props":664,"children":665},{},[666],{"type":49,"value":667},"Low cardinality (country code: US gets 50% of traffic)",{"type":43,"tag":435,"props":669,"children":670},{},[671],{"type":49,"value":672},"Skewed distribution (first letter of name: \"S\" has 4x more than \"Q\")",{"type":43,"tag":435,"props":674,"children":675},{},[676],{"type":49,"value":677},"Doesn't appear in the most frequent query's WHERE clause",{"type":43,"tag":435,"props":679,"children":680},{},[681],{"type":49,"value":682},"Changes over time (user can move between tenants)",{"type":43,"tag":64,"props":684,"children":686},{"id":685},"patterns",[687],{"type":49,"value":688},"Patterns",{"type":43,"tag":690,"props":691,"children":693},"h3",{"id":692},"tenant-based-sharding-most-common-for-saas",[694],{"type":49,"value":695},"Tenant-based sharding (most common for SaaS)",{"type":43,"tag":697,"props":698,"children":703},"pre",{"className":699,"code":700,"language":701,"meta":702,"style":702},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","function getShardForTenant(tenantId: string): DatabaseConnection {\n  const shardIndex = consistentHash(tenantId, shardCount);\n  return shardConnections[shardIndex];\n}\n\nasync function getOrders(tenantId: string, filters: OrderFilters) {\n  const shard = getShardForTenant(tenantId);\n  \u002F\u002F Single-shard query -- no scatter-gather\n  return shard.query(\n    `SELECT * FROM orders WHERE tenant_id = $1 AND status = $2\n     ORDER BY created_at DESC LIMIT $3`,\n    [tenantId, filters.status, filters.limit]\n  );\n}\n","typescript","",[704],{"type":43,"tag":127,"props":705,"children":706},{"__ignoreMap":702},[707,762,816,849,858,868,929,966,976,1003,1018,1037,1089,1102],{"type":43,"tag":708,"props":709,"children":711},"span",{"class":710,"line":30},"line",[712,718,724,730,736,741,747,752,757],{"type":43,"tag":708,"props":713,"children":715},{"style":714},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA",[716],{"type":49,"value":717},"function",{"type":43,"tag":708,"props":719,"children":721},{"style":720},"--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF",[722],{"type":49,"value":723}," getShardForTenant",{"type":43,"tag":708,"props":725,"children":727},{"style":726},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[728],{"type":49,"value":729},"(",{"type":43,"tag":708,"props":731,"children":733},{"style":732},"--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic",[734],{"type":49,"value":735},"tenantId",{"type":43,"tag":708,"props":737,"children":738},{"style":726},[739],{"type":49,"value":740},":",{"type":43,"tag":708,"props":742,"children":744},{"style":743},"--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B",[745],{"type":49,"value":746}," string",{"type":43,"tag":708,"props":748,"children":749},{"style":726},[750],{"type":49,"value":751},"):",{"type":43,"tag":708,"props":753,"children":754},{"style":743},[755],{"type":49,"value":756}," DatabaseConnection",{"type":43,"tag":708,"props":758,"children":759},{"style":726},[760],{"type":49,"value":761}," {\n",{"type":43,"tag":708,"props":763,"children":765},{"class":710,"line":764},2,[766,771,777,782,787,792,796,801,806,811],{"type":43,"tag":708,"props":767,"children":768},{"style":714},[769],{"type":49,"value":770},"  const",{"type":43,"tag":708,"props":772,"children":774},{"style":773},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8",[775],{"type":49,"value":776}," shardIndex",{"type":43,"tag":708,"props":778,"children":779},{"style":726},[780],{"type":49,"value":781}," =",{"type":43,"tag":708,"props":783,"children":784},{"style":720},[785],{"type":49,"value":786}," consistentHash",{"type":43,"tag":708,"props":788,"children":790},{"style":789},"--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178",[791],{"type":49,"value":729},{"type":43,"tag":708,"props":793,"children":794},{"style":773},[795],{"type":49,"value":735},{"type":43,"tag":708,"props":797,"children":798},{"style":726},[799],{"type":49,"value":800},",",{"type":43,"tag":708,"props":802,"children":803},{"style":773},[804],{"type":49,"value":805}," shardCount",{"type":43,"tag":708,"props":807,"children":808},{"style":789},[809],{"type":49,"value":810},")",{"type":43,"tag":708,"props":812,"children":813},{"style":726},[814],{"type":49,"value":815},";\n",{"type":43,"tag":708,"props":817,"children":818},{"class":710,"line":26},[819,825,830,835,840,845],{"type":43,"tag":708,"props":820,"children":822},{"style":821},"--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic",[823],{"type":49,"value":824},"  return",{"type":43,"tag":708,"props":826,"children":827},{"style":773},[828],{"type":49,"value":829}," shardConnections",{"type":43,"tag":708,"props":831,"children":832},{"style":789},[833],{"type":49,"value":834},"[",{"type":43,"tag":708,"props":836,"children":837},{"style":773},[838],{"type":49,"value":839},"shardIndex",{"type":43,"tag":708,"props":841,"children":842},{"style":789},[843],{"type":49,"value":844},"]",{"type":43,"tag":708,"props":846,"children":847},{"style":726},[848],{"type":49,"value":815},{"type":43,"tag":708,"props":850,"children":852},{"class":710,"line":851},4,[853],{"type":43,"tag":708,"props":854,"children":855},{"style":726},[856],{"type":49,"value":857},"}\n",{"type":43,"tag":708,"props":859,"children":861},{"class":710,"line":860},5,[862],{"type":43,"tag":708,"props":863,"children":865},{"emptyLinePlaceholder":864},true,[866],{"type":49,"value":867},"\n",{"type":43,"tag":708,"props":869,"children":871},{"class":710,"line":870},6,[872,877,882,887,891,895,899,903,907,912,916,921,925],{"type":43,"tag":708,"props":873,"children":874},{"style":714},[875],{"type":49,"value":876},"async",{"type":43,"tag":708,"props":878,"children":879},{"style":714},[880],{"type":49,"value":881}," function",{"type":43,"tag":708,"props":883,"children":884},{"style":720},[885],{"type":49,"value":886}," getOrders",{"type":43,"tag":708,"props":888,"children":889},{"style":726},[890],{"type":49,"value":729},{"type":43,"tag":708,"props":892,"children":893},{"style":732},[894],{"type":49,"value":735},{"type":43,"tag":708,"props":896,"children":897},{"style":726},[898],{"type":49,"value":740},{"type":43,"tag":708,"props":900,"children":901},{"style":743},[902],{"type":49,"value":746},{"type":43,"tag":708,"props":904,"children":905},{"style":726},[906],{"type":49,"value":800},{"type":43,"tag":708,"props":908,"children":909},{"style":732},[910],{"type":49,"value":911}," filters",{"type":43,"tag":708,"props":913,"children":914},{"style":726},[915],{"type":49,"value":740},{"type":43,"tag":708,"props":917,"children":918},{"style":743},[919],{"type":49,"value":920}," OrderFilters",{"type":43,"tag":708,"props":922,"children":923},{"style":726},[924],{"type":49,"value":810},{"type":43,"tag":708,"props":926,"children":927},{"style":726},[928],{"type":49,"value":761},{"type":43,"tag":708,"props":930,"children":932},{"class":710,"line":931},7,[933,937,942,946,950,954,958,962],{"type":43,"tag":708,"props":934,"children":935},{"style":714},[936],{"type":49,"value":770},{"type":43,"tag":708,"props":938,"children":939},{"style":773},[940],{"type":49,"value":941}," shard",{"type":43,"tag":708,"props":943,"children":944},{"style":726},[945],{"type":49,"value":781},{"type":43,"tag":708,"props":947,"children":948},{"style":720},[949],{"type":49,"value":723},{"type":43,"tag":708,"props":951,"children":952},{"style":789},[953],{"type":49,"value":729},{"type":43,"tag":708,"props":955,"children":956},{"style":773},[957],{"type":49,"value":735},{"type":43,"tag":708,"props":959,"children":960},{"style":789},[961],{"type":49,"value":810},{"type":43,"tag":708,"props":963,"children":964},{"style":726},[965],{"type":49,"value":815},{"type":43,"tag":708,"props":967,"children":969},{"class":710,"line":968},8,[970],{"type":43,"tag":708,"props":971,"children":973},{"style":972},"--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic",[974],{"type":49,"value":975},"  \u002F\u002F Single-shard query -- no scatter-gather\n",{"type":43,"tag":708,"props":977,"children":979},{"class":710,"line":978},9,[980,984,988,993,998],{"type":43,"tag":708,"props":981,"children":982},{"style":821},[983],{"type":49,"value":824},{"type":43,"tag":708,"props":985,"children":986},{"style":773},[987],{"type":49,"value":941},{"type":43,"tag":708,"props":989,"children":990},{"style":726},[991],{"type":49,"value":992},".",{"type":43,"tag":708,"props":994,"children":995},{"style":720},[996],{"type":49,"value":997},"query",{"type":43,"tag":708,"props":999,"children":1000},{"style":789},[1001],{"type":49,"value":1002},"(\n",{"type":43,"tag":708,"props":1004,"children":1006},{"class":710,"line":1005},10,[1007,1012],{"type":43,"tag":708,"props":1008,"children":1009},{"style":726},[1010],{"type":49,"value":1011},"    `",{"type":43,"tag":708,"props":1013,"children":1015},{"style":1014},"--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D",[1016],{"type":49,"value":1017},"SELECT * FROM orders WHERE tenant_id = $1 AND status = $2\n",{"type":43,"tag":708,"props":1019,"children":1021},{"class":710,"line":1020},11,[1022,1027,1032],{"type":43,"tag":708,"props":1023,"children":1024},{"style":1014},[1025],{"type":49,"value":1026},"     ORDER BY created_at DESC LIMIT $3",{"type":43,"tag":708,"props":1028,"children":1029},{"style":726},[1030],{"type":49,"value":1031},"`",{"type":43,"tag":708,"props":1033,"children":1034},{"style":726},[1035],{"type":49,"value":1036},",\n",{"type":43,"tag":708,"props":1038,"children":1040},{"class":710,"line":1039},12,[1041,1046,1050,1054,1058,1062,1067,1071,1075,1079,1084],{"type":43,"tag":708,"props":1042,"children":1043},{"style":789},[1044],{"type":49,"value":1045},"    [",{"type":43,"tag":708,"props":1047,"children":1048},{"style":773},[1049],{"type":49,"value":735},{"type":43,"tag":708,"props":1051,"children":1052},{"style":726},[1053],{"type":49,"value":800},{"type":43,"tag":708,"props":1055,"children":1056},{"style":773},[1057],{"type":49,"value":911},{"type":43,"tag":708,"props":1059,"children":1060},{"style":726},[1061],{"type":49,"value":992},{"type":43,"tag":708,"props":1063,"children":1064},{"style":773},[1065],{"type":49,"value":1066},"status",{"type":43,"tag":708,"props":1068,"children":1069},{"style":726},[1070],{"type":49,"value":800},{"type":43,"tag":708,"props":1072,"children":1073},{"style":773},[1074],{"type":49,"value":911},{"type":43,"tag":708,"props":1076,"children":1077},{"style":726},[1078],{"type":49,"value":992},{"type":43,"tag":708,"props":1080,"children":1081},{"style":773},[1082],{"type":49,"value":1083},"limit",{"type":43,"tag":708,"props":1085,"children":1086},{"style":789},[1087],{"type":49,"value":1088},"]\n",{"type":43,"tag":708,"props":1090,"children":1092},{"class":710,"line":1091},13,[1093,1098],{"type":43,"tag":708,"props":1094,"children":1095},{"style":789},[1096],{"type":49,"value":1097},"  )",{"type":43,"tag":708,"props":1099,"children":1100},{"style":726},[1101],{"type":49,"value":815},{"type":43,"tag":708,"props":1103,"children":1105},{"class":710,"line":1104},14,[1106],{"type":43,"tag":708,"props":1107,"children":1108},{"style":726},[1109],{"type":49,"value":857},{"type":43,"tag":52,"props":1111,"children":1112},{},[1113],{"type":49,"value":1114},"Works because almost all SaaS operations are scoped to one tenant. Cross-tenant analytics go to a separate data warehouse fed by event streaming.",{"type":43,"tag":690,"props":1116,"children":1118},{"id":1117},"handling-cross-shard-queries-with-a-read-store",[1119],{"type":49,"value":1120},"Handling cross-shard queries with a read store",{"type":43,"tag":697,"props":1122,"children":1124},{"className":699,"code":1123,"language":701,"meta":702,"style":702},"\u002F\u002F Writes go to the shard\nasync function createOrder(tenantId: string, data: OrderInput) {\n  const shard = getShardForTenant(tenantId);\n  const order = await shard.insert(\"orders\", { tenantId, ...data });\n\n  \u002F\u002F Async: replicate to a global read store for cross-shard queries\n  await eventBus.publish(\"order.created\", { orderId: order.id, tenantId, ...data });\n  return order;\n}\n\n\u002F\u002F Cross-shard queries hit the read store (eventually consistent)\nasync function globalOrderStats() {\n  return analyticsDb.query(`\n    SELECT DATE_TRUNC('day', created_at) as day, COUNT(*), SUM(amount)\n    FROM orders_read_model GROUP BY day ORDER BY day DESC\n  `);\n}\n",[1125],{"type":43,"tag":127,"props":1126,"children":1127},{"__ignoreMap":702},[1128,1136,1194,1229,1322,1329,1337,1438,1453,1460,1467,1475,1500,1529,1537,1546,1563],{"type":43,"tag":708,"props":1129,"children":1130},{"class":710,"line":30},[1131],{"type":43,"tag":708,"props":1132,"children":1133},{"style":972},[1134],{"type":49,"value":1135},"\u002F\u002F Writes go to the shard\n",{"type":43,"tag":708,"props":1137,"children":1138},{"class":710,"line":764},[1139,1143,1147,1152,1156,1160,1164,1168,1172,1177,1181,1186,1190],{"type":43,"tag":708,"props":1140,"children":1141},{"style":714},[1142],{"type":49,"value":876},{"type":43,"tag":708,"props":1144,"children":1145},{"style":714},[1146],{"type":49,"value":881},{"type":43,"tag":708,"props":1148,"children":1149},{"style":720},[1150],{"type":49,"value":1151}," createOrder",{"type":43,"tag":708,"props":1153,"children":1154},{"style":726},[1155],{"type":49,"value":729},{"type":43,"tag":708,"props":1157,"children":1158},{"style":732},[1159],{"type":49,"value":735},{"type":43,"tag":708,"props":1161,"children":1162},{"style":726},[1163],{"type":49,"value":740},{"type":43,"tag":708,"props":1165,"children":1166},{"style":743},[1167],{"type":49,"value":746},{"type":43,"tag":708,"props":1169,"children":1170},{"style":726},[1171],{"type":49,"value":800},{"type":43,"tag":708,"props":1173,"children":1174},{"style":732},[1175],{"type":49,"value":1176}," data",{"type":43,"tag":708,"props":1178,"children":1179},{"style":726},[1180],{"type":49,"value":740},{"type":43,"tag":708,"props":1182,"children":1183},{"style":743},[1184],{"type":49,"value":1185}," OrderInput",{"type":43,"tag":708,"props":1187,"children":1188},{"style":726},[1189],{"type":49,"value":810},{"type":43,"tag":708,"props":1191,"children":1192},{"style":726},[1193],{"type":49,"value":761},{"type":43,"tag":708,"props":1195,"children":1196},{"class":710,"line":26},[1197,1201,1205,1209,1213,1217,1221,1225],{"type":43,"tag":708,"props":1198,"children":1199},{"style":714},[1200],{"type":49,"value":770},{"type":43,"tag":708,"props":1202,"children":1203},{"style":773},[1204],{"type":49,"value":941},{"type":43,"tag":708,"props":1206,"children":1207},{"style":726},[1208],{"type":49,"value":781},{"type":43,"tag":708,"props":1210,"children":1211},{"style":720},[1212],{"type":49,"value":723},{"type":43,"tag":708,"props":1214,"children":1215},{"style":789},[1216],{"type":49,"value":729},{"type":43,"tag":708,"props":1218,"children":1219},{"style":773},[1220],{"type":49,"value":735},{"type":43,"tag":708,"props":1222,"children":1223},{"style":789},[1224],{"type":49,"value":810},{"type":43,"tag":708,"props":1226,"children":1227},{"style":726},[1228],{"type":49,"value":815},{"type":43,"tag":708,"props":1230,"children":1231},{"class":710,"line":851},[1232,1236,1241,1245,1250,1254,1258,1263,1267,1272,1277,1281,1285,1290,1295,1299,1304,1309,1314,1318],{"type":43,"tag":708,"props":1233,"children":1234},{"style":714},[1235],{"type":49,"value":770},{"type":43,"tag":708,"props":1237,"children":1238},{"style":773},[1239],{"type":49,"value":1240}," order",{"type":43,"tag":708,"props":1242,"children":1243},{"style":726},[1244],{"type":49,"value":781},{"type":43,"tag":708,"props":1246,"children":1247},{"style":821},[1248],{"type":49,"value":1249}," await",{"type":43,"tag":708,"props":1251,"children":1252},{"style":773},[1253],{"type":49,"value":941},{"type":43,"tag":708,"props":1255,"children":1256},{"style":726},[1257],{"type":49,"value":992},{"type":43,"tag":708,"props":1259,"children":1260},{"style":720},[1261],{"type":49,"value":1262},"insert",{"type":43,"tag":708,"props":1264,"children":1265},{"style":789},[1266],{"type":49,"value":729},{"type":43,"tag":708,"props":1268,"children":1269},{"style":726},[1270],{"type":49,"value":1271},"\"",{"type":43,"tag":708,"props":1273,"children":1274},{"style":1014},[1275],{"type":49,"value":1276},"orders",{"type":43,"tag":708,"props":1278,"children":1279},{"style":726},[1280],{"type":49,"value":1271},{"type":43,"tag":708,"props":1282,"children":1283},{"style":726},[1284],{"type":49,"value":800},{"type":43,"tag":708,"props":1286,"children":1287},{"style":726},[1288],{"type":49,"value":1289}," {",{"type":43,"tag":708,"props":1291,"children":1292},{"style":773},[1293],{"type":49,"value":1294}," tenantId",{"type":43,"tag":708,"props":1296,"children":1297},{"style":726},[1298],{"type":49,"value":800},{"type":43,"tag":708,"props":1300,"children":1301},{"style":726},[1302],{"type":49,"value":1303}," ...",{"type":43,"tag":708,"props":1305,"children":1306},{"style":773},[1307],{"type":49,"value":1308},"data",{"type":43,"tag":708,"props":1310,"children":1311},{"style":726},[1312],{"type":49,"value":1313}," }",{"type":43,"tag":708,"props":1315,"children":1316},{"style":789},[1317],{"type":49,"value":810},{"type":43,"tag":708,"props":1319,"children":1320},{"style":726},[1321],{"type":49,"value":815},{"type":43,"tag":708,"props":1323,"children":1324},{"class":710,"line":860},[1325],{"type":43,"tag":708,"props":1326,"children":1327},{"emptyLinePlaceholder":864},[1328],{"type":49,"value":867},{"type":43,"tag":708,"props":1330,"children":1331},{"class":710,"line":870},[1332],{"type":43,"tag":708,"props":1333,"children":1334},{"style":972},[1335],{"type":49,"value":1336},"  \u002F\u002F Async: replicate to a global read store for cross-shard queries\n",{"type":43,"tag":708,"props":1338,"children":1339},{"class":710,"line":931},[1340,1345,1350,1354,1359,1363,1367,1372,1376,1380,1384,1389,1393,1397,1401,1406,1410,1414,1418,1422,1426,1430,1434],{"type":43,"tag":708,"props":1341,"children":1342},{"style":821},[1343],{"type":49,"value":1344},"  await",{"type":43,"tag":708,"props":1346,"children":1347},{"style":773},[1348],{"type":49,"value":1349}," eventBus",{"type":43,"tag":708,"props":1351,"children":1352},{"style":726},[1353],{"type":49,"value":992},{"type":43,"tag":708,"props":1355,"children":1356},{"style":720},[1357],{"type":49,"value":1358},"publish",{"type":43,"tag":708,"props":1360,"children":1361},{"style":789},[1362],{"type":49,"value":729},{"type":43,"tag":708,"props":1364,"children":1365},{"style":726},[1366],{"type":49,"value":1271},{"type":43,"tag":708,"props":1368,"children":1369},{"style":1014},[1370],{"type":49,"value":1371},"order.created",{"type":43,"tag":708,"props":1373,"children":1374},{"style":726},[1375],{"type":49,"value":1271},{"type":43,"tag":708,"props":1377,"children":1378},{"style":726},[1379],{"type":49,"value":800},{"type":43,"tag":708,"props":1381,"children":1382},{"style":726},[1383],{"type":49,"value":1289},{"type":43,"tag":708,"props":1385,"children":1386},{"style":789},[1387],{"type":49,"value":1388}," orderId",{"type":43,"tag":708,"props":1390,"children":1391},{"style":726},[1392],{"type":49,"value":740},{"type":43,"tag":708,"props":1394,"children":1395},{"style":773},[1396],{"type":49,"value":1240},{"type":43,"tag":708,"props":1398,"children":1399},{"style":726},[1400],{"type":49,"value":992},{"type":43,"tag":708,"props":1402,"children":1403},{"style":773},[1404],{"type":49,"value":1405},"id",{"type":43,"tag":708,"props":1407,"children":1408},{"style":726},[1409],{"type":49,"value":800},{"type":43,"tag":708,"props":1411,"children":1412},{"style":773},[1413],{"type":49,"value":1294},{"type":43,"tag":708,"props":1415,"children":1416},{"style":726},[1417],{"type":49,"value":800},{"type":43,"tag":708,"props":1419,"children":1420},{"style":726},[1421],{"type":49,"value":1303},{"type":43,"tag":708,"props":1423,"children":1424},{"style":773},[1425],{"type":49,"value":1308},{"type":43,"tag":708,"props":1427,"children":1428},{"style":726},[1429],{"type":49,"value":1313},{"type":43,"tag":708,"props":1431,"children":1432},{"style":789},[1433],{"type":49,"value":810},{"type":43,"tag":708,"props":1435,"children":1436},{"style":726},[1437],{"type":49,"value":815},{"type":43,"tag":708,"props":1439,"children":1440},{"class":710,"line":968},[1441,1445,1449],{"type":43,"tag":708,"props":1442,"children":1443},{"style":821},[1444],{"type":49,"value":824},{"type":43,"tag":708,"props":1446,"children":1447},{"style":773},[1448],{"type":49,"value":1240},{"type":43,"tag":708,"props":1450,"children":1451},{"style":726},[1452],{"type":49,"value":815},{"type":43,"tag":708,"props":1454,"children":1455},{"class":710,"line":978},[1456],{"type":43,"tag":708,"props":1457,"children":1458},{"style":726},[1459],{"type":49,"value":857},{"type":43,"tag":708,"props":1461,"children":1462},{"class":710,"line":1005},[1463],{"type":43,"tag":708,"props":1464,"children":1465},{"emptyLinePlaceholder":864},[1466],{"type":49,"value":867},{"type":43,"tag":708,"props":1468,"children":1469},{"class":710,"line":1020},[1470],{"type":43,"tag":708,"props":1471,"children":1472},{"style":972},[1473],{"type":49,"value":1474},"\u002F\u002F Cross-shard queries hit the read store (eventually consistent)\n",{"type":43,"tag":708,"props":1476,"children":1477},{"class":710,"line":1039},[1478,1482,1486,1491,1496],{"type":43,"tag":708,"props":1479,"children":1480},{"style":714},[1481],{"type":49,"value":876},{"type":43,"tag":708,"props":1483,"children":1484},{"style":714},[1485],{"type":49,"value":881},{"type":43,"tag":708,"props":1487,"children":1488},{"style":720},[1489],{"type":49,"value":1490}," globalOrderStats",{"type":43,"tag":708,"props":1492,"children":1493},{"style":726},[1494],{"type":49,"value":1495},"()",{"type":43,"tag":708,"props":1497,"children":1498},{"style":726},[1499],{"type":49,"value":761},{"type":43,"tag":708,"props":1501,"children":1502},{"class":710,"line":1091},[1503,1507,1512,1516,1520,1524],{"type":43,"tag":708,"props":1504,"children":1505},{"style":821},[1506],{"type":49,"value":824},{"type":43,"tag":708,"props":1508,"children":1509},{"style":773},[1510],{"type":49,"value":1511}," analyticsDb",{"type":43,"tag":708,"props":1513,"children":1514},{"style":726},[1515],{"type":49,"value":992},{"type":43,"tag":708,"props":1517,"children":1518},{"style":720},[1519],{"type":49,"value":997},{"type":43,"tag":708,"props":1521,"children":1522},{"style":789},[1523],{"type":49,"value":729},{"type":43,"tag":708,"props":1525,"children":1526},{"style":726},[1527],{"type":49,"value":1528},"`\n",{"type":43,"tag":708,"props":1530,"children":1531},{"class":710,"line":1104},[1532],{"type":43,"tag":708,"props":1533,"children":1534},{"style":1014},[1535],{"type":49,"value":1536},"    SELECT DATE_TRUNC('day', created_at) as day, COUNT(*), SUM(amount)\n",{"type":43,"tag":708,"props":1538,"children":1540},{"class":710,"line":1539},15,[1541],{"type":43,"tag":708,"props":1542,"children":1543},{"style":1014},[1544],{"type":49,"value":1545},"    FROM orders_read_model GROUP BY day ORDER BY day DESC\n",{"type":43,"tag":708,"props":1547,"children":1549},{"class":710,"line":1548},16,[1550,1555,1559],{"type":43,"tag":708,"props":1551,"children":1552},{"style":726},[1553],{"type":49,"value":1554},"  `",{"type":43,"tag":708,"props":1556,"children":1557},{"style":789},[1558],{"type":49,"value":810},{"type":43,"tag":708,"props":1560,"children":1561},{"style":726},[1562],{"type":49,"value":815},{"type":43,"tag":708,"props":1564,"children":1566},{"class":710,"line":1565},17,[1567],{"type":43,"tag":708,"props":1568,"children":1569},{"style":726},[1570],{"type":49,"value":857},{"type":43,"tag":52,"props":1572,"children":1573},{},[1574],{"type":49,"value":1575},"Accept that cross-shard queries need a different store. The read store is eventually consistent but avoids scatter-gather.",{"type":43,"tag":690,"props":1577,"children":1579},{"id":1578},"hot-tenant-isolation",[1580],{"type":49,"value":1581},"Hot tenant isolation",{"type":43,"tag":697,"props":1583,"children":1585},{"className":699,"code":1584,"language":701,"meta":702,"style":702},"const DEDICATED_TENANTS = new Map([\n  [\"acme-corp\", dedicatedShardConnection],\n  [\"megacorp\", dedicatedShardConnection2],\n]);\n\nfunction getShardForTenant(tenantId: string): DatabaseConnection {\n  \u002F\u002F Large tenants get their own shard\n  const dedicated = DEDICATED_TENANTS.get(tenantId);\n  if (dedicated) return dedicated;\n\n  \u002F\u002F Everyone else shares the pool\n  const shardIndex = consistentHash(tenantId, sharedShardCount);\n  return sharedShardConnections[shardIndex];\n}\n",[1586],{"type":43,"tag":127,"props":1587,"children":1588},{"__ignoreMap":702},[1589,1622,1656,1689,1701,1708,1747,1755,1801,1837,1844,1852,1896,1924],{"type":43,"tag":708,"props":1590,"children":1591},{"class":710,"line":30},[1592,1597,1602,1607,1612,1617],{"type":43,"tag":708,"props":1593,"children":1594},{"style":714},[1595],{"type":49,"value":1596},"const",{"type":43,"tag":708,"props":1598,"children":1599},{"style":773},[1600],{"type":49,"value":1601}," DEDICATED_TENANTS ",{"type":43,"tag":708,"props":1603,"children":1604},{"style":726},[1605],{"type":49,"value":1606},"=",{"type":43,"tag":708,"props":1608,"children":1609},{"style":726},[1610],{"type":49,"value":1611}," new",{"type":43,"tag":708,"props":1613,"children":1614},{"style":720},[1615],{"type":49,"value":1616}," Map",{"type":43,"tag":708,"props":1618,"children":1619},{"style":773},[1620],{"type":49,"value":1621},"([\n",{"type":43,"tag":708,"props":1623,"children":1624},{"class":710,"line":764},[1625,1630,1634,1639,1643,1647,1652],{"type":43,"tag":708,"props":1626,"children":1627},{"style":773},[1628],{"type":49,"value":1629},"  [",{"type":43,"tag":708,"props":1631,"children":1632},{"style":726},[1633],{"type":49,"value":1271},{"type":43,"tag":708,"props":1635,"children":1636},{"style":1014},[1637],{"type":49,"value":1638},"acme-corp",{"type":43,"tag":708,"props":1640,"children":1641},{"style":726},[1642],{"type":49,"value":1271},{"type":43,"tag":708,"props":1644,"children":1645},{"style":726},[1646],{"type":49,"value":800},{"type":43,"tag":708,"props":1648,"children":1649},{"style":773},[1650],{"type":49,"value":1651}," dedicatedShardConnection]",{"type":43,"tag":708,"props":1653,"children":1654},{"style":726},[1655],{"type":49,"value":1036},{"type":43,"tag":708,"props":1657,"children":1658},{"class":710,"line":26},[1659,1663,1667,1672,1676,1680,1685],{"type":43,"tag":708,"props":1660,"children":1661},{"style":773},[1662],{"type":49,"value":1629},{"type":43,"tag":708,"props":1664,"children":1665},{"style":726},[1666],{"type":49,"value":1271},{"type":43,"tag":708,"props":1668,"children":1669},{"style":1014},[1670],{"type":49,"value":1671},"megacorp",{"type":43,"tag":708,"props":1673,"children":1674},{"style":726},[1675],{"type":49,"value":1271},{"type":43,"tag":708,"props":1677,"children":1678},{"style":726},[1679],{"type":49,"value":800},{"type":43,"tag":708,"props":1681,"children":1682},{"style":773},[1683],{"type":49,"value":1684}," dedicatedShardConnection2]",{"type":43,"tag":708,"props":1686,"children":1687},{"style":726},[1688],{"type":49,"value":1036},{"type":43,"tag":708,"props":1690,"children":1691},{"class":710,"line":851},[1692,1697],{"type":43,"tag":708,"props":1693,"children":1694},{"style":773},[1695],{"type":49,"value":1696},"])",{"type":43,"tag":708,"props":1698,"children":1699},{"style":726},[1700],{"type":49,"value":815},{"type":43,"tag":708,"props":1702,"children":1703},{"class":710,"line":860},[1704],{"type":43,"tag":708,"props":1705,"children":1706},{"emptyLinePlaceholder":864},[1707],{"type":49,"value":867},{"type":43,"tag":708,"props":1709,"children":1710},{"class":710,"line":870},[1711,1715,1719,1723,1727,1731,1735,1739,1743],{"type":43,"tag":708,"props":1712,"children":1713},{"style":714},[1714],{"type":49,"value":717},{"type":43,"tag":708,"props":1716,"children":1717},{"style":720},[1718],{"type":49,"value":723},{"type":43,"tag":708,"props":1720,"children":1721},{"style":726},[1722],{"type":49,"value":729},{"type":43,"tag":708,"props":1724,"children":1725},{"style":732},[1726],{"type":49,"value":735},{"type":43,"tag":708,"props":1728,"children":1729},{"style":726},[1730],{"type":49,"value":740},{"type":43,"tag":708,"props":1732,"children":1733},{"style":743},[1734],{"type":49,"value":746},{"type":43,"tag":708,"props":1736,"children":1737},{"style":726},[1738],{"type":49,"value":751},{"type":43,"tag":708,"props":1740,"children":1741},{"style":743},[1742],{"type":49,"value":756},{"type":43,"tag":708,"props":1744,"children":1745},{"style":726},[1746],{"type":49,"value":761},{"type":43,"tag":708,"props":1748,"children":1749},{"class":710,"line":931},[1750],{"type":43,"tag":708,"props":1751,"children":1752},{"style":972},[1753],{"type":49,"value":1754},"  \u002F\u002F Large tenants get their own shard\n",{"type":43,"tag":708,"props":1756,"children":1757},{"class":710,"line":968},[1758,1762,1767,1771,1776,1780,1785,1789,1793,1797],{"type":43,"tag":708,"props":1759,"children":1760},{"style":714},[1761],{"type":49,"value":770},{"type":43,"tag":708,"props":1763,"children":1764},{"style":773},[1765],{"type":49,"value":1766}," dedicated",{"type":43,"tag":708,"props":1768,"children":1769},{"style":726},[1770],{"type":49,"value":781},{"type":43,"tag":708,"props":1772,"children":1773},{"style":773},[1774],{"type":49,"value":1775}," DEDICATED_TENANTS",{"type":43,"tag":708,"props":1777,"children":1778},{"style":726},[1779],{"type":49,"value":992},{"type":43,"tag":708,"props":1781,"children":1782},{"style":720},[1783],{"type":49,"value":1784},"get",{"type":43,"tag":708,"props":1786,"children":1787},{"style":789},[1788],{"type":49,"value":729},{"type":43,"tag":708,"props":1790,"children":1791},{"style":773},[1792],{"type":49,"value":735},{"type":43,"tag":708,"props":1794,"children":1795},{"style":789},[1796],{"type":49,"value":810},{"type":43,"tag":708,"props":1798,"children":1799},{"style":726},[1800],{"type":49,"value":815},{"type":43,"tag":708,"props":1802,"children":1803},{"class":710,"line":978},[1804,1809,1814,1819,1824,1829,1833],{"type":43,"tag":708,"props":1805,"children":1806},{"style":821},[1807],{"type":49,"value":1808},"  if",{"type":43,"tag":708,"props":1810,"children":1811},{"style":789},[1812],{"type":49,"value":1813}," (",{"type":43,"tag":708,"props":1815,"children":1816},{"style":773},[1817],{"type":49,"value":1818},"dedicated",{"type":43,"tag":708,"props":1820,"children":1821},{"style":789},[1822],{"type":49,"value":1823},") ",{"type":43,"tag":708,"props":1825,"children":1826},{"style":821},[1827],{"type":49,"value":1828},"return",{"type":43,"tag":708,"props":1830,"children":1831},{"style":773},[1832],{"type":49,"value":1766},{"type":43,"tag":708,"props":1834,"children":1835},{"style":726},[1836],{"type":49,"value":815},{"type":43,"tag":708,"props":1838,"children":1839},{"class":710,"line":1005},[1840],{"type":43,"tag":708,"props":1841,"children":1842},{"emptyLinePlaceholder":864},[1843],{"type":49,"value":867},{"type":43,"tag":708,"props":1845,"children":1846},{"class":710,"line":1020},[1847],{"type":43,"tag":708,"props":1848,"children":1849},{"style":972},[1850],{"type":49,"value":1851},"  \u002F\u002F Everyone else shares the pool\n",{"type":43,"tag":708,"props":1853,"children":1854},{"class":710,"line":1039},[1855,1859,1863,1867,1871,1875,1879,1883,1888,1892],{"type":43,"tag":708,"props":1856,"children":1857},{"style":714},[1858],{"type":49,"value":770},{"type":43,"tag":708,"props":1860,"children":1861},{"style":773},[1862],{"type":49,"value":776},{"type":43,"tag":708,"props":1864,"children":1865},{"style":726},[1866],{"type":49,"value":781},{"type":43,"tag":708,"props":1868,"children":1869},{"style":720},[1870],{"type":49,"value":786},{"type":43,"tag":708,"props":1872,"children":1873},{"style":789},[1874],{"type":49,"value":729},{"type":43,"tag":708,"props":1876,"children":1877},{"style":773},[1878],{"type":49,"value":735},{"type":43,"tag":708,"props":1880,"children":1881},{"style":726},[1882],{"type":49,"value":800},{"type":43,"tag":708,"props":1884,"children":1885},{"style":773},[1886],{"type":49,"value":1887}," sharedShardCount",{"type":43,"tag":708,"props":1889,"children":1890},{"style":789},[1891],{"type":49,"value":810},{"type":43,"tag":708,"props":1893,"children":1894},{"style":726},[1895],{"type":49,"value":815},{"type":43,"tag":708,"props":1897,"children":1898},{"class":710,"line":1091},[1899,1903,1908,1912,1916,1920],{"type":43,"tag":708,"props":1900,"children":1901},{"style":821},[1902],{"type":49,"value":824},{"type":43,"tag":708,"props":1904,"children":1905},{"style":773},[1906],{"type":49,"value":1907}," sharedShardConnections",{"type":43,"tag":708,"props":1909,"children":1910},{"style":789},[1911],{"type":49,"value":834},{"type":43,"tag":708,"props":1913,"children":1914},{"style":773},[1915],{"type":49,"value":839},{"type":43,"tag":708,"props":1917,"children":1918},{"style":789},[1919],{"type":49,"value":844},{"type":43,"tag":708,"props":1921,"children":1922},{"style":726},[1923],{"type":49,"value":815},{"type":43,"tag":708,"props":1925,"children":1926},{"class":710,"line":1104},[1927],{"type":43,"tag":708,"props":1928,"children":1929},{"style":726},[1930],{"type":49,"value":857},{"type":43,"tag":52,"props":1932,"children":1933},{},[1934],{"type":49,"value":1935},"When one tenant generates 40% of traffic, hash-based distribution doesn't help. Move them to dedicated infrastructure.",{"type":43,"tag":64,"props":1937,"children":1939},{"id":1938},"anti-patterns",[1940],{"type":49,"value":1941},"Anti-Patterns",{"type":43,"tag":697,"props":1943,"children":1945},{"className":699,"code":1944,"language":701,"meta":702,"style":702},"\u002F\u002F Premature: \"We might have millions of users someday\"\n\u002F\u002F You have 50,000 users and 5GB of data. A single PostgreSQL instance\n\u002F\u002F on a $600\u002Fmonth machine handles 100x this. Sharding infrastructure\n\u002F\u002F will cost more in engineering time than 5 years of vertical scaling.\n\n\u002F\u002F Wrong shard key: sharded by user_id but dashboard queries by org_id\n\u002F\u002F Every dashboard query must scatter to ALL shards and aggregate\nconst results = await Promise.all(\n  shards.map(shard =>\n    shard.query(`SELECT count(*), sum(amount) FROM orders WHERE org_id = $1`, [orgId])\n  )\n);\n\u002F\u002F If the primary access pattern is per-org, shard by org, not user.\n\n\u002F\u002F Assumes single database: JOIN across entity types\nconst orderDetails = await db.query(`\n  SELECT o.*, p.name, u.email\n  FROM orders o\n  JOIN products p ON o.product_id = p.id\n  JOIN users u ON o.user_id = u.id\n  WHERE o.id = $1\n`, [orderId]);\n\u002F\u002F If orders, products, and users are on different shards, this doesn't work.\n",[1946],{"type":43,"tag":127,"props":1947,"children":1948},{"__ignoreMap":702},[1949,1957,1965,1973,1981,1988,1996,2004,2042,2073,2115,2123,2134,2142,2149,2157,2198,2206,2215,2224,2233,2242,2263],{"type":43,"tag":708,"props":1950,"children":1951},{"class":710,"line":30},[1952],{"type":43,"tag":708,"props":1953,"children":1954},{"style":972},[1955],{"type":49,"value":1956},"\u002F\u002F Premature: \"We might have millions of users someday\"\n",{"type":43,"tag":708,"props":1958,"children":1959},{"class":710,"line":764},[1960],{"type":43,"tag":708,"props":1961,"children":1962},{"style":972},[1963],{"type":49,"value":1964},"\u002F\u002F You have 50,000 users and 5GB of data. A single PostgreSQL instance\n",{"type":43,"tag":708,"props":1966,"children":1967},{"class":710,"line":26},[1968],{"type":43,"tag":708,"props":1969,"children":1970},{"style":972},[1971],{"type":49,"value":1972},"\u002F\u002F on a $600\u002Fmonth machine handles 100x this. Sharding infrastructure\n",{"type":43,"tag":708,"props":1974,"children":1975},{"class":710,"line":851},[1976],{"type":43,"tag":708,"props":1977,"children":1978},{"style":972},[1979],{"type":49,"value":1980},"\u002F\u002F will cost more in engineering time than 5 years of vertical scaling.\n",{"type":43,"tag":708,"props":1982,"children":1983},{"class":710,"line":860},[1984],{"type":43,"tag":708,"props":1985,"children":1986},{"emptyLinePlaceholder":864},[1987],{"type":49,"value":867},{"type":43,"tag":708,"props":1989,"children":1990},{"class":710,"line":870},[1991],{"type":43,"tag":708,"props":1992,"children":1993},{"style":972},[1994],{"type":49,"value":1995},"\u002F\u002F Wrong shard key: sharded by user_id but dashboard queries by org_id\n",{"type":43,"tag":708,"props":1997,"children":1998},{"class":710,"line":931},[1999],{"type":43,"tag":708,"props":2000,"children":2001},{"style":972},[2002],{"type":49,"value":2003},"\u002F\u002F Every dashboard query must scatter to ALL shards and aggregate\n",{"type":43,"tag":708,"props":2005,"children":2006},{"class":710,"line":968},[2007,2011,2016,2020,2024,2029,2033,2038],{"type":43,"tag":708,"props":2008,"children":2009},{"style":714},[2010],{"type":49,"value":1596},{"type":43,"tag":708,"props":2012,"children":2013},{"style":773},[2014],{"type":49,"value":2015}," results ",{"type":43,"tag":708,"props":2017,"children":2018},{"style":726},[2019],{"type":49,"value":1606},{"type":43,"tag":708,"props":2021,"children":2022},{"style":821},[2023],{"type":49,"value":1249},{"type":43,"tag":708,"props":2025,"children":2026},{"style":743},[2027],{"type":49,"value":2028}," Promise",{"type":43,"tag":708,"props":2030,"children":2031},{"style":726},[2032],{"type":49,"value":992},{"type":43,"tag":708,"props":2034,"children":2035},{"style":720},[2036],{"type":49,"value":2037},"all",{"type":43,"tag":708,"props":2039,"children":2040},{"style":773},[2041],{"type":49,"value":1002},{"type":43,"tag":708,"props":2043,"children":2044},{"class":710,"line":978},[2045,2050,2054,2059,2063,2068],{"type":43,"tag":708,"props":2046,"children":2047},{"style":773},[2048],{"type":49,"value":2049},"  shards",{"type":43,"tag":708,"props":2051,"children":2052},{"style":726},[2053],{"type":49,"value":992},{"type":43,"tag":708,"props":2055,"children":2056},{"style":720},[2057],{"type":49,"value":2058},"map",{"type":43,"tag":708,"props":2060,"children":2061},{"style":773},[2062],{"type":49,"value":729},{"type":43,"tag":708,"props":2064,"children":2065},{"style":732},[2066],{"type":49,"value":2067},"shard",{"type":43,"tag":708,"props":2069,"children":2070},{"style":714},[2071],{"type":49,"value":2072}," =>\n",{"type":43,"tag":708,"props":2074,"children":2075},{"class":710,"line":1005},[2076,2081,2085,2089,2093,2097,2102,2106,2110],{"type":43,"tag":708,"props":2077,"children":2078},{"style":773},[2079],{"type":49,"value":2080},"    shard",{"type":43,"tag":708,"props":2082,"children":2083},{"style":726},[2084],{"type":49,"value":992},{"type":43,"tag":708,"props":2086,"children":2087},{"style":720},[2088],{"type":49,"value":997},{"type":43,"tag":708,"props":2090,"children":2091},{"style":773},[2092],{"type":49,"value":729},{"type":43,"tag":708,"props":2094,"children":2095},{"style":726},[2096],{"type":49,"value":1031},{"type":43,"tag":708,"props":2098,"children":2099},{"style":1014},[2100],{"type":49,"value":2101},"SELECT count(*), sum(amount) FROM orders WHERE org_id = $1",{"type":43,"tag":708,"props":2103,"children":2104},{"style":726},[2105],{"type":49,"value":1031},{"type":43,"tag":708,"props":2107,"children":2108},{"style":726},[2109],{"type":49,"value":800},{"type":43,"tag":708,"props":2111,"children":2112},{"style":773},[2113],{"type":49,"value":2114}," [orgId])\n",{"type":43,"tag":708,"props":2116,"children":2117},{"class":710,"line":1020},[2118],{"type":43,"tag":708,"props":2119,"children":2120},{"style":773},[2121],{"type":49,"value":2122},"  )\n",{"type":43,"tag":708,"props":2124,"children":2125},{"class":710,"line":1039},[2126,2130],{"type":43,"tag":708,"props":2127,"children":2128},{"style":773},[2129],{"type":49,"value":810},{"type":43,"tag":708,"props":2131,"children":2132},{"style":726},[2133],{"type":49,"value":815},{"type":43,"tag":708,"props":2135,"children":2136},{"class":710,"line":1091},[2137],{"type":43,"tag":708,"props":2138,"children":2139},{"style":972},[2140],{"type":49,"value":2141},"\u002F\u002F If the primary access pattern is per-org, shard by org, not user.\n",{"type":43,"tag":708,"props":2143,"children":2144},{"class":710,"line":1104},[2145],{"type":43,"tag":708,"props":2146,"children":2147},{"emptyLinePlaceholder":864},[2148],{"type":49,"value":867},{"type":43,"tag":708,"props":2150,"children":2151},{"class":710,"line":1539},[2152],{"type":43,"tag":708,"props":2153,"children":2154},{"style":972},[2155],{"type":49,"value":2156},"\u002F\u002F Assumes single database: JOIN across entity types\n",{"type":43,"tag":708,"props":2158,"children":2159},{"class":710,"line":1548},[2160,2164,2169,2173,2177,2182,2186,2190,2194],{"type":43,"tag":708,"props":2161,"children":2162},{"style":714},[2163],{"type":49,"value":1596},{"type":43,"tag":708,"props":2165,"children":2166},{"style":773},[2167],{"type":49,"value":2168}," orderDetails ",{"type":43,"tag":708,"props":2170,"children":2171},{"style":726},[2172],{"type":49,"value":1606},{"type":43,"tag":708,"props":2174,"children":2175},{"style":821},[2176],{"type":49,"value":1249},{"type":43,"tag":708,"props":2178,"children":2179},{"style":773},[2180],{"type":49,"value":2181}," db",{"type":43,"tag":708,"props":2183,"children":2184},{"style":726},[2185],{"type":49,"value":992},{"type":43,"tag":708,"props":2187,"children":2188},{"style":720},[2189],{"type":49,"value":997},{"type":43,"tag":708,"props":2191,"children":2192},{"style":773},[2193],{"type":49,"value":729},{"type":43,"tag":708,"props":2195,"children":2196},{"style":726},[2197],{"type":49,"value":1528},{"type":43,"tag":708,"props":2199,"children":2200},{"class":710,"line":1565},[2201],{"type":43,"tag":708,"props":2202,"children":2203},{"style":1014},[2204],{"type":49,"value":2205},"  SELECT o.*, p.name, u.email\n",{"type":43,"tag":708,"props":2207,"children":2209},{"class":710,"line":2208},18,[2210],{"type":43,"tag":708,"props":2211,"children":2212},{"style":1014},[2213],{"type":49,"value":2214},"  FROM orders o\n",{"type":43,"tag":708,"props":2216,"children":2218},{"class":710,"line":2217},19,[2219],{"type":43,"tag":708,"props":2220,"children":2221},{"style":1014},[2222],{"type":49,"value":2223},"  JOIN products p ON o.product_id = p.id\n",{"type":43,"tag":708,"props":2225,"children":2227},{"class":710,"line":2226},20,[2228],{"type":43,"tag":708,"props":2229,"children":2230},{"style":1014},[2231],{"type":49,"value":2232},"  JOIN users u ON o.user_id = u.id\n",{"type":43,"tag":708,"props":2234,"children":2236},{"class":710,"line":2235},21,[2237],{"type":43,"tag":708,"props":2238,"children":2239},{"style":1014},[2240],{"type":49,"value":2241},"  WHERE o.id = $1\n",{"type":43,"tag":708,"props":2243,"children":2245},{"class":710,"line":2244},22,[2246,2250,2254,2259],{"type":43,"tag":708,"props":2247,"children":2248},{"style":726},[2249],{"type":49,"value":1031},{"type":43,"tag":708,"props":2251,"children":2252},{"style":726},[2253],{"type":49,"value":800},{"type":43,"tag":708,"props":2255,"children":2256},{"style":773},[2257],{"type":49,"value":2258}," [orderId])",{"type":43,"tag":708,"props":2260,"children":2261},{"style":726},[2262],{"type":49,"value":815},{"type":43,"tag":708,"props":2264,"children":2266},{"class":710,"line":2265},23,[2267],{"type":43,"tag":708,"props":2268,"children":2269},{"style":972},[2270],{"type":49,"value":2271},"\u002F\u002F If orders, products, and users are on different shards, this doesn't work.\n",{"type":43,"tag":64,"props":2273,"children":2275},{"id":2274},"related-traps",[2276],{"type":49,"value":2277},"Related Traps",{"type":43,"tag":660,"props":2279,"children":2280},{},[2281,2298,2308,2318,2328],{"type":43,"tag":435,"props":2282,"children":2283},{},[2284,2289,2290,2296],{"type":43,"tag":58,"props":2285,"children":2286},{},[2287],{"type":49,"value":2288},"Race Conditions",{"type":49,"value":444},{"type":43,"tag":127,"props":2291,"children":2293},{"className":2292},[],[2294],{"type":49,"value":2295},"SELECT FOR UPDATE",{"type":49,"value":2297}," doesn't work across shards. Distributed locking adds latency and failure modes. Cross-shard operations need saga patterns or optimistic concurrency.",{"type":43,"tag":435,"props":2299,"children":2300},{},[2301,2306],{"type":43,"tag":58,"props":2302,"children":2303},{},[2304],{"type":49,"value":2305},"Idempotency",{"type":49,"value":2307}," -- the idempotency key and the data it protects must be on the same shard. Otherwise you can't atomically check for duplicates and perform the operation.",{"type":43,"tag":435,"props":2309,"children":2310},{},[2311,2316],{"type":43,"tag":58,"props":2312,"children":2313},{},[2314],{"type":49,"value":2315},"Hot Partitions",{"type":49,"value":2317}," -- even with sharding, one shard can receive disproportionate traffic. Shard key distribution determines whether you've solved the problem or just renamed it.",{"type":43,"tag":435,"props":2319,"children":2320},{},[2321,2326],{"type":43,"tag":58,"props":2322,"children":2323},{},[2324],{"type":49,"value":2325},"Consistency Models",{"type":49,"value":2327}," -- within a shard: strong consistency. Across shards: eventual consistency at best. Cross-shard queries see data at different points in time.",{"type":43,"tag":435,"props":2329,"children":2330},{},[2331,2336],{"type":43,"tag":58,"props":2332,"children":2333},{},[2334],{"type":49,"value":2335},"Denormalization",{"type":49,"value":2337}," -- cross-shard queries often require maintaining denormalized read stores. This creates consistency obligations (see denormalization trap).",{"type":43,"tag":2339,"props":2340,"children":2341},"style",{},[2342],{"type":49,"value":2343},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"items":2345,"total":1548},[2346,2355,2367,2377,2389,2400,2412],{"slug":2347,"name":2347,"fn":2348,"description":2349,"org":2350,"tags":2351,"stars":26,"repoUrl":27,"updatedAt":2354},"staff-engineering-skills-backpressure","implement backpressure in streaming pipelines","Prevent unbounded resource growth when producers outpace consumers. Use when writing producer-consumer code, queue processing, batch operations, fan-out patterns, streaming pipelines, or any code where work is generated faster than it can be processed. Activates on patterns like Promise.all on dynamic-sized arrays, unbounded in-memory queues, producer loops without depth checks, fire-and-forget async calls in loops, or any buffer between a producer and consumer without a size limit.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2352,2353],{"name":18,"slug":19,"type":16},{"name":14,"slug":15,"type":16},"2026-06-17T08:40:42.723559",{"slug":2356,"name":2356,"fn":2357,"description":2358,"org":2359,"tags":2360,"stars":26,"repoUrl":27,"updatedAt":2366},"staff-engineering-skills-cache-invalidation","implement cache invalidation strategies","Prevent stale data bugs caused by missing or incomplete cache invalidation. Use when adding caching layers (Redis, in-memory Map, CDN, HTTP cache headers), optimizing read performance, or reviewing code that caches query results, API responses, or computed values. Activates on patterns like cache.set without cache.delete on write paths, unbounded Map\u002Fobject caches, TTL-only invalidation on security-sensitive data, Cache-Control headers on mutable content, or any caching where the write path doesn't invalidate the read cache.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2361,2362,2364,2365],{"name":18,"slug":19,"type":16},{"name":201,"slug":2363,"type":16},"caching",{"name":24,"slug":25,"type":16},{"name":14,"slug":15,"type":16},"2026-06-17T08:40:45.194583",{"slug":2368,"name":2368,"fn":2369,"description":2370,"org":2371,"tags":2372,"stars":26,"repoUrl":27,"updatedAt":2376},"staff-engineering-skills-cardinality","prevent cardinality traps in systems code","Detect and prevent cardinality traps in systems code. Use when writing code that iterates over collections, stores items in memory, creates per-entity resources, or fans out operations across a set of items. Activates on patterns like loops over query results, in-memory Maps\u002FSets populated from databases, or \"one X per Y\" resource allocation.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2373,2374,2375],{"name":18,"slug":19,"type":16},{"name":24,"slug":25,"type":16},{"name":14,"slug":15,"type":16},"2026-06-17T08:40:40.264608",{"slug":2378,"name":2378,"fn":2379,"description":2380,"org":2381,"tags":2382,"stars":26,"repoUrl":27,"updatedAt":2388},"staff-engineering-skills-clock-skew","prevent clock skew bugs in distributed systems","Prevent bugs caused by assuming clocks are synchronized or monotonic. Use when writing code that compares timestamps across machines, measures durations, sets lock expiry, orders distributed events, deduplicates by time window, or uses Date.now() for anything other than logging or display. Activates on patterns like Date.now() used for duration measurement, absolute timestamp expiry shared across machines, wall clock timestamps for event ordering, last-write-wins conflict resolution with timestamps, or timeout calculations using wall clock time.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2383,2384,2387],{"name":18,"slug":19,"type":16},{"name":2385,"slug":2386,"type":16},"Debugging","debugging",{"name":24,"slug":25,"type":16},"2026-06-17T08:40:37.803541",{"slug":2390,"name":2390,"fn":2391,"description":2392,"org":2393,"tags":2394,"stars":26,"repoUrl":27,"updatedAt":2399},"staff-engineering-skills-consistency-models","manage consistency models in distributed systems","Prevent stale read bugs caused by replica lag, cache staleness, and index delay. Use when writing code that reads data after writing it, uses read replicas, caches query results, reads from search indexes, or builds event-driven read models. Activates on patterns like create-then-redirect, update-then-read, cache-aside with TTL, search-after-create, or any write followed by a read that might hit a different data source.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2395,2396,2397,2398],{"name":18,"slug":19,"type":16},{"name":21,"slug":22,"type":16},{"name":24,"slug":25,"type":16},{"name":14,"slug":15,"type":16},"2026-06-17T08:40:46.442182",{"slug":2401,"name":2401,"fn":2402,"description":2403,"org":2404,"tags":2405,"stars":26,"repoUrl":27,"updatedAt":2411},"staff-engineering-skills-denormalization","prevent data model denormalization traps","Detect and prevent denormalization traps when designing data models. Use when writing code that copies fields between tables, embeds related data in documents, caches composed objects, or adds redundant columns to avoid joins. Activates on patterns like storing derived\u002Fcopied data, syncing fields across tables, or embedding nested objects in document stores.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2406,2407,2410],{"name":18,"slug":19,"type":16},{"name":2408,"slug":2409,"type":16},"Data Modeling","data-modeling",{"name":21,"slug":22,"type":16},"2026-06-17T08:40:53.835868",{"slug":2413,"name":2413,"fn":2414,"description":2415,"org":2416,"tags":2417,"stars":26,"repoUrl":27,"updatedAt":2423},"staff-engineering-skills-distributed-system-fallacies","design resilient distributed systems","Prevent failures caused by treating network calls like function calls. Use when writing code that calls external services, splits a monolith into microservices, chains multiple API calls, orchestrates distributed workflows, or handles requests that depend on other services. Activates on patterns like sequential service calls without timeouts, fetch\u002Faxios calls without error handling, multi-service writes without compensation, hardcoded service URLs, or any code that assumes the network is reliable, fast, or free.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2418,2421,2422],{"name":2419,"slug":2420,"type":16},"API Development","api-development",{"name":18,"slug":19,"type":16},{"name":24,"slug":25,"type":16},"2026-06-17T08:40:47.666795",{"items":2425,"total":2579},[2426,2445,2458,2468,2485,2500,2514,2531,2544,2556,2567,2572],{"slug":2427,"name":2427,"fn":2428,"description":2429,"org":2430,"tags":2431,"stars":2442,"repoUrl":2443,"updatedAt":2444},"trigger-authoring-chat-agent","author durable AI chat agents with Trigger.dev","Author and run a durable AI chat agent with chat.agent from @trigger.dev\u002Fsdk\u002Fai: the per-turn run loop, why you MUST spread ...chat.toStreamTextOptions() first, returning a StreamTextResult vs calling chat.pipe(), the two server actions (chat.createStartSessionAction + auth.createPublicToken), and wiring useChat to useTriggerChatTransport. Load this when building, modifying, or debugging a chat backend (the agent task or its lifecycle hooks) or its React transport, when declaring typed tools or custom data parts, or when migrating a plain AI SDK streamText route to chat.agent.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2432,2435,2438,2439],{"name":2433,"slug":2434,"type":16},"Agents","agents",{"name":2436,"slug":2437,"type":16},"SDK","sdk",{"name":9,"slug":8,"type":16},{"name":2440,"slug":2441,"type":16},"Workflow Automation","workflow-automation",14401,"https:\u002F\u002Fgithub.com\u002Ftriggerdotdev\u002Ftrigger.dev","2026-07-02T17:12:52.307135",{"slug":2446,"name":2446,"fn":2447,"description":2448,"org":2449,"tags":2450,"stars":2442,"repoUrl":2443,"updatedAt":2457},"trigger-authoring-tasks","author backend Trigger.dev tasks","Covers writing backend Trigger.dev tasks with @trigger.dev\u002Fsdk: defining task() and schemaTask(), the run function and its ctx, retries, waits, queues and concurrency, idempotency keys, run metadata, logging, triggering other tasks (and the Result shape), scheduled\u002Fcron tasks, and the essentials of trigger.config.ts. Load this whenever you are authoring or editing code inside a \u002Ftrigger directory, defining a task, or writing backend code that triggers tasks. Realtime\u002FReact hooks and AI chat are covered by separate skills.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2451,2454,2455,2456],{"name":2452,"slug":2453,"type":16},"Backend","backend",{"name":2436,"slug":2437,"type":16},{"name":9,"slug":8,"type":16},{"name":2440,"slug":2441,"type":16},"2026-07-02T17:12:48.396964",{"slug":2459,"name":2459,"fn":2460,"description":2461,"org":2462,"tags":2463,"stars":2442,"repoUrl":2443,"updatedAt":2467},"trigger-chat-agent-advanced","manage Trigger.dev chat sessions and transports","Advanced and operational chat.agent capabilities for Trigger.dev, loaded on demand. Load this when working on the raw Sessions primitive (sessions \u002F SessionHandle), a custom chat transport or the realtime wire protocol, durable sub-agents (AgentChat, chat.stream.writer), human-in-the-loop, steering, actions, background injection (chat.defer \u002F chat.inject), fast starts (preload, Head Start via @trigger.dev\u002Fsdk\u002Fchat-server), context resilience (compaction, recovery boot, OOM, large payloads), chat.local run-scoped state, offline testing with mockChatAgent, or prerelease\u002Fversion upgrades. For the everyday chat.agent({...}) definition and the useTriggerChatTransport happy path, use the trigger-authoring-chat-agent skill instead.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2464,2465,2466],{"name":2433,"slug":2434,"type":16},{"name":9,"slug":8,"type":16},{"name":2440,"slug":2441,"type":16},"2026-07-02T17:12:51.03018",{"slug":2469,"name":2469,"fn":2470,"description":2471,"org":2472,"tags":2473,"stars":2442,"repoUrl":2443,"updatedAt":2484},"trigger-realtime-and-frontend","subscribe to Trigger.dev runs in realtime","Trigger.dev client\u002Ffrontend surface: subscribe to runs in realtime (runs.subscribeToRun and the @trigger.dev\u002Freact-hooks hook useRealtimeRun), consume metadata and AI\u002Ftext streams in React (useRealtimeStream), trigger tasks from the browser (useTaskTrigger, useRealtimeTaskTrigger), and mint scoped frontend credentials with auth.createPublicToken \u002F auth.createTriggerPublicToken. Load when wiring a frontend (React\u002FNext.js\u002FRemix) or backend-for-frontend to show live run progress, status badges, token streams, trigger buttons, or wait-token approval UIs. NOT for writing the backend task itself (streams.define \u002F metadata.set is trigger-authoring-tasks territory); this is the consumer side.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2474,2477,2480,2483],{"name":2475,"slug":2476,"type":16},"Frontend","frontend",{"name":2478,"slug":2479,"type":16},"React","react",{"name":2481,"slug":2482,"type":16},"Real-time","real-time",{"name":9,"slug":8,"type":16},"2026-07-02T17:12:49.717706",{"slug":2486,"name":2486,"fn":2487,"description":2488,"org":2489,"tags":2490,"stars":2497,"repoUrl":2498,"updatedAt":2499},"trigger-agents","orchestrate AI agents with Trigger.dev","AI agent patterns with Trigger.dev - orchestration, parallelization, routing, evaluator-optimizer, and human-in-the-loop. Use when building LLM-powered tasks that need parallel workers, approval gates, tool calling, or multi-step agent workflows.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2491,2492,2495,2496],{"name":2433,"slug":2434,"type":16},{"name":2493,"slug":2494,"type":16},"Multi-Agent","multi-agent",{"name":9,"slug":8,"type":16},{"name":2440,"slug":2441,"type":16},30,"https:\u002F\u002Fgithub.com\u002Ftriggerdotdev\u002Fskills","2026-04-06T18:54:46.023553",{"slug":2501,"name":2501,"fn":2502,"description":2503,"org":2504,"tags":2505,"stars":2497,"repoUrl":2498,"updatedAt":2513},"trigger-config","configure Trigger.dev project settings","Configure Trigger.dev projects with trigger.config.ts. Use when setting up build extensions for Prisma, Playwright, FFmpeg, Python, or customizing deployment settings.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2506,2509,2512],{"name":2507,"slug":2508,"type":16},"Configuration","configuration",{"name":2510,"slug":2511,"type":16},"Deployment","deployment",{"name":9,"slug":8,"type":16},"2026-04-06T18:54:44.764258",{"slug":2515,"name":2515,"fn":2516,"description":2517,"org":2518,"tags":2519,"stars":2497,"repoUrl":2498,"updatedAt":2530},"trigger-cost-savings","optimize Trigger.dev task costs","Analyze Trigger.dev tasks, schedules, and runs for cost optimization opportunities. Use when asked to reduce spend, optimize costs, audit usage, right-size machines, or review task efficiency. Requires Trigger.dev MCP tools for run analysis.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2520,2523,2526,2529],{"name":2521,"slug":2522,"type":16},"Analytics","analytics",{"name":2524,"slug":2525,"type":16},"Cost Optimization","cost-optimization",{"name":2527,"slug":2528,"type":16},"Operations","operations",{"name":9,"slug":8,"type":16},"2026-04-06T18:54:48.555552",{"slug":2532,"name":2532,"fn":2533,"description":2534,"org":2535,"tags":2536,"stars":2497,"repoUrl":2498,"updatedAt":2543},"trigger-realtime","monitor Trigger.dev tasks in real-time","Subscribe to Trigger.dev task runs in real-time from frontend and backend. Use when building progress indicators, live dashboards, streaming AI\u002FLLM responses, or React components that display task status.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2537,2538,2541,2542],{"name":2475,"slug":2476,"type":16},{"name":2539,"slug":2540,"type":16},"Observability","observability",{"name":2481,"slug":2482,"type":16},{"name":9,"slug":8,"type":16},"2026-04-06T18:54:47.293822",{"slug":2545,"name":2545,"fn":2546,"description":2547,"org":2548,"tags":2549,"stars":2497,"repoUrl":2498,"updatedAt":2555},"trigger-setup","set up Trigger.dev in projects","Set up Trigger.dev in your project. Use when adding Trigger.dev for the first time, creating trigger.config.ts, or initializing the trigger directory.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2550,2551,2554],{"name":2507,"slug":2508,"type":16},{"name":2552,"slug":2553,"type":16},"Local Development","local-development",{"name":9,"slug":8,"type":16},"2026-04-06T18:54:42.280816",{"slug":2557,"name":2557,"fn":2558,"description":2559,"org":2560,"tags":2561,"stars":2497,"repoUrl":2498,"updatedAt":2566},"trigger-tasks","build durable background tasks with Trigger.dev","Build AI agents, workflows and durable background tasks with Trigger.dev. Use when creating tasks, triggering jobs, handling retries, scheduling cron jobs, or implementing queues and concurrency control.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2562,2563,2564,2565],{"name":2433,"slug":2434,"type":16},{"name":2452,"slug":2453,"type":16},{"name":9,"slug":8,"type":16},{"name":2440,"slug":2441,"type":16},"2026-04-06T18:54:43.514369",{"slug":2347,"name":2347,"fn":2348,"description":2349,"org":2568,"tags":2569,"stars":26,"repoUrl":27,"updatedAt":2354},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2570,2571],{"name":18,"slug":19,"type":16},{"name":14,"slug":15,"type":16},{"slug":2356,"name":2356,"fn":2357,"description":2358,"org":2573,"tags":2574,"stars":26,"repoUrl":27,"updatedAt":2366},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[2575,2576,2577,2578],{"name":18,"slug":19,"type":16},{"name":201,"slug":2363,"type":16},{"name":24,"slug":25,"type":16},{"name":14,"slug":15,"type":16},26]