<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Adan Potyondy</title>
    <description>The latest articles on DEV Community by Adan Potyondy (@adan_potyondy).</description>
    <link>https://dev.to/adan_potyondy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4200982%2F3fbf0d3c-5569-4503-826c-c238b4d4a214.jpeg</url>
      <title>DEV Community: Adan Potyondy</title>
      <link>https://dev.to/adan_potyondy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adan_potyondy"/>
    <language>en</language>
    <item>
      <title>Load an on-prem database into a cloud warehouse: 1 million rows, measured</title>
      <dc:creator>Adan Potyondy</dc:creator>
      <pubDate>Tue, 10 Nov 2026 14:36:05 +0000</pubDate>
      <link>https://dev.to/adan_potyondy/load-an-on-prem-database-into-a-cloud-warehouse-1-million-rows-measured-nb</link>
      <guid>https://dev.to/adan_potyondy/load-an-on-prem-database-into-a-cloud-warehouse-1-million-rows-measured-nb</guid>
      <description>&lt;p&gt;To load an on-prem database into a cloud warehouse, read it in pages inside your network, check and mask each page there, keep the failed rows locally, and send the rest in batches of a few thousand rows per statement. In our test of 1 million rows, rows per insert set the speed. Our first checks caught 55% of the bad rows; after Claude profiled the data and rewrote the checks through the Neblex MCP server, they caught 94%, including in a half of the table they were never shown. The better checks cost about 2 minutes per million rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The request we hear often
&lt;/h2&gt;

&lt;p&gt;Moving data from a database inside a company's network to a cloud warehouse, usually as a scheduled ETL job, is a request we hear often. The questions are always the same: how long will it take, will bad rows reach the warehouse, and does personal data leave the building?&lt;/p&gt;

&lt;p&gt;Our &lt;a href="https://www.neblex.com/blog/onprem-integration-agent-resource-usage" rel="noopener noreferrer"&gt;measurement post&lt;/a&gt; covered what the runtime costs your server. This one runs the load: 1 million rows from an on-prem PostgreSQL database into Supabase, checked and masked on the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we tested
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Source: PostgreSQL 18 on the test machine, with a test table of 1,000,000 customer records (206 MB).&lt;/li&gt;
&lt;li&gt;Problem rows: 18,675 rows, about 1.9%, with a bad email, state or phone, or a test account. Each problem came in one of 6 to 9 forms we see in real data, such as a misspelled domain, "N/A" in the state field or an all-zeros phone, and about 1 in 9 of these rows had a second problem.&lt;/li&gt;
&lt;li&gt;Unusual but valid rows: 5,325 rows that should pass, such as a surname starting with "Test", a phone with an extension or a state written in full.&lt;/li&gt;
&lt;li&gt;Answer key: a separate table recorded every problem row and every unusual row, so we could score each run.&lt;/li&gt;
&lt;li&gt;Runtime: the &lt;a href="https://www.neblex.com/blog/on-prem-runtime-security" rel="noopener noreferrer"&gt;On-Prem Worker&lt;/a&gt;, our local integration runtime, running as a background service on the test machine.&lt;/li&gt;
&lt;li&gt;Destination: Supabase, a hosted Postgres database, in us-west-2, through its connection pooler. It stood in for the cloud warehouse.&lt;/li&gt;
&lt;li&gt;Line: a home internet connection, 30 to 44 Mbps up. Measured before every timed run: 31 to 110 ms idle latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb29pncwsuuxcz4u6pwhg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb29pncwsuuxcz4u6pwhg.webp" alt="On-prem PostgreSQL feeds the On-Prem Worker, which quarantines failed rows locally and sends only masked rows to Supabase" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Raw and failed rows stay inside the network; the warehouse gets masked rows and the Neblex cloud gets the run report. Diagram by Neblex.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We kept the error rate low on purpose. In a peer-reviewed study of 75 data quality assessments, &lt;a href="https://ideas.repec.org/a/eee/bushor/v63y2020i3p325-337.html" rel="noopener noreferrer"&gt;47% of recently created records had at least one critical error&lt;/a&gt;; our test had problems in 1.9% of rows, across four fields.&lt;/p&gt;

&lt;p&gt;We sampled the runtime's CPU, memory and network traffic throughout, and after each run compared what arrived in Supabase with the answer key. Supabase is Postgres, not a warehouse like Snowflake or BigQuery, which usually load from staged files; the lesson below carries over either way: send fewer, larger loads.&lt;/p&gt;

&lt;h2&gt;
  
  
  What set the speed: rows per insert
&lt;/h2&gt;

&lt;p&gt;The runtime spent most of each run waiting for the warehouse to accept inserts, so the number of rows per insert decided the time.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rows per insert&lt;/th&gt;
&lt;th&gt;Rows read&lt;/th&gt;
&lt;th&gt;Runs&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Rows per second (median)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;500 (sample)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;195 s&lt;/td&gt;
&lt;td&gt;2.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;21 min 46 s to 22 min 53 s (median 21 min 53 s)&lt;/td&gt;
&lt;td&gt;762&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;9 min 56 s to 10 min 38 s (median 10 min 22 s)&lt;/td&gt;
&lt;td&gt;1,608&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;8 min 18 s to 8 min 48 s (median 8 min 23 s)&lt;/td&gt;
&lt;td&gt;1,988&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;7 min 56 s to 8 min 59 s (median 8 min 37 s)&lt;/td&gt;
&lt;td&gt;1,934&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd1gjp9uyhxe12is9xiim.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd1gjp9uyhxe12is9xiim.webp" alt="Bar chart: 2.6 rows/s at 1 row per insert, 762 at 100, 1,608 at 1,000, 1,988 at 5,000 and 1,934 at 10,000 (medians)" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Rows per insert set the speed; past 5,000 there was no further gain. Source: Neblex internal tests, October 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Each setting ran five times on the same day. At 5,000 rows per insert, the slowest run took 6% longer than the fastest, and 10,000 overlapped it.&lt;/p&gt;

&lt;p&gt;One row per insert took about 0.39 seconds per row, or four and a half days for 1 million rows. Each insert pays a full round trip to the warehouse, which dwarfs the work of inserting one row.&lt;/p&gt;

&lt;p&gt;Engineering write-ups show the same shape at higher speeds. In one, run against a database on the same laptop, batching took inserts &lt;a href="https://hatchet.run/blog/fastest-postgres-inserts" rel="noopener noreferrer"&gt;from about 2,284 to 37,465 rows per second&lt;/a&gt;. The &lt;a href="https://www.postgresql.org/docs/current/populate.html" rel="noopener noreferrer"&gt;PostgreSQL documentation&lt;/a&gt; gives the same advice: fewer, larger statements, and &lt;code&gt;COPY&lt;/code&gt; where you can use it.&lt;/p&gt;

&lt;p&gt;Past 5,000 rows per insert there was no further gain: each insert gets bigger and slower to send and parse. Your best size depends on latency, row width and warehouse, so measure a few sizes rather than picking the largest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the time went
&lt;/h2&gt;

&lt;p&gt;Step timings for one page of 100,000 rows, from the run logs, with our first checks and the rewritten checks described below:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;First checks&lt;/th&gt;
&lt;th&gt;Rewritten checks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Checks&lt;/td&gt;
&lt;td&gt;1.7 s&lt;/td&gt;
&lt;td&gt;11.9 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normalize email, domain and phone&lt;/td&gt;
&lt;td&gt;1.8 s&lt;/td&gt;
&lt;td&gt;2.8 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route rows to load or quarantine&lt;/td&gt;
&lt;td&gt;1.1 s&lt;/td&gt;
&lt;td&gt;1.2 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With the first checks, the 20 inserts took 33.5 of the page's 43 seconds. The rewritten checks added about 11 seconds per 100,000 rows. Inserts still set the pace, but thorough rules are not free: checks and normalizing went from about 8% of the page's time to nearly 30%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which checks should run before rows leave the network?
&lt;/h2&gt;

&lt;p&gt;Run the checks that decide whether a row may leave at all; anything you can only fix at the source belongs here. We started with four, each a single rule on one field:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;First rule&lt;/th&gt;
&lt;th&gt;Bad rows caught (held-out half)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;td&gt;contains an "@"&lt;/td&gt;
&lt;td&gt;46%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;not empty&lt;/td&gt;
&lt;td&gt;60%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;at least 10 digits&lt;/td&gt;
&lt;td&gt;74%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test account&lt;/td&gt;
&lt;td&gt;on the test domain, or starting with "test"&lt;/td&gt;
&lt;td&gt;43%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Together they caught 54.6% of the bad rows in the half we later held out, and wrongly quarantined 5.6% of the unusual but valid rows, such as surnames starting with "Test". Each miss has a plain cause: a misspelled domain still contains an "@", "N/A" is not an empty state, and an all-zeros phone has 10 digits.&lt;/p&gt;

&lt;p&gt;The weak rules also hid two masking bugs. Emails with two "@" signs put part of the address in the domain field of 402 warehouse rows, and for phones with an extension, the "last 4 digits" were the extension's.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improving the rules from a profile of the data
&lt;/h2&gt;

