pool 객체를 트랜잭션과 함께 쓰지마라.
Do not use pool.query if you need transactional integrity: the pool will dispatch every query passed to pool.query on the first available idle client. Transactions within PostgreSQL are scoped to a single client and so dispatching individual queries within a single transaction across multiple, random clients will cause big problems in your app and not work. For more info please read transactions.
- node-postgre docs
PostgreSQL 에서 모든 트랜잭션은 단일 클라이언트 범위 내에서 이뤄진다.
connection pool 은 모든 pool.query 를 1번째로 놀고있는 클라이언트에 dispatch 하게 되어있다.
따라서 단일 트랜잭션 내에서 여러, 랜덤한 클라이언트로 쿼리 내용을 displatch 하는 것은 매우 큰 문제를 야기한다.
Dispatch?
메모리기반의 데이터를 다루는 Object가 버퍼링과 노출 (최종 execute)하여 메모리 다음 블록에 적용하는 것.
=> 트랜잭션은 최종 COMMIT이나 ROLLBACK 하기 전에 모든 쿼리를 미리 연속된 블록에 써둔다.
이 쓰는 동작을 'dispatch' 라고 한다.
pool vs poolCLient
pool (⚠️) 위험한 코드 사례
커넥션 풀 객체
pool.query() 는 트랜잭션 내에 써선 안된다.
const { Pool } = require('pg')
const pool = new Pool();
await pool.query('BEGIN');
// 아래 2개의 query는
// 놀고 있는 1번째 클라이언트에 dispatch 한다. (블록에 query 기록)
await pool.query(renameUserQuery, [
id, 'new_name'
]);
await pool.query(renewModifiedDatetime, [
new Date()
]);
// 앞서 dispatch 한 모든 쿼리를 COMMIT
await pool.query('COMMIT')
pool.query 의 장점은 내부적으로 acquire / release 를 한번에 해준다는 것이다.
그러나 다음과 같은 케이스에서 매우 위험하다.
`renameUserQuery` 를 할 때의 디스패치 할 클라이언트와 `renewModifiedDatetime` 할 때 디스패치 할 클라이언트가 다를 수 있다.
따라서 트랜잭션 사용시 pool 대신 일반 poolClient 를 쓰는 것이 안전하다.
poolClient ✅
커넥션 풀로부터 받아온 커넥션 객체로,
트랜잭션과 사용하기 적절하다.
단, try ~ catch ~ finally 를 통해 에러 핸들링을 하고, `release()`를 반드시 해줘야한다.
(내부적으로 client 를 acquire ~ release 를 자동으로 해주지 않음)
const { Pool } = require('pg')
const pool = new Pool();
(async ()=>{
// 커넥션 풀로부터 단일 클라이언트를 빌려온다.
// => 앱에서 여러 쿼리를 실행해도 독립된 클라이언트 내에서 dispatch 가 일어난다.
const poolClient = pool.connect();
try {
await poolClient.query('BEGIN');
await poolClient.query(renameUserQuery, [
id, 'new_name'
]);
await poolClient.query(renewModifiedDatetime, [
new Date()
]);
// 앞서 dispatch 한 모든 쿼리를 COMMIT
await poolClient.query('COMMIT');
} catch(e) {
await poolClient.query('ROLLBACK');
} finally {
// 명시적 해제
poolClient.release();
}
})();
📝 결론
poolClient 를 pool 로부터 받아와서 사용하라. (pool 객체를 그대로 사용하길 지양할 것)
🔗 Reference
'DataBase > PostgreSQL' 카테고리의 다른 글
[PostgreSQL] CHECK Constraint (0) | 2022.05.04 |
---|---|
[PostgreSQL] Trigger + Transaction (0) | 2022.04.18 |
[PostgreSQL] Function (feat. trigger) (0) | 2022.03.29 |
[PostgreSQL] Notify (0) | 2022.03.25 |
[PostgreSQL] Transaction 왜, 언제, 어떻게 동작하나? (0) | 2022.03.25 |