0Pricing
MongoDB Academy · 강의

증가, 곱셈, 최솟값/최댓값 연산자

$inc, $mul, $min, $max 업데이트 연산자를 사용하여 숫자 값을 원자적으로 변경합니다.

증가, 곱셈, 최솟값/최댓값 연산자은(는) CoddyKit의 무료 MongoDB Academy 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 MongoDB Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. MongoDB Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

Atomic Numeric Updates

A common application pattern is reading a numeric value, computing a new value, and writing it back. If done as separate read and write operations, a race condition can occur: two concurrent requests might both read the same value, compute different results, and overwrite each other.

MongoDB's numeric update operators—$inc, $mul, $min, and $max—perform numeric mutations atomically at the database level. The computation and write happen in a single operation, eliminating race conditions without needing application-level locking.

$inc: Increment and Decrement

The $inc operator increments a numeric field by a given amount. Pass a negative value to decrement. If the field does not exist, $inc creates it with the given increment value as the initial value (treating a missing field as 0).

Common uses: view counters, like counts, inventory decrement, score tracking, retry attempt counters. Because the increment is atomic, multiple concurrent requests can all safely call $inc on the same document without losing any increments—every request's change is applied.

// Increment page view counter
db.posts.updateOne(
  { slug: 'mongodb-intro' },
  { $inc: { views: 1 } }
);
// views: 100 -> 101 (atomic, safe under concurrent traffic)

// Decrement stock (atomic inventory deduction)
db.products.updateOne(
  { _id: productId, stock: { $gte: 1 } },  // Only if in stock
  { $inc: { stock: -1 } }
);

// Increment two fields at once
db.users.updateOne(
  { _id: userId },
  { $inc: { loginCount: 1, totalSessions: 1 } }
);

Atomic Inventory Decrement Pattern

The atomic inventory decrement is a classic MongoDB pattern. The filter includes both the document identifier AND the minimum stock condition: { _id: productId, stock: { $gte: quantity } }. If the stock check fails (not enough inventory), the filter matches zero documents, modifiedCount is 0, and no inventory is decremented.

This pattern guarantees that you never decrement stock below zero, even under high concurrent load with many simultaneous purchase attempts. No application-level locks or transactions are needed for this single-document atomic check-and-decrement.

async function decrementStock(productId, quantity) {
  const result = await db.collection('products').updateOne(
    {
      _id: new ObjectId(productId),
      stock: { $gte: quantity }  // Only if enough stock
    },
    {
      $inc: { stock: -quantity }
    }
  );

  if (result.modifiedCount === 0) {
    throw new Error('INSUFFICIENT_STOCK');
  }
  return true;  // Successfully decremented
}

$mul: Multiply a Field

The $mul operator multiplies a numeric field by a given factor. Like $inc, it is atomic. If the field does not exist, $mul creates it with a value of 0 (not the multiplier—multiplying 0 by any factor is still 0, which is the mathematically correct behavior for a missing/zero field).

Practical uses: applying bulk price changes (multiply all prices by 1.05 for a 5% price increase), scaling scores or weights, adjusting currency values. $mul is much more efficient than fetching each document, multiplying in code, and writing back.

// Apply 5% price increase to all electronics products
db.products.updateMany(
  { category: 'Electronics' },
  { $mul: { price: 1.05 } }
);
// price: 100.00 -> 105.00 (atomically for each document)

// Apply 10% discount to sale items
db.products.updateMany(
  { onSale: true },
  { $mul: { price: 0.90 } }
);

// Multiply two fields at once
db.metrics.updateOne(
  { _id: metricId },
  { $mul: { valueA: 2, valueB: 0.5 } }
);

$min: Conditional Set to Smaller Value

The $min operator updates a field only if the new value is less than the current value. If the current value is already smaller, the field is left unchanged. If the field does not exist, $min sets it to the provided value.

This is useful for tracking record lows, ensuring a value never exceeds a ceiling, or recording the earliest date. For example, tracking the minimum price a product has ever been sold for, or the earliest sign-in date for a user.

// Track the all-time lowest price seen for a product
db.products.updateOne(
  { _id: productId },
  { $min: { lowestPriceEver: currentSalePrice } }
);
// Only updates if currentSalePrice < lowestPriceEver
// If lowestPriceEver is 45 and currentSalePrice is 50: no change
// If lowestPriceEver is 45 and currentSalePrice is 39: updates to 39

// Record earliest login date
db.users.updateOne(
  { _id: userId },
  { $min: { firstLoginAt: new Date() } }
);

$max: Conditional Set to Larger Value

The $max operator is the complement of $min: it updates a field only if the new value is greater than the current value. This is ideal for tracking high scores, peak values, and latest dates.

Common applications: leaderboard high scores (only update if the new score beats the current best), tracking the maximum concurrent users, or recording the most recent activity timestamp (ensuring you never overwrite a later event with an earlier one).

// Leaderboard: only update score if new score is higher
db.leaderboard.updateOne(
  { userId: userId },
  {
    $max: { highScore: newScore },
    $set: { lastPlayedAt: new Date() }
  }
);
// highScore: 9500. newScore: 8700 -> no change (8700 < 9500)
// highScore: 9500. newScore: 9800 -> updates to 9800

// Track most recent activity timestamp
db.users.updateOne(
  { _id: userId },
  { $max: { lastActiveAt: new Date() } }
);

Combining Numeric Operators

You can combine multiple numeric update operators in a single update operation. They are all applied atomically together. For example, incrementing a counter while also ensuring a timestamp is updated to the latest value.

Combining $inc with $max/$min is a powerful pattern for metrics tracking: increment total event count while conditionally updating peak values, all in a single round-trip to the database.

