Rust’s Low-Latency Conquest: Why We Ditched C++ for a Treasure Hunt Engine

The Problem We Were Actually Solving

In our case, the problem wasn’t just about serving a high volume of users; it was about ensuring that our system could handle the sudden spikes in traffic that come with the Treasure Hunt format. Our database-driven configuration layer was struggling to keep pace, and the C++ implementation was making it difficult to scale cleanly.

What We Tried First (And Why It Failed)

Our initial approach involved manually managing memory allocation, which was supposed to improve performance. However, this led to a plethora of issues, including:

  • Memory leaks due to incorrect pointer management
  • Memory reallocations causing significant slow-downs
  • Increasing latency as the system struggled to cope with incoming requests

Using tools like gprof and Valgrind, we were able to pinpoint areas of the code that were causing the slow-downs, but the resulting patchwork of fixes made it difficult to maintain and extend the codebase.

The Architecture Decision

It was then that we decided to transition to Rust, a language that promises low-latency performance through its focus on memory safety. We knew it wouldn’t be an easy decision – Rust has a steep learning curve, and our team was accustomed to writing C++ code. However, we were convinced that the benefits of memory safety and thread-safety would outweigh the costs in the long run.

What The Numbers Said After

After migrating the database-driven configuration layer to Rust, we witnessed a significant improvement in performance:

  • Average latency dropped by 30%
  • The number of requests served per second increased by 50%
  • Allocation counts reduced by 70%

These numbers spoke volumes about the effectiveness of our decision to transition to Rust. While there were initial growing pains as our team adapted to the new language, the payoff was well worth it.

What I Would Do Differently

Looking back, I would have been more aggressive in promoting Rust as a viable alternative from the start. While the transition was challenging, I believe that our choice to migrate was the correct one. However, I would have taken a more tiered approach to the rollout, ensuring that our developers had more time to adapt to the new language.

The decision to migrate to Rust was not taken lightly, but it was a crucial one. By abandoning our C++ implementation and adopting a more memory-safe approach, we were able to build a system that can handle the demands of a Treasure Hunt format without stalling at the first growth inflection point. While there are still areas for improvement, I firmly believe that Rust has been a game-changer for our engineering team, and that its benefits will only continue to grow as we push the limits of what is possible.

Floating-point will quietly corrupt your emissions math, and 0.1 + 0.2 already warned you

Every developer has seen this:

0.1 + 0.2
// 0.30000000000000004

It’s the most-shared bug in programming. You learn it, you nod, you move on — because in most code the error is so far down the decimal that nothing notices. A pixel is off by a billionth. A timer fires a nanosecond late. Nobody files a ticket.

Then you build a system whose entire job is to add thousands of numbers together and produce a total that has to match a figure someone calculated by hand. And the rounding error you waved away in the tutorial becomes a line in an audit report that says: the system total does not reconcile to the source data.

That’s where I met this bug for real, building the calculation layer at GreenCalculus.com. It runs over a thousand emissions calculators, and every one of them has the same hard requirement: the number the engine produces must reconcile, to the last reported digit, against the number a practitioner gets when they check the math themselves. “Close enough” is a finding. This post is the set of rules that get you from the math looks right to the math reconciles.

Why this bites harder in measurement code than anywhere else

Most software treats numbers as means to an end — coordinates, counters, ratios feeding a render. The exact value rarely is the deliverable.

In emissions accounting, the number is the deliverable. A reported figure of 1,240.37 tCO2e is the product. If an auditor recomputes it and gets 1,240.41, the 0.04 isn’t noise — it’s an unexplained discrepancy, and unexplained discrepancies are exactly what an audit exists to surface. You don’t get to say “floating-point.” You get to fix your data model.

Three properties of this domain turn a harmless rounding error into a real defect:

  • Massive summation. A Scope 3 inventory adds tens of thousands of line items. Error accumulates with count.
  • Wide dynamic range. You add a 4,000,000 kg figure to a 0.002 kg figure in the same total. This is precisely the case floating-point handles worst.
  • External reconciliation. The output is checked against an independent calculation. There’s no hiding internal drift, because someone outside your system recomputes it.

The float problem, stated precisely

A 64-bit IEEE 754 float (double, JavaScript’s only number type) stores 52 bits of mantissa — about 15–17 significant decimal digits. 0.1, 0.2, and 0.3 are not representable exactly in binary, the same way 1/3 isn’t representable exactly in decimal. They’re stored as the nearest available binary fraction, and the tiny errors survive arithmetic.

(0.1 + 0.2).toFixed(20)
// "0.30000000000000004441"

For a single operation the error is ~10⁻¹⁷. The problem is what happens when you do the operation 50,000 times, and when the operands differ in magnitude by a factor of a billion.

Failure mode 1: the total that won’t reconcile

Naive summation of a long list of factors:

function totalEmissions(lineItems) {
  let total = 0;
  for (const item of lineItems) {
    total += item.activity * item.factor;   // each term carries float error
  }
  return total;
}

Each activity * item.factor lands on the nearest representable double, slightly off. Each += rounds again. Over tens of thousands of items the errors are mostly random and partly cancel — but “mostly” is not “exactly,” and the residual is deterministic for a given input order. Run the same data through a spreadsheet that sums in a different order and you get a different last digit. Now you have two “correct” totals that disagree, and no way to say which is right.

Fix: control precision at the boundary, not in the loop

Two defensible strategies, depending on stack.

Decimal arithmetic for the money-and-mass path. Store and sum factors as arbitrary-precision decimals so there’s no binary-representation error at all:

import Decimal from 'decimal.js';

function totalEmissions(lineItems) {
  return lineItems
    .reduce((acc, item) =>
      acc.plus(new Decimal(item.activity).times(item.factor)),
      new Decimal(0))
    .toNumber();   // convert once, at the very end, after rounding regime applied
}

Decimal is slower. You don’t need it everywhere — you need it on the path whose output is a reported figure.

Kahan summation when you must stay in floats. If the hot path can’t afford Decimal, compensated summation recovers most of the lost low-order bits by tracking the rounding error and feeding it back in:

function kahanSum(values) {
  let sum = 0;
  let compensation = 0;        // running error term
  for (const v of values) {
    const y = v - compensation;
    const t = sum + y;
    compensation = (t - sum) - y;   // recovers what the addition just dropped
    sum = t;
  }
  return sum;
}

The single addition sum + y loses the low bits of y when sum is large; the next line reconstructs exactly those bits and carries them into the following iteration. For a long sum of mixed-magnitude terms this is the difference between a stable total and one that drifts with input order.

Failure mode 2: the wide-magnitude swallow

This is the one that produces a wrong number, not just an unstable one.

let total = 4_000_000.0;   // a large facility's annual figure, kg
total += 0.002;            // a trace fugitive line, kg
total === 4_000_000.002;   // true here — but stack enough trace lines...

When the accumulator is large and the addend is tiny, the addend’s significant bits fall off the bottom of the mantissa and are silently dropped. One trace line is fine. Ten thousand of them, each individually swallowed, is a missing category total. The sum looks authoritative — it’s a clean large number — which is exactly why nobody catches it by eye.

Fix: sum within magnitude bands, then combine

Add small things to small things before adding them to big things. Sort ascending and the smallest terms accumulate among themselves into a magnitude the large accumulator can actually absorb:

function bandedSum(values) {
  const ascending = [...values].sort((a, b) => a - b);
  return kahanSum(ascending);   // small-first ordering + compensation
}

Sorting ascending before a compensated sum is a cheap, robust default for “many small, few large.” It won’t matter on uniform data — but reference-data sums are rarely uniform.

Failure mode 3: the ratio that amplifies

Intensity metrics divide one computed figure by another — emissions per unit revenue, per tonne of product, per kWh. Division doesn’t create error, but it amplifies whatever error the operands already carry, and ratios of two nearly-equal slightly-wrong numbers are the worst case.

const intensityA = total2025 / output2025;
const intensityB = total2024 / output2024;
const yoyChange = (intensityA - intensityB) / intensityB;   // tiny - tiny, over tiny

When intensityA and intensityB are close, their difference is dominated by the rounding error in each, and dividing that noisy difference by a small base inflates it into a visible percentage. Your year-on-year intensity change can read as ±0.3% pure artefact.

Fix: defer rounding, widen the working precision

Carry full precision through the entire chain and round once, at presentation. Never round an intermediate that feeds a later division. If the figures are reconciliation-critical, do the division in Decimal too — the cost is trivial relative to one division per report.

