블로그

죽은 연결 없이 데이터베이스 깨우기

Mythex에서 게시된 앱과 그 데이터베이스는 아무도 사용하지 않으면 잠듭니다. 앱이 깨어날 때, 잠든 동안 죽어 버린 데이터베이스 연결을 재사용하려고 해서 첫 요청을 10~12초 동안 붙잡고 있을 수 있었습니다. 이제 Spring Boot 앱에는 절전 후에도 안전한 데이터베이스 설정을 제공하고, 에이전트에게는 모든 스택의 앱을 같은 방식으로 빌드하라고 알려 줍니다. 호스팅 플랫폼에서 깨어난 뒤 첫 요청은 12.7초에서 2.3초가 되었습니다.

앱과 데이터베이스가 잠드는 이유

게시된 Mythex 앱은 몇 분 동안 유휴 상태가 되면 일시 정지되는 머신에서 실행됩니다. 그리고 그 데이터베이스는 5분 유휴 후 잠들고 다음 요청에 깨어나는 서버리스 Postgres입니다. 이것이 조용한 앱을 저렴하게 유지합니다. 데이터베이스 비용은 실제로 사용한 만큼 청구되며, 잠든 앱은 컴퓨트에 아무 비용도 쓰지 않습니다. 청구 쪽 이야기는 데이터베이스를 실제 사용량 기준으로 청구하는 방식에서 다뤘습니다.

문제는 둘이 따로 잠든다는 것이고, 연결이 무엇인지에 대해 서로 동의하지 않는다는 것입니다.

증상: 첫 방문이 느림

대부분의 서버 앱은 연결 풀을 유지합니다. 즉, 요청마다 새 연결을 여는 비용을 치르지 않도록 몇 개의 데이터베이스 연결을 열어 두고 대기시킵니다. 항상 켜져 있는 앱에는 올바른 설계입니다.

잠들면 이런 일이 일어납니다.

  1. 앱이 실행 중이고, 풀에는 유휴 연결이 몇 개 있습니다.
  2. 아무도 방문하지 않습니다. 앱의 머신이 일시 정지됩니다. 풀은 그대로인 채로 얼어붙습니다.
  3. 데이터베이스가 잠들고, 연결의 자기 쪽 끝을 모두 끊어 버립니다.
  4. 방문자가 옵니다. 앱은 정확히 그대로 깨어나지만, 이미 데이터베이스가 더 이상 알지 못하는 연결들을 여전히 들고 있습니다.
  5. 풀은 그중 하나를 내줍니다. 그러면 앱은 그것이 죽었다는 것을 — 시도해 보고 기다린 뒤에야 — 알아내야 하고, 그 뒤에야 포기할 수 있습니다.

Spring Boot 앱에서는 죽은 연결을 검증하는 동안 첫 요청이 약 10초 동안 막혔고, 그 뒤 “Connection is not available”로 실패했습니다. 방문자에게는 사이트가 그냥 멈춰 있다가, 결국 오류를 보여 주는 것처럼 보였습니다.

왜 Spring 앱이 가장 큰 피해를 봤나

Spring Boot는 널리 쓰이는 Java 프레임워크이고, 기본적으로 HikariCP라는 연결 풀을 사용합니다. Spring은 설정을 통해 이 풀을 구성할 수 있으며, 유휴 연결을 몇 개 유지할지도 포함됩니다.

하지만 Mythex는 모든 앱에 데이터베이스를 postgresql://user:pass@host/db 형식의 단일 DATABASE_URL로 제공합니다. Spring은 이 형식을 직접 읽을 수 없습니다. 그래서 Mythex에서 만든 Spring 앱은 그 URL을 파싱해서 풀을 코드로 직접 만들고 있었습니다. 그리고 손으로 만든 풀은 Spring의 spring.datasource.* 설정을 모두 무시합니다. 누군가 올바른 풀 설정을 알고 있었더라도, 그 설정이 적용될 곳이 없었습니다.

수정 1부: Spring이 사용할 수 있는 설정

이제 프로젝트에 데이터베이스가 있으면, Spring이 기본적으로 읽는 환경 변수도 함께 제공합니다.

  • SPRING_DATASOURCE_URL, SPRING_DATASOURCE_USERNAME, SPRING_DATASOURCE_PASSWORD. DATABASE_URL에서 변환하며, URL에 달리 명시되지 않으면 SSL을 요구합니다.
  • 잠들어도 살아남는 풀 설정:
