프로덕션 앱을 열었는데 앱이 점점 멈추는 것을 발견했습니다. 프런트엔드가 응답하지 않습니다. 백엔드 API가 시간 초과되었습니다. MongoDB 쿼리가 무기한 실행되는 것으로 보입니다. 귀하의 받은 편지함에는 사용자 불만 사항이 가득합니다. 팀은 상황을 분류하기 위해 함께 모입니다.
거기 가보셨나요? 응, 나도 마찬가지야.
저는 수석 풀 스택 개발자입니다. 단일 사용자로만 사용하거나 문제 공간이 단순하지만 실제 트래픽이나 교통 정체로 인해 시들고 무너지는 앱에는 질렸습니다. 약간 더 까다로운 작업입니다.
저와 함께 계시다면 제가 React, Node.js, MongoDB를 사용하여 이러한 문제를 어떻게 해결했는지 안내해 드리겠습니다.
저는 여러분에게 또 다른 평범하고 오래된 튜토리얼을 제공하는 것이 아니라 이야기를 공유할 것입니다. 실제 문제를 해결하는 방법과 시간의 테스트와 어떤 어려움도 통과할 수 있는 빠르고 확장성이 뛰어난 애플리케이션을 구축하는 방법에 대한 이야기입니다.
1: React가 병목 현상을 일으켰을 때
우리 직장에서 React로 개발된 웹 앱 업데이트를 막 출시했습니다. 우리는 사용자들이 새로운 기능을 높이 평가할 것이라는 자신감이 넘쳤습니다.
그러나 얼마 지나지 않아 불만 사항이 접수되기 시작했습니다. 앱이 매우 느리게 로드되고 전환이 끊기며 사용자의 불만이 점점 커지고 있었습니다. 새로운 기능이 유익하다는 것을 알면서도 의도치 않게 성능 문제가 발생했습니다. 조사 결과 문제가 드러났습니다. 앱이 모든 구성 요소를 단일 패키지로 묶었기 때문에 사용자가 앱에 액세스할 때마다 모든 것을 다운로드해야 했습니다.
수정 사항: Lazy Loading이라는 매우 유용한 개념을 구현했습니다. 이 아이디어는 이전에 접한 적이 있었지만 정확히 우리에게 필요한 것이었습니다. 필요할 때만 필요한 구성 요소만 로드할 수 있도록 앱의 구조를 완전히 개편했습니다.
다음은 이 솔루션을 구현한 방법을 간략하게 보여줍니다.
const Dashboard = React.lazy(() => import('./Dashboard')); const Profile = React.lazy(() => import('./Profile'));Loading...}>
결과: 이 변화의 영향은 그야말로 놀랍습니다. 우리는 번들에서 무려 30%의 축소를 보았고 사용자는 훨씬 더 빠른 초기 로드를 경험했습니다. 가장 좋은 점은 사용자가 앱의 특정 부분이 아직 로드 중이라는 사실을 전혀 몰랐다는 점입니다. 우리는 Suspense를 현명하게 사용하여 방해가 되지 않는 간단한 로딩 메시지를 표시했습니다.
2: React에서 최고의 상태 관리 길들이기
몇 달이 지나면서 우리 개발팀은 발전을 이루었고 많은 새로운 기능을 출시했습니다. 하지만 성장과 함께 우리는 의도치 않게 제가 더 복잡한 앱이라고 부르는 것을 만들기 시작했습니다. Redux는 간단한 상호작용을 촉진하는 보조자가 아니라 곧 책임이 되었습니다.
그래서 더 나은 대안을 위해 POC를 만드는 데 시간을 보냈습니다. 나는 그 내용을 모두 문서화하고 그 접근 방식이 어떤 것인지에 대한 여러 지식 공유 회의를 진행했습니다. 우리는 상태 관리를 위해 제안된 솔루션으로 React Hooks(특히 useReducer)를 그룹으로 사용하기로 결정했습니다. 왜냐하면 궁극적으로 우리는 더 간단한 코드와 더 작은 독립형 코드로 인해 오버헤드가 증가하는 Redux의 대규모 런타임 공간을 줄이고 싶었기 때문입니다. 상태.
이후의 변화는 그야말로 혁명적이었습니다. 우리는 수십 줄의 상용구 코드를 간결하고 이해하기 쉬운 후크 논리로 대체하는 것을 발견했습니다. 다음은 이 새로운 접근 방식을 구현한 방법을 보여주는 예시입니다.
const initialState = { count: 0 }; function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } } const CounterContext = React.createContext(); function CounterProvider({ children }) { const [state, dispatch] = useReducer(reducer, initialState); return ({children} ); }
결과: 이 전환의 영향은 심오하고 광범위했습니다. 우리의 애플리케이션은 훨씬 더 예측 가능해지고 추론하기 쉬워졌습니다. 이제 더 간결하고 직관적인 코드베이스를 통해 우리 팀은 훨씬 더 빠른 속도로 반복할 수 있었습니다. 아마도 가장 중요한 것은 주니어 개발자들이 코드베이스를 탐색하고 이해하는 능력이 눈에 띄게 향상되었다고 보고한 것입니다. 최종 결과는 윈윈(win-win) 상황이었습니다. 유지해야 할 코드가 적고, 스쿼시해야 할 버그가 적으며, 개발 팀이 눈에 띄게 행복하고 생산적이 되었습니다.
3: 백엔드 전쟁터 정복 — 최고의 성능을 위해 Node.js API 최적화
프론트엔드에 많은 개선 사항을 도입할 수 있었지만 백엔드에 여러 문제가 발생한 직후였습니다. 우리의 API 성능은 끔찍해졌고 특히 성능이 극도로 저하되기 시작한 엔드포인트는 거의 없었습니다. 이러한 엔드포인트는 다양한 타사 서비스에 대한 일련의 호출을 수행하며 사용자 기반이 증가함에 따라 시스템이 이 로드를 처리할 수 없었습니다.
잘못된 점은 매우 상식적이었습니다. 우리는 평행하지 않았습니다! 즉, 각 끝점에 대한 요청은 순차적인 방식으로 처리되었습니다. 즉, 모든 다음 호출은 이전 호출이 완료될 때까지 기다립니다. 이 대규모(10만 요청) 시스템에서는 재앙이었음이 입증되었습니다.
해결책: 이 문제를 해결하기 위해 우리는 많은 코드를 다시 작성하고 동시에 API 요청을 생성하기 위해 Promise.all()의 기능을 사용하기로 결정했습니다. 즉, 여러 요청을 실행하면 다음 요청을 실행하기 위해 모든 호출이 끝날 때까지 기다릴 필요가 없습니다.
이를 위해 API 호출을 시작하지 않고, 완료될 때까지 기다리고, 다른 API 호출을 만드는 등의 작업을 수행하지 않습니다…
단순히 Promise.all()을 사용하는 대신 모든 것이 한 번에 훨씬 더 빠르게 시작되었습니다.
다음은 이 솔루션을 구현한 방법을 간략하게 보여줍니다.
const getUserData = async () => { const [profile, posts, comments] = await Promise.all([ fetch('/api/profile'), fetch('/api/posts'), fetch('/api/comments') ]); return { profile, posts, comments }; };
결과: 이 최적화의 효과는 즉각적이고 상당했습니다. 우리는 응답 시간이 50%나 감소하는 것을 확인했으며, 백엔드는 과부하 상태에서 복원력이 크게 향상된 것으로 나타났습니다. 사용자는 더 이상 실망스러운 지연을 경험하지 않았으며 서버 시간 초과 횟수가 크게 감소했습니다. 이러한 개선으로 사용자 경험이 향상되었을 뿐만 아니라 시스템이 성능 저하 없이 훨씬 더 많은 양의 요청을 처리할 수 있게 되었습니다.
4: MongoDB 퀘스트 — 데이터 야수 길들이기
애플리케이션이 인기를 얻고 사용자 기반이 엄청나게 증가함에 따라 우리는 새로운 장애물에 직면해야 했습니다. 데이터를 어떻게 확장할 것인가? 한때 반응이 좋았던 MongoDB 인스턴스는 수백만 개의 문서를 처리해야 할 때 질식하기 시작했습니다. 밀리초 단위로 실행되던 쿼리를 완료하는 데 몇 초가 걸렸거나 시간이 초과되었습니다.
우리는 며칠 동안 MongoDB의 성능 분석 도구를 조사한 결과 가장 큰 악당인 색인화되지 않은 쿼리를 식별했습니다. 가장 일반적인 쿼리(예: 사용자 프로필 요청) 중 일부는 견고한 인덱스를 사용할 수 있는 전체 컬렉션을 검색하는 것이었습니다.
해결책: 우리가 갖고 있는 정보를 바탕으로 우리가 해야 할 일은 가장 많이 요청된 필드에 복합 인덱스를 생성하는 것뿐이며 이렇게 하면 데이터베이스 본문 조회 시간이 영구적으로 해결될 것이라는 것을 알았습니다. "사용자 이름" 및 "이메일" 필드에 대한 작업 방법은 다음과 같습니다.
db.users.createIndex({ "username": 1, "email": 1 });
결과: 이 최적화의 효과는 그야말로 놀랍습니다. 이전에는 실행하는 데 최대 2초가 걸렸던 쿼리가 이제 200밀리초 미만으로 완료되어 성능이 10배 향상되었습니다. 우리 데이터베이스는 빠른 반응성을 되찾아 눈에 띄는 속도 저하 없이 훨씬 더 많은 양의 트래픽을 처리할 수 있게 되었습니다.
그러나 우리는 거기서 멈추지 않았습니다. 우리는 급속한 성장 궤도가 계속될 것이라는 점을 인식하고 장기적인 확장성을 보장하기 위해 적극적인 조치를 취했습니다. 우리는 여러 서버에 데이터를 배포하기 위해 샤딩을 구현했습니다. 이 전략적 결정을 통해 우리는 수평적으로 확장할 수 있게 되었고, 사용자 기반 확대와 함께 데이터 처리 능력도 함께 성장할 수 있었습니다.
5. 마이크로서비스 수용 — 확장성 문제 해결
사용자 기반이 계속 증가함에 따라 인프라를 확장해야 할 뿐만 아니라 자신 있게 확장할 수 있도록 애플리케이션을 발전시켜야 한다는 사실이 점점 더 분명해졌습니다. 모놀리식 아키텍처는 우리가 소규모 팀이었을 때는 잘 맞았지만 시간이 지나면서 상당히 번거로워졌습니다. 우리는 마이크로서비스 아키텍처를 향해 도약하고 구축을 시작해야 한다는 것을 알고 있었습니다. 이는 모든 엔지니어링 팀에게 어려운 작업이지만 상당한 확장성과 안정성이 있는 작업입니다.
가장 큰 문제 중 하나는 서비스 간 통신이었습니다. HTTP 요청은 실제로 우리의 경우에는 작동하지 않으며 엄청난 양의 작업이 모두 응답을 쉬지 않고 기다리고 있고 필요한 경우 할 일이 너무 많아 프로그램을 종료했기 때문에 시스템에 병목 현상이 하나 더 생겼습니다. 이때 우리는 RabbitMQ를 사용하는 것이 당연한 답이라는 것을 깨달았고, 너무 많이 생각하지 않고 적용했습니다.
다음은 이 솔루션을 구현한 방법을 간략하게 보여줍니다.
const amqp = require('amqplib/callback_api'); amqp.connect('amqp://localhost', (err, conn) => { conn.createChannel((err, ch) => { const queue = 'task_queue'; const msg = 'Hello World'; ch.assertQueue(queue, { durable: true }); ch.sendToQueue(queue, Buffer.from(msg), { persistent: true }); console.log(`Sent ${msg}`); }); });
결과: RabbitMQ를 통한 커뮤니케이션과 전환 자체는 우리의 관점에서 마술처럼 보였습니다… 그리고 숫자가 그것을 확인했습니다!!! 우리는 각 서비스가 자체적으로 확장될 수 있는 느슨하게 결합된 마이크로서비스의 운 좋은 소유자가 되었습니다. 갑자기 콘크리트 DNS 영역의 실제 트래픽 급증에는 시스템이 다운될 것이라는 두려움이 포함되지 않았지만(항상 캐스케이드이기 때문에 어떤 서비스 작업이 동일하게 요청하더라도) 나머지 부분/작업이 침착하게 ''라고 말하면서 훌륭하게 작동했습니다. 난 잠을 잘 수 있어, 자기야.' 새로운 기능이나 업데이트를 추가하면 유지 관리가 더 쉬워지고 문제가 줄어들어 작업에 더욱 자신감이 생겼습니다.
결론: 미래 혁신을 위한 방향 모색
이 스릴 넘치는 여정의 각 단계는 풀 스택 개발이 코드 작성 이상의 것임을 상기시켜주는 교훈이었습니다. 프런트엔드를 더 빠르게 만들고 오류를 견딜 수 있는 백엔드를 구축하는 것부터 사용자 기반이 폭발적으로 증가하는 동안 확장되는 데이터베이스를 처리하는 것까지 복잡한 상호 연관된 문제를 이해하고 해결하는 것입니다.
2024년 하반기 이후에도 웹 애플리케이션에 대한 수요 증가는 둔화되지 않을 것입니다. 확장 가능하고 성능이 최적화되었으며 잘 설계된 애플리케이션을 구축하는 데 계속 집중한다면 현재의 모든 문제를 해결하고 미래의 다른 과제를 활용할 수 있는 위치에 있게 됩니다. 이러한 실제 경험은 제가 풀 스택 개발에 접근하는 방식에 큰 영향을 미쳤으며 이러한 영향이 계속해서 우리 업계를 어디로 이끌지 기대됩니다!
근데 당신은 어때요? 유사한 장애물에 직면했거나 이러한 문제를 극복할 수 있는 다른 창의적인 방법을 찾았습니까? 여러분의 이야기나 통찰력을 듣고 싶습니다. 댓글로 알려주시거나 저에게 연락해주세요!
부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.
Copyright© 2022 湘ICP备2022001581号-3