데이터베이스(영어: database, DB)는 여러 사람이 공유하여 사용할 목적으로 체계화해 통합, 관리하는 데이터의 집합이다. 작성된 목록으로써 여러 응용 시스템들의 통합된 정보들을 저장하여 운영할 수 있는 공용 데이터들의 묶음이다. 데이터베이스에 속해있는 모델은 다양하다.
현재까지 앱을 만들었을 때 어떠한 유저를 하나 더 생성하든지 POST 요청을 통해서 데이터를 추가했을 때, 그 당시에는 데이터가 추가되지만 서버를 다시 시작하면 생성한 데이터가 없어지게 된다. 그래서 데이터베이스를 이용해서 영구적으로 데이터를 보관해 줄 필요가 있다.
데이터베이스는 데이터의 집합이며, DBMS는 데이터베이스를 관리하고 운영하는 소프트웨어이다. 이 소프트웨어를 이용해서 데이터를 저장하고 검색하는 기능등을 제공한다.
DBMS는 계층형, 네트워크형, 관계형, 객체형등이 있다.
그 중에서 현재는 관계형(RDBMS, Relational DBMS)를 주로 사용하고 있다.
대표적인 RDBMS에는 Oracle, MySQL, PostgrSQL 등이 있다.
RDBMS 데이터베이스는 테이블을 사용하여 데이터를 저장한다. 테이블은 관련 데이터 항목의 모음이며 데이터를 저장할 행과 열을 포함한다. 각 테이블은 정보가 수집되는 사람, 장소 또는 이벤트와 같은 실제 개체를 나타낸다.
테이블이 여러개가 되면, 아래와 같이 "관계"가 나타나게 된다.
단어 뜻 그 자체를 따지자면 "Not only SQL"로, SQL만을 사용하지 않는 데이터베이스 관리 시스템(DBMS)을 지칭하는 단어이다. 관계형 데이터베이스를 사용하지 않는다는 의미가 아닌, 여러 유형의 데이터베이스를 사용하는 것이다.
NoSQL이라고 하는 말은 대한민국이 아닌 모든 나라를 외국이라고 부르는 것과 비슷하다. 세상에는 한국 말고도 각자의 문화를 가진 수많은 나라가 존재한다. MongoDB에서 사용하는 쿼리 언어와 CouchDB에서 사용하는 쿼리 언어는 서로 전혀 다르다. 그럼에도 이 두 쿼리 언어는 같은 NoSQL 카테고리에 속한다. 어쨌거나 SQL이 아니기 때문이다. 또한 NoSQL이 No RDBMS를 의미하지는 않는다.
NoSQL에 내려진 구체적인 정의는 없으며, MongoDB나 CouchDB등이 NoSQL 이라고 불리는 이유는 둘이 사용하는 쿼리 언어는 다르지만 SQL이 아니기 때문에 NoSQL이라고 불린다.
그래도 NoSQL은 전체적으로 비슷한 특징을 가지고 있다.
이러한 문제점이나 변화를 해결하고 반영하기 위해서 2000년대에 No SQL이 등장
NoSQL 데이터베이스의 공통적인 특징은 스키마 없이 동작한다는 점이다. 따라서 데이터 구조를 미리 정의할 필요가 없으며, 시간이 지나더라도 언제든지 바꿀 수 있기 때문에 비형식적인 데이터를 저장하는 데 용이하다. 하지만 이는 데이터베이스가 스키마를 직접 관리하지 않는 것을 의미할 뿐, 데이터 타입에 따른 암묵적인 스키마는 여전히 존재한다. 이 때문에 단일 값에 대한 데이터 타입에서 불일치가 발생할 수 있다.
예를 들어 필드의 이름을 “Quantity”라고 하기로 했다고 하자. 이것은 앞으로 저장할 때 반드시 필드의 이름을 “Quantity”라고 저장하겠다는 암묵적인 스키마가 된다. 암묵적인 스키마를 무시할 경우 Quantity를 quantity, qty, QUANTITY 등으로 저장할지도 모른다. 뜬금없이 qty, quantity 같은 새로운 필드가 추가되는 것이나 마찬가지이다. 이는 NoSQL 데이터베이스가 암묵적인 스키마에 대해 전혀 알지 못하며, 이를 강제하지 않기 때문에 발생하는 일이다. 그렇게 때문에 NoSQL을 사용할 때에는 주의를 기울일 필요가 있다.
스키마를 SQL보다 훨씬 자유롭게 작성하고 수정할 수 있다.
"Ability to scale"은 시스템의 용량을 늘려 성능을 향상시키거나 더 많은 사용자나 데이터를 처리할 수 있는 능력을 의미한다. 이를 위한 주요 두 가지 방법은 "수직 스케일링"과 "수평 스케일링"이다.
수직 스케일링 (Vertical Scaling)
수평 스케일링 (Horizontal Scaling)
SQL과 NoSQL 데이터베이스의 스케일링 관련 특성은 아래와 같다.
- SQL => Vertical Scale
- ==> 사용하던 것을 장비를 더 좋은 장비로 바꿔야한다. (비용이 많이 든다.)
- NoSQL => Horizontal Scale
- ==> 사용하던 것에 새로운 노드 또는 시스템을 더해준다. (분산 저장을 지원함)
데이터베이스를 선택할 때, 프로젝트의 요구 사항, 데이터의 복잡성, 예상되는 트래픽 및 성장 등 여러 요인을 고려해야 한다. NoSQL이 항상 SQL보다 좋은 선택인 것은 아니며, 반대의 경우도 마찬가지이다. 각 데이터베이스 유형의 장점과 단점을 고려하여 올바른 선택을 해야 한다.
ACID와 BASE는 데이터베이스 트랜잭션의 특성과 일관성을 설명하기 위한 용어로 사용된다. 이 두 용어는 데이터베이스의 일관성과 가용성에 대한 서로 다른 접근 방식을 나타낸다.
ACID (원자성, 일관성, 고립성, 지속성)
ACID는 전통적인 관계형 데이터베이스 시스템 (RDBMS)의 트랜잭션 특성을 설명하기 위해 사용된다.
BASE (기본적으로 사용 가능, 부드러운 상태, 최종적으로 일관성)
BASE는 분산 데이터베이스 시스템, 특히 NoSQL 데이터베이스에서의 일관성과 가용성을 설명하기 위해 사용된다.
요약하면, ACID는 강력한 일관성을 중시하는 반면, BASE는 시스템의 가용성과 확장성을 중시하며, 시간이 지나면서 일관성을 달성하는 것을 목표로 한다.
가용성(可用性, Availability)이란 서버와 네트워크, 프로그램 등의 정보 시스템이 정상적으로 사용 가능한 정도를 말한다. 가동률과 비슷한 의미이다.
트랜잭션은 데이터베이스 용어로, 하나 이상의 연산을 포함하는 단일 논리적 작업 단위를 의미한다. 트랜잭션은 데이터의 일관성과 무결성을 유지하기 위해 설계되었으며, 주로 데이터베이스에서 데이터를 추가, 수정, 삭제 또는 조회할 때 사용된다.
예제로, 은행에서 두 계좌 간의 자금 이체를 생각해보면 좋다. 한 계좌에서 돈을 빼고 다른 계좌에 입금하는 두 개의 연산이 포함된 하나의 트랜잭션이다. 만약 두 번째 연산, 즉 입금 연산이 실패한다면 첫 번째 연산, 즉 출금 연산도 취소되어야 한다. 이렇게 전체 작업이 원자적으로 처리되기 때문에 데이터의 일관성이 유지된다.
이러한 트랜잭션 처리는 데이터의 안정성을 보장하며, 여러 사용자나 응용 프로그램이 동시에 데이터베이스에 액세스할 때 데이터의 무결성을 보호한다.
mongoose.connect('mongodb+srv://<name>:<password>@cluster0.e7emq.mongodb.net/<dbname>?retryWrites=true&w=majority')
.then(() => console.log('MongoDb Connected...'))
.catch(err => console.log(err));
몽구스 ODM이 하는 역할
- 애플리케이션 계층에서 특정 스키마를 적용
- 모델 유효성 검사
- MongoDB 작업을 쉽게 하기 위한 기타 기능
BSON은 "Binary JSON"의 약자로, JSON 데이터를 이진 형식으로 표현하는 방법이다. BSON은 주로 MongoDB와 같은 NoSQL 데이터베이스에서 사용되며, JSON보다 더 효율적인 저장, 검색 및 직렬화, 역직렬화 작업을 가능하게 한다.
BSON의 주요 특징은 다음과 같다.
그러나, BSON은 JSON보다 일반적으로 약간 더 큰 용량을 차지한다는 단점도 있다. 이러한 이유로, BSON은 데이터의 크기나 전송 효율성이 중요한 주요 관심사가 아닌 경우에 JSON을 대체하는 것을 목적으로 하지 않는다. 대신, BSON은 MongoDB와 같은 특정 시스템에서 데이터 저장 및 검색의 효율성과 성능 향상을 목적으로 사용된다.
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
description: {
type: String,
required: true
},
price: {
type: Number
}
})
const Product = mongoose.model("Product", productSchema)
module.exports = Product
const productModel = require('../models/Product');
async function createProduct(req, res, next) {
try {
const createdProduct = await productModel.create(req.body);
res.status(201).json(createdProduct);
} catch (error) {
next(error);
}
};
const express = require('express');
const productsController = require('../controllers/products.controller');
const productsRouter = express.Router();
productsRouter.post('/', productsController.createProduct) // POST localhost:3000/posts
productsRouter.get('/', productsController.getProducts) // GET localhost:3000/posts
productsRouter.get('/:productId', productsController.getProductById)
productsRouter.put('/:productId', productsController.updateProduct) // PUT localhost:3000/products/1
productsRouter.delete('/:productId', productsController.deleteProduct)
async function getProducts(req, res, next) {
try {
const allProducts = await productModel.find({});
res.status(200).json(allProducts);
} catch (error) {
next(error)
}
}
async function getProductById(req, res, next) {
try {
const product = await productModel.findById(req.params.productId);
if (product) {
res.status(200).json(product)
} else {
res.status(404).send()
}
} catch (error) {
next(error)
}
}
async function updateProduct(req, res, next) {
try {
let updatedProduct = await productModel.findByIdAndUpdate(
req.params.productId,
req.body,
{ new: true }
// 업데이트가 적용된 후 document를 반환하려면 new옵션을 true로 설정해야 한다.
)
if (updatedProduct) {
res.status(200).json(updateProduct)
} else {
res.status(404).send();
}
} catch (error) {
next (error)
}
};
async function deleteProduct(req, res, next) {
try {
let deletedProduct = await productModel.findByIdAndDelete(req.params.productId)
if (deletedProduct) {
res.status(200).json(deletedProduct)
} else {
res.status(404).send();
}
} catch (error) {
next(error)
}
};
const app = require('express')();
app.get('*', function(req, res, next) {
// 이렇게 이 미들웨어에 에러가 발생을 하면
// 익스프레스는 이 에러를 에러 처리기(Handler)로 보내준다.
throw new Error('woops');
});
app.get('*', function(req, res, next) {
// 위의 에러가 발생했기 때문에 에러 처리기로 바로 가야 하기
// 때문에 이 미들웨어는 생략해준다.
// 왜냐하면 이 미들웨어는 에러 처리기(Error Handler)가 아니기 때문이다.
console.log('this will nor print')
});
app.use(function(error, req, res, next) {
// 에러 처리기는 이렇게 4개의 인자가 들어간다.
// 그래서 첫 번째 미들웨어에서 발생한 에러 메시지를 이곳에서 처리해준다.
res.json({ message: error.message });
});
app.listen(3000);
원래는 위와 같이 에러처리를 해주면 되지만, 비동기 요청으로 인한 에러를 이렇게 처리해주면 에러 처리기에서 저 에러 메시지를 받지 못하기 때문에 서버가 Crash 되어 버린다.
const app = require('express')();
app.get('*', function(req, res, next) {
// Will crash the server on every HTTP request
setImmediate(() => { throw new Error('woops'); });
});
app.use(function(error, req, res, next) {
// Won't get here, because Express doesn't catch the above error
res.json({ message: error.message });
});
app.listen(3000);
const app = require('express')();
app.get('*', function(req, res, next) {
// Reporting async errors *must* go through `next()`
setImmediate(() => { next(new Error('woops')); });
});
app.use(function(error, req, res, next) {
// Will get here
res.json({ message: error.message });
});
app.listen(3000);