&lt;p&gt;Rules only catch what someone thought to write down, so we did what a data team would do: look at the data, then rewrite the rules. The difference is that Claude did the work through the &lt;a href="https://www.neblex.com/integration-fabric/docs/guide/mcp" rel="noopener noreferrer"&gt;Neblex MCP server&lt;/a&gt;, and nobody edited the flow by hand.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;1We split the table into two halves by a hash of each row's id.&lt;/li&gt;
&lt;li&gt;2Claude built and ran a profiling flow on the first half through MCP: counts and patterns, not rows. For example, every state value and its count, the shapes of phone numbers, the most common email domains, and values that repeated 4 or more times.&lt;/li&gt;
&lt;li&gt;3A second Claude session, given only that profile and our four rules, drafted new rules and listed what they would still miss. It never saw the data, the answer key or how the test data was made.&lt;/li&gt;
&lt;li&gt;4Claude wrote the new rules into the flow's check and masking steps through MCP. The checks went in exactly as drafted; the masking fixes were written to the drafted spec.&lt;/li&gt;
&lt;li&gt;5We scored the new rules on both halves against the answer key. The second half was never profiled.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The model worked from a summary produced inside the network, never from customer rows, and it changed the same governed flow a person would edit in the designer. One caution if you do this on real data: repeated values can be real phone numbers or emails, so mask them or raise the threshold before the profile goes to a model.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;First rule&lt;/th&gt;
&lt;th&gt;Rewritten rule (summary)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;td&gt;contains an "@"&lt;/td&gt;
&lt;td&gt;exactly one "@", valid characters and domain, not a known misspelled domain or placeholder such as noreply@&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;not empty&lt;/td&gt;
&lt;td&gt;a US state code or name; "N/A", "unknown" and similar count as missing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phone&lt;/td&gt;
&lt;td&gt;at least 10 digits&lt;/td&gt;
&lt;td&gt;10 digits after dropping a leading 1 and any extension, and not a placeholder such as all zeros&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test account&lt;/td&gt;
&lt;td&gt;test domain, or starts with "test"&lt;/td&gt;
&lt;td&gt;test domains, "test" or "testuser" as a name or address, keyboard mashes such as asdf@&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwcq6z5vllbxo3c110i3q.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwcq6z5vllbxo3c110i3q.webp" alt="Flow diagram: read page, checks, normalize, hash, mask, batch and insert into Supabase, with failed rows to a local quarantine; checks and normalize changed" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Claude changed what two steps do; the flow's steps and connections stayed the same. Diagram by Neblex.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;First checks&lt;/th&gt;
&lt;th&gt;Rewritten checks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bad rows caught, profiled half&lt;/td&gt;
&lt;td&gt;55.3%&lt;/td&gt;
&lt;td&gt;94.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bad rows caught, held-out half&lt;/td&gt;
&lt;td&gt;54.6%&lt;/td&gt;
&lt;td&gt;93.7% (8,701 of 9,289)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unusual but valid rows quarantined, held-out half&lt;/td&gt;
&lt;td&gt;5.6%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal rows quarantined, held-out half&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0 of 485,638&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The held-out half scored within a point of the profiled half, so the rules were not fitted to particular rows. Both masking bugs were fixed.&lt;/p&gt;

&lt;p&gt;The rewritten rules still missed some rows in the held-out half:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Domain misspellings not on their list: 62 of 613. The drafted rules themselves suggested an edit-distance check for these.&lt;/li&gt;
&lt;li&gt;Test accounts the profile never showed: "demo" and "qa+" addresses, and placeholder names such as John Doe. These were caught in 17%, 14% and 49% of cases.&lt;/li&gt;
&lt;li&gt;Emails ending in a dot: the rules strip the dot and load the address. Our answer key counted these as bad. If you count stripping the dot as a fix, the rewritten rules handled about 95% of the bad rows in the held-out half.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One limit: the held-out half had the same kinds of problems as the profiled half. The test shows the rules generalize to new rows, not to kinds of problems nobody has seen yet, which is exactly what the misses above are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where do the rows that fail go?
&lt;/h2&gt;

&lt;p&gt;To a quarantine table in the same on-prem database, with their raw values and the reason they failed. No row is dropped, and the raw values stay inside the network.&lt;/p&gt;

&lt;p&gt;The raw values are kept because a malformed email can only be fixed by someone who can see it. With the rewritten rules, every run quarantined the same 17,564 rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Masking before the load, and what hashing does not do
&lt;/h2&gt;

&lt;p&gt;We kept three things per customer in place of the raw email and phone: a hash of the trimmed, lower-cased email, so two records with the same email still match; the email domain, for company-level reporting; and the last 4 digits of the phone, for support lookups. Names, company, city and state went through unchanged, because the reports needed them. That is a choice each team has to make field by field.&lt;/p&gt;

&lt;p&gt;A hash is not anonymisation. The UK Information Commissioner's Office &lt;a href="https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/data-sharing/anonymisation/pseudonymisation/" rel="noopener noreferrer"&gt;treats pseudonymised data as personal data&lt;/a&gt; and advises against hashes that use no salt, pepper or key. The US FTC put it more bluntly: &lt;a href="https://www.ftc.gov/policy/advocacy-research/tech-at-ftc/2024/07/no-hashing-still-doesnt-make-your-data-anonymous" rel="noopener noreferrer"&gt;"hashes aren't 'anonymous' and can still be used to identify users"&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So use a keyed hash (HMAC) with a secret that stays on your side. Our timed runs used plain SHA-256. In an earlier test, a version built for production use, with a keyed hash and upserts instead of inserts, loaded 1 million rows in 8 minutes. The warehouse can still join on the hash; an outsider with a list of email addresses cannot reverse it.&lt;/p&gt;

&lt;p&gt;After each run we checked the warehouse: 982,436 rows, no empty hashes, every phone field exactly four digits, and no "@" in any text column. The Neblex cloud received the run's status, diagnostics, counts and integrity receipts, not records; ask any vendor you evaluate to show this in their run logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  After the first load: send only what changed
&lt;/h2&gt;

&lt;p&gt;A one-time load is a backfill; after it, a scheduled job should move only the rows that changed. In an earlier production-style version tracking changes by &lt;code&gt;updated_at&lt;/code&gt; and id, a run with nothing new took 0.3 seconds and a run with five changed rows took 1.8 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the runtime used
&lt;/h2&gt;

&lt;p&gt;CPU averaged 25 to 47% of one core. Upload was 372 to 395 MB per 1 million rows, around 6 Mbps of the 30 to 44 Mbps uplink, so the line was never the limit.&lt;/p&gt;

&lt;p&gt;Memory varied more. Across 21 runs of 1 million rows, peaks ranged from 379 to 985 MB, highest at 100 and 1,000 rows per insert, while the median stayed under 210 MB in all but five runs. The runtime returned to 52 MB afterwards; size the machine for the peak. Our &lt;a href="https://www.neblex.com/blog/onprem-integration-agent-resource-usage" rel="noopener noreferrer"&gt;measurement post&lt;/a&gt; covers how the runtime handles memory as batches grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can go wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A slow line stretches the load but should not change the result. Over a poor mobile connection (500 to 950 ms latency), 1 million rows took about 29 minutes instead of about 8, and every row still arrived in the warehouse or quarantine.&lt;/li&gt;
&lt;li&gt;A write can fail with its outcome unknown: the warehouse may or may not have committed it. On the poor line, one insert dropped mid-flight and the run stopped for a person to decide instead of retrying blindly.&lt;/li&gt;
&lt;li&gt;Rules miss what the profile never showed. Profile again when the source changes.&lt;/li&gt;
&lt;li&gt;Incremental loads have edges. A row that later fails its checks stays in the warehouse with its last good values, and deletes in the source are not copied unless you handle them.&lt;/li&gt;
&lt;li&gt;Quarantine needs an owner. A quarantine table nobody reviews is a slower way of dropping rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to set it up yourself
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;1Create the target table in the warehouse with only the masked columns, and a quarantine table in the source database with the row's id, the reason and the raw record.&lt;/li&gt;
&lt;li&gt;2Read the source in pages ordered by key (&lt;code&gt;WHERE id &amp;gt; last_id ORDER BY id LIMIT 100000&lt;/code&gt;), on a runtime or job inside the network.&lt;/li&gt;
&lt;li&gt;3Profile the source before writing rules: distinct values of short fields, shapes of phones and emails, common domains. An AI assistant connected through MCP can run the profile and update the checks.&lt;/li&gt;
&lt;li&gt;4Check each row and route failures to the quarantine table with their reason. Score the rules on a part of the data they were not written from.&lt;/li&gt;
&lt;li&gt;5Mask the passing rows: a keyed hash of the normalised email, the email domain, the last 4 phone digits, and whatever else your policy requires.&lt;/li&gt;
&lt;li&gt;6Group the masked rows into batches of a few thousand and send each batch as one statement. One way in PostgreSQL is to pass the batch as a single JSON parameter and expand it with &lt;a href="https://www.postgresql.org/docs/current/functions-json.html" rel="noopener noreferrer"&gt;&lt;code&gt;jsonb_to_recordset&lt;/code&gt;&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO customers_masked (id, email_hash, email_domain, phone_last4, city, state)
SELECT r.id, r.email_hash, r.email_domain, r.phone_last4, r.city, r.state
FROM jsonb_to_recordset(CAST(:batch AS jsonb))
  AS r(id bigint, email_hash text, email_domain text, phone_last4 text, city text, state text)
