레이블이 Node.js인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Node.js인 게시물을 표시합니다. 모든 게시물 표시

20210908

Prisma ORM 써본 1년 회고

Prisma를 node 서비스 한 개에 본격 도입한 지 정확히 1년 됐다. 2.0 GA 직후 쓰기 시작했으니까. 지금은 2.30 정도. 매월 버전 빠르게 올라오는 중. 회고 정리.

좋았던 것

타입 생성이 진짜 강력하다. schema.prisma 한 파일이 DB 스키마와 TS 타입의 single source of truth가 된다. migration 돌리면 TS 타입이 같이 업데이트되니까 drift가 안 난다. TypeORM 쓸 때 겪던 "런타임 가서야 컬럼 이름 틀린 거 안다" 같은 문제 사라짐.

쿼리 API가 명시적. prisma.order.findMany({ where, include })처럼 인자 구조로 쓰니까 오토컴플릿 안내 보면서 짜게 됨. ORM 메서드 체이닝보다 훨씬 안정적.

마이그레이션 툴 좋음. prisma migrate dev로 스키마 변경하면 SQL까지 보여주고 확인. git으로 추적되기 때문에 리뷰하기 쉽다.

걸렸던 것 — 솔직하게

복잡한 쿼리는 힘들다. 서브쿼리, window function, recursive CTE 필요한 순간 Prisma로는 못 쓴다. $queryRaw로 빠지는데 그럼 타입 이득이 줄어듦. 우리는 조회 API 중 20% 정도는 raw sql이 됐고, 이 부분만 따로 관리하게 됨. 처음부터 "복잡한 쿼리는 raw로 간다"로 선을 그었어야 함.

connection 관리. 서버리스 환경에서 Prisma Client 인스턴스 재생성되면 커넥션 누수 쉽게 남. 이걸 모르고 AWS Lambda에 그대로 쓰다가 RDS 커넥션 상한 맞음. 글로벌 싱글턴 패턴 강제 필요. 지금은 Prisma Data Proxy라는 게 프리뷰로 나왔는데 아직 안 써봄.

N+1. include로 relation 먹는 건 편하지만, 여러 레이어 깊어지면 쿼리 수가 폭발. 2.16쯤부터 findMany + in 조합으로 최적화 들어갔지만 여전히 조심스럽게 써야 함.

migrate deploy의 롤백. 없다. 사실상 forward-only. 롤백이 필요하면 역마이그레이션 직접 짜야 함. 이건 도입 전 몰랐던 부분이라 운영 중 한 번 프로덕션에서 당황했음.

그래서 다시 선택하겠는가

Node 기반 서비스면 yes. TypeORM 대비 장점이 너무 명확하다. 단, 팀에 SQL 잘 쓰는 사람이 있어야 한다. Prisma만 안다고 복잡한 DB 설계는 못 한다. Prisma는 클라이언트일 뿐이고 DB 설계는 따로 해야 한다는 마인드로 써야 함.

뭘 해봐도 결국 ORM은 taste의 영역인 것 같다. 팀이 편한 거 쓰면 된다.

20170117

Async/Await on Node 7 — production memo

Node 7.x에서 --harmony 없이 async/await 쓸 수 있게 됨. 6.10 LTS에선 아직 flag 필요하긴 한데, 7.6부터는 기본 활성화. 프로덕션 API 하나 이걸로 작성하면서 느낀 것들.

promise chain 지옥을 벗어나니까 확실히 코드가 읽힘:

// before (promise chain)
function getOrder(id) {
  return db.Order.findById(id)
    .then(order => {
      return db.User.findById(order.userId)
        .then(user => ({ order, user }));
    })
    .then(({ order, user }) => {
      return api.tracking(order.shipmentId)
        .then(tracking => ({ order, user, tracking }));
    });
}

// after
async function getOrder(id) {
  const order = await db.Order.findById(id);
  const user = await db.User.findById(order.userId);
  const tracking = await api.tracking(order.shipmentId);
  return { order, user, tracking };
}

운영에서 배운 것들:

1. error handling은 try/catch. 안 감싸면 unhandledRejection 뜨고 메모리 새기 시작. express 핸들러는 래퍼 하나 만들어서 씀:

const asyncHandler = fn => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

app.get('/orders/:id', asyncHandler(async (req, res) => {
  const result = await getOrder(req.params.id);
  res.json(result);
}));

2. 병렬화 놓치기 쉬움. 위 예시처럼 await를 줄줄이 쓰면 순차 실행이라 느림. 병렬 가능한 건 Promise.all로 묶어야 함:

const [user, tracking] = await Promise.all([
  db.User.findById(order.userId),
  api.tracking(order.shipmentId)
]);

3. 스택 트레이스. async 함수는 예전 callback/promise보다 스택이 잘 남음. 7.x에서 꽤 개선된 편. 그래도 async_hooks 같은 도구는 아직 실험적.

4. V8 버전. Node 7은 단명 (LTS 아님). 6 LTS 쓰려면 --harmony-async-await. 8이 4월 릴리즈 예정이고 곧 LTS 전환될테니 정식 production 이관은 8 나온 다음이 안전.

코드 리뷰 때 await 붙이고 Promise.all로 묶는 타이밍 훈련만 되면 생산성 상승이 체감됨. callback hell 시절에서 정말 많이 왔다.

20141024

CasperJS로 간단한 스크래핑 파이프라인

특정 사이트에서 주기적으로 데이터 긁어와야 하는 업무가 생김. 그 사이트가 js 렌더링이라 curl로는 안됨. phantomjs 기반 CasperJS로 붙여봄.

var casper = require('casper').create({
    verbose: false,
    pageSettings: {
        userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36'
    }
});

var results = [];

casper.start('https://example.com/list', function() {
    this.waitForSelector('.item-row');
});

casper.then(function() {
    results = this.evaluate(function() {
        var rows = document.querySelectorAll('.item-row');
        return Array.prototype.map.call(rows, function(r) {
            return {
                title: r.querySelector('.title').innerText,
                price: r.querySelector('.price').innerText
            };
        });
    });
});

casper.run(function() {
    require('fs').write('out.json', JSON.stringify(results), 'w');
    this.exit();
});

파이프라인:

cron → casperjs script.js → out.json → node에서 읽어서 postgres에 insert. 단순.

문제들:

1. 메모리 누수. 오래 돌리면 phantomjs 프로세스가 1GB씩 먹음. 일회성으로 exit 하는게 속편함.
2. 사이트가 CAPTCHA 걸면 끝. 실제로 몇 달 후 차단당함ㅠ
3. HTTPS 인증서 에러 (TLS) 뜰 때가 있음. --ignore-ssl-errors=yes 옵션으로 우회.

이 사이트는 robots.txt도 딱히 막혀있진 않지만 업무 관련이라 느리게 돌림. 요청 사이 casper.wait(3000, ...) 딜레이 줌. 매너는 기본.

요즘 나오는 nightmare.js도 슬쩍 보는중. electron 기반이라 phantom 불안정 이슈는 좀 나을듯.