◈Home ▸Blog ◇Contact
Home/Blog/Why Klaviyo Segments Lag Past Millions of Pro…
ArticleKlaviyoKlaviyoSegmentationScale

Why Klaviyo Segments Lag Past Millions of Profiles

9 min readBy Miloš Mitrović

Past roughly two to three million profiles, a Klaviyo segment stops behaving like a saved filter and starts behaving like a query with a cost you pay every time something changes. The symptom operators notice first is not an error. It is a campaign that was scheduled for 9am going out at 11:40am, or a count that reads 412,000 in the builder and 388,000 on the send screen. The fix is almost never a cleverer condition. It is removing work from evaluation time by precomputing the expensive parts into profile properties, and by rewriting definitions so the narrowest filter does the most work.

Key takeaways

  • Segment cost scales with the number of profiles that must be re-checked on every event, not with the size of the result.
  • Nested OR groups wrapped around date-bounded metric conditions are the single most common cause of a definition that never settles.
  • After an edit, a large segment backfills. Treat the count as provisional until it flattens across two consecutive checks an hour apart.
  • Repeated logic used by five or more segments belongs in a profile property written once, not evaluated five times.
  • Schedule sends against a definition that was stable yesterday, not one you edited an hour ago.

Why a segment that was instant last year now takes hours

Because the work is proportional to how many profiles have to be re-tested, and your profile count grew while your definitions stayed the same. A segment in Klaviyo is a stored definition that is continuously evaluated, unlike a list, which is static membership you add to explicitly (Klaviyo documents this distinction directly). At 300,000 profiles, a badly shaped definition is still fast enough that nobody files a ticket. At four million, the same shape is doing more than ten times the comparisons on every inbound event, and the account is also processing more events per second because the store is bigger.

The second driver is fan-out. Every segment that references a metric has to wake up when that metric fires. If eighty segments in the account reference Placed Order, a single order touches eighty definitions. Most accounts I inherit have accumulated segments nobody has used in a year, and they are all still being evaluated. Deleting twenty dead segments is the cheapest performance work available and it takes an afternoon.

Which conditions actually cost you

The expensive conditions are the ones that cannot be answered by looking at a single stored value on the profile. Anything that requires walking an event history, summing across it, or comparing two moving dates is in a different cost class from a property equality check.

Condition shapeWhy it costsCheaper shape
Sum of $value over all timeAggregates the full event history per profileWrite lifetime revenue to a profile property nightly
Count of a metric between two relative datesRecomputes a moving window on every evaluationBucket into a property such as orders_last_365
Nested OR inside AND inside OR, three levels deepCannot short-circuit, evaluates every branchFlatten to two levels, put the narrowest filter first
What someone has done or not done with a product filter on a JSON arrayScans item arrays inside each event payloadPrecompute a category flag on the profile at order time
Predictive fields combined with several metric conditionsJoins a modelled value against event historySplit into two segments, or precompute the tier

Property equality checks are close to free. A segment defined as country equals US AND vip_tier equals gold resolves without touching event storage at all. That is the target shape for anything you send to on a schedule.

Why the count never settles after you edit a definition

Because an edit invalidates existing membership and triggers a backfill across the whole profile base, and on a large account that backfill runs for a while. The number you see in the builder during that period is a partial result that climbs, and operators read the climb as instability in the data rather than an incomplete pass. This is a different problem from a count that drifts on a settled definition, which usually traces to relative date conditions doing exactly what they were asked.

The practical rule: after editing a segment on an account above a million profiles, check the count, wait an hour, check again, wait another hour, check again. Two consecutive readings within about half a percent means it has settled. Anything still moving by five percent per hour is still backfilling. Do not schedule a campaign against it, and do not let anyone screenshot the number for a report.

Small edits are not cheap edits. Changing a date window from 90 to 120 days invalidates everything, same as rewriting the definition from scratch. Batch your changes. If three edits are coming this week, make them in one sitting and pay for one backfill.

How to move repeated logic into precomputed properties

Pick the logic that appears in more than three segments and write it to the profile once, on a schedule, from outside Klaviyo. The pattern is a nightly job that reads from your order system or warehouse, computes the values, and writes them back as profile properties. Klaviyo's bulk profile import endpoint is built for exactly this and accepts batched payloads rather than one request per profile (see the bulk profile import job reference). Watch your budget against the documented rate limits and error handling rules, which is the same constraint that bites when one key serves several brands.