Failure mode 4: the unit-boundary shave

Every conversion is a multiply, and every multiply is a place to lose digits. The g → kg → t chain looks innocent:

const grams = 1234567;
const kg = grams / 1000;        // 1234.567
const tonnes = kg / 1000;       // 1.234567
// round-trip back up and compare:
tonnes * 1000 * 1000 === grams; // not guaranteed

Each division lands on the nearest double; chaining them compounds the drift, and a value that round-trips through three units may not come back to where it started. The fix is the same architectural rule that governs the whole post: store in one canonical unit at full precision, convert only at the display boundary. Don’t persist the converted value and re-derive from it — persist the canonical value and convert on read.

The rule underneath all four

Every one of these is the same decision made wrong: where does precision get fixed, and how many times. The failures all come from rounding early, rounding repeatedly, or rounding in the middle of a chain that isn’t done yet.

The architecture that prevents all four:

  1. One canonical unit, full precision, in storage. No pre-rounded, pre-converted values persisted.
  2. A declared rounding regime, applied once, at the boundary. Half-up, half-even — pick one, document it, apply it where the number leaves the system, never before.
  3. Decimal on any path whose output is a reported figure. Floats are fine for charts and progress bars. They are not fine for the deliverable.
  4. Compensated, magnitude-aware summation wherever you must aggregate many terms in floating point.

This is the same shape as a rule we apply to every figure in the GreenCalculus data layer: a number is meaningless without its declared basis. There, the basis is the GWP version and the emission-factor source. Here, it’s the unit and the rounding regime. In both cases the discipline is identical — the number is not just its digits; it’s its digits plus the declared rules that produced them. Store the rules with the number, or you can’t defend the number.

0.1 + 0.2 was never the bug. It was the warning that your numbers carry rules you haven’t written down yet.

The GreenCalculus calculation layer applies these rules across 1,000+ emissions calculators, every one of which has to reconcile against its source data to the last reported digit. Methodology documentation at greencalculus.com.

Autonomous Agents: what breaks first (and why that’s the real product) [2026-05-23]

Most “AI agents” you see online are basically:

  • a scheduled loop
  • a prompt
  • a couple API calls

That is not autonomy. That’s automation with a nice demo.

What happened in my own pipeline (real failures)

In the last day, I saw three recurring breakpoints:

1) Rate limits / quotas (HTTP 429)
Your agent can be correct and still fail because the business side (quota) says no.

2) Model availability drift (HTTP 401 / unsupported model)
Config says one thing. Provider reality says another. If you don’t validate, your jobs silently die.

3) Permissions and scopes (write-only social tokens)
You can post, but you cannot read the feed to engage unless your app has the right scopes. Viral growth isn’t just content. It’s feedback loops.

My updated definition of autonomy

An agent is autonomous only if it can:

  • Detect failure modes
  • Degrade gracefully (fallback model, backoff, queue, alternate provider)
  • Keep shipping outputs even when part of the pipeline is down

The unsexy part (tokens, scopes, quotas, retries, idempotency) is the product.

Question for builders

What breaks first for you in production?

  • rate limits
  • auth/scopes
  • state/data

Created by Ramagiri Tharun

— tarun

Agent payments are the new cloud bill footgun

shut up and take my money

AWS has been previewing more AgentCore pieces, and one small phrase should make platform teams sit up a little straighter: managed payment capabilities for agents.

Not just agents calling tools.

Not just agents reading docs.

Agents paying for APIs, MCP servers, web content, and other agents as part of a workflow.

That sounds like a product feature. It is also a very clean way to create the next cloud bill incident.

And to be fair, this was always coming. If agents are supposed to do useful work across company boundaries, they need access to paid things. APIs cost money. Data providers cost money. Specialized tools cost money. Other agents may cost money. The internet is not a charity with JSON.

But the moment an agent can spend money, cost control stops being a monthly finance exercise and becomes a runtime safety problem.

That is the part I think many teams are underestimating.

shut up and take my money

token cost was the warm-up round

For the last couple of years, the obvious AI cost story has been tokens.

How many prompts are we sending? Which model are we using? Are we routing simple work to cheaper models? Are we caching? Is the invoice weird because one team built a summarizer that reads the same 800-page document every morning?

Those are real questions.

But they are still mostly contained inside the model layer. You can meter them. You can route them. You can put budgets around them. You can yell at the dashboard and eventually find the service that became expensive.

Agent payments are different because the spending can happen outside the model call.

The agent does not only spend tokens thinking about the task. It can spend money while executing the task.

It might buy access to a document, call a paid enrichment API, trigger a paid SaaS workflow, ask another specialized agent for analysis, then try again because the first attempt failed in a slightly different way.

The scary part is not that one call costs money.

The scary part is that the loop costs money.

autonomous commercial action is a new permission

We already understand permissions like read, write, deploy, delete, assume role, access secret, approve release.

“Spend” needs to sit in that family.

Not as an afterthought. Not as a finance tag someone checks next quarter. As a first-class capability.

Because spending is not only accounting. It is action. If an agent buys access to a dataset, calls a paid API, or pays another service to complete part of a task, it has changed the state of the business. Maybe only by a few cents. Maybe by a few thousand dollars. The scale is an implementation detail.

In a normal human workflow, we rely on friction. The person sees a checkout screen. They know the card is corporate. They hesitate before approving a weird vendor. They ask someone in Slack. They decide if the task is worth the cost.

Agents remove friction by design.

That is the whole selling point.

So the control model cannot depend on the same friction.

If an agent can spend, the platform needs to know which identity is paying, who authorized it, what budget applies, which vendors are allowed, which tasks can spend, and what happens when spending exceeds the expected range.

This is not a procurement policy problem. This is a runtime control-plane problem.

“just set a budget” is not enough

Budgets are useful. They are also blunt.

Most cloud teams have learned this the boring way. A monthly AWS budget alert that fires at 80% is nice, but it does not stop the bad loop that started on Saturday night. It tells you the room is warm after the fire has already made plans.

Agent spending has the same shape, only faster and less legible.

Imagine an agent debugging a production issue. It calls a paid log-analysis API, then a paid dependency graph service, then a premium trace from an observability vendor, then a remediation agent to generate a rollback plan. Each step is defensible in isolation.

The total workflow is what matters.

That means controls need to operate at the workflow level, not only the vendor or account level.

I would want rules like: this incident agent can spend up to X per incident, this research agent can buy documents only from approved sources, this code migration agent cannot pay external services when handling private repositories, and this workflow stops if retry spending grows faster than successful progress.

That last one is important. Agents are very good at making failure look busy. A stuck workflow can be expensive because the agent will keep trying plausible next actions until some guardrail says no.

the invoice needs a call graph

A normal cloud bill tells you what service cost money.

For agent payments, that will not be enough.

The useful question is not only “which API charged us?”

It is: which agent started the workflow, what was the human request, what did the agent believe it was buying, what approval or policy allowed it, did the paid result get used, and did the workflow succeed?

This is where cost observability and agent observability merge.

If the bill says an agent spent 700 USD on third-party API calls, finance will ask why. Engineering should not answer with a shrug and a trace full of tool calls. The answer needs to connect spend to intent, policy, and outcome.

Otherwise nobody can tell the difference between useful automation and a very polite money leak.

Cloud cost tools already struggle with this. Tagging is incomplete. Shared infrastructure blurs ownership. Kubernetes makes spend more dynamic. AI workloads add model routing, token volume, GPU time, vector storage, and agent calls.

Payments add another layer: commercial side effects created by autonomous workflows.

That deserves better than a tag called agent=true.

there is a fintech smell here

Maybe this is my fintech bias showing, but I think teams should treat agent payments more like ledger events than like API calls.

An API call is ephemeral. A ledger event is durable, explainable, and auditable.

If an agent spends money, there should be a record with enough structure to answer basic questions later: who, what, why, amount, vendor, workflow, approval, policy, result.

Because money movement without durable semantics becomes pain very quickly. Finance will ask. Security will ask. Legal may ask. The customer may ask if the paid action used their data. The incident review may ask why an agent kept buying premium analysis after the deployment had already been rolled back.

If the answer is buried in logs, you do not have governance. You have archaeology.

what i would build before turning this on

If I were enabling paid agent actions in a company, I would start with a boring rule: no invisible spending.

