← 블로그 목록
AI 코딩Cursor배포 오류인증DB 마이그레이션

AI 코딩 할 때 자주 깨지는 10가지: 배포·인증·DB 마이그레이션 실전 트러블슈팅

Cursor·Claude Code로 작성한 코드가 프로덕션에서 실패하는 10가지 전형적 지점. 배포·인증·DB 마이그레이션 트러블슈팅 가이드와 재현 가능한 해결책.

Vibeollio 팀-

AI 코딩 할 때 자주 깨지는 10가지: 배포·인증·DB 마이그레이션 실전 트러블슈팅

포스트 대표 이미지

Cursor나 Claude Code로 코드를 작성하면 개발 환경에서는 잘 돌아가는데 프로덕션 배포 후 갑자기 터지는 경험, 많이 하셨나요? AI 코딩 도구가 강력해졌지만, 실제 운영 환경의 복잡성까지 완벽히 반영하지 못하는 경우가 많습니다. 이 글에서는 실제 프로젝트에서 자주 마주치는 10가지 실패 지점과 각각의 해결책을 정리했습니다.

1. 환경 변수 누락으로 인한 인증 실패

섹션 이미지

AI 코딩 도구는 코드 샘플에서 process.env.API_KEY 같은 환경 변수를 참조하는 코드를 생성합니다. 하지만 실제 배포 환경에서 이 변수가 설정되지 않으면 undefined로 전달되어 API 호출이 실패합니다.

전형적인 시나리오:

// AI가 생성한 코드
const apiKey = process.env.STRIPE_API_KEY;
const stripe = require('stripe')(apiKey);
// 배포 후: TypeError: apiKey is undefined

해결책:

  • .env.example 파일을 프로젝트에 포함하고 필수 환경 변수 목록을 명시
  • 배포 스크립트에서 환경 변수 검증 단계 추가
if (!process.env.STRIPE_API_KEY) {
  throw new Error('STRIPE_API_KEY is required');
}
  • Docker 또는 CI/CD 파이프라인에서 환경 변수 주입 확인

2. 데이터베이스 연결 타임아웃

AI 도구가 생성한 DB 연결 코드는 종종 타임아웃 설정을 생략합니다. 로컬 개발 환경에서는 빠르지만, 프로덕션의 네트워크 지연이나 DB 부하로 인해 무한 대기 상태에 빠집니다.

전형적인 시나리오:

// AI가 생성한 기본 연결
const connection = await mongoose.connect(mongoUri);
// 타임아웃 설정 없음 → 30초 이상 응답 없으면 클라이언트 요청 실패

해결책:

const connection = await mongoose.connect(mongoUri, {
  serverSelectionTimeoutMS: 5000,
  socketTimeoutMS: 45000,
  connectTimeoutMS: 10000
});

3. 마이그레이션 스크립트의 트랜잭션 미처리

섹션 이미지

AI가 생성한 DB 마이그레이션 코드는 종종 단일 쿼리만 실행합니다. 여러 테이블을 수정해야 할 때 중간에 실패하면 데이터 무결성이 깨집니다.

전형적인 시나리오:

-- AI가 생성한 마이그레이션
ALTER TABLE users ADD COLUMN role VARCHAR(50);
UPDATE users SET role = 'admin' WHERE id = 1;
-- 두 번째 쿼리 실패 시 테이블은 이미 변경됨

해결책:

// 트랜잭션으로 래핑
await db.transaction(async (trx) => {
  await trx.schema.table('users', (table) => {
    table.string('role');
  });
  await trx('users').where('id', 1).update({ role: 'admin' });
});

4. CORS 정책 위반

AI 코드는 로컬 개발 환경에 맞춰 localhost:3000에서만 CORS를 허용하도록 생성합니다. 실제 프로덕션 도메인이 다르면 브라우저가 요청을 차단합니다.

전형적인 시나리오:

// AI가 생성한 CORS 설정
app.use(cors({ origin: 'http://localhost:3000' }));
// 프로덕션 도메인 https://myapp.com에서 요청 → 차단됨

해결책:

const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [];
app.use(cors({
  origin: (origin, callback) => {
    if (allowedOrigins.includes(origin) || !origin) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  }
}));

5. 파일 업로드 경로의 절대/상대 경로 혼용

AI가 생성한 파일 업로드 코드는 상대 경로(./uploads)를 사용합니다. 서버가 다른 디렉토리에서 시작되거나 컨테이너 환경에서 실행되면 파일이 저장되지 않습니다.

전형적인 시나리오:

// AI가 생성한 코드
const uploadDir = './uploads';
fs.writeFileSync(`${uploadDir}/${filename}`, data);
// Docker 컨테이너에서는 ./uploads가 존재하지 않음

해결책:

const path = require('path');
const uploadDir = path.join(__dirname, '..', 'uploads');
if (!fs.existsSync(uploadDir)) {
  fs.mkdirSync(uploadDir, { recursive: true });
}
fs.writeFileSync(path.join(uploadDir, filename), data);

6. 비동기 작업의 에러 핸들링 부재