Good candidates, in the order I usually do them:

  1. orders_lifetime and revenue_lifetime as integers. Replaces every sum-over-all-time condition in the account.
  2. days_since_last_order, refreshed nightly. Replaces most relative date arithmetic in winback and replenishment logic.
  3. primary_category and a small set of category flags. Replaces every product array scan.
  4. engagement_tier, a single string derived from your open and click history using the window your repurchase cycle actually supports.

The cost is staleness. A nightly property is up to 24 hours behind. That is acceptable for tiering, targeting and suppression. It is not acceptable for anything a flow triggers on within minutes, so leave flow trigger filters alone and only convert campaign and suppression segments. If you are deciding where this computation should live in the first place, that is a broader question about tags, metafields or a warehouse.

How to get a campaign out while a segment is still settling

Send to a snapshot instead of a live definition. For a one-time campaign, membership only needs to be correct at the moment of send, so pull the current members through the API and push them to a static list, then send to the list. The segment profiles endpoint supports cursor pagination for exactly this kind of export (endpoint reference). A list has no evaluation cost, so the send starts when you tell it to. This is the same reasoning behind choosing a list over a segment for one-time sends.

The thing to guard against is sending the snapshot to people who should have been excluded by a suppression rule that had not finished evaluating. Build your suppression segments out of cheap property conditions so they settle in minutes, and apply them as an exclusion on the campaign rather than baking them into the audience definition. Sending a stale audience to unengaged recipients is how complaint rates climb past the 0.3 percent threshold Google publishes in its sender guidelines, and that damage outlasts the campaign.

The audit that finds your five worst definitions

Start by listing every segment in the account with its definition, which you can pull in one pass rather than clicking through the UI. Then sort by cost signals, not by how important the segment feels.

  1. Pull the inventory. Fetch all segments with their definitions and creation dates. Note which ones are referenced by an active flow or have been sent to in the last 90 days.
  2. Delete the dead ones. Any segment not referenced by a flow and not sent to in 90 days is overhead. In most accounts this is 30 to 50 percent of the total.
  3. Flag the deep ones. Count nesting levels in each remaining definition. Three or more levels of grouping goes on the rewrite list regardless of how well it appears to work.
  4. Flag the aggregators. Any condition summing or counting over all time, or over a window longer than 180 days, goes on the precompute list.
  5. Time the settle. For your top ten sending segments, touch a trivial part of the definition and record how long the count takes to flatten. Anything over two hours is a problem you will feel on send day.
  6. Rewrite in place, one at a time. Change one segment, let it settle, compare the final count to the old one. A variance above two percent means you changed the meaning, not the shape.

Step six is where people get hurt. A rewritten definition that returns a materially different audience is a logic change, and if that segment drives a flow you have just altered who receives what. If a count comes back far lower than expected after a rewrite, work through why a segment stops matching before you assume the optimisation was correct.

Trade-offs I would accept

Precomputed properties win over live evaluation for anything that targets a campaign, and I would take that trade in almost every account above two million profiles. You give up freshness measured in hours and you gain sends that start on time and counts that mean something in a report. Marketing teams rarely notice the staleness. They always notice a campaign going out two hours late.

Where I would not precompute: flow entry conditions, cart and browse logic, and anything tied to inventory. Those need current state and the evaluation cost is bounded because a flow filter only runs for profiles already entering the flow, not across the whole base.

On nesting, I would accept a slightly less precise audience in exchange for a definition I can read. A two-level definition that includes an extra three percent of marginal profiles is better operationally than a four-level one that is exactly right and that nobody, including the person who built it, can safely modify six months later. Precision you cannot maintain is not precision.

The one place I would spend real effort rather than simplify is suppression. Getting an extra three percent into a promotional audience costs you a little margin. Failing to suppress three percent who should never have received the send costs you complaints, and that shows up in the same reporting fragility you hit when order volume passes a million. Keep suppression definitions simple, cheap, and built entirely from properties so they settle fast and you can prove what they contained.

Sources

M
Miloš Mitrović
Email Marketing for Ecommerce

Have a question or a project?

Whether it is about this post or a system you want built, I'm happy to talk.

Get in touch

404

Post not found. It may have been moved or the link is incorrect.

← Back to the blog
Summarize with AI
ChatGPT, Perplexity, and Grok open with the prompt ready to run. Claude, Gemini, and Copilot open a chat with the prompt copied; press Ctrl+V (Cmd+V on Mac) to paste. The full text is included, so it works even without web access.