Docs Menu
Docs Home
/
MongoDB Ops Manager
/

MongoDB Atlas를 사용해 견고한 애플리케이션 구축하기

이 페이지의 내용

  • 최신 드라이버 설치
  • 연결 문자열
  • 재시도 가능 쓰기 및 읽기
  • 쓰기 및 읽기 고려
  • Error Handling
  • 회복 탄력성 애플리케이션 예시

MongoDB의 기능을 활용하고 복제본 세트 투표를 정상적으로 처리하는 애플리케이션 코드를 작성하려면 다음을 수행해야 합니다.

  • 최신 드라이버를 설치합니다.

  • 모든 호스트를 지정하는 연결 문자열을 사용합니다.

  • 재시도 가능 쓰기 및 재시도 가능 읽기를 사용합니다.

  • 애플리케이션에 적합한 majority 쓰기 고려와 읽기 고려를 사용합니다.

  • 애플리케이션의 오류를 처리합니다.

먼저 MongoDB 드라이버에서 해당 언어에 맞는 최신 드라이버를 설치합니다. 드라이버는 애플리케이션에서 데이터베이스에 쿼리를 연결하고 전달합니다. 최신 드라이버를 사용하면 최신 MongoDB 기능을 사용할 수 있습니다.

그런 다음 애플리케이션에서 종속성을 가져옵니다.

Maven 를 사용하는 경우 pom.xml 종속성 목록에 다음을 추가합니다.

<dependencies>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>

Gradle을 사용하는 경우, build.gradle 종속성 목록에 다음을 추가합니다:

dependencies {
compile 'org.mongodb:mongodb-driver-sync:4.0.1'
}
// Latest 'mongodb' version installed with npm
const MongoClient = require('mongodb').MongoClient;

배포의 모든 호스트를 지정하는 연결 string 을 사용하여 애플리케이션을 데이터베이스에 연결합니다. 배포에서 복제본 세트 투표 를 수행하고 새 프라이머리가 선택되면 배포의 모든 호스트를 지정하는 연결 string 이 애플리케이션 로직 없이 새 프라이머리를 검색합니다.

다음 중 하나를 사용하여 배포의 모든 호스트를 지정할 수 있습니다.

연결 string 은 옵션, 특히 retryWriteswriteConcern을 지정할 수도 있습니다.

다음도 참조하세요.

연결 string 형식 지정에 대한 도움말은 MongoDB 드라이버 를 사용하여 배포서버에 연결을 참조하세요.

연결 문자열을 사용하여 애플리케이션에서 MongoDB 클라이언트를 인스턴스화합니다.

// Create a variable for your connection string
String uri = "mongodb://[<username>:<password>@]hostname0<:port>[,hostname1:<port1>][,hostname2:<port2>][...][,hostnameN:<portN>]";
// Instantiate the MongoDB client with the URI
MongoClient client = MongoClients.create(uri);
// Create a variable for your connection string
const uri = "mongodb://[<username>:<password>@]hostname0<:port>[,hostname1:<port1>][,hostname2:<port2>][...][,hostnameN:<portN>]";
// Instantiate the MongoDB client with the URI
const client = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true
});

참고

MongoDB 버전 3.6 부터 4.2호환 드라이버 를 사용하는 경우, MongoDB는 기본적으로 쓰기와 읽기를 모두 한 번 재시도합니다.

재시도 가능 쓰기 를 사용하면 특정 쓰기 작업이 실패할 경우 한 번만 다시 시도할 수 있습니다.

애플리케이션이 일시적으로 정상적인 기본 노드를 찾을 수 없는 일시적인 네트워크 오류와 복제본 세트 투표를 처리하기 위해서는 쓰기를 정확히 한 번만 재시도하는 것이 가장 좋습니다. 재시도가 성공하면 작업 전체가 성공하고 오류가 반환되지 않습니다. 작업이 실패하는 경우 다음과 같은 이유 때문일 수 있습니다.

  • 지속적인 네트워크 오류 또는

  • 잘못된 명령입니다.

다음도 참조하세요.

재시도 가능 쓰기 활성화에 대한 자세한 내용은 재시도 가능 쓰기 활성화를 참조하세요.

작업이 실패하면 애플리케이션에서 오류 자체를 처리해야 합니다.

읽기 작업은 MongoDB 버전 3.6 및 4.2호환 드라이버 에서 시작하여 실패할 경우 자동으로 한 번만 재시도됩니다. 읽기 재시도를 위해 애플리케이션을 구성할 필요가 없습니다.