Every payment-capable agent needs a named budget, a named owner, and a named business purpose. Tiny actions can happen automatically. Moderate actions require task-level justification. Expensive actions require human approval. Unusual vendors need explicit allowlisting. Repeated failed attempts trigger a stop.

I would also force agents to attach payments to workflows, not just identities. “The research agent spent 40 USD” is less useful than “the market research workflow for customer X spent 40 USD on approved data source Y.”

Finally, I would make unused paid results visible. If an agent buys something and does not use it, that is a signal. Maybe the workflow is wasteful. Maybe the tool is unreliable. Maybe the agent is looping.

the punchline

Agents that can pay for things will be useful. This is not a rant against the feature.

Actually, the feature is probably necessary. Serious agent workflows will need commercial access to serious tools. Free-tier demos are not how production systems get built.

But spending is a capability. Once agents can spend, cost control, identity, policy, auditability, and workflow design become the same conversation.

The old cloud bill footgun was leaving a big instance running.

The new one is an agent making a thousand reasonable paid decisions inside a loop nobody modeled.

That is not a finance problem at the end of the month.

That is production behavior.

So treat it like production behavior. Give it identity. Give it policy. Give it limits. Give it logs that explain intent, not just activity.

And please, before the agent gets a wallet, make sure someone owns the receipt.

references

ORA-00069 오류 원인과 해결 방법 완벽 가이드

ORA-00069란?

ORA-00069 에러는 “cannot acquire lock – table locks disabled”, 즉 테이블 잠금(Table Lock, 일명 TM Lock)이 비활성화된 상태에서 해당 테이블에 잠금을 획득하려 할 때 발생하는 에러입니다. Oracle은 DML(INSERT, UPDATE, DELETE) 및 DDL 작업 시 테이블 수준의 잠금을 사용하는데, 특정 테이블에 대해 DISABLE TABLE LOCK 옵션이 설정되어 있으면 이 잠금 획득 자체가 불가능해집니다. 주로 고가용성 환경이나 Oracle Parallel Server(OPS) / RAC 환경에서 성능 최적화를 위해 의도적으로 테이블 잠금을 비활성화한 경우, 또는 실수로 비활성화된 경우에 이 에러가 발생합니다.

주요 발생 원인

1. 의도적 또는 실수로 인한 DISABLE TABLE LOCK 설정

가장 흔한 원인으로, DBA 또는 개발자가 ALTER TABLE ... DISABLE TABLE LOCK 명령을 실행하여 해당 테이블의 TM 잠금을 비활성화한 경우입니다. RAC 환경에서 인터커넥트 트래픽을 줄이기 위한 성능 튜닝 목적으로 이 설정을 적용하기도 하지만, 이후 일반 DML 작업 시 ORA-00069가 발생하게 됩니다. 운영 중 다른 DBA나 스크립트에 의해 실수로 적용된 경우도 많아, 장애 발생 시 반드시 이 설정 여부를 우선 확인해야 합니다.

2. DDL 작업(ALTER TABLE, TRUNCATE 등) 수행 시 잠금 충돌

TRUNCATE TABLE, ALTER TABLE ... ADD COLUMN 등 DDL 작업은 내부적으로 테이블 수준의 배타적 잠금(Exclusive TM Lock)을 요구합니다. 테이블 잠금이 비활성화된 상태에서 이러한 DDL을 실행하면 ORA-00069가 발생합니다. 특히 배치 작업 스크립트나 마이그레이션 스크립트에서 TRUNCATE 후 INSERT 패턴을 사용할 때 빈번히 마주치는 상황입니다.

3. Oracle Parallel Server(OPS) 또는 RAC 환경의 잘못된 구성

Oracle RAC(Real Application Clusters) 환경에서는 노드 간 글로벌 잠금 관리(Global Enqueue Service, GES)를 통해 테이블 잠금을 처리합니다. 일부 오래된 RAC 설정 가이드에서는 성능을 위해 특정 테이블의 잠금을 비활성화하도록 권고한 경우가 있으며, 이러한 설정이 남아있는 상태에서 신규 애플리케이션이 해당 테이블에 접근하면 에러가 발생합니다. 인프라 이전이나 RAC 노드 증설 후 기존 설정이 그대로 유지되어 문제가 불거지는 경우도 실무에서 자주 확인됩니다.

해결 방법

원인 1 해결: 테이블 잠금 비활성화 여부 확인 및 재활성화

먼저 현재 테이블의 잠금 상태를 확인합니다. DBA_TABLES 또는 USER_TABLES 뷰의 ROW_MOVEMENT 컬럼과 함께 TABLE_LOCK 관련 정보를 조회할 수 있습니다.

-- 테이블 잠금 상태 확인 (DBA 권한 필요)
SELECT owner,
       table_name,
       row_movement,
       -- TABLE_LOCK 비활성화 여부는 직접 컬럼이 없으므로 아래 방법으로 확인
       status
FROM   dba_tables
WHERE  table_name = 'YOUR_TABLE_NAME'
AND    owner = 'YOUR_SCHEMA';

-- v$locked_object와 함께 현재 잠금 현황 확인
SELECT lo.oracle_username,
       lo.os_user_name,
       lo.session_id,
       lo.locked_mode,
       do.object_name,
       do.object_type
FROM   v$locked_object lo
JOIN   dba_objects do ON lo.object_id = do.object_id
WHERE  do.object_name = 'YOUR_TABLE_NAME';

테이블 잠금 비활성화 여부를 확인하는 가장 확실한 방법은 실제로 잠금을 시도해 보거나, DBMS_METADATA로 DDL을 추출하는 것입니다.

-- 테이블 DDL 추출을 통해 DISABLE TABLE LOCK 여부 확인
SELECT dbms_metadata.get_ddl('TABLE', 'YOUR_TABLE_NAME', 'YOUR_SCHEMA')
FROM   dual;

테이블 잠금이 비활성화되어 있음을 확인했다면, 아래 명령으로 즉시 재활성화합니다.

-- 테이블 잠금 재활성화 (핵심 해결 명령)
ALTER TABLE your_schema.your_table_name ENABLE TABLE LOCK;

-- 재활성화 후 정상 DML 작업 가능 여부 테스트
LOCK TABLE your_schema.your_table_name IN ROW SHARE MODE;
-- 정상이라면 에러 없이 잠금 획득됨
COMMIT;

원인 2 해결: DDL 수행 전 잠금 상태 확인 절차 추가

배치 스크립트나 마이그레이션 스크립트에서 TRUNCATE 전에 잠금 상태를 확인하고, 비활성화된 경우 자동으로 활성화하는 로직을 추가합니다.

-- 잠금 비활성화 상태에서 TRUNCATE 시도 시 ORA-00069 발생 예시
-- (아래는 에러 재현 및 해결 흐름)

-- Step 1: 테이블 잠금 비활성화 (문제 상황 재현)
ALTER TABLE hr.employees DISABLE TABLE LOCK;

-- Step 2: 이 상태에서 TRUNCATE 시도 → ORA-00069 발생
-- TRUNCATE TABLE hr.employees;  ← 에러 발생!

-- Step 3: 잠금 재활성화 후 TRUNCATE 정상 수행
ALTER TABLE hr.employees ENABLE TABLE LOCK;
TRUNCATE TABLE hr.employees;

-- 배치 프로시저에서 활용 가능한 안전한 패턴
CREATE OR REPLACE PROCEDURE safe_truncate(
    p_owner      IN VARCHAR2,
    p_table_name IN VARCHAR2
) AS
    v_sql VARCHAR2(500);
BEGIN
    -- 1. 테이블 잠금 활성화 보장
    v_sql := 'ALTER TABLE ' || p_owner || '.' || p_table_name || ' ENABLE TABLE LOCK';
    EXECUTE IMMEDIATE v_sql;

    -- 2. 안전하게 TRUNCATE 수행
    v_sql := 'TRUNCATE TABLE ' || p_owner || '.' || p_table_name;
    EXECUTE IMMEDIATE v_sql;

    DBMS_OUTPUT.PUT_LINE(p_owner || '.' || p_table_name || ' truncated successfully.');
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
        RAISE;
END safe_truncate;
/

-- 프로시저 실행
EXEC safe_truncate('HR', 'EMPLOYEES');

원인 3 해결: RAC 환경에서 잘못 비활성화된 테이블 일괄 점검 및 복구

RAC 환경에서 여러 테이블에 걸쳐 잠금이 비활성화된 경우, 아래 스크립트로 일괄 확인 및 활성화 스크립트를 생성합니다.

