GTM Galaxy

Article

The Custom Object Tax: The Real Cost of Streaming Usage Data

It usually starts with a specific request from a Sales Manager: "I want to see exactly what my prospects are doing during their trial." They want the granular logs—every button click, every report export, and every teammate invite—visible directly on the Lead or Contact record.

On the surface, the request is logical. If the data is in the CRM, the sales team can build reports and trigger alerts using the tools they already know. Modern Reverse ETL platforms have made this technically trivial. You point your warehouse at a Salesforce or HubSpot custom object, map the fields, and turn on the sync.

But this ease of implementation is a trap. What looks like a simple data sync quickly evolves into an architectural and financial burden I call the Custom Object Tax.

The Performance Tax: Row-Level Locking

The first sign of trouble isn't a high bill; it’s a failing sync. CRMs are transactional databases designed for human-speed updates, not high-velocity telemetry.

Salesforce, for example, uses pessimistic row-level locking. When you insert a child record—like a "Product Event" custom object—and link it to a parent Account, the system places a lock on that parent record to maintain data integrity.

If you are streaming events in high volume, you eventually hit a concurrency wall. Hundreds of records try to lock the same Account simultaneously, triggering the UNABLE_TO_LOCK_ROW error. To keep the sync from failing, you are forced to move API calls into "Serial" mode or reduce batch sizes to one. You’ve taken a high-speed data highway and narrowed it to a single lane. This doesn't just slow down your usage data; it bottlenecks every other automation or integration trying to touch those parent records.

The Economic Tax: Premium Storage at Scale

Beyond the performance hits, the financial math of custom objects is brutal. CRMs sell database storage at a premium that makes Snowflake or BigQuery look like a charity.

In the Salesforce ecosystem, data storage is typically allocated in blocks (often 2GB base plus an increment per user). Once you cross that, additional storage is famously expensive. Standard overage pricing can hover around $250 per month for a measly 500MB.

Consider the math: Every custom object record consumes roughly 2KB of storage. A PLG company with 5,000 active users generating 20 events per day will create 3 million records a month. That’s 6GB of data. Within a quarter, you aren't just paying for CRM licenses; you are paying a five-figure annual surcharge to store logs that your sales reps will likely never read.

HubSpot’s gatekeeping is equally rigid. Custom objects are restricted to Enterprise-tier hubs. If you are on a Professional plan, the desire to store structured product telemetry forces a massive licensing jump ($3,600+/month floor). Even at the Enterprise level, you face association limits—HubSpot caps associations at 10,000 to 50,000 records per parent depending on the object. For a high-usage account, you can hit that ceiling in weeks, at which point your data orchestration simply breaks.

The Utility Tax: Signal vs. Noise

There is a human cost to this data dumping. We often assume more data equals more insight, but for a sales rep, the opposite is true.

If an Account Executive opens a record and sees a related list of 5,000 "Page View" events, they aren't going to find the "aha moment." They are going to ignore the data entirely. When we stream raw telemetry into a CRM, we are offloading the job of data analysis onto the sales team. We are asking them to be data analysts rather than closers.

A Better Architecture: Milestone Aggregation

The solution is to move the heavy lifting to the warehouse layer. Instead of streaming raw events, use a transformation tool like dbt to compute the signals that actually matter. This follows a simple rule: Sync the state, not the log.

Instead of a "Product Event" custom object, create high-value summary fields on the Account or Contact record. Or, if you must use a custom object, reserve it for significant milestones only.

The Comparison:

  • The Bad Way: Streaming 500 "Document Created" events into a custom object.
  • The Better Way: A single dbt model that calculates documents_created_last_7_days and total_active_collaborators.
-- A simple aggregation example for a 'Usage Summary' sync
SELECT 
    account_id,
    count(CASE WHEN event_name = 'document_created' THEN 1 END) as docs_7d,
    max(timestamp) as last_active_at,
    count(DISTINCT user_id) as active_users_7d
FROM raw_events
WHERE timestamp > current_date - 7
GROUP BY 1

This architectural shift solves the three dimensions of the tax:

  1. Zero Locking: You are performing one update per Account per day (or hour), rather than hundreds of inserts per minute.
  2. Predictable Costs: You can store billions of raw events in BigQuery or Snowflake for pennies, while your CRM storage stays lean and focused on transactional data.
  3. High Utility: The rep doesn't see a mess of logs; they see a clear signal that "Usage increased by 40% this week." That is an actionable talk track.

Determining the Boundary

When deciding if a piece of data belongs in a CRM custom object, ask yourself: Does a human need to see the specific instance of this event to make a decision?

If the answer is no, leave it in the warehouse. Use your CRM for the summary, the state change, and the milestone. Your database performance, your budget, and your sales team’s sanity depend on keeping the noise out of the system of record.

— C.B.