쓰기 고려와 읽기 고려를 사용하여 애플리케이션의 일관성과 가용성을 조정할 수 있습니다. 고려가 엄격할 수록 데이터베이스 작업이 더 강력한 데이터 일관성 보장을 기다리며, 일관성 요구 사항을 완화하면 더 높은 가용성을 제공합니다.

예시

애플리케이션에서 금전적 잔액을 처리하는 경우 일관성이 매우 중요합니다. 오래된 데이터나 롤백될 수 있는 데이터를 읽지 않도록 하기 위해 majority 쓰기 및 읽기 고려 (read concern)를 사용할 수 있습니다.

또는 애플리케이션이 수백 개의 센서에서 초당 온도 데이터를 기록하는 경우, 가장 최근의 판독값이 포함되지 않은 데이터를 읽더라도 걱정하지 않아도 됩니다. 일관성 요구 사항을 완화하여 해당 데이터에 더 빠르게 액세스할 수 있습니다.

연결 URI를 통해 복제본 세트의 쓰기 고려 수준 을 설정할 수 있습니다. string majority 쓰기 고려를 사용하여 데이터가 데이터베이스에 성공적으로 기록되고 지속되는지 확인합니다. 이는 권장되는 기본값이며 대부분의 사용 사례에 충분합니다.

majority와 같이 승인이 필요한 쓰기 고려를 사용하는 경우, 해당 승인 수준에 도달하기 위한 최대 쓰기 시간 제한을 지정할 수도 있습니다.

  • 모든 쓰기에 대한 wtimeoutMS 연결 문자열 매개 변수 또는

  • 단일 쓰기 작업을 위한 wtimeout 옵션입니다.

시간 제한의 사용 여부와 사용하는 값은 애플리케이션의 컨텍스트에 따라 다릅니다.

다음도 참조하세요.

쓰기 고려 (write concern) 수준 설정에 대한 자세한 내용은 쓰기 고려 옵션을 참조하세요.

중요

쓰기에 대한 시간 제한을 지정하지 않았고 쓰기 고려 (write concern) 수준을 달성할 수 없는 경우에는 쓰기 작업이 완료되지 않습니다.

연결 URI를 통해 복제본 string 세트 설정하다 읽기 고려 (read concern) 수준 을 설정할 수 있습니다. 이상적인 읽기 고려 (read concern) 는 애플리케이션 요구 사항에 따라 다르지만 대부분의 사용 사례에는 기본값 으로 충분합니다. 기본값 읽기 고려를 사용하는 데 연결 string 매개변수는 필요하지 않습니다.

읽기 고려 (read concern)를 지정하면 애플리케이션이 데이터베이스에서 수신하는 데이터에 대한 보장을 개선할 수 있습니다.

다음도 참조하세요.

읽기 고려 수준 설정에 대한 자세한 내용은 읽기 고려 옵션을 참조하세요.

참고

애플리케이션 에서 사용하는 쓰기 (write) 및 읽기 고려 (read concern) 의 특정 조합은 작업 순서 보장 에 영향을 줍니다. 이를 인과적 일관성 이라고 합니다. 인과적 일관성 보장 에 대한 자세한 내용은 인과 인과적 일관성 및 읽기 및 쓰기 고려를 참조하세요.

Invalid commands, network outages, and network errors that are not handled by retryable writes return errors. 오류에 대한 자세한 내용은 드라이버의 API 설명서를 참조하세요.

예를 들어 애플리케이션이 중복 _id 이(가) 있는 문서를 삽입하려고 하면 드라이버는 다음을 포함하는 오류를 반환합니다.

Unable to insert due to an error: com.mongodb.MongoWriteException:
E11000 duplicate key error collection: <db>.<collection> ...
{
"name": : "MongoError",
"message": "E11000 duplicate key error collection on: <db>.<collection> ... ",
...
}

적절한 오류 처리가 없으면 오류로 인해 애플리케이션이 다시 시작될 때까지 요청 처리가 차단될 수 있습니다.

애플리케이션은 충돌이나 부작용 없이 오류를 처리해야 합니다. 중복된 _id 을(를) 삽입하는 애플리케이션의 이전 예에서 해당 애플리케이션은 다음과 같이 오류를 처리할 수 있습니다.