ON CONFLICT (id) DO NOTHING;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After each run, compare counts on both sides: rows read, rows quarantined, rows in the warehouse. Once the first load is done, switch to incremental runs by &lt;code&gt;updated_at&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These checks catch broken values, not duplicates. The data also held 20,000 near-duplicate customers that passed every check and loaded as clean rows; an upcoming post covers finding and merging them.&lt;/p&gt;

&lt;p&gt;The takeaway: to load an on-prem database into a cloud warehouse, tune rows per insert first, check and mask inside your network, and write your rules from a profile of your own data, then test them on rows they have not seen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Nagle T, Redman T, Sammon D, &lt;a href="https://doi.org/10.1016/j.bushor.2020.01.006" rel="noopener noreferrer"&gt;Assessing data quality: A managerial call to action&lt;/a&gt;, Business Horizons 63(3), 2020, pp. 325-337&lt;/li&gt;
&lt;li&gt;FTC Office of Technology, &lt;a href="https://www.ftc.gov/policy/advocacy-research/tech-at-ftc/2024/07/no-hashing-still-doesnt-make-your-data-anonymous" rel="noopener noreferrer"&gt;No, hashing still doesn't make your data anonymous&lt;/a&gt;, July 24, 2024&lt;/li&gt;
&lt;li&gt;UK Information Commissioner's Office, &lt;a href="https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/data-sharing/anonymisation/pseudonymisation/" rel="noopener noreferrer"&gt;Pseudonymisation&lt;/a&gt; (guidance)&lt;/li&gt;
&lt;li&gt;PostgreSQL documentation, &lt;a href="https://www.postgresql.org/docs/current/populate.html" rel="noopener noreferrer"&gt;Populating a Database&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PostgreSQL documentation, &lt;a href="https://www.postgresql.org/docs/current/functions-json.html" rel="noopener noreferrer"&gt;JSON Functions and Operators&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Alexander Belanger, &lt;a href="https://hatchet.run/blog/fastest-postgres-inserts" rel="noopener noreferrer"&gt;The fastest Postgres inserts&lt;/a&gt;, Hatchet, May 15, 2025&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.neblex.com/integration-fabric/docs/guide/mcp" rel="noopener noreferrer"&gt;Connect an AI client (MCP)&lt;/a&gt;, Neblex Integration Fabric docs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last checked: November 2, 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do you load an on-prem database into a cloud warehouse?
&lt;/h3&gt;

&lt;p&gt;Read it in pages by key on a runtime or job inside your network, check and mask each page there, keep failed rows locally, and send the rest in batches of a few thousand rows per statement. After the first load, send only rows that changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  What batch size should you use for bulk inserts into Postgres over the internet?
&lt;/h3&gt;

&lt;p&gt;Large enough that round trips stop dominating. In our test, 5,000 rows per insert loaded 1 million rows in 8 min 18 s to 8 min 48 s across five runs; 10,000 was no faster; one row per insert ran at 2.6 rows per second.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you improve data quality rules?
&lt;/h3&gt;

&lt;p&gt;Profile the data, write rules for the patterns the profile shows, and test them on data they were not written from. In our test, Claude profiled half of a 1 million row table and rewrote the flow's checks through the Neblex MCP server; the new rules caught 93.7% of bad rows in the other half, up from 54.6%, with no false alarms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should you mask PII before or after loading it into a data warehouse?
&lt;/h3&gt;

&lt;p&gt;Before, when you can. Masking at the source means the raw values never travel or sit in the warehouse, its backups or its logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is a hashed email address still personal data?
&lt;/h3&gt;

&lt;p&gt;Usually yes. Regulators treat hashing as pseudonymisation, not anonymisation, and an unsalted hash of an email can be reversed by hashing a list of known addresses. Use a keyed hash (HMAC) with a secret kept on your side, or a token.&lt;/p&gt;

&lt;h3&gt;
  
  
  What should happen to rows that fail validation during a load?
&lt;/h3&gt;

&lt;p&gt;Keep them, with the reason, somewhere they can be fixed. In our test they went to a quarantine table in the same on-prem database, with their raw values, so nothing personal left the network and nothing was silently dropped.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Adan Potyondy leads go-to-market at Neblex. Originally published at &lt;a href="https://www.neblex.com/blog/load-onprem-database-to-cloud-warehouse" rel="noopener noreferrer"&gt;neblex.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>postgres</category>
      <category>database</category>
      <category>etl</category>
    </item>
    <item>
      <title>Bad data costs real money. Here is the HubSpot to Supabase workflow that fixed it.</title>
      <dc:creator>Adan Potyondy</dc:creator>
      <pubDate>Thu, 05 Nov 2026 19:36:21 +0000</pubDate>
      <link>https://dev.to/adan_potyondy/bad-data-costs-real-money-here-is-the-hubspot-to-supabase-workflow-that-fixed-it-4ca5</link>
      <guid>https://dev.to/adan_potyondy/bad-data-costs-real-money-here-is-the-hubspot-to-supabase-workflow-that-fixed-it-4ca5</guid>
      <description>&lt;p&gt;In September I worked with a customer on a data quality problem and recorded a short demo of the general case. Contacts come out of HubSpot, get cleaned and deduplicated, and only the rows that pass land in Supabase.&lt;/p&gt;

&lt;p&gt;The interactive demo is on the original post. Here is a separate 6-minute live build of the same flow; it matches duplicates with fuzzy (Jaro-Winkler) matching rather than the exact email match described below.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/gqG4XZaHC-g" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem the customer brought us
&lt;/h2&gt;

&lt;p&gt;Their sales team lives in HubSpot. Their product and reporting live on a Supabase database. A nightly job copied contacts from one to the other, and it copied everything: the same person three times under three spellings of their email, trailing spaces in names, companies typed in four different ways, and records with no usable email at all.&lt;/p&gt;

&lt;p&gt;None of that looks dramatic in a CRM. It looks dramatic in the results. Duplicate contacts get counted twice in pipeline reports, two reps reach out to the same person in the same week, and the product team builds features for a customer base that is smaller and cleaner than the one the numbers describe. Bad data does not stay in the database. It shows up in decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the demo shows
&lt;/h2&gt;

&lt;p&gt;The recording is a walk through the general case, not the customer's data. It uses the Spanish version of the Neblex workspace, which is the language the customer works in; every screen in Neblex Integration Fabric is available in English, German, and Spanish.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pull the contacts from HubSpot.&lt;/strong&gt; The &lt;a href="https://www.neblex.com/integration-fabric/connectors/hubspot" rel="noopener noreferrer"&gt;HubSpot connector&lt;/a&gt; reads contacts with the properties the customer cares about. The flow can run on a schedule or start when a contact is created or changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean the rows.&lt;/strong&gt; Emails are lower-cased and trimmed, names lose their stray spaces, and company names are normalized so "Acme", "ACME Inc" and "acme, inc." stop being three companies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove duplicates.&lt;/strong&gt; The Remove duplicates step matches records on the normalized email and drops the repeats. It can also keep every record and tag each one as unique or duplicate, with a pointer to the match, so a review branch can look at the borderline cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check quality before writing.&lt;/strong&gt; The Assert data quality step checks the batch against the rules the customer chose, such as required fields and an email address on every row. If a rule fails, the run stops with the failing records and the rule named, so nothing questionable reaches the database and someone can fix the source in HubSpot instead of quietly inheriting the problem downstream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insert into Supabase.&lt;/strong&gt; Only the rows that passed are written to the Supabase table, with the run recorded end to end.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last point is the one the customer cared about most. Every run shows each step, what stopped the run and why, and how many records were written; with run data kept for replay, the records themselves are there too. If a number looks wrong in a report, the answer is in the run history, not in a guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we clean at the boundary
&lt;/h2&gt;

&lt;p&gt;It is tempting to fix data quality in the database with a cleanup script that runs once a month. It never stays fixed, because the source keeps producing the same problems. Cleaning at the boundary, in the flow that moves the data, means every record is checked every time and the source system gets the feedback it needs.&lt;/p&gt;

&lt;p&gt;It also means the cleanup is not a separate tool. Bulk data processing and data sync and change data capture run on the same platform, with the same connectors and the same audit trail as everything else. When the customer later wants to push a cleaned company list back into HubSpot, that is another flow, not another product.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for your team
&lt;/h2&gt;

&lt;p&gt;If your reporting and your CRM disagree, the fix is rarely a bigger dashboard. It is usually a clean, checked path from one system to the other, with the failures visible instead of silently loaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does the cleanup change the data in HubSpot?
&lt;/h3&gt;

&lt;p&gt;No. The flow reads from HubSpot and writes to Supabase. When a quality rule fails, the run stops and names the records and the rule, so the team can correct them in HubSpot, and the next run picks up the corrected contacts. Writing corrections back to HubSpot would be a separate flow you choose to turn on.&lt;/p&gt;