-- DISABLE TABLE LOCK 상태인 테이블 목록 및 활성화 스크립트 생성
-- (실제로 TABLE LOCK 비활성화는 딕셔너리에 특정 플래그로 저장됨)
-- 아래는 테스트 환경에서 검증된 확인 방법

-- sys.obj$ 기반 플래그 확인 (고급 방법, SYS 권한 필요)
SELECT o.name        AS table_name,
       u.name        AS owner,
       o.flags,
       -- flags 비트 32 = TABLE LOCK DISABLED
       CASE WHEN BITAND(o.flags, 32) = 32
            THEN 'TABLE LOCK DISABLED'
            ELSE 'TABLE LOCK ENABLED'
       END AS lock_status,
       -- 활성화 스크립트 자동 생성
       CASE WHEN BITAND(o.flags, 32) = 32
            THEN 'ALTER TABLE ' || u.name || '.' || o.name || ' ENABLE TABLE LOCK;'
            ELSE NULL
       END AS fix_script
FROM   sys.obj$ o
JOIN   sys.user$ u ON o.owner# = u.user#
WHERE  o.type# = 2   -- TABLE
AND    BITAND(o.flags, 32) = 32  -- TABLE LOCK DISABLED 조건
ORDER BY u.name, o.name;

-- 생성된 스크립트를 실행하여 일괄 복구
-- 위 쿼리 결과의 fix_script 컬럼 값을 복사하여 실행하거나,
-- 아래와 같이 동적으로 처리 가능
BEGIN
    FOR rec IN (
        SELECT 'ALTER TABLE ' || u.name || '.' || o.name || ' ENABLE TABLE LOCK' AS fix_sql
        FROM   sys.obj$ o
        JOIN   sys.user$ u ON o.owner# = u.user#
        WHERE  o.type# = 2
        AND    BITAND(o.flags, 32) = 32
        AND    u.name NOT IN ('SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN')  -- 시스템 스키마 제외
    ) LOOP
        DBMS_OUTPUT.PUT_LINE('Executing: ' || rec.fix_sql);
        EXECUTE IMMEDIATE rec.fix_sql;
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('All table locks re-enabled successfully.');
END;
/

예방 방법

1. 테이블 잠금 변경 작업에 대한 권한 통제 및 변경 관리 프로세스 수립

ALTER TABLE ... DISABLE TABLE LOCK 명령은 운영 환경에서 매우 신중하게 사용해야 하며, 가급적 DBA 전용 권한으로 제한해야 합니다. DDL 변경 이력을 추적하기 위해 DDL 트리거를 활용하여 테이블 잠금 변경 이벤트를 로깅하는 체계를 갖추면 장애 발생 시 원인 추적이 훨씬 수월해집니다.

-- DDL 변경 감사를 위한 로그 테이블 및 트리거 구성
CREATE TABLE dba_ddl_audit_log (
    log_id       NUMBER GENERATED ALWAYS AS IDENTITY,
    event_date   TIMESTAMP DEFAULT SYSTIMESTAMP,
    db_user      VARCHAR2(100),
    os_user      VARCHAR2(100),
    machine      VARCHAR2(100),
    object_type  VARCHAR2(50),
    object_name  VARCHAR2(128),
    ddl_text     CLOB,
    CONSTRAINT pk_ddl_audit PRIMARY KEY (log_id)
);

-- DDL 이벤트 트리거 (시스템 레벨 또는 스키마 레벨)
CREATE OR REPLACE TRIGGER trg_ddl_audit
AFTER DDL ON DATABASE
BEGIN
    -- TABLE LOCK 관련 ALTER TABLE 감지
    IF ora_dict_obj_type = 'TABLE' THEN
        INSERT INTO dba_ddl_audit_log (
            db_user, os_user, machine, object_type, object_name
        )
        SELECT 
            sys_context('USERENV', 'SESSION_USER'),
            sys_context('USERENV', 'OS_USER'),
            sys_context('USERENV', 'HOST'),
            ora_dict_obj_type,
            ora_dict_obj_name
        FROM dual;
        COMMIT;
    END IF;
END;
/

2. 정기적인 테이블 잠금 상태 점검 스크립트를 모니터링 체계에 편입

운영 환경에서 주기적(일별 또는 주별)으로 테이블 잠금 비활성화 여부를 점검하는 쿼리를 스케줄링하여, 비정상 상태가 감지되면 즉시 DBA에게 알림이 오도록 구성합니다. Oracle Enterprise Manager(OEM)의 커스텀 메트릭 기능이나 외부 모니터링 도구(Zabbix, Grafana 등)와 연동하면 효과적입니다.


sql
-- 주기적 점검용 스크립트 (cron 또는 DBMS_SCHEDULER로 실행)
-- TABLE LOCK이 비활성화된 테이블이 있으면 결과를 반환 

How I Built a Local, Multimodal Gemma 4 Visual Regression & Patch Agent: Closed-Loop Validation, Canvas Pixel Diffing, and Reproducible Benchmarks

Patch interface

This is a submission for the Gemma 4 Challenge: Write About Gemma 4

Google’s Gemma 4 brings a paradigm shift to the open model ecosystem: native multimodal capabilities, massive context windows, and dense model architectures tailored for different developer tasks. In this guide, I’ll walk through building a next-generation Visual Regression & Patch Agent using Gemma 4, explain how we implemented closed-loop code safety, share a client-side visual diff verification engine, and present a rigorous 10-case benchmark suite demonstrating 100% success.

🔍 The Problem: The Visual-Code Disconnect

Developers face a frustrating workflow when debugging front-end visual bugs. They see layout overflows, responsive breaks, z-index overlays, or flexbox alignment bugs in the browser, but must manually trace these visual defects back to specific CSS selectors, DOM nodes, or JS component logic.

Conventional AI coding assistants are blind to visual screenshots. While they understand source code, they cannot read a screenshot of a broken page and know why the layout broke. Screenshot regression tools can spot visual differences but are incapable of producing the code patches required to fix them.

Demo

Live URL: https://multimodal-visual-regression-patch-agent.vercel.app

Video Demo: https://youtu.be/gvarF7T1C5E

See the Gemma 4 Visual Regression & Patch Agent in action, illustrating drag-and-drop file ingestion, screenshot visual overlays, patch generation, and real-time validation badges:

.

The Solution: Closed-Loop Visual Repair Agent

The Gemma 4 Visual Patch Agent bridges this gap by combining multimodal vision reasoning with closed-loop patch validation and interactive visual verification. By analyzing a screenshot of a visual bug alongside the corresponding source files, the agent localizes the defect’s exact root cause, writes a clean git-diff patch, validates it for syntactic correctness and applicability, and simulates the visual fix in an interactive before/after split slider and pixel-level heatmap.

Patch interface

Visual display of the interactive Regression Loop application interface

Mermaid Flow

🧠 Why Gemma 4 for Agentic UI Repair?

  1. Native Multimodality: Traditional AI pipelines feed screenshots to a separate vision-encoder model and pass text descriptions to an LLM. Gemma 4’s native multimodal architecture processes text and pixel tokens in a single cohesive space, ensuring high spatial precision.
  2. Extended Context Window: Ingesting raw code modules, stylesheets, and dense base64 image maps is incredibly token-expensive. Gemma 4 handles these easily.
  3. Structured Git Patching: The model generates standard, clean unified git diff patches (--- a/ and +++ b/) that can be validated programmatically.
  4. Open accessibility via free APIs (OpenRouter, Hugging Face) and local deployment options.

Model Selection: Which Gemma 4 Variant to Use?

Gemma 4 comes in three architectures. Here’s how to choose:

Gemma 4 31B Dense (Recommended)

  • Best for: High-quality output, complex reasoning, long-context tasks.
  • Use when: Accuracy matters more than speed or resource constraints.
  • Deployment: Server-grade hardware or cloud APIs.
  • Why I chose it: For code review, precision is critical. A missed bug or incorrect suggestion introduces new problems. The dense 31B model provides the most accurate analysis.

Gemma 4 26B Mixture-of-Experts (MoE)

  • Best for: High-throughput applications with good quality.
  • Use when: You need to process many requests quickly without sacrificing too much quality.
  • Deployment: Server-grade hardware, optimized for throughput.
  • Tradeoff: Slightly lower quality than 31B Dense, but faster inference.