// Declare a logger instance from java.util.logging.Logger
private static final Logger LOGGER = ...
...
try {
InsertOneResult result = collection.insertOne(new Document()
.append("_id", 1)
.append("body", "I'm a goofball trying to insert a duplicate _id"));
// Everything is OK
LOGGER.info("Inserted document id: " + result.getInsertedId());
// Refer to the API documentation for specific exceptions to catch
} catch (MongoException me) {
// Report the error
LOGGER.severe("Failed due to an error: " + me);
}
...
collection.insertOne({
_id: 1,
body: "I'm a goofball trying to insert a duplicate _id"
})
.then(result => {
response.sendStatus(200) // send "OK" message to the client
},
err => {
response.sendStatus(400); // send "Bad Request" message to the client
});

이 예시의 삽입 작업은 _id 필드가 고유해야 하기 때문에 두 번째로 호출할 때 "중복 키" 오류가 발생합니다. 애플리케이션은 오류를 포착하고 클라이언트에게 알림을 보내며 앱은 계속 실행됩니다. 그러나 삽입 작업은 실패하므로 사용자에게 메시지를 표시할지, 작업을 다시 시도할지 또는 다른 작업을 수행할지를 결정해야 합니다.

항상 오류를 기록해야 합니다. 추가 처리 오류에 대한 일반적인 전략은 다음과 같습니다.

  • 오류 메시지와 함께 오류를 클라이언트에 반환합니다. 이는 오류를 해결할 수 없어 사용자에게 조치를 완료할 수 없음을 알려야 할 때 좋은 전략입니다.

  • 데이터베이스 백업에 쓰기. 이는 오류를 해결할 수 없지만 요청 데이터가 손실되는 위험을 감수하고 싶지 않을 때 좋은 전략입니다.

  • 1회 기본 재시 도 이후에 작업을 다시 시도합니다. 이는 오류의 원인을 프로그래밍 방식으로 해결한 다음 다시 시도할 수 있는 경우에 좋은 전략입니다.

애플리케이션 컨텍스트에서 가장 적합한 전략을 선택해야 합니다.

예시

중복 키 오류의 경우, 오류를 기록해야 하지만 작업이 실패할 수 있으므로 작업을 다시 시도해서는 안 됩니다. 대신 대체 데이터베이스에 쓰고 나중에 해당 데이터베이스의 내용을 검토하여 정보가 손실되지 않도록 할 수 있습니다. 사용자는 다른 작업을 수행할 필요가 없으며 데이터가 기록되므로 클라이언트에 오류 메시지를 전송하지 않도록 선택할 수 있습니다.

오류를 반환하는 것은 작업이 완료되지 않고 애플리케이션이 새 작업을 실행하지 못하도록 차단할 때 바람직한 동작일 수 있습니다. maxTimeMS 메서드를 사용하여 개별 작업에 시간 제한을 설정하고, 해당 시간 제한을 초과할 경우 애플리케이션에서 처리할 오류를 반환할 수 있습니다.

각 작업에 설정하는 시간 제한은 해당 작업의 컨텍스트에 따라 다릅니다.

예시

애플리케이션에서 inventory 컬렉션의 간단한 제품 정보를 읽고 표시하는 경우, 이러한 읽기 작업은 오래 걸리지 않는다고 가정할 수 있습니다. 비정상적으로 오래 실행되는 쿼리는 지속적인 네트워크 문제가 있음을 나타내는 지표입니다. 이 작업에서 maxTimeMS를 5000, 즉 5초로 설정하면 네트워크 문제가 있다고 확신하는 즉시 애플리케이션이 피드백을 받습니다.

다음 예제 애플리케이션은 복원력이 뛰어난 애플리케이션을 구축하기 위한 권장 사항을 한데 모아 놓은 것입니다.

이 애플리케이션 은 http://localhost:3000 에 두 개의 엔드포인트를 노출하는 간단한 사용자 기록 API 입니다.

메서드
엔드포인트
설명

GET

/users

users 컬렉션에서 사용자 이름 목록을 가져옵니다.

POST

/users

요청 본문에 name이 필요합니다. users 컬렉션에 새 사용자를 추가합니다.

참고

다음 서버 애플리케이션 은 NanoHTTPD 를 사용합니다. 및 실행 하기 전에 프로젝트 JSON 에 종속성으로 추가해야 합니다.