&lt;h3&gt;
  
  
  How are duplicates decided?
&lt;/h3&gt;

&lt;p&gt;By the field you choose, or the whole record. In the demo it is the normalized email address. Matching can be exact or fuzzy with a similarity threshold, and instead of dropping duplicates the step can tag them for a review branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can this run as contacts change instead of on a schedule?
&lt;/h3&gt;

&lt;p&gt;Yes. The HubSpot connector has 46 polled triggers, checked as often as every minute, so a flow can start when a contact is created or changed and check the new record on its way through.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it work with a plain PostgreSQL database instead of Supabase?
&lt;/h3&gt;

&lt;p&gt;Yes. Supabase is PostgreSQL underneath, and the same flow writes to any PostgreSQL, MySQL, SQL Server, or Snowflake target through their connectors.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Maria Soria works in customer success at Neblex. Originally published at &lt;a href="https://www.neblex.com/blog/hubspot-to-supabase-clean-contacts" rel="noopener noreferrer"&gt;neblex.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hubspot</category>
      <category>supabase</category>
      <category>dataengineering</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Headless iPaaS: what an AI assistant should and shouldn't do in your integration platform</title>
      <dc:creator>Adan Potyondy</dc:creator>
      <pubDate>Thu, 05 Nov 2026 18:05:46 +0000</pubDate>
      <link>https://dev.to/adan_potyondy/headless-ipaas-what-an-ai-assistant-should-and-shouldnt-do-in-your-integration-platform-4p23</link>
      <guid>https://dev.to/adan_potyondy/headless-ipaas-what-an-ai-assistant-should-and-shouldnt-do-in-your-integration-platform-4p23</guid>
      <description>&lt;p&gt;A headless iPaaS is an integration platform you can fully use without its visual interface: through an API, a command line, or an AI assistant connected over MCP. Done right, the assistant builds, validates, tests and publishes integrations, and a person keeps the secrets, the production publish and the access grants.&lt;/p&gt;

&lt;p&gt;The term is new, and vendors use it for very different things. Some let an assistant run a workflow somebody else built. Others let it build the whole integration and push it to production.&lt;/p&gt;

&lt;p&gt;This post compares the designs by what an assistant can actually do at each stage of an integration's life, and what should still need a person. At the end you'll find ten questions to put to any vendor.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a headless iPaaS?
&lt;/h2&gt;

&lt;p&gt;An integration platform whose capabilities don't depend on its screens.&lt;/p&gt;

&lt;p&gt;The term comes from content management. A &lt;a href="https://en.wikipedia.org/wiki/Headless_content_management_system" rel="noopener noreferrer"&gt;headless CMS&lt;/a&gt; is a back end that stores content and serves it through an API, with no display layer of its own. Applied to an iPaaS (integration platform as a service), it means anything you can do in the visual builder, you can also do through an API.&lt;/p&gt;

&lt;p&gt;What changed in the last year is who calls that API. The &lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; (MCP), an open standard for connecting AI applications to outside systems, is now supported by Claude, ChatGPT, VS Code, Cursor and others. A platform with an MCP server can be driven from the same assistant your developers already use to write code.&lt;/p&gt;

&lt;p&gt;How useful that is depends on what the server exposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways a platform can work with an AI assistant
&lt;/h2&gt;

&lt;p&gt;You'll run into three designs, and they differ in how much of the job the assistant can finish.&lt;/p&gt;

&lt;p&gt;Let's use one job as the running example, with the same CRM as our HubSpot contacts post. In Claude Code you type: "When a HubSpot deal closes, create the customer in our billing database and post the deal to the #sales Slack channel."&lt;/p&gt;

&lt;h3&gt;
  
  
  Assistant inside the visual builder
&lt;/h3&gt;

&lt;p&gt;The platform ships its own assistant on the canvas. It turns your sentence into draft steps: a HubSpot trigger, a database insert, a Slack message. Then a person checks the mapping, saves, tests and publishes in the browser.&lt;/p&gt;

&lt;p&gt;That works well for people who live in the builder. But you can't use it from the coding assistant you already have open, and it stops at the draft.&lt;/p&gt;

&lt;h3&gt;
  
  
  Action-only MCP server
&lt;/h3&gt;

&lt;p&gt;The platform exposes actions, or workflows that already exist, as tools. Your assistant can post to Slack or kick off a workflow someone already built, but it can't create or change one.&lt;/p&gt;

&lt;p&gt;In our example, the assistant could send a test Slack message. Somebody still has to build the integration itself in the builder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full-lifecycle headless platform
&lt;/h3&gt;

&lt;p&gt;The platform exposes the same capabilities its visual builder uses. Your assistant lists the connectors and the connections you already have, reads the HubSpot and database operations, writes the workflow, validates it, tests it on a sample deal, reads the result, fixes the mapping and asks to publish.&lt;/p&gt;

&lt;p&gt;A person still steps in at a few deliberate points, covered below. Everything else happens in one conversation, and the result opens in the visual builder like anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an assistant needs at each stage of the integration lifecycle
&lt;/h2&gt;

&lt;p&gt;Building is one stage of many. An integration gets discovered, built, tested, published, run, debugged and eventually retired. An assistant that only covers the first two leaves the rest to a person.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;What the assistant needs&lt;/th&gt;
&lt;th&gt;In-builder assistant&lt;/th&gt;
&lt;th&gt;Action-only MCP&lt;/th&gt;
&lt;th&gt;Full-lifecycle headless&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Discover&lt;/td&gt;
&lt;td&gt;List connectors, their operations and schemas, and existing connections&lt;/td&gt;
&lt;td&gt;Within the canvas&lt;/td&gt;
&lt;td&gt;Only the exposed actions&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;Create and edit the workflow definition&lt;/td&gt;
&lt;td&gt;Draft only&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validate&lt;/td&gt;
&lt;td&gt;Check the definition before saving&lt;/td&gt;
&lt;td&gt;When a person saves&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test&lt;/td&gt;
&lt;td&gt;Run on sample data without publishing, and read step results&lt;/td&gt;
&lt;td&gt;A person runs it&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Publish&lt;/td&gt;
&lt;td&gt;Make it live, with approval where required&lt;/td&gt;
&lt;td&gt;A person&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes, with approval for protected environments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run and observe&lt;/td&gt;
&lt;td&gt;Start runs, read run history and step events&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;Trigger only&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debug&lt;/td&gt;
&lt;td&gt;Read the failed step's input and error, fix, re-test&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retire&lt;/td&gt;
&lt;td&gt;Unpublish, delete to a trash, restore&lt;/td&gt;
&lt;td&gt;A person&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In our example, the problems show up during testing. A deal without a company attached, or a currency the billing table won't accept, shows up in the test run's step events. An assistant that can read those events fixes the mapping and tests again in the same conversation.&lt;/p&gt;

&lt;p&gt;One that can't hands you an error message to chase. And after a failed production run, those same events tell you which step to re-run from. We covered why that matters in Replay from the failed step, not from record one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails: what should still need a person
&lt;/h2&gt;

&lt;p&gt;Handing an assistant write access to the system that moves your customer data is not a small decision. Keep the high-impact steps with a person.&lt;/p&gt;

&lt;p&gt;The standards agree. The &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/server/tools" rel="noopener noreferrer"&gt;MCP specification&lt;/a&gt; says there "SHOULD always be a human in the loop with the ability to deny tool invocations," and that servers must implement proper access controls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://genai.owasp.org/llmrisk/llm062025-excessive-agency/" rel="noopener noreferrer"&gt;OWASP lists "excessive agency"&lt;/a&gt; among the top risks for LLM applications: too much functionality, too many permissions or too much autonomy. Its fix is least privilege plus human approval for high-impact actions.&lt;/p&gt;

&lt;p&gt;For an integration platform, that comes down to five things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secrets stay with a person.&lt;/strong&gt; The assistant can create a connection, but a person enters the API key or completes the OAuth sign-in in the browser. The secret never passes through the conversation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Building needs a strong sign-in.&lt;/strong&gt; Authoring and publishing should require a person's sign-in with a second factor, not a long-lived API key sitting in a config file. A key can be allowed to run workflows. It shouldn't be able to change them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access is scoped.&lt;/strong&gt; Whoever grants access picks the projects and environments the assistant can reach, and what it may do there: operate, author or publish. Revoking it takes effect immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production publishes wait for approval.&lt;/strong&gt; The assistant can build and test freely in a development environment. Publishing to production waits until a person approves it in the browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything is reversible and logged.&lt;/strong&gt; An edit that collides with someone else's is rejected instead of overwriting it, deletes go to a trash you can restore from, and every run leaves events you can read, with secrets scrubbed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcqr7a8lsttk76oap1ca8.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcqr7a8lsttk76oap1ca8.webp" alt="The AI assistant runs eight stages from discover to retire; a person grants access, enters credentials and approves the production publish" width="800" height="751"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The assistant does the work. A person keeps access, secrets and the production publish.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;None of this stops the assistant from doing the work. In our example, a person steps in exactly three times: to grant access, to enter the HubSpot and database credentials, and to approve the production publish.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes when evaluating AI access
&lt;/h2&gt;