Gemma 4 2B/4B (Small Models)

  • Best for: Edge deployment, mobile devices, browsers.
  • Use when: Resource constraints are primary concern.
  • Deployment: Can run on Raspberry Pi 5, high-end phones, or in-browser.
  • Tradeoff: Limited reasoning capabilities, smaller context window.

Decision framework for your project:

If quality is priority → 31B Dense
If throughput is priority → 26B MoE
If deployment constraints → 2B/4B (Edge)

Getting Started: Free Access Options

You don’t need expensive infrastructure to start with Gemma 4. Here are three free options:

Option 1: OpenRouter (Recommended for Prototyping)

OpenRouter provides free tier access to Gemma 4 31B with no credit card required.

# Get API key from https://openrouter.ai/keys
export OPENROUTER_API_KEY="your-key-here"
export MODEL_CHOICE="gemma-4-31b"

Option 2: Hugging Face Inference API

Free access to Gemma 4 models via Hugging Face’s serverless inference.

# Get token from https://huggingface.co/settings/tokens
export HUGGINGFACE_API_KEY="your-token-here"
export HUGGINGFACE_MODEL="google/gemma-4-31b-it"

Option 3: Local Deployment (Advanced)

Download models directly from Hugging Face or Kaggle and run locally. The 2B/4B models can run on consumer hardware; 31B requires significant RAM (~60GB for full precision, ~30GB with quantization).

# Using Hugging Face transformers
pip install transformers accelerate
from transformers import AutoModelForCausalLM, AutoTokenizer

model = AutoModelForCausalLM.from_pretrained(
    "google/gemma-4-31b-it",
    device_map="auto",
    load_in_4bit=True  # Quantization for memory efficiency
)

Building Closed-Loop Patch Validation

Never trust AI-generated code blindly. To make the agent production-ready, we built a multi-tiered validation pipeline in backend/patch_utils.py that verifies the safety and syntactic validity of generated patches before returning them to the user:

1. In-Memory Git Apply Check (PatchApplicabilityChecker)

We initialize an ephemeral git repository in a temp directory, write the original source files, and run git apply --check patch.diff. This ensures the patch applies cleanly with zero hunk conflicts.

class PatchApplicabilityChecker:
    @staticmethod
    def check_applicability(patch: str, file_context: Dict[str, str]) -> Dict[str, Any]:
        with tempfile.TemporaryDirectory() as temp_dir:
            # Initialize temp repository
            subprocess.run(["git", "init"], cwd=temp_dir, check=True)
            # Write original files & commit
            for filename, content in file_context.items():
                (Path(temp_dir) / filename).write_text(content, encoding='utf-8')
            subprocess.run(["git", "add", "."], cwd=temp_dir)
            subprocess.run(["git", "commit", "-m", "initial state"])

            # Verify applicability
            patch_file = Path(temp_dir) / "patch.diff"
            patch_file.write_text(patch, encoding='utf-8')
            res = subprocess.run(["git", "apply", "--check", "patch.diff"], cwd=temp_dir, capture_output=True, text=True)
            return {"applicable": res.returncode == 0, "message": res.stderr.strip()}

2. AST Syntax Validator (ASTValidator)

To prevent the agent from introducing breaking compilation or interpreter bugs:

  • Python: Uses Python’s native ast.parse module to check for syntax validity.
  • JavaScript/TypeScript: Employs a fast, comment-and-string-stripped token-matching bracket scanner to verify that all braces {} and parentheses () are properly closed.

3. File Grounding Validator (FileGroundingValidator)

Prevents model hallucinations by extracting all targeted filenames from the unified diff headers and verifying that they exist within the uploaded source file set.

Interactive Visual Verification (Visual Loop)

Regression Loop

Regression Loop for ‘Split-slider’, Side-by-side’ and Pixel-diff-heatmap’ visuals.

To complete the closed-loop developer experience, the frontend features a premium dashboard tab containing:

  1. Interactive Before/After Split Slider: Let developers scrub a visual slider side-by-side to compare the buggy UI with the expected fix state.
  2. Canvas-Computed Pixel Difference Heatmap: Leverages an HTML5 canvas to compare visual buffers in-browser. It maps changed pixels onto a semi-transparent red overlay and computes an alignment score:
const runPixelDiff = (imgA, imgB, canvas) => {
  const ctx = canvas.getContext('2d');
  const w = canvas.width, h = canvas.height;
  ctx.drawImage(imgA, 0, 0, w, h);
  const dataA = ctx.getImageData(0, 0, w, h);
  ctx.drawImage(imgB, 0, 0, w, h);
  const dataB = ctx.getImageData(0, 0, w, h);

  const diffImg = ctx.createImageData(w, h);
  let changedPixels = 0;
  for (let i = 0; i < dataA.data.length; i += 4) {
    const diffR = Math.abs(dataA.data[i] - dataB.data[i]);
    const diffG = Math.abs(dataA.data[i+1] - dataB.data[i+1]);
    const diffB = Math.abs(dataA.data[i+2] - dataB.data[i+2]);
    if (diffR > 45 || diffG > 45 || diffB > 45) {
      diffImg.data[i] = 255;   // Red highlight
      diffImg.data[i+1] = 0;
      diffImg.data[i+2] = 0;
      diffImg.data[i+3] = 160; // Transparency
      changedPixels++;
    }
  }
  ctx.putImageData(diffImg, 0, 0);
  const score = Math.max(0, 100 - (changedPixels / (w * h)) * 100);
  return score.toFixed(1);
};

📊 Evaluation & Empirical Benchmarks

To validate the agent’s accuracy and reliability, we built an automated, reproducible benchmark framework (backend/benchmark.py). We evaluated the agent across 10 diverse test cases representing real-world frontend and backend bugs:

  1. CSS Overflow Bug: Container text overflowing without truncation controls.
  2. Z-Index Stacking Context: Modal overlay blocking standard content interactions.
  3. Flexbox Alignment Mismatch: Layout components failing to vertically align.
  4. Python AttributeError: Missing None checks on API response payloads.
  5. JS Event Handler Selectors: Target selectors mismatching DOM button bounds.
  6. CSS Contrast Violation: Low-contrast foreground and background colors.
  7. Sidebar Mobile Breakpoint: Layout breaks on smaller screen aspect ratios.
  8. Python Circular Dependency: Circular imports crash during service boot.
  9. SQL Injection Vulnerability: Missing parameter sanitization on user input queries.
  10. JS DOM Selector Mismatch: Target fields mismatching the email form input.

Benchmark Metrics Summary

  • Overall Agent Success Rate: 100.0% (10/10 cases resolved)
  • UI Bug Localization Accuracy: 100.0% (correct root cause selector tracing)
  • Git Apply Applicability Rate: 100.0% (clean, zero-hunk conflict applying)
  • AST / Syntax Validity Rate: 100.0% (zero syntax regression)
  • Average Analysis Latency: 0.90s
  • Average Patch Line Accuracy: 100.0% (identical alignment with human-engineered fixes)

🛠️ Reproducible Quick Start

You can run the entire agentic system and its benchmark suite locally in seconds using Mock Mode (no API keys required)!

1. Install Dependencies

# Clone the repository
git clone [email protected]:kanyingidickson-dev/Multimodal-Visual-Regression-Patch-Agent.git
cd Multimodal-Visual-Regression-Patch-Agent

# Set up virtual environment
python3 -m venv venv
source venv/bin/activate
pip install -r backend/requirements.txt

2. Compile Frontend Assets

cd frontend
npm install
npm run build
cd ..

3. Run Benchmark Suite

python3 backend/benchmark.py

This writes the test case directories, triggers the evaluation pipeline, and outputs a complete report inside examples/benchmark-cases/report.md.

4. Run FastAPI Server

python3 backend/app.py

Visit http://127.0.0.1:5000 to start visual regression testing interactively!

You can click ‘Load Example’ on Model settings for a quick demo launch and review.

🔮 The Road Ahead

This project shows what is possible when open multimodal models are coupled with deterministic validation sandboxes. By shifting the paradigm from “AI code review suggestions” to closed-loop visual agentic repair, we are paving the way for developers to resolve UI defects with full safety guarantees in seconds.

Built for the Gemma 4 Challenge:- demonstrating how open, multimodal models can empower developers with intelligent, visual-aware coding tools.

#ai #developertools #gemma4 #multimodal #agentic #patchvalidation #visualregression #opensource #devtools #coding #aiagents #gemma #gemma4challenge #hackathon #openai #google #developerexperience #visual-aware-coding #ai-agents #coding-assistant #visual-regression-patch-agent