1// File: App.java
2
3import java.util.Map;
4import java.util.logging.Logger;
5
6import org.bson.Document;
7import org.json.JSONArray;
8
9import com.mongodb.MongoException;
10import com.mongodb.client.MongoClient;
11import com.mongodb.client.MongoClients;
12import com.mongodb.client.MongoCollection;
13import com.mongodb.client.MongoDatabase;
14
15import fi.iki.elonen.NanoHTTPD;
16
17public class App extends NanoHTTPD {
18 private static final Logger LOGGER = Logger.getLogger(App.class.getName());
19
20 static int port = 3000;
21 static MongoClient client = null;
22
23 public App() throws Exception {
24 super(port);
25
26 // Replace the uri string with your MongoDB deployment's connection string
27 String uri = "mongodb://<username>:<password>@hostname0:27017,hostname1:27017,hostname2:27017/?retryWrites=true&w=majority";
28 client = MongoClients.create(uri);
29
30 start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
31 LOGGER.info("\nStarted the server: http://localhost:" + port + "/ \n");
32 }
33
34 public static void main(String[] args) {
35 try {
36 new App();
37 } catch (Exception e) {
38 LOGGER.severe("Couldn't start server:\n" + e);
39 }
40 }
41
42 @Override
43 public Response serve(IHTTPSession session) {
44 StringBuilder msg = new StringBuilder();
45 Map<String, String> params = session.getParms();
46
47 Method reqMethod = session.getMethod();
48 String uri = session.getUri();
49
50 if (Method.GET == reqMethod) {
51 if (uri.equals("/")) {
52 msg.append("Welcome to my API!");
53 } else if (uri.equals("/users")) {
54 msg.append(listUsers(client));
55 } else {
56 msg.append("Unrecognized URI: ").append(uri);
57 }
58 } else if (Method.POST == reqMethod) {
59 try {
60 String name = params.get("name");
61 if (name == null) {
62 throw new Exception("Unable to process POST request: 'name' parameter required");
63 } else {
64 insertUser(client, name);
65 msg.append("User successfully added!");
66 }
67 } catch (Exception e) {
68 msg.append(e);
69 }
70 }
71
72 return newFixedLengthResponse(msg.toString());
73 }
74
75 static String listUsers(MongoClient client) {
76 MongoDatabase database = client.getDatabase("test");
77 MongoCollection<Document> collection = database.getCollection("users");
78
79 final JSONArray jsonResults = new JSONArray();
80 collection.find().forEach((result) -> jsonResults.put(result.toJson()));
81
82 return jsonResults.toString();
83 }
84
85 static String insertUser(MongoClient client, String name) throws MongoException {
86 MongoDatabase database = client.getDatabase("test");
87 MongoCollection<Document> collection = database.getCollection("users");
88
89 collection.insertOne(new Document().append("name", name));
90 return "Successfully inserted user: " + name;
91 }
92}

참고

다음 서버 애플리케이션은 Express를 사용하며, 이를 실행하려면 프로젝트에 종속성으로 추가해야 합니다.

1const express = require('express');
2const bodyParser = require('body-parser');
3
4// Use the latest drivers by installing & importing them
5const MongoClient = require('mongodb').MongoClient;
6
7const app = express();
8app.use(bodyParser.json());
9app.use(bodyParser.urlencoded({ extended: true }));
10
11// Use a connection string that lists all hosts
12// with retryable writes & majority write concern
13const uri = "mongodb://<username>:<password>@hostname0:27017,hostname1:27017,hostname2:27017/?retryWrites=true&w=majority";
14
15const client = new MongoClient(uri, {
16 useNewUrlParser: true,
17 useUnifiedTopology: true
18});
19
20// ----- API routes ----- //
21app.get('/', (req, res) => res.send('Welcome to my API!'));
22
23app.get('/users', (req, res) => {
24 const collection = client.db("test").collection("users");
25
26 collection
27 .find({})
28 // In this example, 'maxTimeMS' throws an error after 5 seconds,
29 // alerting the application to a lasting network outage
30 .maxTimeMS(5000)
31 .toArray((err, data) => {
32 if (err) {
33 // Handle errors in your application
34 // In this example, by sending the client a message
35 res.send("The request has timed out. Please check your connection and try again.");
36 }
37 return res.json(data);
38 });
39});
40
41app.post('/users', (req, res) => {
42 const collection = client.db("test").collection("users");
43 collection.insertOne({ name: req.body.name })
44 .then(result => {
45 res.send("User successfully added!");
46 }, err => {
47 // Handle errors in your application
48 // In this example, by sending the client a message
49 res.send("An application error has occurred. Please try again.");
50 })
51});
52// ----- End of API routes ----- //
53
54app.listen(3000, () => {
55 console.log(`Listening on port 3000.`);
56 client.connect(err => {
57 if (err) {
58 console.log("Not connected: ", err);
59 process.exit(0);
60 }
61 console.log('Connected.');
62 });
63});

돌아가기

감사 이벤트