&lt;p&gt;Most of them come from treating "it has an MCP server" as the answer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Only checking the build.&lt;/strong&gt; A demo that turns a sentence into a workflow is the easy part. Ask to see the assistant test it, read a failed step and fix it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Letting the assistant hold the keys.&lt;/strong&gt; If connecting a system means pasting its API key into the chat or a config file, that key now lives in the assistant's context and logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using one environment.&lt;/strong&gt; If the assistant builds where production runs, every test writes real records. Keep development, test and production separate, and gate the last one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Granting everything.&lt;/strong&gt; A grant that covers every project and every permission is exactly the "excessive permissions" OWASP warns about. Start with one project and the author scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assuming the visual builder goes away.&lt;/strong&gt; It shouldn't. Somebody will review the assistant's work, and most people do that in the builder.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Questions to ask before you let an AI assistant build in your integration platform
&lt;/h2&gt;

&lt;p&gt;They work with any vendor, and the answers tell you which design you're really getting.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Can the assistant create and edit integrations, or only run existing ones?&lt;/li&gt;
&lt;li&gt;Does it have the same capabilities as the visual builder, or a subset? Which parts are missing?&lt;/li&gt;
&lt;li&gt;Can it test an integration without publishing it, and read each step's input, output and error?&lt;/li&gt;
&lt;li&gt;Can it read run history and debug a failed run?&lt;/li&gt;
&lt;li&gt;How are credentials entered? Does a secret ever pass through the assistant?&lt;/li&gt;
&lt;li&gt;What sign-in does building and publishing require? What can a plain API key do?&lt;/li&gt;
&lt;li&gt;How is access scoped: by project, by environment, by action?&lt;/li&gt;
&lt;li&gt;What needs a person's approval, and where does that approval happen?&lt;/li&gt;
&lt;li&gt;What happens when the assistant and a colleague edit the same integration at the same time?&lt;/li&gt;
&lt;li&gt;Can deletes be undone, and is every change and run logged?&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What this means for IT and security
&lt;/h3&gt;

&lt;p&gt;Treat the assistant as its own user with its own grant, not as an extension of the developer's account.&lt;/p&gt;

&lt;p&gt;Give it one project in a development environment, the author scope, and no publish rights to production. Then watch what it asks a person for. A well-built platform sends you a browser link for every credential and every production publish, and for nothing else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Neblex fits
&lt;/h2&gt;

&lt;p&gt;Neblex Integration Fabric is a full-lifecycle headless platform. The visual builder and the &lt;a href="https://www.neblex.com/integration-fabric/docs/guide/mcp" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt; work with the same building blocks, so an assistant connected to Neblex can do what a person does in the builder, under the same rules.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any MCP client can connect, including Claude Code, Codex, Cursor, VS Code and Windsurf.&lt;/li&gt;
&lt;li&gt;The assistant works across flows, forms, apps, data tables, transform modules, data models, AI agents and connections, not just workflows.&lt;/li&gt;
&lt;li&gt;It discovers connectors and their operations, validates before saving, tests before publishing, runs flows, reads secret-scrubbed run events and checks flow health.&lt;/li&gt;
&lt;li&gt;Secrets never go through MCP. Creating a connection returns a link where a person enters them.&lt;/li&gt;
&lt;li&gt;Building and publishing require a user-approved sign-in with a second factor. A static API key can inspect and run the flows it's granted, and can never create or publish.&lt;/li&gt;
&lt;li&gt;On the consent screen, the person picks the scopes (operate, author, publish), the projects or flows, and the environment. Revoking access cuts it off immediately.&lt;/li&gt;
&lt;li&gt;Publishing into a protected environment waits for a person's approval in the browser.&lt;/li&gt;
&lt;li&gt;An edit that collides with someone else's is rejected, so the assistant re-reads and re-applies its change instead of overwriting. Deletes go to a trash, and a restore re-checks dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two things stay in the browser on purpose: entering secrets, and promoting work between environments. That's how we build and test our own flows: from Claude Code, in a development environment, then promoted to production in the GUI.&lt;/p&gt;

&lt;p&gt;So before your team connects an AI assistant to its integration platform, find out two things. Can it test and debug as well as build? And which steps still go to a person? Those answers tell you whether a headless iPaaS will speed up your integration work or just move the risk somewhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Headless_content_management_system" rel="noopener noreferrer"&gt;Headless content management system&lt;/a&gt;, Wikipedia&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro" rel="noopener noreferrer"&gt;What is the Model Context Protocol (MCP)?&lt;/a&gt;, modelcontextprotocol.io&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/server/tools" rel="noopener noreferrer"&gt;MCP specification: Tools (2025-06-18)&lt;/a&gt;, modelcontextprotocol.io&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://genai.owasp.org/llmrisk/llm062025-excessive-agency/" rel="noopener noreferrer"&gt;LLM06:2025 Excessive Agency&lt;/a&gt;, OWASP Top 10 for LLM Applications, OWASP Foundation, 2025&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.neblex.com/integration-fabric/docs/guide/mcp" rel="noopener noreferrer"&gt;MCP guide&lt;/a&gt;, Neblex Integration Fabric docs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last checked: October 15, 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a headless iPaaS?
&lt;/h3&gt;

&lt;p&gt;An integration platform you can fully use without its visual interface, through an API, a command line, or an AI assistant connected over MCP. The visual builder usually stays; it just stops being the only way in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can an AI assistant like Claude Code build integrations in an iPaaS?
&lt;/h3&gt;

&lt;p&gt;Yes, if the platform exposes building and not just running. Check that the assistant can discover connectors, create and validate a workflow, test it and read the run results. Some platforms only let an assistant trigger existing workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it safe to give an AI assistant write access to an integration platform?
&lt;/h3&gt;

&lt;p&gt;It can be, if the platform limits what the assistant can reach and keeps the high-impact steps with a person. Look for access scoped per project, a strong sign-in for building, secrets entered by a person, approval before publishing to production, and a full run log.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between an iPaaS MCP server and a headless iPaaS?
&lt;/h3&gt;

&lt;p&gt;The MCP server is the connection. What matters is what it exposes. Some only let an assistant run existing actions or workflows. A headless iPaaS exposes the same capabilities as the visual interface, so an assistant can build, test, publish and debug.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a headless iPaaS replace the visual builder?
&lt;/h3&gt;

&lt;p&gt;No. An assistant can build an integration that you then open, review and change in the visual builder. Headless adds a way in. It doesn't take one away.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Daniel Wiegand is the founder of Neblex. Originally published at &lt;a href="https://www.neblex.com/blog/headless-ipaas" rel="noopener noreferrer"&gt;neblex.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>api</category>
      <category>automation</category>
    </item>
    <item>
      <title>On-prem runtime security: what leaves your network</title>
      <dc:creator>Adan Potyondy</dc:creator>
      <pubDate>Mon, 02 Nov 2026 19:57:17 +0000</pubDate>
      <link>https://dev.to/adan_potyondy/on-prem-runtime-security-what-leaves-your-network-38a1</link>
      <guid>https://dev.to/adan_potyondy/on-prem-runtime-security-what-leaves-your-network-38a1</guid>
      <description>&lt;p&gt;Does data leave your network with an on-prem integration runtime? It depends on the design. With the Neblex On-Prem Worker, our local integration runtime (often called an on-prem integration agent), the Neblex cloud gets run status, diagnostics, record counts and integrity receipts over one outbound encrypted connection on port 443. Records go only to the systems each flow is built to reach. AI agents work the same way: an agent, its reasoning model and its embedding model can all run on premises, just as flows do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two questions a security review asks
&lt;/h2&gt;

&lt;p&gt;Before an integration runtime goes inside the network, a security review asks what it will send out and what it needs from the outside. Here is how the On-Prem Worker answers both: what it sends, what it listens on, and what it does when the internet drops.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpdo4n99n1am0cft6gy02.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpdo4n99n1am0cft6gy02.webp" alt="The Neblex cloud control plane above three networks; each On-Prem Worker sends it only run reports, and records reach SaaS apps directly" width="800" height="626"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The control plane in the Neblex cloud coordinates; each On-Prem Worker works inside its own network and sends only run reports to the cloud. Records reach the systems a flow uses, including SaaS apps, directly from the On-Prem Worker, never through the Neblex cloud.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;How much memory, CPU and disk it uses is covered in &lt;a href="https://dev.to/adan_potyondy/what-does-an-on-premise-integration-runtime-cost-your-server-we-measured-ours-5nd"&gt;What does an on-prem integration runtime cost your server? We measured ours&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What goes to the Neblex cloud?
&lt;/h2&gt;