설정값이유
최소 유휴 연결 수0요청 사이에 아무것도 열어 두지 않아, 낡을 연결이 없습니다
유휴 시간 제한30초쓰지 않는 연결을 빨리 닫습니다
최대 수명2분오래되기 전에 모든 연결을 폐기합니다
연결 시간 제한5초데이터베이스 응답이 느릴 때 요청을 오래 붙잡지 않습니다

유휴 연결을 전혀 들고 있지 않으므로, 깨어난 앱은 죽은 연결을 헤치고 나아가는 대신 새 연결 하나를 엽니다.

이 변수들이 있으면 Spring은 풀을 직접 구성하며, 설정이 적용됩니다. 프로젝트가 직접 설정한 값은 항상 우선하며, 우리는 빠진 것만 채웁니다. Spring이 아닌 앱은 이 변수를 무시합니다.

거의 아무 효과도 없을 뻔했던 세부 사항

Spring은 환경 변수를 설정에 대응시키는 규칙이 있는데, 쉽게 틀릴 수 있습니다. minimum-idle 설정은 SPRING_DATASOURCE_HIKARI_MINIMUMIDLE로 써야 합니다. 대시를 빼고 단어를 붙여 씁니다. 더 자연스러워 보이는 SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE로 쓰면, 존재하지 않는 다른 설정(minimum.idle)에 매핑되어 조용히 무시됩니다.

이런 실수는 모든 리뷰를 통과하면서도 프로덕션에서는 아무 일도 하지 않습니다. 그래서 테스트는 값이 설정되었는지와, 어떤 변수도 밑줄로 구분된 형태를 쓰지 않는지를 모두 확인합니다.

수정 2부: 그 밖의 모든 스택

Spring만 풀을 가진 프레임워크는 아닙니다. 모든 언어의 모든 라이브러리를 미리 설정해 둘 수는 없습니다. 그래서 여러분의 앱을 작성하는 AI인 Mythex 에이전트는 이제 모든 스택에 대해 상시 지침을 받습니다. 게시된 앱과 그 데이터베이스는 유휴 상태에서 잠들므로, 열어 둔 데이터베이스 연결은 다음 요청에서 죽어 있을 수 있습니다. 에이전트는 다음을 해야 합니다.

  • 유휴 데이터베이스 연결을 열어 두지 않거나, 사용하기 전에 각각을 확인합니다.
  • 연결 오류가 나면 한 번 재시도합니다.
  • DATABASE_URL을 파싱해서 자체 풀을 만드는 대신, 환경에서 플랫폼이 제공하는 데이터베이스 설정을 사용합니다.

마지막 항목은 1부와 맞물립니다. 제공된 설정을 사용하는 앱은 잠듦에 안전한 구성을 자동으로 받습니다.

결과

우리가 게시된 앱을 운영하는 호스팅 플랫폼에서 측정한 결과, 깨어난 뒤 첫 요청은 12.7초에서 2.3초가 되었습니다.

우리가 얻은 교훈

  • 잠드는 모든 계층은 무엇이 살아남는지에 동의해야 합니다. 얼어붙은 앱과 잠든 데이터베이스는 각각 합리적인 선택을 했습니다. 함께 놓이자, 앱은 더 이상 참이 아닌 상태를 들고 깨어났습니다.
  • 항상 켜져 있는 서버를 위해 설계된 기본값은 제로로 축소되는 앱에 맞지 않습니다. 연결을 따뜻하게 유지하는 것은 주변의 모든 것이 식어 버리기 전까지는 좋은 생각입니다.
  • 프레임워크에는 자기 형식으로 데이터를 주세요. URL 하나는 우리에게는 단순했지만, 모든 Spring 앱이 자기 설정을 무시하는 풀을 손으로 만들게 했습니다.
  • 설정 이름의 철자가 프레임워크가 읽는 방식과 맞는지 테스트하세요. 잘못된 이름 아래의 올바른 값은 조용히 아무 일도 하지 않습니다.

Mythex에서 데이터베이스가 어떻게 작동하는지 알고 싶다면, 데이터베이스 문서와 Postgres란 무엇인가 설명을 참고하세요.

Mythex · Pricing · AI로 무엇이든 만들기 · 웹사이트 및 앱 템플릿 · Mythex 비교 · AI 앱 개발 용어집 · 무료 AI 앱 개발 도구 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)