// Update user metrics: increment logins, track latest/earliest dates
db.userMetrics.updateOne(
  { userId: userId },
  {
    $inc:   { totalLogins: 1 },
    $max:   { lastLoginAt: new Date() },
    $min:   { firstLoginAt: new Date() }
  },
  { upsert: true }  // Create document if user has no metrics yet
);
// All three numeric operators apply atomically in one operation

Using $inc for Distributed Counters

A common architecture challenge is maintaining accurate counters (page views, likes, downloads) at high throughput. Using $inc atomically on a document is highly reliable but can become a write bottleneck if millions of requests hit the same document per second—because MongoDB uses document-level locking for writes.

Patterns to scale high-throughput counters: counter sharding (split the counter across N documents and sum them), buffered batching (accumulate increments in memory and flush periodically), or time-bucketed counters (one document per time period to distribute writes).

// Time-bucketed counter: one document per hour
const hourBucket = new Date();
hourBucket.setMinutes(0, 0, 0); // Round to current hour

db.pageViews.updateOne(
  { page: 'home', hour: hourBucket },
  { $inc: { count: 1 } },
  { upsert: true }  // Create bucket if first view this hour
);
// Writes distribute across hourly documents,
// reducing contention vs a single global counter

Numeric Operators in the Aggregation Pipeline

The arithmetic concepts behind $inc, $mul, $min, and $max also appear in the aggregation pipeline as expression operators and accumulator operators. In $project you can compute new numeric fields with $add, $multiply, $subtract. In $group you use $sum, $avg, $min, $max as accumulators.

Understanding the update operator semantics now makes the aggregation operators intuitive—they perform the same mathematical operations, just in a pipeline context rather than a write context.

// Aggregation: compute total revenue and max single order
db.orders.aggregate([
  { $match: { status: 'completed' } },
  {
    $group: {
      _id: '$customerId',
      totalSpent:  { $sum: '$total' },     // Sum of all totals
      orderCount:  { $sum: 1 },           // Count documents
      maxOrder:    { $max: '$total' },    // Largest single order
      minOrder:    { $min: '$total' }     // Smallest single order
    }
  }
]);

Decimal Precision With $inc

When incrementing floating-point values with $inc, be aware of floating-point precision issues inherited from IEEE 754 double arithmetic. For financial amounts, always use NumberDecimal (BSON Decimal128) to avoid rounding errors.

Incrementing a Decimal128 field with $inc requires passing a NumberDecimal value in the shell or Decimal128.fromString in Node.js. The result will be exact decimal arithmetic without the floating-point drift that plagues 64-bit double arithmetic.

// Floating-point issue with double
// 0.1 + 0.2 = 0.30000000000000004 in IEEE 754!
db.accounts.updateOne(
  { _id: accountId },
  { $inc: { balance: 0.1 } }  // Potential precision drift
);

// Correct: use Decimal128 for financial amounts
const { Decimal128 } = require('mongodb');
db.accounts.updateOne(
  { _id: accountId },
  { $inc: { balance: Decimal128.fromString('0.10') } }
);
// Exact decimal arithmetic - no floating point errors

Atomic Check-and-Update Patterns

A powerful MongoDB pattern is the conditional update with filter: include the condition you want to check as part of the query filter rather than reading first and deciding in application code. This makes the check-and-update atomic, eliminating race conditions.

Patterns: decrement stock only if sufficient quantity (filter includes stock: { : qty }), update only if a field has a specific value (optimistic locking pattern), or apply a promotion only if the user has not already received it. The operation either succeeds (modifiedCount: 1) or the condition was not met (modifiedCount: 0).

// Optimistic locking: update only if version matches
async function updateWithLock(id, currentVersion, newData) {
  const result = await db.collection('items').updateOne(
    { _id: id, version: currentVersion },  // Version check in filter!
    {
      : { ...newData },
      : { version: 1 }  // Increment version on each update
    }
  );
  if (result.modifiedCount === 0) {
    throw new Error('VERSION_CONFLICT');
    // Another process updated this document since we read it
  }
}

Quick Check

Test your understanding of MongoDB & NoSQL Databases concepts from this lesson.

Lesson Recap

In this lesson you learned: $inc atomically increments or decrements a numeric field—ideal for counters, view counts, and inventory decrements combined with a stock-check filter to prevent over-selling, $mul multiplies a field by a factor for bulk price adjustments and scaling operations, and $min/$max conditionally update only when the new value is smaller/larger than the current—perfect for tracking all-time records, high scores, and earliest/latest timestamps. Next up we explore array update operators: $push, $pull, and $addToSet.

자주 묻는 질문

“증가, 곱셈, 최솟값/최댓값 연산자” 강의는 무료인가요?

네 — “증가, 곱셈, 최솟값/최댓값 연산자” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 MongoDB Academy 강의 전체를 잠금 해제할 수 있습니다. MongoDB Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“증가, 곱셈, 최솟값/최댓값 연산자”에서 뭘 배우나요?

$inc, $mul, $min, $max 업데이트 연산자를 사용하여 숫자 값을 원자적으로 변경합니다. 브라우저에서 직접 실행하는 실습 코드로 MongoDB Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

MongoDB Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 MongoDB Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“증가, 곱셈, 최솟값/최댓값 연산자” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 MongoDB Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 MongoDB Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. $set 및 $unset을 사용한 updateOne과 updateMany
  2. 증가, 곱셈, 최솟값/최댓값 연산자
  3. 배열 업데이트 연산자: $push, $pull, $addToSet
  4. deleteOne과 deleteMany로 안전하게 문서 삭제하기
← MongoDB Academy(으)로 돌아가기