&lt;p&gt;Run status, diagnostics, record counts and integrity receipts. Record contents stay with the On-Prem Worker, unless a flow shows something to someone through the Neblex cloud: a webhook answer, a form page or an App result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsfni3r971npc61jvcpio.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsfni3r971npc61jvcpio.webp" alt="Inside the On-Prem Worker: scheduler, run reports, flow engine, run journal and a 127.0.0.1 console; one outbound link carries reports out" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The On-Prem Worker runs flows next to your systems and keeps records on your disk; one outbound connection carries run reports to Neblex.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The On-Prem Worker opens one outbound, encrypted WebSocket (wss) connection to the Neblex cloud on port 443, over TLS 1.3 with a post-quantum key exchange. It opens no port to the internet.&lt;/li&gt;
&lt;li&gt;It proves its identity inside that connection with two keys created on its own machine, which never leave it: the key for the certificate Neblex issues it, and a post-quantum key.&lt;/li&gt;
&lt;li&gt;Its only listener on a standalone install is a local console bound to 127.0.0.1, which other machines can't reach. Members of an On-Prem Worker cluster also accept a link from the other members of the same cluster.&lt;/li&gt;
&lt;li&gt;Every run on it is reported in local custody: the cloud gets a compact run report, and the records stay with the On-Prem Worker.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What goes to your own systems?
&lt;/h2&gt;

&lt;p&gt;Whatever each flow is built to send, directly. The On-Prem Worker connects to every system a flow uses on that system's own port: HTTPS on port 443 for most SaaS APIs, 5432 for a hosted PostgreSQL database, and the usual ports for local databases, queues and file servers. A flow that writes contacts and deals to a CRM calls the CRM's API; a flow that loads rows into a cloud PostgreSQL database connects to it on port 5432.&lt;/p&gt;

&lt;p&gt;Those records leave your network because you chose that destination, and they travel from the On-Prem Worker straight to it, never through the Neblex cloud. Plan outbound firewall rules around your flows' destinations as well as the one connection to the Neblex cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where are records held during a run?
&lt;/h2&gt;

&lt;p&gt;On your machine. Records do pass through the On-Prem Worker, and a large batch is buffered in its local run journal on your disk until the run is settled. They don't go to the Neblex cloud.&lt;/p&gt;

&lt;p&gt;The On-Prem Worker purges the journal once the run is settled, so batch data doesn't accumulate on disk. Neblex doesn't store your records; anything that must persist lives in your own database.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when the internet connection drops?
&lt;/h2&gt;

&lt;p&gt;The run keeps going, and only the status reports wait. The flow executes on the On-Prem Worker, not in the Neblex cloud, so the connection to Neblex isn't in the data path.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The On-Prem Worker keeps reading from and writing to every system it can still reach.&lt;/li&gt;
&lt;li&gt;It keeps its run reports in its local run journal and delivers them once it reconnects.&lt;/li&gt;
&lt;li&gt;The run's final status reaches the Neblex cloud with the rest of those reports.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A step that needs the Neblex cloud itself, such as a form a person fills in, waits for the connection and then continues. No step fails because the Neblex cloud is out of reach.&lt;/p&gt;

&lt;p&gt;Recovering from a failed step, rather than an outage, is covered in Replay from the failed step, not from record one.&lt;/p&gt;

&lt;p&gt;The On-Prem Worker also keeps a local, encrypted copy of the scheduled, polling and streaming flows placed on it, so it starts them itself while the cloud is out of reach. Flows started by a webhook, a form or a person in the app come in through the Neblex cloud, so they start once it's reachable again.&lt;/p&gt;

&lt;p&gt;Other local runtimes also keep running when their platform is out of reach. Ask any vendor to show you what theirs does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can AI agents and their models run on premises?
&lt;/h2&gt;

&lt;p&gt;Yes, the same way flows do. An AI agent runs on the On-Prem Worker as a step in a flow, and both models it depends on can run inside your network too: the large language model (LLM) that does the reasoning, such as an open-weight model behind an OpenAI-compatible or Ollama endpoint, and the embedding model that turns your documents and records into vectors for the agent's knowledge base. Those vectors are stored in your own PostgreSQL database with pgvector, and the agent's long-term memory is stored only in your own database.&lt;/p&gt;

&lt;p&gt;Set up that way, an AI step sends no more out of your network than any other step: prompts, model responses, records, documents and embeddings stay on premises, and the Neblex cloud gets the same run status it gets for any other flow.&lt;/p&gt;

&lt;p&gt;If an agent uses a hosted provider for either model instead, whatever is sent to that model goes to that provider. Pick one whose data residency fits your rules, or keep the model local.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to ask any vendor
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Does the integration run on your machine, or are records relayed to the vendor's cloud to be processed?&lt;/li&gt;
&lt;li&gt;Which ports does the runtime open, inbound and outbound?&lt;/li&gt;
&lt;li&gt;Which destinations and ports will your flows need opened outbound, and does any of that traffic pass through the vendor?&lt;/li&gt;
&lt;li&gt;What exactly is in its run reports?&lt;/li&gt;
&lt;li&gt;What happens to a running job when the internet drops, and to a scheduled one?&lt;/li&gt;
&lt;li&gt;Where is an in-flight batch held, and when is it deleted?&lt;/li&gt;
&lt;li&gt;Can AI agents, and both the reasoning and embedding models they use, run inside your network the same way flows do?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask to see the answers in the runtime's logs and network connections, not only in the documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.neblex.com/integration-fabric/docs/guide/agents" rel="noopener noreferrer"&gt;On-Prem Workers&lt;/a&gt;, Neblex Integration Fabric docs&lt;/li&gt;
&lt;li&gt;Offline controls, Neblex Integration Fabric docs&lt;/li&gt;
&lt;li&gt;AI agents guide, Neblex Integration Fabric docs&lt;/li&gt;
&lt;li&gt;On-Prem Worker architecture and run reporting, as designed and documented by Neblex engineering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last checked: October 30, 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does an on-prem integration runtime need inbound firewall ports?
&lt;/h3&gt;

&lt;p&gt;Not all of them do. The On-Prem Worker opens one outbound, encrypted WebSocket connection to the Neblex cloud on port 443 and opens no port to the internet. On a standalone install, its only listener is a local console bound to 127.0.0.1, which other machines can't reach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does my data pass through the vendor's cloud?
&lt;/h3&gt;

&lt;p&gt;It depends on the design. Some runtimes relay records to the vendor's cloud for processing; others run the integration locally. The On-Prem Worker runs the flow on your machine and sends Neblex run status, diagnostics, record counts and integrity receipts, not record contents, unless a flow shows something to someone through the Neblex cloud, such as a form page or an App result.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens to a running job if the runtime loses its internet connection?
&lt;/h3&gt;

&lt;p&gt;With the On-Prem Worker, it keeps running. The flow executes on the On-Prem Worker, which keeps its run reports in its local run journal and delivers them after reconnecting. Ask any vendor what their runtime does in this case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can AI agents run on an on-prem runtime?
&lt;/h3&gt;

&lt;p&gt;With Neblex, yes, just as flows do. An AI agent runs on the On-Prem Worker, and both its reasoning model (LLM) and its embedding model can run inside your network, with the agent's knowledge base in your own PostgreSQL with pgvector and its memory in your own database. Set up that way, all processing stays on premises and no AI provider outside your network sees your data.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Adan Potyondy leads go-to-market at Neblex. Originally published at &lt;a href="https://www.neblex.com/blog/on-prem-runtime-security" rel="noopener noreferrer"&gt;neblex.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>networking</category>
      <category>architecture</category>
    </item>
    <item>
      <title>What does an on-premise integration runtime cost your server? We measured ours</title>
      <dc:creator>Adan Potyondy</dc:creator>
      <pubDate>Thu, 22 Oct 2026 17:50:12 +0000</pubDate>
      <link>https://dev.to/adan_potyondy/what-does-an-on-premise-integration-runtime-cost-your-server-we-measured-ours-5nd</link>
      <guid>https://dev.to/adan_potyondy/what-does-an-on-premise-integration-runtime-cost-your-server-we-measured-ours-5nd</guid>
      <description>&lt;p&gt;We measured on-premise resource usage for the Neblex Worker, our local integration runtime, across 15 everyday flows and three large workloads. Idle, it used about 25 MB of memory. Fourteen of the 15 flows averaged 29 to 94 MB, and a 1 million row load into a cloud database peaked at about 450 MB. CPU stayed low except during heavy matching.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question IT and security teams ask first
&lt;/h2&gt;

&lt;p&gt;A request we hear often from prospects is to move data from a database inside their network to a cloud warehouse, on a schedule, without opening that database to the internet. That means installing software inside the network: an agent or a runtime. Before anyone discusses the warehouse, IT and security teams ask what that software will cost their servers and what it can reach.&lt;/p&gt;

&lt;p&gt;The usual answer is a page of minimum requirements. We wanted to answer with measured numbers, so we ran the Neblex Worker through a week of tests, from small event flows to million-row loads, and recorded what it used. This post gives the numbers, the method and the limits.&lt;/p&gt;

&lt;p&gt;This post answers the first question for a local runtime. What the runtime sends out of your network, and what happens when the internet drops, are covered in a separate post.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we measured on-prem integration agent resource usage
&lt;/h2&gt;