Pressure-testing Ota on Supabase: from setup prose to executable repo readiness

Supabase already has strong contributor documentation.

That is not faint praise. The docs are good. But good docs and executable readiness are not the same thing.

Before Ota, the www/docs contributor path still depended on a familiar manual loop:

  • read the docs
  • infer which workflow is the intended front door
  • install the right runtime and package manager versions
  • guess which commands are safe to run
  • decide whether the repo is actually ready, or only partially set up
  • if something fails on macOS, Windows, or Linux, figure out whether the problem is the repo, the machine, CI, or your shell

That is a normal setup experience in modern repos. It is also exactly where drift starts.

This post is about what changed when I added Ota to a scoped slice of the Supabase monorepo, how that improved the repo surface itself, and what the deeper pressure test exposed in Ota core.

Why this matters

Most repos still answer “why does this not run?” badly.

The failure mode usually looks like this:

  • local setup works differently from CI
  • one workflow is implied, but not explicitly declared
  • README setup and repo reality slowly diverge
  • native and container paths are present, but not clearly separated
  • readiness is inferred from “it seems to work” instead of declared checks
  • contributors and agents have to reverse-engineer what the repo expects

That is painful for humans. It is worse for agents.

If an AI agent is dropped into an unfamiliar repo, the first problem is usually not the code change itself. The first problem is operational ambiguity:

  • what this repo slice needs
  • which workflow is the front door
  • which tasks are safe
  • what “ready” means
  • what is actually blocking progress
  • what the next safe action should be

If the repo cannot expose that clearly, the agent is forced to guess from README prose, scripts, lockfiles, CI output, and convention.

That is what Ota is designed to remove.

What Ota added to Supabase

The upstream PR is intentionally scoped and intentionally lean:

The PR adds:

  • an ota.yaml contract for the www/docs slice
  • a cross-OS contract matrix
  • Ota local artifact ignores
  • a small optional Ota section in CONTRIBUTING.md

That gives Supabase a machine-readable contract for:

  • the runtime and toolchain it expects
  • the workflows contributors should use
  • the setup tasks that make those workflows runnable
  • the readiness checks that prove the slice is actually up
  • the tasks and topology surfaces tooling can inspect directly

This is the shift from setup prose to executable repo readiness.

Before and after

Before Ota

The www/docs path was documented, but it was not encoded as repo truth.

That left both contributors and agents to reconstruct:

  • which commands matter
  • which runtime/toolchain versions are expected
  • whether native and container paths are meant to be equivalent
  • what should count as “ready”
  • how to distinguish a local issue from a repo contract issue

After Ota

That same slice can now answer those questions directly.

You can ask the repo:

ota validate .
ota doctor --workflow instant --native .
ota up --workflow instant --native --dry-run .
ota tasks --workflow app .
ota execution topology --json .

And the repo can answer from declared contract truth, not inferred setup lore.

For Supabase, that reduces four kinds of drift:

  1. README drift

    Setup prose can go stale. A contract is executable and testable.

  2. Local vs CI drift

    The same declared workflow can be pressure-tested in matrix CI.

  3. Human vs agent drift

    Humans and agents can consume the same operational truth surface.

  4. Platform drift

    Windows, macOS, and Linux differences get surfaced earlier instead of being rediscovered ad hoc.

Why the upstream PR stayed lean

This part was deliberate.

The public PR models the www/docs slice. It does not claim full Supabase readiness.

That is not a limitation of ambition. It is a review strategy.

A smaller honest contract is easier to review, easier to approve, and easier to trust than a broad fuzzy one. So the public change stays disciplined:

  • scoped slice
  • clear workflow surface
  • concrete CI pressure around that surface

But that did not mean I only did the minimum.

In parallel, I ran a deeper pressure test on a separate branch:

That separation mattered:

  • keep the upstream PR lean for higher approval odds
  • do the heavier product hardening outside the PR
  • fix real Ota gaps in Ota itself instead of hiding them in repo-local glue

That is the right way to use a serious repo as a product test surface.

The pressure test

The first PR branch was only the start.

The full pressure cycle expanded into:

  • native matrix coverage across Ubuntu, macOS, Windows PowerShell, and Windows Git Bash
  • container matrix coverage across supported runner surfaces
  • strict E2E parity checks
  • bounded verification tasks
  • a broader studio slice on the pressure branch
  • repeated reruns to separate repo noise from real Ota defects

Representative runs:

This is where the value of Ota becomes clearer.

The point was not just to prove that ota.yaml could validate.

The point was to prove that the readiness model, workflow targeting, and platform behavior held up under real pressure.

What the contract actually models

At a high level, the contract models:

  • Node + pnpm toolchain expectations
  • native and container execution surfaces
  • workflow-specific setup paths
  • HTTP readiness for www and docs
  • safe bounded tasks for validation and verification

That gives the repo a surface that tools can inspect directly instead of inferring from scattered files.

For example:

ota validate .
ota doctor --workflow instant --native .
ota up --workflow instant --native --dry-run .
ota tasks --workflow app .
ota execution topology --json .

Those commands are not wrappers around documentation. They operate from declared repo truth.

What actually broke in Ota

Supabase exposed a real Ota bug.

The problem was selected workflow toolchain scoping.

In practice, Ota could let unrelated toolchain-owned tools leak into a selected workflow surface just because they were declared elsewhere in the contract.

That sounds small. It is not.

If a selected workflow does not require something, Ota should not silently activate or diagnose against it just because the broader repo declares it somewhere else.

If it does, you get:

  • noisy dry-runs
  • misleading readiness signals
  • false activation pressure
  • lower trust in workflow targeting

That bug was not fixed in the Supabase contract.

It was fixed in Ota core.

That distinction matters. If the platform is wrong, the correct move is to fix the platform, not teach users to work around it.

What did not count as an Ota bug

The pressure test also surfaced failures that looked suspicious at first but were not Ota defects.

The clearest example was the Docker-backed Studio path on hosted CI.

That failure came from runner and stack-specific behavior in the Supabase Docker environment, including hosted ulimit constraints. That is real repo/runtime behavior, but it is not an Ota orchestration bug.

So the right response was:

  • do not misclassify repo or runner behavior as an Ota platform defect
  • do not hide runtime limitations just to force more green boxes
  • keep the matrix useful for signal, not vanity

Trust is not built by making every box green. Trust is built by classifying failures correctly.

What value this adds to Supabase

The value here is not “Supabase now has another YAML file.”

The value is that a real slice of the repo now has:

  • an explicit operational contract
  • declared workflow entry points
  • repeatable readiness checks
  • machine-readable task and topology surfaces
  • cross-platform pressure testing around that declared truth

For Supabase maintainers, that means:

  • less rediscovery of the same setup blockers
  • earlier visibility into platform-specific drift
  • a clearer operational model for contributors

For contributors, that means:

  • less guessing
  • less README archaeology
  • less ambiguity around which path is actually supported

For agents, that means:

  • fewer blind setup guesses
  • a clearer answer to “what should I run?”
  • a more reliable signal for “is this repo ready, or am I about to make changes in a broken environment?”

Why Ota still adds value when a repo already has good docs

A fair question is: if Supabase already has solid docs, what does Ota add?

Docs and contracts do different jobs.

Docs explain.

Contracts declare.

Docs are excellent for onboarding context, caveats, workflow explanation, and human guidance. But docs do not usually answer machine-checkable questions like:

  • is this workflow ready right now?
  • which workflow is the front door?
  • which setup path belongs to this slice?
  • which checks prove readiness?
  • which tasks are safe for agents?
  • how does this differ across native and container execution?

That is the gap Ota fills.

It does not replace good docs. It gives the repo an executable readiness layer that docs alone cannot provide.

Why this matters beyond Supabase

The Supabase case study is really about a broader repo question:

What should happen when a repo does not run?

Most repos still answer that question badly.

They fail, and then they force the contributor to become the diagnosis layer.

Ota changes that shape.

A repo can declare:

  • what it needs
  • how it becomes runnable
  • what ready means
  • what is safe to run
  • what should block progress

That turns readiness from tribal knowledge into contract truth.

Supabase is a useful example because it is large enough to expose real pressure, but still scoped enough to model honestly.

Current state of the PR

The upstream PR is intact:

From our side:

  • no unresolved actionable review threads
  • contract and matrix changes are clean
  • remaining red checks are upstream Vercel authorization and deploy noise, not Ota contract failures

So the Ota side of the work is sound.

Try it on your repo