AI 코드는 async/await를 생성하지만 .catch() 또는 try-catch를 빠뜨립니다. 프로덕션에서 예상치 못한 에러가 발생하면 전체 애플리케이션이 중단됩니다.

전형적인 시나리오:

// AI가 생성한 코드
app.get('/data', async (req, res) => {
  const data = await fetchExternalAPI();
  res.json(data);
});
// API 호출 실패 → 에러 핸들링 없음 → 500 에러

해결책:

app.get('/data', async (req, res) => {
  try {
    const data = await fetchExternalAPI();
    res.json(data);
  } catch (error) {
    console.error('API fetch failed:', error);
    res.status(500).json({ error: 'Failed to fetch data' });
  }
});

7. 데이터베이스 인덱스 누락

AI가 생성한 마이그레이션은 컬럼만 추가하고 인덱스를 생성하지 않습니다. 데이터가 많아지면서 쿼리 성능이 급격히 저하됩니다.

전형적인 시나리오:

// AI가 생성한 마이그레이션
await knex.schema.table('orders', (table) => {
  table.string('customer_id');
});
// customer_id로 검색하는 쿼리가 느려짐

해결책:

await knex.schema.table('orders', (table) => {
  table.string('customer_id');
  table.index('customer_id');
});

8. JWT 토큰 만료 시간 설정 불일치

AI가 생성한 인증 코드는 토큰 발급 시 만료 시간을 설정하지만, 검증할 때는 다른 설정을 사용합니다. 프로덕션에서 토큰이 예상과 다르게 만료됩니다.

전형적인 시나리오:

// 발급
const token = jwt.sign(payload, secret, { expiresIn: '7d' });

// 검증 (다른 파일)
jwt.verify(token, secret, { expiresIn: '1h' }); // 불일치

해결책:

const TOKEN_EXPIRY = '7d';

// 발급
const token = jwt.sign(payload, secret, { expiresIn: TOKEN_EXPIRY });

// 검증
jwt.verify(token, secret); // 만료 시간은 토큰에 포함됨

9. 환경별 설정 파일 미분리

AI는 하나의 config.js 파일에 모든 설정을 넣습니다. 개발·스테이징·프로덕션 환경에서 다른 값이 필요하면 배포할 때마다 코드를 수정해야 합니다.

전형적인 시나리오:

// config.js
module.exports = {
  dbUrl: 'mongodb://localhost:27017/dev',
  apiUrl: 'http://localhost:3000'
};
// 프로덕션에서도 localhost로 연결됨

해결책:

// config.js
const env = process.env.NODE_ENV || 'development';
const configs = {
  development: {
    dbUrl: 'mongodb://localhost:27017/dev',
    apiUrl: 'http://localhost:3000'
  },
  production: {
    dbUrl: process.env.DATABASE_URL,
    apiUrl: 'https://myapp.com'
  }
};
module.exports = configs[env];

10. 캐시 무효화 로직 부재

AI가 생성한 캐싱 코드는 데이터를 캐시하지만, 데이터 업데이트 후 캐시를 지우지 않습니다. 사용자가 오래된 데이터를 계속 보게 됩니다.

전형적인 시나리오:

// AI가 생성한 코드
app.get('/user/:id', async (req, res) => {
  const cached = cache.get(`user:${req.params.id}`);
  if (cached) return res.json(cached);
  
  const user = await db.users.findById(req.params.id);
  cache.set(`user:${req.params.id}`, user);
  res.json(user);
});

app.put('/user/:id', async (req, res) => {
  await db.users.update(req.params.id, req.body);
  // 캐시 무효화 없음 → GET 요청은 여전히 옛 데이터 반환
});

해결책:

app.put('/user/:id', async (req, res) => {
  await db.users.update(req.params.id, req.body);
  cache.delete(`user:${req.params.id}`); // 캐시 무효화
  res.json({ success: true });
});

실전 체크리스트

AI 코딩 도구로 작성한 코드를 배포하기 전에 확인할 항목:

  • 모든 환경 변수가 .env.example에 문서화되어 있는가?
  • DB 연결에 타임아웃이 설정되어 있는가?
  • 마이그레이션이 트랜잭션으로 래핑되어 있는가?
  • CORS, 파일 경로 등이 환경별로 설정되어 있는가?
  • 비동기 작업에 에러 핸들링이 있는가?
  • 중요 쿼리에 인덱스가 있는가?
  • 인증 관련 설정이 일관되게 적용되어 있는가?
  • 캐시 무효화 로직이 구현되어 있는가?

프로젝트에서 배운 점을 공유하세요

AI 코딩 도구를 사용하면서 마주친 다른 버그나 해결책이 있다면, Vibeollio에 프로젝트 등록해서 커뮤니티와 나눠보세요. 실제 프로덕션 환경에서의 경험담은 다른 개발자들의 배포 실패를 줄이는 데 큰 도움이 됩니다. 특히 특정 프레임워크(Django, Rails, Spring 등)에서의 AI 코딩 문제나 특이한 트러블슈팅 사례는 더욱 가치 있습니다.

AI 코딩 할 때 자주 깨지는 10가지: 배포·인증·DB 마이그레이션 실전 트러블슈팅 | Vibeollio