&lt;p&gt;All tests used synthetic or test data, generated by us, on our own hardware, between October 2 and 8, 2026.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deployments: we ran the runtime two ways, as a background service and in a container capped at 4 CPU cores and 4 GB of RAM. The test machine also hosted the PostgreSQL, Valkey (an open-source, Redis-compatible cache) and Kafka instances it read from and wrote to, so the runtime competed for memory more than it would on a dedicated server.&lt;/li&gt;
&lt;li&gt;The workloads: 15 flows deployed to the runtime (events, files, CRM syncs and database loads); a 1 million row load from a local PostgreSQL database into a cloud database, with data-quality checks and masking; fuzzy duplicate matching over about 970,000 records; and, to isolate the effect of batch size, a plain copy from PostgreSQL to Valkey at 1,000 to 5 million rows.&lt;/li&gt;
&lt;li&gt;The tools: standard process tools sampled the runtime's memory and CPU every half second, and the container's own memory statistics separated process memory from file cache. Start and finish times came from the runtime's log, and the 15 flows' averages came from the Neblex console.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your records, connectors and hardware will shape your own numbers; the patterns are what carry over.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the runtime use when it's idle?
&lt;/h2&gt;

&lt;p&gt;Very little. The runtime is a single executable, about 74 MB on macOS and 92 MB on Linux x64.&lt;/p&gt;

&lt;p&gt;It needs no separate language runtime, such as Java, .NET or Node.js, and it starts no child processes. Builds are published for Linux x64, macOS on Apple Silicon and Windows x64.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure (idle)&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Install&lt;/td&gt;
&lt;td&gt;One executable, about 74 MB (macOS) or 92 MB (Linux x64)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime's own memory&lt;/td&gt;
&lt;td&gt;22 to 27 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resident memory (RSS)&lt;/td&gt;
&lt;td&gt;27 to 35 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;Under 1% of one core&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Listening ports&lt;/td&gt;
&lt;td&gt;One, on loopback only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a security reviewer, the small number matters less than what it implies: there is no separate runtime to patch and no helper processes to account for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the runtime use, by kind of flow?
&lt;/h2&gt;

&lt;p&gt;It depends on what the flow does. Memory follows how much the flow holds at once, CPU follows how much it computes per record, and run time follows the destination.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kind of flow&lt;/th&gt;
&lt;th&gt;What we ran&lt;/th&gt;
&lt;th&gt;Runtime memory&lt;/th&gt;
&lt;th&gt;Runtime CPU (one core = 100%)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Idle&lt;/td&gt;
&lt;td&gt;Connected, no runs&lt;/td&gt;
&lt;td&gt;22 to 27 MB&lt;/td&gt;
&lt;td&gt;Under 1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everyday flows&lt;/td&gt;
&lt;td&gt;15 flows: MQTT, Kafka and AMQP events, CRM syncs, file transfers, database loads&lt;/td&gt;
&lt;td&gt;29 to 94 MB average for 14 flows; 122 MB for one bulk load&lt;/td&gt;
&lt;td&gt;0 to 31% average&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load to a cloud database&lt;/td&gt;
&lt;td&gt;1 million rows, local PostgreSQL to a cloud PostgreSQL database, with checks and masking&lt;/td&gt;
&lt;td&gt;Peak 320 to 445 MB; median 53 to 118 MB&lt;/td&gt;
&lt;td&gt;12 to 26% average&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local bulk copy&lt;/td&gt;
&lt;td&gt;1,000 to 1 million rows, PostgreSQL to Valkey&lt;/td&gt;
&lt;td&gt;41 MB to about 250 MB&lt;/td&gt;
&lt;td&gt;About 22% average&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heavy computation&lt;/td&gt;
&lt;td&gt;Fuzzy duplicate matching over about 970,000 records&lt;/td&gt;
&lt;td&gt;Peak 534 to 947 MB (resident)&lt;/td&gt;
&lt;td&gt;93 to 102% average&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One very large batch&lt;/td&gt;
&lt;td&gt;5 million rows&lt;/td&gt;
&lt;td&gt;About 420 MB, plus 1.7 GB of journal on disk&lt;/td&gt;
&lt;td&gt;22 to 24% average; brief peaks near one core&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sections below take each row in turn.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do everyday flows use?
&lt;/h2&gt;

&lt;p&gt;Most use about as much memory as the idle runtime. We deployed 15 different integrations to one Neblex Worker, covering events, file transfers, CRM syncs, cache updates, database loads and an AI step, which gives a broad sample of how the runtime behaves. The Neblex console reports CPU and memory for each flow, averaged across its runs, on synthetic or test data. Run times come from the runtime's log, from start to finish of each successful run.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flow (what it does)&lt;/th&gt;
&lt;th&gt;Avg CPU (one core)&lt;/th&gt;
&lt;th&gt;Avg memory&lt;/th&gt;
&lt;th&gt;Avg run time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Local Postgres customers to Supabase (bulk load)&lt;/td&gt;
&lt;td&gt;19.1%&lt;/td&gt;
&lt;td&gt;122 MB&lt;/td&gt;
&lt;td&gt;Varies by batch size*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AMQP order events to CRM deals, with dead-letter queue&lt;/td&gt;
&lt;td&gt;4.3%&lt;/td&gt;
&lt;td&gt;94 MB&lt;/td&gt;
&lt;td&gt;0.4 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Postgres customers: AI industry tagging with an LLM&lt;/td&gt;
&lt;td&gt;1.7%&lt;/td&gt;
&lt;td&gt;41 MB&lt;/td&gt;
&lt;td&gt;19.6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local Postgres customers to Valkey cache&lt;/td&gt;
&lt;td&gt;30.9%&lt;/td&gt;
&lt;td&gt;40 MB&lt;/td&gt;
&lt;td&gt;8.8 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E-commerce new order to CSV file on FTP (ERP handoff)&lt;/td&gt;
&lt;td&gt;2.1%&lt;/td&gt;
&lt;td&gt;39 MB&lt;/td&gt;
&lt;td&gt;0.7 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Valkey usage counters to cloud Postgres daily usage, with quarantine&lt;/td&gt;
&lt;td&gt;26.8%&lt;/td&gt;
&lt;td&gt;38 MB&lt;/td&gt;
&lt;td&gt;3.0 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MQTT machine alarm to work-management task, with quarantine&lt;/td&gt;
&lt;td&gt;5.5%&lt;/td&gt;
&lt;td&gt;37 MB&lt;/td&gt;
&lt;td&gt;1.2 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MQTT plant telemetry to Postgres history, with quarantine&lt;/td&gt;
&lt;td&gt;6.1%&lt;/td&gt;
&lt;td&gt;37 MB&lt;/td&gt;
&lt;td&gt;0.9 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kafka signup events to CRM contacts, with dead-letter topic&lt;/td&gt;
&lt;td&gt;4.5%&lt;/td&gt;
&lt;td&gt;36 MB&lt;/td&gt;
&lt;td&gt;1.0 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MQTT machine alarm to service-desk maintenance request, with quarantine&lt;/td&gt;
&lt;td&gt;4.7%&lt;/td&gt;
&lt;td&gt;35 MB&lt;/td&gt;
&lt;td&gt;1.4 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRM company changes to Valkey account cache&lt;/td&gt;
&lt;td&gt;4.3%&lt;/td&gt;
&lt;td&gt;32 MB&lt;/td&gt;
&lt;td&gt;0.5 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Postgres customer data-quality check to work-management task&lt;/td&gt;
&lt;td&gt;6.1%&lt;/td&gt;
&lt;td&gt;31 MB&lt;/td&gt;
&lt;td&gt;1.4 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Postgres new customers to CRM contacts sync&lt;/td&gt;
&lt;td&gt;1%&lt;/td&gt;
&lt;td&gt;31 MB&lt;/td&gt;
&lt;td&gt;0.4 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FTP contacts file to CRM contacts import&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;30 MB&lt;/td&gt;
&lt;td&gt;0.1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nightly FTP contacts file to Supabase, with data-quality checks&lt;/td&gt;
&lt;td&gt;6.1%&lt;/td&gt;
&lt;td&gt;29 MB&lt;/td&gt;
&lt;td&gt;4.1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*A 1 million row load took 402 to 1,240 seconds, from 5,000 down to 100 rows per insert statement.&lt;/p&gt;

&lt;p&gt;Fourteen of the 15 flows averaged 29 to 94 MB, close to the runtime's idle memory. Only the bulk load from local Postgres went above 100 MB. The event-driven flows, reading from MQTT, Kafka and AMQP, averaged 4 to 6% of one core. Twelve of the 15 finished in under 5 seconds on average; the AI tagging flow's 20 seconds is mostly spent waiting for the language model, with CPU under 2%.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does a 1 million row load to a cloud database use?
&lt;/h2&gt;

&lt;p&gt;Less than 450 MB at its peak. Moving data from a database inside the network to a cloud database is the request we hear most, so we ran it at full size: 1 million synthetic customer rows from a local PostgreSQL database into a Supabase table, which is hosted PostgreSQL, over a home internet line (about 175 Mbps down and 44 Mbps up).&lt;/p&gt;