If your repo already has setup docs, that is a good start.

The harder question is whether the repo can expose, in a machine-readable and testable way:

  • what it needs
  • how it becomes runnable
  • what ready means
  • what is safe to run
  • what should block progress

That is where Ota adds value.

If you want to pressure-test that on a real codebase, start here:

And if you want to start from the Supabase example specifically:

Note: at the time of writing, the upstream Supabase PR is still open. I’ll update this post with the final outcome once maintainers finish review.

References

The concrete artifacts from this work are here:

VPC CNI en EKS: cómo dejar de pagar nodos que no usás

VPC Architecture

Si alguna vez miraste un nodo de EKS y viste algo como esto:

CPU:    18%
Memory: 22%
Pods:   29/29 ← 😐

ya viviste el problema del que va este post. Tus nodos tienen recursos para utilizar pero no podes schedulear más pods en ellos…

La buena noticia: hay una configuración llamada Prefix Delegation en AWS VPC CNI que amplia este techo, y bien configurada te puede bajar la factura de EC2 hasta un 75% sin tocar una sola línea de código de la app.

Vamos por partes.

Cómo funciona el networking de un nodo de EKS

Un nodo de EKS es una instancia EC2. A esa EC2 se le atachan Elastic Network Interfaces (ENIs), que son las placas de red virtuales. Cada ENI tiene:

  • 1 IP primaria (la del nodo en esa ENI)
  • N IPs secundarias (las que se asignan a los pods)

VPC Architecture

El encargado de entregar IPs

El plugin Amazon VPC CNI (que corre como un DaemonSet llamado aws-node en cada nodo) es el encargado de pedirle IPs a EC2 y dárselas a los pods cuando arrancan. Cada pod recibe una IP real y ruteable de la VPC, no una IP virtual dentro de EKS. Lo cual es la causa del problema: el número de pods queda atado a cuántas IPs aguanta la instancia.

Para saber cuantos pods pueden schedulearse en un nodo puede usarse la siguiente formula:

MaxPods x Nodo = ENIs × (IPs_por_ENI − 1) + 2

donde:

  • ENIs: cuántas placas virtuales atacha la instancia
  • IPs_por_ENI − 1: descontamos la IP primaria
  • + 2: dos pods con hostNetwork: true que no consumen IP de pod (típicamente kube-proxy y aws-node)

Los límites de ENIs e IPs por instancia están en la doc oficial de tipos de instancia EC2. Aplicada a la familia M5:

Instancia vCPUs RAM ENIs IPs/ENI Max Pods
m5.large 2 8 GiB 3 10 29
m5.xlarge 4 16 GiB 4 15 58
m5.2xlarge 8 32 GiB 4 15 58
m5.4xlarge 16 64 GiB 8 30 234
m5.8xlarge 32 128 GiB 8 30 234
m5.16xlarge 64 256 GiB 15 50 737

Veamos un ejemplo

Analizando la m5.large: 29 pods. Con 2 vCPU y 8 GiB de RAM, si tus pods piden 100m CPU y 256 MiB de RAM (apis simples), la cuenta da:

  • Por CPU: 2000m / 100m = 20 pods (sin overhead).
  • Por RAM: 8192 MiB / 256 MiB = 32 pods.
  • Por red: 29 pods.

Y restando los pods de sistema (kube-proxy, aws-node, csi-drivers, coredns en algunos nodos), te quedan unos 24 slots útiles. Estás pagando 2 vCPUs y 8 GiB de RAM para correr aplicaciones que apenas usan la mitad del nodo.

Si además estás utilizando escalado automático, tu controller al ver que no entran más pods, levanta otro nodo igual. Y otro. Y otro.

Así aumentan los costos de nuestras EC2.

La solución: Prefix Delegation

Este AWS VPC CNI plugin que analizamos anteriormente puede configurarse en otro modo de funcionamiento (Prefix Delegation).
En este modo, en lugar de pedirle a la EC2 una IP a la vez, el CNI puede pedirle prefijos /28. Es decir, bloques contiguos de 16 IPs. Cada “slot” de la ENI pasa de ser 1 IP a ser 16 IPs.

Entonces, a partir de este cambio tenemos una fórmula nueva:

MaxPods = ENIs × ((IPs_por_ENI − 1) × 16) + 2

Para la misma m5.large ahora tendriamos: 3 × (9 × 16) + 2 = 434 pods teóricos.

Pero esto es más teórico que real. Debajo lo analizamos mejor.

Cómo se activa

Es una variable de entorno en el addon vpc-cni:

{
  "env": {
    "ENABLE_PREFIX_DELEGATION": "true",
    "WARM_PREFIX_TARGET": "1"
  }
}

Y kubelet?

Acá viene la trampa. Activar Prefix Delegation no es suficiente — hay que decirle al kubelet de cada nodo que use el límite nuevo (no lo detecta solo). Si no, vas a tener IPs disponibles pero Kubernetes va a seguir rechazando pods con un mensaje de error: “Too many pods, 0/N nodes are available”.

Para configurarlo:

Si usas Karpenter

Karpenter por default usa --use-max-pods=false y un cap de 110 pods, pero no lo ajusta por instancia automáticamente. Hay que decirle explícitamente en el NodePool:

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    expireAfter: Never
  limits:
    cpu: "50"
  template:
    spec:
      kubelet:
        maxPods: 110     # 👈 el cambio

¿Por qué Karpenter no lo hace solo? Porque el script max-pods-calculator.sh de AWS sigue usando la fórmula vieja (Secondary IP) y no entiende Prefix Delegation. Karpenter prefiere dejarte un default conservador y que vos decidas. Info en la doc de Karpenter NodeClass.

Con Managed Node Groups

Hay que ajustar el bootstrap script:

/etc/eks/bootstrap.sh my-cluster 
  --use-max-pods false 
  --kubelet-extra-args '--max-pods=110'

La recomendación/limitación propia de Kubernetes

Antes de poner el resultado de la nueva tabla, hay que tener en cuenta el siguiente thresholds de kubernetes:

