Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions cosmosdb/nodejs/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,130 @@ spec:

---

## 3. 다중 클라이언트를 통한 랜덤 라우팅 전략

### 문제점
Section 1의 `preferredLocations` 방식은 첫 번째 리전에서 우선적으로 읽기를 수행하고, 장애 시에만 다음 리전으로 **순차적으로 failover**를 수행합니다. 이로 인해:
- ❌ 정상 상황에서는 첫 번째 리전에 부하가 집중됨
- ❌ Replica를 균등하게 활용하지 못함
- ❌ 특정 리전의 부하가 높아질 때 대응이 어려움

### 해결 방안
여러 개의 CosmosDB 클라이언트를 생성하되, 각 클라이언트마다 다른 우선순위의 `preferredLocations`를 설정합니다. 그런 다음 요청마다 **랜덤하게 클라이언트를 선택**하여 읽기 트래픽을 모든 Replica에 고르게 분산시킵니다.

### 구현 코드

#### 3.1. 다중 클라이언트 생성
```javascript
import { CosmosClient } from '@azure/cosmos';
import { DefaultAzureCredential } from '@azure/identity';

// 사용할 리전 목록
const regions = process.env.COSMOS_PREFERRED_LOCATIONS
? process.env.COSMOS_PREFERRED_LOCATIONS.split(',').map(region => region.trim())
: ['West US', 'East US', 'North Europe'];

const credential = new DefaultAzureCredential();
const endpoint = process.env.COSMOS_ENDPOINT;

// 각 리전을 우선순위로 하는 클라이언트 배열 생성
const cosmosClients = regions.map((region, index) => {
// 현재 리전을 첫 번째로, 나머지를 순환 배치
const preferredLocations = [
region,
...regions.slice(index + 1),
...regions.slice(0, index)
];

return new CosmosClient({
endpoint,
aadCredentials: credential,
connectionPolicy: { preferredLocations }
});
});

// 랜덤 클라이언트 선택 함수
function getRandomClient() {
const randomIndex = Math.floor(Math.random() * cosmosClients.length);
return cosmosClients[randomIndex];
}
```

#### 3.2. 라우팅 래퍼 함수
```javascript
// 읽기 요청용 랜덤 라우팅
async function queryWithRandomRouting(databaseId, containerId, querySpec) {
const client = getRandomClient();
const container = client.database(databaseId).container(containerId);
const { resources } = await container.items.query(querySpec).fetchAll();
return resources;
}

// 쓰기 요청은 첫 번째 클라이언트 사용 (모든 클라이언트가 동일한 Write Region으로 전송)
async function createItemWithRouting(databaseId, containerId, item) {
const client = cosmosClients[0]; // 쓰기는 항상 첫 번째 클라이언트 사용
const container = client.database(databaseId).container(containerId);
const { resource } = await container.items.create(item);
return resource;
}
```

#### 3.3. 실제 사용 예시
```javascript
// Express.js 예시
import express from 'express';

const app = express();

app.get('/users/:id', async (req, res) => {
try {
const querySpec = {
query: 'SELECT * FROM c WHERE c.id = @id',
parameters: [{ name: '@id', value: req.params.id }]
};

// 랜덤하게 선택된 클라이언트로 읽기 수행
const users = await queryWithRandomRouting('mydb', 'users', querySpec);
res.json(users[0]);
} catch (error) {
res.status(500).json({ error: error.message });
}
});

app.post('/users', async (req, res) => {
try {
// 쓰기는 첫 번째 클라이언트로 수행
const newUser = await createItemWithRouting('mydb', 'users', req.body);
res.status(201).json(newUser);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
```

### 동작 원리
1. **클라이언트 생성**: 3개 리전이 있다면 3개의 클라이언트를 생성
- Client 1: `preferredLocations: ['West US', 'East US', 'North Europe']`
- Client 2: `preferredLocations: ['East US', 'North Europe', 'West US']`
- Client 3: `preferredLocations: ['North Europe', 'West US', 'East US']`
2. **랜덤 라우팅**: 각 읽기 요청마다 클라이언트를 랜덤하게 선택
3. **부하 분산**: 통계적으로 모든 리전에 읽기 트래픽이 균등하게 분산
4. **자동 Failover**: 각 클라이언트는 여전히 SDK의 Failover 기능 활용

### 효과
✅ 모든 Replica에 읽기 트래픽 균등 분산
✅ 특정 리전 부하 집중 문제 해결
✅ SDK의 자동 Failover 기능 유지
✅ 코드 레벨에서 간단하게 구현 가능
✅ Deployment 분리 없이 단일 애플리케이션에서 구현 가능

### 주의사항
⚠️ **연결 수 증가**: 클라이언트 수만큼 연결이 생성되므로 리소스 사용량 고려 필요
⚠️ **메모리 사용**: 각 클라이언트가 독립적인 캐시와 메타데이터를 유지
⚠️ **쓰기 처리**: 쓰기는 모든 클라이언트에서 동일한 Write Region으로 전송되므로 첫 번째 클라이언트 사용 권장

---

## 참고 자료
- [Cosmos DB Connection Policy](https://learn.microsoft.com/javascript/api/@azure/cosmos/connectionpolicy)

Expand Down