&lt;p&gt;Each row went through four data-quality checks, and its email was hashed and its phone number cut to the last four digits before it left the machine. Rows that failed stayed in a quarantine table on the local network. We repeated the load four times, with 100, 1,000, 5,000 and 10,000 rows per insert statement.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The runtime's own memory peaked at 320 to 445 MB across the four runs and spent most of each run near its median of 53 to 118 MB.&lt;/li&gt;
&lt;li&gt;CPU averaged 12 to 26% of one core.&lt;/li&gt;
&lt;li&gt;Every run loaded exactly 976,000 rows and kept 24,000 in local quarantine. The fastest took 402 seconds.&lt;/li&gt;
&lt;li&gt;The internet line was never the limit: uploads averaged about 5 to 7 Mbps of the 44 available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A later post covers the checks, the masking and how batch size changed the timing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does heavy computation cost?
&lt;/h2&gt;

&lt;p&gt;About one CPU core, and more memory. Comparing records with each other is a different kind of work from passing them through, so we ran fuzzy duplicate matching on the runtime: about 970,000 customer records, grouped by zip code, with names and streets scored for similarity inside each group. The candidate pairs stayed in a local table.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First run, 970,000 records: 1,510 seconds. CPU averaged about 102% of one core, peaking at 217%, and resident memory peaked at 534 MB.&lt;/li&gt;
&lt;li&gt;Repeat with extra preparation, 976,000 records, on the same machine with the runtime running as a background service: 1,683 seconds. CPU averaged 93%, and resident memory peaked at 947 MB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you plan matching, scoring or heavy transformation on the runtime, size for this case rather than for the everyday flows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the destination sets the pace
&lt;/h2&gt;

&lt;p&gt;In every bulk run, the runtime spent most of its time waiting for the destination, not computing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We built the Valkey flow to write one record per command and wait for each reply, so the local copy ran at roughly 7,500 to 9,400 rows per second. Raising the flow's concurrency setting to 4 and then 64 didn't change that rate. That fits what &lt;a href="https://valkey.io/topics/pipelining/" rel="noopener noreferrer"&gt;Valkey's documentation on pipelining&lt;/a&gt; describes: when a client sends one command at a time, round-trip time is the limit.&lt;/li&gt;
&lt;li&gt;Inserting into the cloud database one row per statement took about 0.35 seconds per round trip, under 3 rows per second. Grouping thousands of rows into each statement brought the full 1 million rows down to under 7 minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reading was far faster than writing, so millions of rows were waiting to be written at once. The runtime absorbed that gap: its memory stayed flat while the waiting rows sat in its local run journal on disk, covered below.&lt;/p&gt;

&lt;p&gt;For large loads, batching writes is the lever, with any agent or runtime. The reassuring part for a server owner is the other side of the same finding: a runtime that is mostly waiting uses little CPU.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do memory and CPU change as the batch grows?
&lt;/h2&gt;

&lt;p&gt;Memory grows with the batch the runtime holds at once; CPU stays low. To isolate batch size we used the plainest flow we had, a copy from PostgreSQL to a local Valkey cache.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rows in one run&lt;/th&gt;
&lt;th&gt;Duration&lt;/th&gt;
&lt;th&gt;Peak memory&lt;/th&gt;
&lt;th&gt;Average CPU (one core = 100%)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;about 20 s&lt;/td&gt;
&lt;td&gt;about 41 MB (RSS)&lt;/td&gt;
&lt;td&gt;not averaged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;12.3 s&lt;/td&gt;
&lt;td&gt;about 121 MB (RSS)&lt;/td&gt;
&lt;td&gt;about 23%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;106 s&lt;/td&gt;
&lt;td&gt;about 250 MB*&lt;/td&gt;
&lt;td&gt;about 22%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*From a repeat 1 million row run on varied data (116.6 s), measured as the process's own memory including compressed pages.&lt;/p&gt;

&lt;p&gt;The 1 million row run took 106 seconds, about 9,400 rows per second. The runtime held about 250 MB during the run and dropped to about 70 MB around 20 seconds after it finished. CPU averaged about 22% of one core at every size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does a large batch live: memory or disk?
&lt;/h2&gt;

&lt;p&gt;On disk. During a 5 million row batch, the runtime's own memory stayed flat at about 420 MB while it wrote the in-flight batch to its local run journal, about 1.7 GB or roughly 350 bytes per row.&lt;/p&gt;

&lt;p&gt;The run journal is a durable local record of the batch that lets a run recover from an interruption. The runtime's logs describe it as hash-chained, sealed and flushed to disk with fsync.&lt;/p&gt;

&lt;p&gt;The runtime purges it once the run is settled: after that run, its data folder went from about 1.7 GB back to about 1 MB. Neblex doesn't store your records; anything that must persist lives in your own database. Plan disk for the journal, roughly the size of the largest batch you expect to move.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does the runtime allocate memory and disk?
&lt;/h2&gt;

&lt;p&gt;It sizes itself to the deployment it runs in, rather than to a fixed setting. Installed as a service on a server it reads the machine's resources; in a container it reads the container's limits, not the host's.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memory sets how much work it accepts. At start, the runtime checks the memory available to it, including a container's limit, and sets how many jobs it will take on. The limit scaled with memory: on the larger test machine it allowed several times as many jobs as in the 4 GB container, and capped at 1 GB it accepted no work at first. CPU didn't change it.&lt;/li&gt;
&lt;li&gt;Disk holds what is in flight. It reserves disk for its run journal at start, from about 5 GB to about 25 GB depending on the deployment in our tests, and writes large in-flight batches there, so its memory stays flat while a slow destination catches up.&lt;/li&gt;
&lt;li&gt;The journal is emptied as runs settle, so the disk use is temporary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For sizing, our tests ran comfortably within 4 CPU cores and 4 GB of RAM, and we treat that as the practical minimum for a production install. Add disk for the journal as described above. Published minimum requirements for the runtime are being finalized by our engineering team.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to plan for when you size it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Size for your heaviest flow, not the average. Everyday flows sit close to idle; matching, scoring and heavy transformation keep about one core busy and can approach 1 GB.&lt;/li&gt;
&lt;li&gt;Plan disk for the run journal, roughly the size of the largest batch you expect to move.&lt;/li&gt;
&lt;li&gt;Batch your writes. Throughput follows how the flow writes: one record per command waits on every round trip, while grouping rows per statement took a 1 million row load from an estimated four days to under seven minutes.&lt;/li&gt;
&lt;li&gt;Account for record size. Our test records were about 275 to 290 bytes; larger records raise memory, journal size and run time in proportion.&lt;/li&gt;
&lt;li&gt;Give it room. Capped at 1 GB, the runtime accepted no work at first; 4 CPU cores and 4 GB of RAM is a comfortable starting point.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whatever on-prem integration agent you evaluate, measure its resource usage before and during a real job, and ask the vendor exactly what the agent sends out of your network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How much memory does an on-premises integration agent need?
&lt;/h3&gt;

&lt;p&gt;It depends on the agent and the workload.&lt;/p&gt;

&lt;p&gt;In our tests on synthetic and test data, the Neblex Worker used about 25 MB idle, 29 to 94 MB on average for 14 of 15 everyday flows, and at most about 450 MB while loading 1 million rows into a cloud database. Heavy duplicate matching peaked near 950 MB. Our tests ran comfortably within 4 CPU cores and 4 GB of RAM.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much disk does an on-premises integration runtime need?
&lt;/h3&gt;

&lt;p&gt;Enough for its run journal. Large in-flight batches are written there and purged once the run settles; a 5 million row batch took about 1.7 GB, roughly 350 bytes per row. At start the Neblex runtime also reserves journal space, about 5 GB to 25 GB depending on the deployment in our tests. Plan disk roughly equal to your largest batch.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much CPU does an on-premises integration agent use?
&lt;/h3&gt;

&lt;p&gt;Less than most people expect, because it spends most of its time waiting for the systems it connects. In our tests, everyday flows averaged 0 to 31% of one core and a 1 million row cloud load 12 to 26%. Heavy computation is the exception: fuzzy duplicate matching kept about one core busy.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I measure an agent's memory correctly?
&lt;/h3&gt;

&lt;p&gt;Sample the process while a representative job runs. On macOS, use the process footprint, because resident memory leaves out compressed pages. On Linux in a container, look at anonymous memory in the cgroup's &lt;code&gt;memory.stat&lt;/code&gt;, because the container total includes reclaimable file cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Neblex internal tests, October 2 to 8, 2026: synthetic data generated by Neblex, on Neblex hardware (a shared test machine, and a container capped at 4 CPU cores and 4 GB of RAM), with timings from the runtime's log and per-flow averages from the Neblex console.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://valkey.io/topics/pipelining/" rel="noopener noreferrer"&gt;Valkey documentation: Pipelining&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://valkey.io/topics/mass-insertion/" rel="noopener noreferrer"&gt;Valkey documentation: Bulk loading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/admin-guide/cgroup-v2.html" rel="noopener noreferrer"&gt;Linux kernel documentation: Control Group v2, memory.stat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ps(1)&lt;/code&gt; and &lt;code&gt;footprint(1)&lt;/code&gt; manual pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last checked: October 7, 2026.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Adan leads go-to-market at Neblex. Originally published at &lt;a href="https://www.neblex.com/blog/onprem-integration-agent-resource-usage" rel="noopener noreferrer"&gt;neblex.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>database</category>
      <category>postgres</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