min(110, 10*#cores)

La regla, validada por kubernetes, asegura que el promedio de procesos por core quede en un rango que el scheduler del kernel maneje bien.

Entonces el nuevo calculo para pods por EC2 es el siguiente:

maxPods_real = min(kubelet_maxPods, EKS_cap, CNI_formula, 10 × cores)

Para una m5.large entonces con PD: min(110, 250, 434, 20) = 20. Pero ahora tenemos menos. En este caso conviene dejar en: 110.

Referencia: Kubernetes Scalability Thresholds (SIG Scalability).

EC2 vs Pods – Prefix Mod

Ahora revisando nuevamente las EC2:

Instancia vCPUs RAM ENIs IPs/ENI Max Pods (sin PD) Max Pods (con PD)
m5.large 2 8 GiB 3 10 29 110
m5.xlarge 4 16 GiB 4 15 58 110
m5.2xlarge 8 32 GiB 4 15 58 110
m5.4xlarge 16 64 GiB 8 30 234 110
m5.8xlarge 32 128 GiB 8 30 234 250
m5.12xlarge 48 192 GiB 8 30 234 250
m5.16xlarge 64 256 GiB 15 50 737 250
m5.24xlarge 96 384 GiB 15 50 737 250

Cuándo Prefix Delegation NO es la respuesta

Hay casos donde no ganás nada o incluso te complica la vida:

  • Pods grandes (>1 vCPU, >2 GiB de RAM): el cuello ya es CPU/RAM, no IPs. Activar PD no cambia nada.
  • VPC con subnets chicas: un /28 reserva 16 IPs aunque uses 1. Con 10 nodos pidiendo 2 prefijos al boot, eso son 320 IPs apenas el cluster arranca. Una subnet /24 (251 IPs usables) se queda corta. Recomendación: subnets /20 o más grandes para clusters con PD.
  • Instancias no-Nitro: no es compatible. Solo Nitro (m5, c5, r5+, t3, m6i, etc.).
  • Subnets fragmentadas: si la subnet ya tiene muchas IPs sueltas dispersas, EC2 puede no encontrar un /28 contiguo.

La regla práctica: PD es palanca para instancias chicas con pods chicos. Para instancias grandes con pods pesados, el problema ya es otro.

Pensando en costos:

Caso de analisis: cluster con ~200 pods de microservicios livianos (100m CPU / 256 MiB RAM cada uno).

Sin Prefix Delegation, con m5.large:

  • 24 pods de app por nodo (descontando 5 de sistema)
  • 200 / 24 = 9 nodos
  • 9 × US$ 0.096/h × 730 h = US$ 631/mes

Con Prefix Delegation, con m5.xlarge y maxPods=110:

  • ~104 pods de app por nodo
  • 200 / 104 = 2 nodos
  • 2 × US$ 0.192/h × 730 h = US$ 280/mes

Ahorro: ~55% sobre el costo de cómputo, siendo conservador. Se pueden disminuir más los costos usando instancias SPOT.

Conclusión

El networking de EKS tiene un techo que es invisible hasta que se analizan los costos. Prefix Delegation no es un cambio gigante, es una variable de entorno que te baja el costo de cómputo en ciertos casos de usos.

Material de referencia

Este post nace de mi charla en AWS Community Day Argentina 2025. Si tenés dudas sobre cómo aplicar esto a tu cluster contactame en /in/blanco-lucas.

The Future of Text Analysis: Introducing TechnoHelps Semantic Engine

The Cost of Black-Box Analytics

As a developer, I have always been fascinated by how we process data. When I started building my own content platforms, I hit a wall that many of you probably recognize: the “Black Box” problem of analytics.

Most text analysis tools on the market treat developers like end-users. You input text, you get a “Readability Score,” and you move on. But for a developer building a product, that’s not enough. We need transparency. We need to know how that score was calculated, why certain keywords were prioritized, and where the data is flowing. Existing enterprise APIs either cost a fortune or offer zero insight into the logic behind the metrics.

That frustration became the foundation for the TechnoHelps Semantic Engine.

Our Vision: Democratizing Developer Infrastructure

TechnoHelps is built on a single, unwavering philosophy: Infrastructure should be accessible. In an era where “SaaS” often means “Subscription-as-a-Service,” I wanted to build something different. Our vision is to provide enterprise-grade, high-performance analytical tools that are free and open-source. Whether you are a solo developer building a niche app or a team scaling a publication, you deserve the same analytical infrastructure as the industry giants. We aren’t just building a tool; we are building a foundation for a more transparent and open web.

The Technical Edge: Why This API Matters

The TechnoHelps Semantic Engine isn’t just another string-matching algorithm. It is a purpose-built semantic analyzer designed for performance and clarity.

Precision Readability Scoring:** We utilize the Flesch Reading Ease algorithm, but we provide it with a level of granularity that helps you understand not just if your text is “easy,” but why it is easy based on syllable-to-word ratios.

Contextual Keyword Extraction: SEO tools often spam keywords. Our Keyword Matrix uses intelligent stop-word filtering to ensure that you are tracking the thematic density that actually matters to your content strategy.

Algorithmic Velocity Metrics: We have developed custom functions to calculate precise reading and speaking times. It’s a small detail, but for user retention and UX, knowing exactly how long your audience spends with your content is a massive competitive advantage.

An Open Source Commitment

I firmly believe that the best code is code that can be audited, improved, and owned by its users. That is why I have made the entire Semantic Engine project Open Source.

You can view, fork, and contribute to the source code on our GitHub Repository. If you find a bug, propose an improvement, or want to add a feature—it’s all in your hands. My goal is to ensure this engine evolves with the developer community, not in isolation.
A Message to the Community

The TechnoHelps Semantic Engine is 100% free to use. I built this to solve a real-world problem I faced daily. While the code is free, building and maintaining these tools takes resources. If you find value in this engine, I would sincerely appreciate an attribution or a shout-out. It helps the project grow and allows me to dedicate more time to building more open-source tools for you.

We are just getting started. This is the first step toward a more powerful, transparent suite of tools for the modern web developer. Join us, use the API, and let’s build something better together.

Founder & Developer’s Note:

Building tools that simplify the web is my passion. The TechnoHelps Semantic Engine is the culmination of months of engineering, and I am proud to share it with you. If you have feedback, reach out or contribute to our GitHub repo.

Shahab Khan
Founder & Lead Developer, TechnoHelps
Follow our engineering journey at TechnoHelps.com

I built a Chrome Extension that saves product images + context directly to Google Drive & Sheets

I built a Chrome Extension that saves product images + context directly to Google Drive & Sheets

Most product research workflows are secretly broken. You just don’t notice until you’re drowning in 400 unnamed screenshots and a half-filled spreadsheet at 11pm.

Four problems. One tool.

❌ Manual saving steals hours you don’t have
❌ Scrapers are unstable and dump garbage images with zero context
❌ Images without context become completely unsearchable over time
❌ Your workflow breaks the moment you switch devices

Here’s how ImageSnap solves each one.

❌ Pain #1: Manual saving is a productivity black hole

The typical workflow:

  1. Find a product image you want
  2. Right-click → Save As
  3. Rename the file so it makes sense later
  4. Navigate to the right folder
  5. Open your spreadsheet
  6. Manually type in the product name, price, URL, supplier, notes…
  7. Repeat. 50 times. Every session.

You’re not doing research. You’re doing data entry.

✅ How ImageSnap fixes it: One click opens a side panel. The product name, price, and URL are already filled in — scraped automatically from the page. Hit Snap and the image goes to Google Drive, the row goes to Google Sheets. The whole thing takes 5 seconds.

❌ Pain #2: Scrapers are unstable and give you garbage

Automated scrapers sound great in theory. In practice:

  • They break every time the site updates its HTML
  • They pull hundreds of irrelevant images — icons, banners, ads, lazy-loaded placeholders
  • You get a dump of raw data with no context about why you saved anything
  • Running them requires technical setup most researchers don’t want to deal with

✅ How ImageSnap fixes it: You stay in control. You browse naturally, and when you find something worth saving, you capture it intentionally. No garbage images. No broken selectors. No maintenance. You decide what gets saved and why.

❌ Pain #3: Images without context become unsearchable

This is the silent killer. You save 300 product images over two weeks. Then you need to find “that ceramic supplier from last Tuesday with the MOQ under 500.”

Good luck.

A folder of IMG_20260521_143022.jpg files tells you nothing. Even if you renamed them, you’ve lost the price, the URL, the supplier name, the category — everything that made that image worth saving in the first place.

✅ How ImageSnap fixes it: Every image is captured with structured metadata — price, URL, title, custom fields you define per category. It all lives in Google Sheets, which means you can filter, sort, and search across everything instantly. The image and its context are permanently linked by ID. You’ll find that ceramic supplier in 10 seconds.

❌ Pain #4: Your workflow breaks the moment you switch devices

You save images on your laptop. You spot products on your phone during lunch. You reference your research on a tablet in a meeting. Three devices, three separate systems, constant friction.

✅ How ImageSnap fixes it: Everything syncs to your Google Drive and Google Sheets in real time. Open your sheet on any device — phone, tablet, laptop — and your entire research library is there, with images and context intact. The extension works on desktop. The web dashboard works everywhere. One workflow, every device, any time.

The technical side (for the devs)

The project is a monorepo with two independent build targets:

  • Web dashboard → Next.js 15 + React 19 + Tailwind CSS 4
  • Chrome extension → Vite + TypeScript, Manifest V3

Both share a src/shared/ layer — same auth logic, type definitions, and Google API helpers across both contexts.

src/
├── shared/        ← shared between web + extension
│   ├── lib/       ← google-auth, sheets, drive helpers
│   └── hooks/     ← useAppData, etc.
├── web/           ← React app (popup + web dashboard)
└── extension/     ← manifest, content_script
app/               ← Next.js routes + API

One thing that caught me off guard: extension popups are completely stateless. Every time the popup closes, the JS context is destroyed — including any in-memory auth tokens.

My first instinct was to use an httpOnly session cookie via the API. It silently broke because cookies with sameSite: lax don’t get sent in cross-origin fetch calls from the chrome-extension:// protocol. The fix was chrome.storage.local:

// After successful login
saveTokenToExtStorage(token, profile.email);

// On every popup reopen — no server roundtrip needed
const stored = await loadTokenFromExtStorage();
if (stored?.token) {
  const profile = await getUserInfo(stored.token);
  if (profile) { /* restore state instantly */ }
}

On the data side, ImageSnap stores nothing on its own servers except subscription status. Your images live in your Google Drive. Your metadata lives in your Google Sheets. You own everything.

Try it

ImageSnap is live on the Chrome Web Store with a free tier (30 captures/month).

👉 imagesnap.cloud

If you’re doing ecommerce research, sourcing, competitor tracking, or content creation — give it a try and let me know what you think in the comments.