{"id":35,"date":"2025-07-16T13:44:54","date_gmt":"2025-07-16T04:44:54","guid":{"rendered":"https:\/\/34.64.61.65\/?p=35"},"modified":"2025-07-16T14:38:26","modified_gmt":"2025-07-16T05:38:26","slug":"production_%ed%99%98%ea%b2%bd_%ea%b5%ac%ec%84%b1%eb%8f%84_%ec%84%a4%ea%b3%84%eb%b2%95","status":"publish","type":"post","link":"https:\/\/hed-g.me\/?p=35","title":{"rendered":"Production \ud658\uacbd \uad6c\uc131\ub3c4 \uc124\uacc4\ubc95: \uc2e4\ubb34\uc5d0\uc11c \ud1b5\ud558\ub294 \uc11c\ube44\uc2a4 \uc544\ud0a4\ud14d\ucc98 \uc644\uc804 \uac00\uc774\ub4dc"},"content":{"rendered":"\n<p>\uc548\ub155\ud558\uc138\uc694, \uc131\uc7a5\ud558\ub294 \uac1c\ubc1c\uc790 \uc5ec\ub7ec\ubd84!<\/p>\n\n\n\n<p>&#8220;\uac1c\ubc1c\uc740 \uc644\ub8cc\ud588\ub294\ub370&#8230; \uc774\uc81c \uc5b4\ub5bb\uac8c \uc11c\ube44\uc2a4\ub97c \uc6b4\uc601\ud558\uc9c0?&#8221; \ud83e\udd14<\/p>\n\n\n\n<p>\ub9ce\uc740 \uc8fc\ub2c8\uc5b4 \uac1c\ubc1c\uc790\ub4e4\uc774 \ucf54\ub4dc \uc791\uc131\uc5d0\ub294 \uc775\uc219\ud558\uc9c0\ub9cc, \uc2e4\uc81c \uc11c\ube44\uc2a4\ub97c <strong>Production \ud658\uacbd\uc5d0 \ubc30\ud3ec\ud558\uace0 \uc6b4\uc601<\/strong>\ud558\ub294 \uac83\uc740 \ub610 \ub2e4\ub978 \uc138\uacc4\uc785\ub2c8\ub2e4. \uc624\ub298\uc740 \uc81c\uac00 \uc2e4\ubb34\uc5d0\uc11c \uacaa\uc740 \ub2e4\uc591\ud55c \ud504\ub85c\uc81d\ud2b8\ub97c \ud1b5\ud574 \ubc30\uc6b4 <strong>Production \ud658\uacbd \uc124\uacc4\uc758 \ubaa8\ub4e0 \uac83<\/strong>\uc744 \uc815\ub9ac\ud574 \ub4dc\ub9ac\uaca0\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<p>\ub2e8\uc21c\ud788 &#8220;\uc11c\ubc84 \ud558\ub098\uc5d0 \uc62c\ub824\uc11c \ub3cc\ub9ac\uba74 \ub418\uc9c0 \uc54a\ub098?&#8221;\ub77c\uace0 \uc0dd\uac01\ud558\uc168\ub2e4\uba74, \uc774 \uae00\uc744 \ub05d\uae4c\uc9c0 \uc77d\uc5b4\ubcf4\uc138\uc694. \uc2e4\uc81c \uc11c\ube44\uc2a4 \uc6b4\uc601\uc5d0\ub294 <strong>\uc0dd\uac01\ubcf4\ub2e4 \ub9ce\uc740 \uac83\ub4e4<\/strong>\uc774 \ud544\uc694\ud569\ub2c8\ub2e4! \ud83d\ude80<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. Production \ud658\uacbd\uc774\ub780? \ud604\uc2e4\uacfc \uc774\uc0c1\uc758 \ucc28\uc774<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\uac1c\ubc1c \ud658\uacbd vs Production \ud658\uacbd<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code># \uac1c\ubc1c \ud658\uacbd (Local)\n- \ub0b4 \ucef4\ud4e8\ud130\uc5d0\uc11c\ub9cc \ub3d9\uc791\n- \ub370\uc774\ud130\ub294 \ud14c\uc2a4\ud2b8\uc6a9\n- \uc5d0\ub7ec\uac00 \ub098\uba74 \uc7ac\uc2dc\uc791\n- \uc0ac\uc6a9\uc790\ub294 \ub098 \ud63c\uc790\n\n# Production \ud658\uacbd (Real Service)\n- 24\/7 \ubb34\uc911\ub2e8 \uc11c\ube44\uc2a4\n- \uc2e4\uc81c \uc0ac\uc6a9\uc790 \ub370\uc774\ud130\n- \uc5d0\ub7ec\ub294 \uace7 \uc190\uc2e4\n- \uc218\ucc9c~\uc218\ub9cc \uba85 \ub3d9\uc2dc \uc0ac\uc6a9<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\uc2e4\uc81c Production\uc5d0\uc11c \uace0\ub824\ud574\uc57c \ud560 \uac83\ub4e4<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\uac00\uc6a9\uc131(Availability)<\/strong>: \uc11c\ube44\uc2a4\uac00 \ud56d\uc0c1 \uc0b4\uc544\uc788\uc5b4\uc57c \ud568<\/li>\n\n\n\n<li><strong>\ud655\uc7a5\uc131(Scalability)<\/strong>: \uc0ac\uc6a9\uc790\uac00 \ub298\uc5b4\ub098\ub3c4 \uacac\ub51c \uc218 \uc788\uc5b4\uc57c \ud568<\/li>\n\n\n\n<li><strong>\ubcf4\uc548(Security)<\/strong>: \ub370\uc774\ud130\uc640 \uc2dc\uc2a4\ud15c\uc744 \uc9c0\ucf1c\uc57c \ud568<\/li>\n\n\n\n<li><strong>\uc131\ub2a5(Performance)<\/strong>: \ube60\ub974\uac8c \uc751\ub2f5\ud574\uc57c \ud568<\/li>\n\n\n\n<li><strong>\ubaa8\ub2c8\ud130\ub9c1(Monitoring)<\/strong>: \ubb38\uc81c\ub97c \ube68\ub9ac \ubc1c\uacac\ud574\uc57c \ud568<\/li>\n\n\n\n<li><strong>\ubc31\uc5c5\/\ubcf5\uad6c(Backup\/Recovery)<\/strong>: \ub370\uc774\ud130\ub97c \uc783\uc5b4\ubc84\ub9ac\uba74 \uc548 \ub428<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">2. Production \ud504\ub85c\uc81d\ud2b8 \uc0dd\uba85\uc8fc\uae30: \uae30\ud68d\ubd80\ud130 \ubc30\ud3ec\uae4c\uc9c0<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\ub2e8\uacc4\ubcc4 \ud504\ub85c\uc138\uc2a4 \uac1c\uc694<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>graph LR\n    A&#91;\uae30\ud68d] --&gt; B&#91;Kick-off]\n    B --&gt; C&#91;Pre Sprint]\n    C --&gt; D&#91;Sprint]\n    D --&gt; E&#91;QA]\n    E --&gt; F&#91;Beta]\n    F --&gt; G&#91;Deploy]<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\uac01 \ub2e8\uacc4\ubcc4 \uc0c1\uc138 \ubd84\uc11d<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">1\ufe0f\u20e3 \uae30\ud68d \ub2e8\uacc4 (Planning)<\/h4>\n\n\n\n<p><strong>\uc8fc\uc694 \ub2f4\ub2f9\uc790<\/strong>: \uae30\ud68d\uc790, \uc81c\ud488 \uc624\ub108<br>\n<strong>\ud575\uc2ec \uc0b0\ucd9c\ubb3c<\/strong>: \uc694\uad6c\uc0ac\ud56d \uba85\uc138\uc11c, \uc0ac\uc6a9\uc790 \uc2a4\ud1a0\ub9ac<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># \uae30\ud68d \ub2e8\uacc4\uc5d0\uc11c \uae30\uc220\uc801\uc73c\ub85c \uace0\ub824\ud560 \uac83\ub4e4\n\n## 1. \uc608\uc0c1 \uc0ac\uc6a9\uc790 \uaddc\ubaa8\n- DAU (Daily Active Users) \uc608\uce21\n- \ub3d9\uc2dc \uc811\uc18d\uc790 \uc218 \uc608\uc0c1\n- \ud2b8\ub798\ud53d \ud328\ud134 \ubd84\uc11d (\uc2dc\uac04\ub300\ubcc4, \uc694\uc77c\ubcc4)\n\n## 2. \ub370\uc774\ud130 \uaddc\ubaa8 \uc608\uce21\n- \uc800\uc7a5\ud560 \ub370\uc774\ud130\ub7c9 \ucd94\uc815\n- \ub370\uc774\ud130 \uc99d\uac00\uc728 \uc608\uce21\n- \uc911\uc694 \ub370\uc774\ud130\uc640 \uc77c\ubc18 \ub370\uc774\ud130 \ubd84\ub958\n\n## 3. \uc131\ub2a5 \uc694\uad6c\uc0ac\ud56d\n- \uc751\ub2f5 \uc2dc\uac04 \ubaa9\ud45c (\uc77c\ubc18\uc801\uc73c\ub85c 2\ucd08 \uc774\ub0b4)\n- \ucc98\ub9ac\ub7c9 \ubaa9\ud45c (\ucd08\ub2f9 \uc694\uccad \uc218)\n- \uac00\uc6a9\uc131 \ubaa9\ud45c (99.9% uptime \ub4f1)<\/code><\/pre>\n\n\n\n<p><strong>\uc2e4\ubb34 \ud301<\/strong>: \uae30\ud68d \ub2e8\uacc4\uc5d0\uc11c <strong>&#8220;\uba87 \uba85\uc774 \uc4f8 \uac83 \uac19\ub098\uc694?&#8221;<\/strong>\ub97c \uaf2d \ubb3c\uc5b4\ubcf4\uc138\uc694. 100\uba85\uc6a9 \uc2dc\uc2a4\ud15c\uacfc 10\ub9cc\uba85\uc6a9 \uc2dc\uc2a4\ud15c\uc740 \uc644\uc804\ud788 \ub2e4\ub985\ub2c8\ub2e4!<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">2\ufe0f\u20e3 Kick-off \ub2e8\uacc4<\/h4>\n\n\n\n<p><strong>\uc8fc\uc694 \ub2f4\ub2f9\uc790<\/strong>: PM, \ud300 \ub9ac\ub354\ub4e4<br>\n<strong>\ud575\uc2ec \ub3c4\uad6c<\/strong>: JIRA, Figma, Adobe XD<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Kick-off\uc5d0\uc11c \uacb0\uc815\ud574\uc57c \ud560 \uae30\uc220 \uc2a4\ud0dd\n\n## Frontend \uae30\uc220 \uc2a4\ud0dd\n- Framework: React, Vue.js, Angular\n- UI Library: Material-UI, Ant Design, Bootstrap\n- \uc0c1\ud0dc \uad00\ub9ac: Redux, Vuex, Context API\n- \ubc88\ub4e4\ub7ec: Webpack, Vite, Parcel\n\n## Backend \uae30\uc220 \uc2a4\ud0dd\n- Language: Node.js, Python, Java, Go\n- Framework: Express, Django, Spring Boot, Gin\n- Database: PostgreSQL, MySQL, MongoDB\n- API \uc124\uacc4: RESTful, GraphQL\n\n## \uc778\ud504\ub77c \uae30\uc220 \uc2a4\ud0dd\n- Cloud Provider: AWS, GCP, Azure\n- Container: Docker, Kubernetes\n- CI\/CD: GitHub Actions, GitLab CI, Jenkins\n- Monitoring: Prometheus, Grafana, ELK Stack<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">3\ufe0f\u20e3 Pre Sprint \ub2e8\uacc4 (\uc778\ud504\ub77c \uc124\uacc4\uc758 \ud575\uc2ec)<\/h4>\n\n\n\n<p><strong>\uc8fc\uc694 \ub2f4\ub2f9\uc790<\/strong>: \uc778\ud504\ub77c \uc5d4\uc9c0\ub2c8\uc5b4, DBA, \uc544\ud0a4\ud14d\ud2b8<\/p>\n\n\n\n<p>\uc774 \ub2e8\uacc4\uac00 <strong>Production \uc124\uacc4\uc758 \ud575\uc2ec<\/strong>\uc785\ub2c8\ub2e4!<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># \uc778\ud504\ub77c \uc6a9\ub7c9 \uacc4\uc0b0 \uc608\uc2dc\n\n## 1. \uc11c\ubc84 \uc0ac\uc591 \uacc4\uc0b0\n# \uc608\uc0c1 \ub3d9\uc2dc \uc811\uc18d\uc790 1,000\uba85\n# \ud3c9\uade0 \uc751\ub2f5 \uc2dc\uac04 500ms\n# \ud544\uc694\ud55c \uc11c\ubc84 \ucc98\ub9ac \ub2a5\ub825 = 1,000 \/ (1 \/ 0.5) = 500 TPS\n\n## 2. \ub370\uc774\ud130\ubca0\uc774\uc2a4 \uc6a9\ub7c9 \uacc4\uc0b0\n# \uc0ac\uc6a9\uc790 1\ub9cc\uba85 \u00d7 \ud3c9\uade0 \ub370\uc774\ud130 1MB = 10GB\n# 1\ub144 \uc131\uc7a5\ub960 200% \uace0\ub824 = 30GB\n# \uc5ec\uc720\ubd84 100% = 60GB SSD\n\n## 3. \ub124\ud2b8\uc6cc\ud06c \ub300\uc5ed\ud3ed \uacc4\uc0b0\n# \ud3c9\uade0 \ud398\uc774\uc9c0 \ud06c\uae30 2MB\n# \ub3d9\uc2dc \uc811\uc18d\uc790 1,000\uba85\n# \ud544\uc694 \ub300\uc5ed\ud3ed = 2MB \u00d7 1,000 \/ 8 = 250Mbps<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\uc2a4\ucf00\uc77c \uc5c5 vs \uc2a4\ucf00\uc77c \uc544\uc6c3 \uc804\ub7b5<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code># Scale Up (\uc218\uc9c1 \ud655\uc7a5)\n\uc7a5\uc810:\n- \uad6c\ud604\uc774 \uac04\ub2e8\n- \ub370\uc774\ud130 \uc77c\uad00\uc131 \ubcf4\uc7a5\n- \uad00\ub9ac \ud3ec\uc778\ud2b8 \uc801\uc74c\n\n\ub2e8\uc810:\n- \uc11c\ube44\uc2a4 \uc911\ub2e8 \ud544\uc694\n- \ube44\uc6a9\uc774 \ube44\uc308\n- \ud55c\uacc4\uac00 \uc788\uc74c\n\n# Scale Out (\uc218\ud3c9 \ud655\uc7a5)\n\uc7a5\uc810:\n- \ubb34\uc81c\ud55c \ud655\uc7a5 \uac00\ub2a5\n- \uc7a5\uc560 \uaca9\ub9ac \uac00\ub2a5\n- \ube44\uc6a9 \ud6a8\uc728\uc801\n\n\ub2e8\uc810:\n- \ubcf5\uc7a1\ud55c \uad6c\uc870\n- \ub370\uc774\ud130 \ub3d9\uae30\ud654 \uc774\uc288\n- \uc6b4\uc601 \ubcf5\uc7a1\ub3c4 \uc99d\uac00<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">4\ufe0f\u20e3 Sprint \ub2e8\uacc4 (\uac1c\ubc1c \ubc0f \uad6c\ud604)<\/h4>\n\n\n\n<p><strong>\uc8fc\uc694 \ub2f4\ub2f9\uc790<\/strong>: \uac1c\ubc1c\ud300<br>\n<strong>\ud575\uc2ec \uac1c\ub150<\/strong>: MVP (Minimum Viable Product)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># MVP \uac1c\ubc1c \uc804\ub7b5\n\n## 1. \ud575\uc2ec \uae30\ub2a5 \uc6b0\uc120 \uc21c\uc704\n1\uc21c\uc704: \uc0ac\uc6a9\uc790 \uc778\uc99d, \uae30\ubcf8 CRUD\n2\uc21c\uc704: \ud575\uc2ec \ube44\uc988\ub2c8\uc2a4 \ub85c\uc9c1\n3\uc21c\uc704: \ubd80\uac00 \uae30\ub2a5, \ucd5c\uc801\ud654\n\n## 2. \uac1c\ubc1c \ud658\uacbd \uc124\uc815\n- \ub85c\uceec \uac1c\ubc1c \ud658\uacbd\n- \uac1c\ubc1c \uc11c\ubc84 (Development)\n- \ud14c\uc2a4\ud2b8 \uc11c\ubc84 (Staging)\n- \uc6b4\uc601 \uc11c\ubc84 (Production)<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">3. Production \uc544\ud0a4\ud14d\ucc98 \uc124\uacc4: \uc2e4\uc804 \ud328\ud134\ub4e4<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\uae30\ubcf8 3-Tier \uc544\ud0a4\ud14d\ucc98<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code># \uc804\ud1b5\uc801\uc778 3\uacc4\uce35 \uad6c\uc870\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502   Presentation  \u2502  \u2190 Frontend (React, Vue.js)\n\u2502      Layer      \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502   Application   \u2502  \u2190 Backend API (Node.js, Spring)\n\u2502      Layer      \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502      Data       \u2502  \u2190 Database (MySQL, PostgreSQL)\n\u2502      Layer      \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\uc18c\uaddc\ubaa8 \uc2a4\ud0c0\ud2b8\uc5c5\uc6a9 \uc544\ud0a4\ud14d\ucc98<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code># \uc0ac\uc6a9\uc790 1,000\uba85 \ubbf8\ub9cc, \uac04\ub2e8\ud55c \uc11c\ube44\uc2a4\n\nInternet\n    \u2193\n&#91;Load Balancer]  \u2190 AWS ALB, Nginx\n    \u2193\n&#91;Web Server]     \u2190 EC2 t3.medium (Frontend + Backend)\n    \u2193\n&#91;Database]       \u2190 RDS MySQL (Multi-AZ)\n    \u2193\n&#91;File Storage]   \u2190 S3 (\uc774\ubbf8\uc9c0, \ud30c\uc77c)\n\n# \uc608\uc0c1 \ube44\uc6a9: \uc6d4 $100-300\n# \ucc98\ub9ac \uac00\ub2a5: \ub3d9\uc2dc \uc811\uc18d\uc790 100-500\uba85<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\uc911\uaddc\ubaa8 \uc11c\ube44\uc2a4\uc6a9 \uc544\ud0a4\ud14d\ucc98<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code># \uc0ac\uc6a9\uc790 1\ub9cc-10\ub9cc\uba85, \uc77c\uc815\ud55c \ud2b8\ub798\ud53d\n\nInternet\n    \u2193\n&#91;CDN]            \u2190 CloudFront (\uc815\uc801 \ud30c\uc77c \uce90\uc2f1)\n    \u2193\n&#91;Load Balancer]  \u2190 ALB (Health Check, SSL \ud130\ubbf8\ub124\uc774\uc158)\n    \u2193\n&#91;Frontend]       \u2190 S3 + CloudFront (SPA \ubc30\ud3ec)\n    \u2193\n&#91;API Gateway]    \u2190 \ub77c\uc6b0\ud305, \uc778\uc99d, Rate Limiting\n    \u2193\n&#91;Backend Servers] \u2190 Auto Scaling Group (EC2 2-5\ub300)\n    \u251c\u2500 User Service\n    \u251c\u2500 Order Service\n    \u2514\u2500 Payment Service\n    \u2193\n&#91;Cache Layer]    \u2190 Redis Cluster\n    \u2193\n&#91;Database]       \u2190 RDS (Master-Slave \uad6c\uc870)\n    \u251c\u2500 Write: Master\n    \u2514\u2500 Read: Read Replica\n    \u2193\n&#91;Message Queue]  \u2190 SQS, RabbitMQ\n    \u2193\n&#91;Monitoring]     \u2190 CloudWatch, Prometheus\n\n# \uc608\uc0c1 \ube44\uc6a9: \uc6d4 $1,000-5,000\n# \ucc98\ub9ac \uac00\ub2a5: \ub3d9\uc2dc \uc811\uc18d\uc790 1,000-10,000\uba85<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\ub300\uaddc\ubaa8 \uc11c\ube44\uc2a4\uc6a9 \ub9c8\uc774\ud06c\ub85c\uc11c\ube44\uc2a4 \uc544\ud0a4\ud14d\ucc98<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code># \uc0ac\uc6a9\uc790 10\ub9cc\uba85 \uc774\uc0c1, \ubcf5\uc7a1\ud55c \ube44\uc988\ub2c8\uc2a4 \ub85c\uc9c1\n\nInternet\n    \u2193\n&#91;Global CDN]     \u2190 Multi-Region CDN\n    \u2193\n&#91;WAF]           \u2190 Web Application Firewall\n    \u2193\n&#91;API Gateway]   \u2190 Kong, AWS API Gateway\n    \u2193\n&#91;Service Mesh]  \u2190 Istio, Consul Connect\n    \u2193\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502           Microservices              \u2502\n\u251c\u2500 User Service      \u251c\u2500 Product Service \u2502\n\u251c\u2500 Order Service     \u251c\u2500 Payment Service \u2502\n\u251c\u2500 Notification      \u251c\u2500 Analytics       \u2502\n\u2514\u2500 Auth Service      \u2514\u2500 Search Service  \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n    \u2193\n&#91;Container Platform] \u2190 Kubernetes, Docker Swarm\n    \u2193\n&#91;Data Layer]\n\u251c\u2500 RDBMS (PostgreSQL Cluster)\n\u251c\u2500 NoSQL (MongoDB, Cassandra)\n\u251c\u2500 Cache (Redis Cluster)\n\u251c\u2500 Search (Elasticsearch)\n\u2514\u2500 Data Warehouse (BigQuery, Redshift)\n    \u2193\n&#91;Message Systems]\n\u251c\u2500 Event Bus (Kafka)\n\u251c\u2500 Queue (RabbitMQ)\n\u2514\u2500 Pub\/Sub (Redis, Google Pub\/Sub)\n\n# \uc608\uc0c1 \ube44\uc6a9: \uc6d4 $10,000+\n# \ucc98\ub9ac \uac00\ub2a5: \ub3d9\uc2dc \uc811\uc18d\uc790 10\ub9cc\uba85+<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">4. \ud575\uc2ec \uad6c\uc131 \uc694\uc18c\ubcc4 \uc124\uacc4 \uac00\uc774\ub4dc<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\ub85c\ub4dc \ubc38\ub7f0\uc11c \uc124\uacc4<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code># Nginx \ub85c\ub4dc \ubc38\ub7f0\uc11c \uc124\uc815 \uc608\uc2dc\nupstream backend {\n    # Round Robin (\uae30\ubcf8)\n    server 10.0.1.10:3000 weight=3;\n    server 10.0.1.11:3000 weight=2;\n    server 10.0.1.12:3000 weight=1;\n\n    # Health Check\n    health_check interval=30s;\n\n    # Sticky Session (\ud544\uc694\ud55c \uacbd\uc6b0)\n    ip_hash;\n}\n\nserver {\n    listen 80;\n    server_name api.example.com;\n\n    # SSL Redirect\n    return 301 https:\/\/$server_name$request_uri;\n}\n\nserver {\n    listen 443 ssl http2;\n    server_name api.example.com;\n\n    # SSL \uc124\uc815\n    ssl_certificate \/path\/to\/cert.pem;\n    ssl_certificate_key \/path\/to\/key.pem;\n\n    # \ubcf4\uc548 \ud5e4\ub354\n    add_header X-Frame-Options \"SAMEORIGIN\" always;\n    add_header X-Content-Type-Options \"nosniff\" always;\n    add_header X-XSS-Protection \"1; mode=block\" always;\n\n    # Rate Limiting\n    limit_req_zone $binary_remote_addr zone=api:10m rate=10r\/s;\n    limit_req zone=api burst=20 nodelay;\n\n    location \/ {\n        proxy_pass http:\/\/backend;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n\n        # Timeout \uc124\uc815\n        proxy_connect_timeout 30s;\n        proxy_send_timeout 30s;\n        proxy_read_timeout 30s;\n    }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\ub370\uc774\ud130\ubca0\uc774\uc2a4 \uc124\uacc4 \uc804\ub7b5<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">Master-Slave \uad6c\uc870<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code># MySQL Master-Slave \uc124\uc815\n\n## Master \uc11c\ubc84 \uc124\uc815 (Write)\n# \/etc\/mysql\/mysql.conf.d\/mysqld.cnf\n&#91;mysqld]\nserver-id = 1 \nlog-bin = mysql-bin \nbinlog-format = ROW gtid-mode = ON \nenforce-gtid-consistency = ON \n\n# \uc77d\uae30 \uc804\uc6a9 \uacc4\uc815 \uc0dd\uc131 \nCREATE USER 'replicator'@'%' IDENTIFIED BY 'password'; \nGRANT REPLICATION SLAVE ON *.* TO 'replicator'@'%'; \n\n## Slave \uc11c\ubc84 \uc124\uc815 (Read) \n# \/etc\/mysql\/mysql.conf.d\/mysqld.cnf\n\n&#91;mysqld]\nserver-id = 2 \nread-only = 1 \nrelay-log = relay-log \ngtid-mode = ON \nenforce-gtid-consistency = ON \n\n# Master\uc5d0 \uc5f0\uacb0\nCHANGE MASTER TO \nMASTER_HOST='master-server-ip', MASTER_USER='replicator', \nMASTER_PASSWORD='password', \nMASTER_AUTO_POSITION = 1; \n\nSTART SLAVE;<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h4 class=\"wp-block-heading\">\uc5f0\uacb0 \ud480 \ucd5c\uc801\ud654<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Node.js MySQL \uc5f0\uacb0 \ud480 \uc124\uc815\nconst mysql = require(\"mysql2\/promise\");\n\n\/\/ Write\uc6a9 \uc5f0\uacb0 \ud480 (Master)\nconst writePool = mysql.createPool({\n  host: \"master.db.example.com\",\n  user: \"app_user\",\n  password: \"secure_password\",\n  database: \"production_db\",\n  waitForConnections: true,\n  connectionLimit: 20, \/\/ \ub3d9\uc2dc \uc5f0\uacb0 \uc218\n  queueLimit: 0,\n  acquireTimeout: 60000, \/\/ \uc5f0\uacb0 \ud68d\ub4dd \ud0c0\uc784\uc544\uc6c3\n  timeout: 60000, \/\/ \ucffc\ub9ac \ud0c0\uc784\uc544\uc6c3\n  reconnect: true,\n  charset: \"utf8mb4\",\n});\n\n\/\/ Read\uc6a9 \uc5f0\uacb0 \ud480 (Slave)\nconst readPool = mysql.createPool({\n  host: \"slave.db.example.com\",\n  user: \"app_readonly\",\n  password: \"readonly_password\",\n  database: \"production_db\",\n  waitForConnections: true,\n  connectionLimit: 50, \/\/ Read\ub294 \ub354 \ub9ce\uc774\n  queueLimit: 0,\n  acquireTimeout: 60000,\n  timeout: 60000,\n  reconnect: true,\n  charset: \"utf8mb4\",\n});\n\n\/\/ \uc0ac\uc6a9 \uc608\uc2dc\nclass DatabaseService {\n  static async write(query, params) {\n    const connection = await writePool.getConnection();\n    try {\n      const &#91;rows] = await connection.execute(query, params);\n      return rows;\n    } finally {\n      connection.release();\n    }\n  }\n\n  static async read(query, params) {\n    const connection = await readPool.getConnection();\n    try {\n      const &#91;rows] = await connection.execute(query, params);\n      return rows;\n    } finally {\n      connection.release();\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\uce90\uc2f1 \uc804\ub7b5<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Redis \uce90\uc2f1 \uacc4\uce35 \uad6c\ud604\nconst Redis = require(\"ioredis\");\n\nconst redis = new Redis.Cluster(\n  &#91;\n    { host: \"redis-1.cache.example.com\", port: 6379 },\n    { host: \"redis-2.cache.example.com\", port: 6379 },\n    { host: \"redis-3.cache.example.com\", port: 6379 },\n  ],\n  {\n    redisOptions: {\n      password: \"redis_password\",\n      maxRetriesPerRequest: 3,\n      retryDelayOnFailover: 100,\n      maxRetriesPerRequest: null,\n      enableReadyCheck: false,\n    },\n  }\n);\n\nclass CacheService {\n  \/\/ 1. \ub2e8\uc21c \uce90\uc2f1\n  static async get(key) {\n    try {\n      const value = await redis.get(key);\n      return value ? JSON.parse(value) : null;\n    } catch (error) {\n      console.error(\"Cache get error:\", error);\n      return null;\n    }\n  }\n\n  static async set(key, value, ttl = 3600) {\n    try {\n      await redis.setex(key, ttl, JSON.stringify(value));\n    } catch (error) {\n      console.error(\"Cache set error:\", error);\n    }\n  }\n\n  \/\/ 2. Cache-Aside \ud328\ud134\n  static async getOrSet(key, fetcher, ttl = 3600) {\n    let value = await this.get(key);\n\n    if (value === null) {\n      value = await fetcher();\n      if (value !== null) {\n        await this.set(key, value, ttl);\n      }\n    }\n\n    return value;\n  }\n\n  \/\/ 3. \ubd84\uc0b0 \ub77d (\ub3d9\uc2dc\uc131 \uc81c\uc5b4)\n  static async withLock(lockKey, timeout, callback) {\n    const lockValue = Date.now() + timeout + 1;\n    const acquired = await redis.set(lockKey, lockValue, \"PX\", timeout, \"NX\");\n\n    if (acquired) {\n      try {\n        return await callback();\n      } finally {\n        \/\/ Lua \uc2a4\ud06c\ub9bd\ud2b8\ub85c \uc548\uc804\ud55c \ub77d \ud574\uc81c\n        await redis.eval(\n          `\n                    if redis.call('GET', KEYS&#91;1]) == ARGV&#91;1] then\n                        return redis.call('DEL', KEYS&#91;1])\n                    else\n                        return 0\n                    end\n                `,\n          1,\n          lockKey,\n          lockValue\n        );\n      }\n    } else {\n      throw new Error(\"Could not acquire lock\");\n    }\n  }\n}\n\n\/\/ \uc0ac\uc6a9 \uc608\uc2dc\napp.get(\"\/api\/users\/:id\", async (req, res) =&gt; {\n  const userId = req.params.id;\n  const cacheKey = `user:${userId}`;\n\n  const user = await CacheService.getOrSet(\n    cacheKey,\n    () =&gt; DatabaseService.read(\"SELECT * FROM users WHERE id = ?\", &#91;userId]),\n    1800 \/\/ 30\ubd84 \uce90\uc2dc\n  );\n\n  res.json(user);\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">5. \ubcf4\uc548 \uc124\uacc4: \ub2e4\uce35 \ubc29\uc5b4 \uc804\ub7b5<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\ub124\ud2b8\uc6cc\ud06c \ubcf4\uc548<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code># AWS Security Group \uc124\uc815 \uc608\uc2dc\n\n## Web Tier Security Group\n# HTTP\/HTTPS\ub9cc \uc778\ud130\ub137\uc5d0\uc11c \uc811\uadfc \ud5c8\uc6a9\naws ec2 authorize-security-group-ingress \n    --group-id sg-web-tier \n    --protocol tcp \n    --port 80 \n    --cidr 0.0.0.0\/0\n\naws ec2 authorize-security-group-ingress \n    --group-id sg-web-tier \n    --protocol tcp \n    --port 443 \n    --cidr 0.0.0.0\/0\n\n## App Tier Security Group\n# Web Tier\uc5d0\uc11c\ub9cc \uc811\uadfc \ud5c8\uc6a9\naws ec2 authorize-security-group-ingress \n    --group-id sg-app-tier \n    --protocol tcp \n    --port 3000 \n    --source-group sg-web-tier\n\n## DB Tier Security Group\n# App Tier\uc5d0\uc11c\ub9cc \uc811\uadfc \ud5c8\uc6a9\naws ec2 authorize-security-group-ingress \n    --group-id sg-db-tier \n    --protocol tcp \n    --port 3306 \n    --source-group sg-app-tier<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \ubcf4\uc548<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Express.js \ubcf4\uc548 \uc124\uc815\nconst express = require(\"express\");\nconst helmet = require(\"helmet\");\nconst rateLimit = require(\"express-rate-limit\");\nconst cors = require(\"cors\");\n\nconst app = express();\n\n\/\/ 1. \uae30\ubcf8 \ubcf4\uc548 \ud5e4\ub354\napp.use(\n  helmet({\n    contentSecurityPolicy: {\n      directives: {\n        defaultSrc: &#91;\"'self'\"],\n        styleSrc: &#91;\"'self'\", \"'unsafe-inline'\"],\n        scriptSrc: &#91;\"'self'\"],\n        imgSrc: &#91;\"'self'\", \"data:\", \"https:\"],\n      },\n    },\n    hsts: {\n      maxAge: 31536000,\n      includeSubDomains: true,\n      preload: true,\n    },\n  })\n);\n\n\/\/ 2. CORS \uc124\uc815\napp.use(\n  cors({\n    origin: process.env.NODE_ENV === \"production\" ? &#91;\"https:\/\/myapp.com\", \"https:\/\/www.myapp.com\"] : &#91;\"http:\/\/localhost:3000\"],\n    credentials: true,\n    optionsSuccessStatus: 200,\n  })\n);\n\n\/\/ 3. Rate Limiting\nconst limiter = rateLimit({\n  windowMs: 15 * 60 * 1000, \/\/ 15\ubd84\n  max: 100, \/\/ \ucd5c\ub300 100 \uc694\uccad\n  message: {\n    error: \"Too many requests, please try again later.\",\n  },\n  standardHeaders: true,\n  legacyHeaders: false,\n});\n\napp.use(\"\/api\/\", limiter);\n\n\/\/ 4. \ud2b9\ubcc4\ud55c \uc5d4\ub4dc\ud3ec\uc778\ud2b8\uc5d0 \ub354 \uac15\ud55c \uc81c\ud55c\nconst strictLimiter = rateLimit({\n  windowMs: 15 * 60 * 1000,\n  max: 5, \/\/ \ub85c\uadf8\uc778\uc740 15\ubd84\uc5d0 5\ubc88\ub9cc\n  skipSuccessfulRequests: true,\n});\n\napp.use(\"\/api\/auth\/login\", strictLimiter);\n\n\/\/ 5. JWT \ud1a0\ud070 \uac80\uc99d\nconst jwt = require(\"jsonwebtoken\");\n\nconst authenticateToken = (req, res, next) =&gt; {\n  const authHeader = req.headers&#91;\"authorization\"];\n  const token = authHeader &amp;&amp; authHeader.split(\" \")&#91;1];\n\n  if (!token) {\n    return res.sendStatus(401);\n  }\n\n  jwt.verify(token, process.env.JWT_SECRET, (err, user) =&gt; {\n    if (err) return res.sendStatus(403);\n    req.user = user;\n    next();\n  });\n};\n\n\/\/ 6. SQL \uc778\uc81d\uc158 \ubc29\uc9c0 (Prepared Statements)\napp.post(\"\/api\/users\", authenticateToken, async (req, res) =&gt; {\n  const { name, email } = req.body;\n\n  \/\/ \uc785\ub825\uac12 \uac80\uc99d\n  if (!name || !email || !email.includes(\"@\")) {\n    return res.status(400).json({ error: \"Invalid input\" });\n  }\n\n  try {\n    \/\/ Prepared Statement \uc0ac\uc6a9\n    const result = await DatabaseService.write(\"INSERT INTO users (name, email) VALUES (?, ?)\", &#91;name, email]);\n    res.json({ id: result.insertId });\n  } catch (error) {\n    console.error(\"Database error:\", error);\n    res.status(500).json({ error: \"Internal server error\" });\n  }\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">6. \ubaa8\ub2c8\ud130\ub9c1\uacfc \ub85c\uae45 \uc2dc\uc2a4\ud15c<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Prometheus + Grafana \ubaa8\ub2c8\ud130\ub9c1<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code># docker-compose.yml - \ubaa8\ub2c8\ud130\ub9c1 \uc2a4\ud0dd\nversion: \"3.8\"\n\nservices:\n  prometheus:\n    image: prom\/prometheus:latest\n    container_name: prometheus\n    ports:\n      - \"9090:9090\"\n    volumes:\n      - .\/prometheus.yml:\/etc\/prometheus\/prometheus.yml\n      - prometheus_data:\/prometheus\n    command:\n      - \"--config.file=\/etc\/prometheus\/prometheus.yml\"\n      - \"--storage.tsdb.path=\/prometheus\"\n      - \"--web.console.libraries=\/etc\/prometheus\/console_libraries\"\n      - \"--web.console.templates=\/etc\/prometheus\/consoles\"\n      - \"--storage.tsdb.retention.time=200h\"\n      - \"--web.enable-lifecycle\"\n\n  grafana:\n    image: grafana\/grafana:latest\n    container_name: grafana\n    ports:\n      - \"3000:3000\"\n    environment:\n      - GF_SECURITY_ADMIN_PASSWORD=admin123\n    volumes:\n      - grafana_data:\/var\/lib\/grafana\n      - .\/grafana\/dashboards:\/var\/lib\/grafana\/dashboards\n      - .\/grafana\/provisioning:\/etc\/grafana\/provisioning\n\n  node-exporter:\n    image: prom\/node-exporter:latest\n    container_name: node-exporter\n    ports:\n      - \"9100:9100\"\n    volumes:\n      - \/proc:\/host\/proc:ro\n      - \/sys:\/host\/sys:ro\n      - \/:\/rootfs:ro\n    command:\n      - \"--path.procfs=\/host\/proc\"\n      - \"--path.rootfs=\/rootfs\"\n      - \"--path.sysfs=\/host\/sys\"\n      - \"--collector.filesystem.mount-points-exclude=^\/(sys|proc|dev|host|etc)($|\/)\"\n\nvolumes:\n  prometheus_data:\n  grafana_data:<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code># prometheus.yml\nglobal:\n  scrape_interval: 15s\n  evaluation_interval: 15s\n\nrule_files:\n  - \"alert_rules.yml\"\n\nalerting:\n  alertmanagers:\n    - static_configs:\n        - targets:\n            - alertmanager:9093\n\nscrape_configs:\n  # Prometheus \uc790\uccb4 \uba54\ud2b8\ub9ad\n  - job_name: \"prometheus\"\n    static_configs:\n      - targets: &#91;\"localhost:9090\"]\n\n  # Node Exporter (\uc2dc\uc2a4\ud15c \uba54\ud2b8\ub9ad)\n  - job_name: \"node-exporter\"\n    static_configs:\n      - targets: &#91;\"node-exporter:9100\"]\n\n  # \uc560\ud50c\ub9ac\ucf00\uc774\uc158 \uba54\ud2b8\ub9ad\n  - job_name: \"app\"\n    static_configs:\n      - targets: &#91;\"app:3000\"]\n    metrics_path: \"\/metrics\"\n    scrape_interval: 5s\n\n  # \ub370\uc774\ud130\ubca0\uc774\uc2a4 \uba54\ud2b8\ub9ad\n  - job_name: \"mysql\"\n    static_configs:\n      - targets: &#91;\"mysql-exporter:9104\"]<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \uba54\ud2b8\ub9ad \uc218\uc9d1<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Express.js \uc560\ud50c\ub9ac\ucf00\uc774\uc158\uc5d0 Prometheus \uba54\ud2b8\ub9ad \ucd94\uac00\nconst prometheus = require(\"prom-client\");\nconst express = require(\"express\");\n\nconst app = express();\n\n\/\/ \uae30\ubcf8 \uba54\ud2b8\ub9ad \uc218\uc9d1\nconst collectDefaultMetrics = prometheus.collectDefaultMetrics;\ncollectDefaultMetrics({ timeout: 5000 });\n\n\/\/ \ucee4\uc2a4\ud140 \uba54\ud2b8\ub9ad \uc815\uc758\nconst httpRequestDuration = new prometheus.Histogram({\n  name: \"http_request_duration_seconds\",\n  help: \"Duration of HTTP requests in seconds\",\n  labelNames: &#91;\"method\", \"route\", \"status\"],\n});\n\nconst httpRequestTotal = new prometheus.Counter({\n  name: \"http_requests_total\",\n  help: \"Total number of HTTP requests\",\n  labelNames: &#91;\"method\", \"route\", \"status\"],\n});\n\nconst activeConnections = new prometheus.Gauge({\n  name: \"active_connections\",\n  help: \"Number of active connections\",\n});\n\nconst databaseConnectionPool = new prometheus.Gauge({\n  name: \"database_connection_pool_size\",\n  help: \"Current database connection pool size\",\n  labelNames: &#91;\"pool_name\", \"state\"],\n});\n\n\/\/ \uba54\ud2b8\ub9ad \uc218\uc9d1 \ubbf8\ub4e4\uc6e8\uc5b4\nconst metricsMiddleware = (req, res, next) =&gt; {\n  const start = Date.now();\n\n  activeConnections.inc();\n\n  res.on(\"finish\", () =&gt; {\n    const duration = (Date.now() - start) \/ 1000;\n    const route = req.route ? req.route.path : req.path;\n\n    httpRequestDuration.labels(req.method, route, res.statusCode).observe(duration);\n\n    httpRequestTotal.labels(req.method, route, res.statusCode).inc();\n\n    activeConnections.dec();\n  });\n\n  next();\n};\n\napp.use(metricsMiddleware);\n\n\/\/ \uba54\ud2b8\ub9ad \uc5d4\ub4dc\ud3ec\uc778\ud2b8\napp.get(\"\/metrics\", (req, res) =&gt; {\n  res.set(\"Content-Type\", prometheus.register.contentType);\n  res.end(prometheus.register.metrics());\n});\n\n\/\/ \ub370\uc774\ud130\ubca0\uc774\uc2a4 \uba54\ud2b8\ub9ad \uc5c5\ub370\uc774\ud2b8 (\uc8fc\uae30\uc801\uc73c\ub85c)\nsetInterval(() =&gt; {\n  \/\/ \uc2e4\uc81c \uc5f0\uacb0 \ud480 \uc0c1\ud0dc \ud655\uc778\n  const poolStats = getConnectionPoolStats();\n\n  databaseConnectionPool.labels(\"main\", \"active\").set(poolStats.active);\n\n  databaseConnectionPool.labels(\"main\", \"idle\").set(poolStats.idle);\n\n  databaseConnectionPool.labels(\"main\", \"waiting\").set(poolStats.waiting);\n}, 30000); \/\/ 30\ucd08\ub9c8\ub2e4 \uc5c5\ub370\uc774\ud2b8<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\ub85c\uadf8 \uc9d1\uc911\ud654 (ELK Stack)<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code># docker-compose.elk.yml\nversion: \"3.8\"\n\nservices:\n  elasticsearch:\n    image: docker.elastic.co\/elasticsearch\/elasticsearch:7.15.0\n    container_name: elasticsearch\n    environment:\n      - node.name=elasticsearch\n      - cluster.name=es-docker-cluster\n      - discovery.type=single-node\n      - bootstrap.memory_lock=true\n      - \"ES_JAVA_OPTS=-Xms1g -Xmx1g\"\n    ulimits:\n      memlock:\n        soft: -1\n        hard: -1\n    volumes:\n      - elasticsearch_data:\/usr\/share\/elasticsearch\/data\n    ports:\n      - \"9200:9200\"\n\n  logstash:\n    image: docker.elastic.co\/logstash\/logstash:7.15.0\n    container_name: logstash\n    volumes:\n      - .\/logstash\/config\/logstash.yml:\/usr\/share\/logstash\/config\/logstash.yml:ro\n      - .\/logstash\/pipeline:\/usr\/share\/logstash\/pipeline:ro\n    ports:\n      - \"5044:5044\"\n      - \"5000:5000\/tcp\"\n      - \"5000:5000\/udp\"\n      - \"9600:9600\"\n    environment:\n      LS_JAVA_OPTS: \"-Xmx1g -Xms1g\"\n    depends_on:\n      - elasticsearch\n\n  kibana:\n    image: docker.elastic.co\/kibana\/kibana:7.15.0\n    container_name: kibana\n    ports:\n      - \"5601:5601\"\n    environment:\n      ELASTICSEARCH_URL: http:\/\/elasticsearch:9200\n      ELASTICSEARCH_HOSTS: '&#91;\"http:\/\/elasticsearch:9200\"]'\n    depends_on:\n      - elasticsearch\n\nvolumes:\n  elasticsearch_data:<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code># logstash\/pipeline\/logstash.conf\ninput {\n  beats {\n    port =&gt; 5044\n  }\n\n  # JSON \ub85c\uadf8 \uc9c1\uc811 \uc218\uc2e0\n  tcp {\n    port =&gt; 5000\n    codec =&gt; json_lines\n  }\n}\n\nfilter {\n  # \ud0c0\uc784\uc2a4\ud0ec\ud504 \ud30c\uc2f1\n  date {\n    match =&gt; &#91; \"timestamp\", \"ISO8601\" ]\n  }\n\n  # \ub85c\uadf8 \ub808\ubca8\ubcc4 \ud544\ud130\ub9c1\n  if &#91;level] == \"ERROR\" {\n    mutate {\n      add_tag =&gt; &#91; \"error\" ]\n    }\n  }\n\n  # User-Agent \ud30c\uc2f1\n  if &#91;user_agent] {\n    useragent {\n      source =&gt; \"user_agent\"\n      target =&gt; \"ua\"\n    }\n  }\n\n  # GeoIP \uc815\ubcf4 \ucd94\uac00\n  if &#91;client_ip] {\n    geoip {\n      source =&gt; \"client_ip\"\n      target =&gt; \"geoip\"\n    }\n  }\n}\n\noutput {\n  elasticsearch {\n    hosts =&gt; &#91;\"elasticsearch:9200\"]\n    index =&gt; \"app-logs-%{+YYYY.MM.dd}\"\n  }\n\n  # \uc5d0\ub7ec \ub85c\uadf8\ub294 \ubcc4\ub3c4 \uc778\ub371\uc2a4\n  if \"error\" in &#91;tags] {\n    elasticsearch {\n      hosts =&gt; &#91;\"elasticsearch:9200\"]\n      index =&gt; \"error-logs-%{+YYYY.MM.dd}\"\n    }\n  }\n\n  stdout { codec =&gt; rubydebug }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">7. CI\/CD \ud30c\uc774\ud504\ub77c\uc778 \uad6c\ucd95<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">GitHub Actions \uc608\uc2dc<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code># .github\/workflows\/deploy.yml\nname: Deploy to Production\n\non:\n  push:\n    branches: &#91;main]\n  pull_request:\n    branches: &#91;main]\n\nenv:\n  NODE_VERSION: \"18\"\n  AWS_REGION: \"ap-northeast-2\"\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n\n    services:\n      mysql:\n        image: mysql:8.0\n        env:\n          MYSQL_ROOT_PASSWORD: test_password\n          MYSQL_DATABASE: test_db\n        ports:\n          - 3306:3306\n        options: --health-cmd=\"mysqladmin ping\" --health-interval=10s --health-timeout=5s --health-retries=3\n\n      redis:\n        image: redis:7\n        ports:\n          - 6379:6379\n        options: --health-cmd=\"redis-cli ping\" --health-interval=10s --health-timeout=5s --health-retries=3\n\n    steps:\n      - uses: actions\/checkout@v3\n\n      - name: Setup Node.js\n        uses: actions\/setup-node@v3\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: \"npm\"\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run linting\n        run: npm run lint\n\n      - name: Run type checking\n        run: npm run type-check\n\n      - name: Run unit tests\n        run: npm run test:unit\n        env:\n          NODE_ENV: test\n\n      - name: Run integration tests\n        run: npm run test:integration\n        env:\n          NODE_ENV: test\n          DB_HOST: localhost\n          DB_PORT: 3306\n          DB_NAME: test_db\n          DB_USER: root\n          DB_PASSWORD: test_password\n          REDIS_URL: redis:\/\/localhost:6379\n\n      - name: Run security audit\n        run: npm audit --audit-level high\n\n      - name: Upload coverage reports\n        uses: codecov\/codecov-action@v3\n\n  build:\n    needs: test\n    runs-on: ubuntu-latest\n    if: github.ref == 'refs\/heads\/main'\n\n    steps:\n      - uses: actions\/checkout@v3\n\n      - name: Configure AWS credentials\n        uses: aws-actions\/configure-aws-credentials@v2\n        with:\n          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}\n          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}\n          aws-region: ${{ env.AWS_REGION }}\n\n      - name: Login to Amazon ECR\n        id: login-ecr\n        uses: aws-actions\/amazon-ecr-login@v1\n\n      - name: Build and push Docker image\n        env:\n          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}\n          ECR_REPOSITORY: myapp-backend\n          IMAGE_TAG: ${{ github.sha }}\n        run: |\n          # Build Docker image\n          docker build -t $ECR_REGISTRY\/$ECR_REPOSITORY:$IMAGE_TAG .\n          docker build -t $ECR_REGISTRY\/$ECR_REPOSITORY:latest .\n\n          # Push to ECR\n          docker push $ECR_REGISTRY\/$ECR_REPOSITORY:$IMAGE_TAG\n          docker push $ECR_REGISTRY\/$ECR_REPOSITORY:latest\n\n          # Save image URI for deployment\n          echo \"IMAGE_URI=$ECR_REGISTRY\/$ECR_REPOSITORY:$IMAGE_TAG\" &gt;&gt; $GITHUB_ENV\n\n  deploy-staging:\n    needs: build\n    runs-on: ubuntu-latest\n    environment: staging\n\n    steps:\n      - uses: actions\/checkout@v3\n\n      - name: Deploy to staging\n        run: |\n          # Update ECS service\n          aws ecs update-service \n            --cluster staging-cluster \n            --service myapp-backend \n            --force-new-deployment\n\n      - name: Wait for deployment\n        run: |\n          aws ecs wait services-stable \n            --cluster staging-cluster \n            --services myapp-backend\n\n      - name: Run smoke tests\n        run: |\n          # \uae30\ubcf8\uc801\uc778 \ud5ec\uc2a4\uccb4\ud06c\n          curl -f https:\/\/staging-api.example.com\/health || exit 1\n\n          # \uc8fc\uc694 API \ud14c\uc2a4\ud2b8\n          npm run test:smoke -- --baseUrl=https:\/\/staging-api.example.com\n\n  deploy-production:\n    needs: &#91;build, deploy-staging]\n    runs-on: ubuntu-latest\n    environment: production\n    if: github.ref == 'refs\/heads\/main'\n\n    steps:\n      - uses: actions\/checkout@v3\n\n      - name: Blue\/Green deployment\n        run: |\n          # \ud604\uc7ac active \uc0c9\uc0c1 \ud655\uc778\n          CURRENT_COLOR=$(aws elbv2 describe-target-groups \n            --target-group-arns ${{ secrets.BLUE_TG_ARN }} \n            --query 'TargetGroups&#91;0].TargetGroupName' \n            --output text | grep -o 'blue|green')\n\n          if &#91; \"$CURRENT_COLOR\" = \"blue\" ]; then\n            NEW_COLOR=\"green\"\n            NEW_TG_ARN=\"${{ secrets.GREEN_TG_ARN }}\"\n            OLD_TG_ARN=\"${{ secrets.BLUE_TG_ARN }}\"\n          else\n            NEW_COLOR=\"blue\"\n            NEW_TG_ARN=\"${{ secrets.BLUE_TG_ARN }}\"\n            OLD_TG_ARN=\"${{ secrets.GREEN_TG_ARN }}\"\n          fi\n\n          echo \"Deploying to $NEW_COLOR environment\"\n\n          # \uc0c8\ub85c\uc6b4 \ud658\uacbd\uc5d0 \ubc30\ud3ec\n          aws ecs update-service \n            --cluster production-cluster \n            --service myapp-backend-$NEW_COLOR \n            --force-new-deployment\n\n          # \ubc30\ud3ec \uc644\ub8cc \ub300\uae30\n          aws ecs wait services-stable \n            --cluster production-cluster \n            --services myapp-backend-$NEW_COLOR\n\n          # \ud5ec\uc2a4\uccb4\ud06c\n          for i in {1..30}; do\n            if curl -f https:\/\/api.example.com\/health; then\n              echo \"Health check passed\"\n              break\n            fi\n            echo \"Health check failed, retrying...\"\n            sleep 10\n          done\n\n          # \ud2b8\ub798\ud53d \uc804\ud658\n          aws elbv2 modify-listener \n            --listener-arn ${{ secrets.PROD_LISTENER_ARN }} \n            --default-actions Type=forward,TargetGroupArn=$NEW_TG_ARN\n\n          echo \"Traffic switched to $NEW_COLOR\"\n\n      - name: Post-deployment verification\n        run: |\n          # 5\ubd84\uac04 \ubaa8\ub2c8\ud130\ub9c1\n          sleep 300\n\n          # \uc5d0\ub7ec\uc728 \ud655\uc778 (Prometheus \ucffc\ub9ac)\n          ERROR_RATE=$(curl -s \"http:\/\/prometheus:9090\/api\/v1\/query?query=rate(http_requests_total{status=~'5..'}&#91;5m])\/rate(http_requests_total&#91;5m])\" | jq -r '.data.result&#91;0].value&#91;1]')\n\n          if (( $(echo \"$ERROR_RATE &gt; 0.01\" | bc -l) )); then\n            echo \"High error rate detected: $ERROR_RATE\"\n            # \ub864\ubc31 \uc2e4\ud589\n            exit 1\n          fi\n\n          echo \"Deployment successful!\"\n\n  notify:\n    needs: &#91;deploy-production]\n    runs-on: ubuntu-latest\n    if: always()\n\n    steps:\n      - name: Notify Slack\n        uses: 8398a7\/action-slack@v3\n        with:\n          status: ${{ job.status }}\n          channel: \"#deployments\"\n          webhook_url: ${{ secrets.SLACK_WEBHOOK }}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">8. \uc7ac\ud574 \ubcf5\uad6c \ubc0f \ubc31\uc5c5 \uc804\ub7b5<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\ub370\uc774\ud130\ubca0\uc774\uc2a4 \ubc31\uc5c5 \uc790\ub3d9\ud654<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n# db_backup.sh - \ub370\uc774\ud130\ubca0\uc774\uc2a4 \ubc31\uc5c5 \uc2a4\ud06c\ub9bd\ud2b8\n\nset -euo pipefail\n\n# \uc124\uc815 \ubcc0\uc218\nDB_HOST=\"prod-db.example.com\"\nDB_USER=\"backup_user\"\nDB_PASSWORD=\"${DB_PASSWORD:-$(cat \/etc\/mysql\/backup_password)}\"\nDB_NAME=\"production_db\"\nBACKUP_DIR=\"\/backups\/mysql\"\nS3_BUCKET=\"my-company-db-backups\"\nRETENTION_DAYS=30\n\n# \ubc31\uc5c5 \ud30c\uc77c\uba85 (\ud0c0\uc784\uc2a4\ud0ec\ud504 \ud3ec\ud568)\nTIMESTAMP=$(date +%Y%m%d_%H%M%S)\nBACKUP_FILE=\"${DB_NAME}_${TIMESTAMP}.sql.gz\"\nBACKUP_PATH=\"${BACKUP_DIR}\/${BACKUP_FILE}\"\n\n# \ub85c\uae45 \ud568\uc218\nlog() {\n    echo \"&#91;$(date '+%Y-%m-%d %H:%M:%S')] $1\" | tee -a \/var\/log\/db_backup.log\n}\n\n# \ubc31\uc5c5 \ub514\ub809\ud1a0\ub9ac \uc0dd\uc131\nmkdir -p \"$BACKUP_DIR\"\n\nlog \"Starting database backup for $DB_NAME\"\n\n# MySQL \ub364\ud504 \uc0dd\uc131 (\uc555\ucd95)\nmysqldump \n    --host=\"$DB_HOST\" \n    --user=\"$DB_USER\" \n    --password=\"$DB_PASSWORD\" \n    --single-transaction \n    --routines \n    --triggers \n    --quick \n    --lock-tables=false \n    \"$DB_NAME\" | gzip &gt; \"$BACKUP_PATH\"\n\n# \ubc31\uc5c5 \ud30c\uc77c \ud06c\uae30 \ud655\uc778\nBACKUP_SIZE=$(ls -lh \"$BACKUP_PATH\" | awk '{print $5}')\nlog \"Backup completed. File size: $BACKUP_SIZE\"\n\n# S3\uc5d0 \uc5c5\ub85c\ub4dc\naws s3 cp \"$BACKUP_PATH\" \"s3:\/\/$S3_BUCKET\/mysql\/$(date +%Y\/%m\/%d)\/\"\n\nif &#91; $? -eq 0 ]; then\n    log \"Backup uploaded to S3 successfully\"\nelse\n    log \"ERROR: Failed to upload backup to S3\"\n    exit 1\nfi\n\n# \ub85c\uceec \ud30c\uc77c \uc815\ub9ac (7\uc77c \uc774\uc0c1 \ub41c \ubc31\uc5c5 \uc0ad\uc81c)\nfind \"$BACKUP_DIR\" -name \"*.sql.gz\" -mtime +7 -delete\n\n# S3\uc5d0\uc11c \uc624\ub798\ub41c \ubc31\uc5c5 \uc815\ub9ac\naws s3 ls \"s3:\/\/$S3_BUCKET\/mysql\/\" --recursive | \n    while read -r line; do\n        createDate=`echo $line | awk {'print $1\" \"$2'}`\n        createDate=`date -d\"$createDate\" +%s`\n        olderThan=`date -d\"-$RETENTION_DAYS days\" +%s`\n        if &#91;&#91; $createDate -lt $olderThan ]]; then\n            fileName=`echo $line | awk {'print $4'}`\n            aws s3 rm \"s3:\/\/$S3_BUCKET\/$fileName\"\n            log \"Deleted old backup: $fileName\"\n        fi\n    done\n\nlog \"Database backup process completed\"\n\n# Slack \uc54c\ub9bc (\uc131\uacf5)\ncurl -X POST -H 'Content-type: application\/json' \n    --data \"{\"text\":\"\u2705 Database backup completed successfully\\nFile: $BACKUP_FILE\\nSize: $BACKUP_SIZE\"}\" \n    \"$SLACK_WEBHOOK_URL\"<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\uc7a5\uc560 \ubcf5\uad6c \uc2dc\ub098\ub9ac\uc624<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n# disaster_recovery.sh - \uc7ac\ud574 \ubcf5\uad6c \uc2a4\ud06c\ub9bd\ud2b8\n\n# 1. \ub370\uc774\ud130\ubca0\uc774\uc2a4 \ubcf5\uad6c\nrestore_database() {\n    local backup_date=$1\n    local backup_file=\"production_db_${backup_date}.sql.gz\"\n\n    echo \"Restoring database from $backup_file\"\n\n    # S3\uc5d0\uc11c \ubc31\uc5c5 \ud30c\uc77c \ub2e4\uc6b4\ub85c\ub4dc\n    aws s3 cp \"s3:\/\/my-company-db-backups\/mysql\/$backup_file\" \/tmp\/\n\n    # \ub370\uc774\ud130\ubca0\uc774\uc2a4 \ubcf5\uad6c\n    gunzip -c \"\/tmp\/$backup_file\" | mysql \n        --host=\"$RECOVERY_DB_HOST\" \n        --user=\"$DB_ADMIN_USER\" \n        --password=\"$DB_ADMIN_PASSWORD\" \n        \"$DB_NAME\"\n\n    echo \"Database restore completed\"\n}\n\n# 2. \uc560\ud50c\ub9ac\ucf00\uc774\uc158 \ubc30\ud3ec\ndeploy_from_backup() {\n    local git_commit=$1\n\n    echo \"Deploying application from commit $git_commit\"\n\n    # \uc774\uc804 \ubc84\uc804\uc73c\ub85c \ub864\ubc31\n    kubectl set image deployment\/myapp-backend \n        backend=\"$ECR_REGISTRY\/myapp-backend:$git_commit\"\n\n    # \ubc30\ud3ec \uc644\ub8cc \ub300\uae30\n    kubectl rollout status deployment\/myapp-backend --timeout=600s\n\n    echo \"Application deployment completed\"\n}\n\n# 3. \ud2b8\ub798\ud53d \uc804\ud658\nswitch_traffic() {\n    local target_cluster=$1\n\n    echo \"Switching traffic to $target_cluster\"\n\n    # DNS \ub808\ucf54\ub4dc \uc5c5\ub370\uc774\ud2b8 (Route53)\n    aws route53 change-resource-record-sets \n        --hosted-zone-id \"$HOSTED_ZONE_ID\" \n        --change-batch file:\/\/dns-change-$target_cluster.json\n\n    echo \"Traffic switch completed\"\n}\n\n# 4. \ud5ec\uc2a4\uccb4\ud06c\nhealth_check() {\n    local endpoint=$1\n    local max_attempts=30\n    local attempt=1\n\n    while &#91; $attempt -le $max_attempts ]; do\n        if curl -f \"$endpoint\/health\" &gt; \/dev\/null 2&gt;&amp;1; then\n            echo \"Health check passed\"\n            return 0\n        fi\n\n        echo \"Health check failed (attempt $attempt\/$max_attempts)\"\n        sleep 10\n        ((attempt++))\n    done\n\n    echo \"Health check failed after $max_attempts attempts\"\n    return 1\n}\n\n# \uba54\uc778 \ubcf5\uad6c \ud504\ub85c\uc138\uc2a4\nmain() {\n    local recovery_type=$1\n\n    case $recovery_type in\n        \"database\")\n            restore_database \"$2\"\n            ;;\n        \"application\")\n            deploy_from_backup \"$2\"\n            ;;\n        \"full\")\n            restore_database \"$2\"\n            deploy_from_backup \"$3\"\n            switch_traffic \"backup\"\n            health_check \"https:\/\/api.example.com\"\n            ;;\n        *)\n            echo \"Usage: $0 {database|application|full} &#91;backup_date] &#91;git_commit]\"\n            exit 1\n            ;;\n    esac\n}\n\nmain \"$@\"<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">9. \uc131\ub2a5 \ucd5c\uc801\ud654 \uc804\ub7b5<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\ub370\uc774\ud130\ubca0\uc774\uc2a4 \ucd5c\uc801\ud654<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>-- \uc778\ub371\uc2a4 \ucd5c\uc801\ud654 \uc608\uc2dc\n-- 1. \uc790\uc8fc \uc870\ud68c\ub418\ub294 \uceec\ub7fc\uc5d0 \uc778\ub371\uc2a4 \uc0dd\uc131\nCREATE INDEX idx_users_email ON users(email);\nCREATE INDEX idx_orders_user_id_created_at ON orders(user_id, created_at);\n\n-- 2. \ubcf5\ud569 \uc778\ub371\uc2a4 (\uc21c\uc11c\uac00 \uc911\uc694!)\n-- WHERE user_id = ? AND status = ? ORDER BY created_at DESC\nCREATE INDEX idx_orders_composite ON orders(user_id, status, created_at);\n\n-- 3. \ubd80\ubd84 \uc778\ub371\uc2a4 (\uc870\uac74\ubd80 \uc778\ub371\uc2a4)\nCREATE INDEX idx_active_users ON users(id) WHERE status = 'active';\n\n-- 4. \ucffc\ub9ac \ucd5c\uc801\ud654\n-- BEFORE: N+1 \ubb38\uc81c\nSELECT * FROM orders;\n-- \uac01 \uc8fc\ubb38\ub9c8\ub2e4: SELECT * FROM users WHERE id = ?\n\n-- AFTER: JOIN \uc0ac\uc6a9\nSELECT o.*, u.name, u.email\nFROM orders o\nJOIN users u ON o.user_id = u.id\nWHERE o.created_at &gt;= '2024-01-01';\n\n-- 5. \ud398\uc774\uc9c0\ub124\uc774\uc158 \ucd5c\uc801\ud654\n-- BEFORE: OFFSET \uc0ac\uc6a9 (\ub290\ub9bc)\nSELECT * FROM products ORDER BY id LIMIT 20 OFFSET 10000;\n\n-- AFTER: \ucee4\uc11c \uae30\ubc18 \ud398\uc774\uc9c0\ub124\uc774\uc158\nSELECT * FROM products WHERE id &gt; 10000 ORDER BY id LIMIT 20;<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \ub808\ubca8 \ucd5c\uc801\ud654<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ 1. \ub370\uc774\ud130\ubca0\uc774\uc2a4 \uc5f0\uacb0 \ud480 \ucd5c\uc801\ud654\nconst poolConfig = {\n  connectionLimit: 20,\n  acquireTimeout: 60000,\n  timeout: 60000,\n  reconnect: true,\n  \/\/ \uc5f0\uacb0 \uc7ac\uc0ac\uc6a9\n  idleTimeout: 300000,\n  \/\/ \ub370\ub4dc\ub77d \uac10\uc9c0\n  deadlockTimeout: 60000,\n};\n\n\/\/ 2. \uce90\uc2f1 \uc804\ub7b5\nclass ProductService {\n  static async getProduct(id) {\n    const cacheKey = `product:${id}`;\n\n    \/\/ 1\ucc28: \uba54\ubaa8\ub9ac \uce90\uc2dc (\uac00\uc7a5 \ube60\ub984)\n    let product = memoryCache.get(cacheKey);\n    if (product) return product;\n\n    \/\/ 2\ucc28: Redis \uce90\uc2dc\n    product = await redisCache.get(cacheKey);\n    if (product) {\n      memoryCache.set(cacheKey, product, 300); \/\/ 5\ubd84\n      return product;\n    }\n\n    \/\/ 3\ucc28: \ub370\uc774\ud130\ubca0\uc774\uc2a4\n    product = await db.query(\"SELECT * FROM products WHERE id = ?\", &#91;id]);\n\n    if (product) {\n      \/\/ \uce90\uc2dc\uc5d0 \uc800\uc7a5 (TTL: 1\uc2dc\uac04)\n      await redisCache.set(cacheKey, product, 3600);\n      memoryCache.set(cacheKey, product, 300);\n    }\n\n    return product;\n  }\n\n  \/\/ \uce90\uc2dc \ubb34\ud6a8\ud654\n  static async updateProduct(id, data) {\n    const result = await db.query(\"UPDATE products SET ? WHERE id = ?\", &#91;data, id]);\n\n    \/\/ \uce90\uc2dc \ubb34\ud6a8\ud654\n    const cacheKey = `product:${id}`;\n    memoryCache.del(cacheKey);\n    await redisCache.del(cacheKey);\n\n    return result;\n  }\n}\n\n\/\/ 3. \ube44\ub3d9\uae30 \ucc98\ub9ac \ucd5c\uc801\ud654\nconst processOrder = async (orderData) =&gt; {\n  try {\n    \/\/ 1. \uc8fc\ubb38 \uc0dd\uc131 (\ub3d9\uae30)\n    const order = await Order.create(orderData);\n\n    \/\/ 2. \ubd80\uac00 \uc791\uc5c5\ub4e4\uc740 \ube44\ub3d9\uae30\ub85c \ucc98\ub9ac\n    await Promise.all(&#91;\n      \/\/ \uc7ac\uace0 \ucc28\uac10\n      updateInventory(orderData.items),\n      \/\/ \uc774\uba54\uc77c \ubc1c\uc1a1 (\ud050\uc5d0 \ucd94\uac00)\n      emailQueue.add(\"order-confirmation\", {\n        orderId: order.id,\n        userEmail: orderData.email,\n      }),\n      \/\/ \ubd84\uc11d \ub370\uc774\ud130 \uc804\uc1a1\n      analyticsQueue.add(\"order-event\", {\n        event: \"order_created\",\n        orderId: order.id,\n        amount: orderData.total,\n      }),\n    ]);\n\n    return order;\n  } catch (error) {\n    \/\/ \ubcf4\uc0c1 \ud2b8\ub79c\uc7ad\uc158\n    await rollbackOrder(orderData);\n    throw error;\n  }\n};\n\n\/\/ 4. \uc774\ubbf8\uc9c0 \ucd5c\uc801\ud654 \uc11c\ube44\uc2a4\nconst sharp = require(\"sharp\");\n\nconst optimizeImage = async (inputBuffer, options = {}) =&gt; {\n  const { width = 800, height = 600, quality = 80, format = \"webp\" } = options;\n\n  try {\n    const optimized = await sharp(inputBuffer)\n      .resize(width, height, {\n        fit: \"inside\",\n        withoutEnlargement: true,\n      })\n      .toFormat(format, { quality })\n      .toBuffer();\n\n    return optimized;\n  } catch (error) {\n    console.error(\"Image optimization failed:\", error);\n    throw error;\n  }\n};\n\n\/\/ 5. \uc694\uccad \ubc30\uce6d (DataLoader \ud328\ud134)\nconst DataLoader = require(\"dataloader\");\n\nconst userLoader = new DataLoader(async (userIds) =&gt; {\n  \/\/ \uc5ec\ub7ec \uc0ac\uc6a9\uc790\ub97c \ud55c \ubc88\uc5d0 \uc870\ud68c\n  const users = await db.query(\"SELECT * FROM users WHERE id IN (?)\", &#91;userIds]);\n\n  \/\/ ID \uc21c\uc11c\ub300\ub85c \uc815\ub82c\n  return userIds.map((id) =&gt; users.find((user) =&gt; user.id === id) || null);\n});\n\n\/\/ \uc0ac\uc6a9 \uc608\uc2dc\nconst orders = await Order.findAll();\nconst enrichedOrders = await Promise.all(\n  orders.map(async (order) =&gt; ({\n    ...order,\n    user: await userLoader.load(order.user_id), \/\/ \ubc30\uce6d\ub428\n  }))\n);<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\ub9c8\uce58\uba70: Production \uc124\uacc4\uc758 \ud575\uc2ec \uc6d0\uce59<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\uccb4\ud06c\ub9ac\uc2a4\ud2b8 \uc694\uc57d<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">\ud83d\udccb \uc124\uacc4 \ub2e8\uacc4<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] <strong>\uc6a9\ub7c9 \uacc4\ud68d<\/strong>: \uc608\uc0c1 \uc0ac\uc6a9\uc790 \uc218\uc640 \ud2b8\ub798\ud53d \ud328\ud134 \ubd84\uc11d<\/li>\n\n\n\n<li>[ ] <strong>\uae30\uc220 \uc2a4\ud0dd \uc120\ud0dd<\/strong>: \ud300 \uc5ed\ub7c9\uacfc \uc694\uad6c\uc0ac\ud56d\uc5d0 \ub9de\ub294 \uae30\uc220 \uc120\ud0dd<\/li>\n\n\n\n<li>[ ] <strong>\uc544\ud0a4\ud14d\ucc98 \uc124\uacc4<\/strong>: \ud655\uc7a5 \uac00\ub2a5\ud558\uace0 \uc720\uc9c0\ubcf4\uc218 \uac00\ub2a5\ud55c \uad6c\uc870<\/li>\n\n\n\n<li>[ ] <strong>\ubcf4\uc548 \uc124\uacc4<\/strong>: \ub2e4\uce35 \ubc29\uc5b4 \uc804\ub7b5 \uc218\ub9bd<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">\ud83d\ude80 \uad6c\ud604 \ub2e8\uacc4<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] <strong>CI\/CD \ud30c\uc774\ud504\ub77c\uc778<\/strong>: \uc790\ub3d9\ud654\ub41c \ube4c\ub4dc, \ud14c\uc2a4\ud2b8, \ubc30\ud3ec<\/li>\n\n\n\n<li>[ ] <strong>\ubaa8\ub2c8\ud130\ub9c1 \uc2dc\uc2a4\ud15c<\/strong>: \uba54\ud2b8\ub9ad, \ub85c\uadf8, \uc54c\ub9bc \uccb4\uacc4<\/li>\n\n\n\n<li>[ ] <strong>\ubc31\uc5c5 \uc804\ub7b5<\/strong>: \uc815\uae30 \ubc31\uc5c5\uacfc \ubcf5\uad6c \uc808\ucc28<\/li>\n\n\n\n<li>[ ] <strong>\uc131\ub2a5 \ucd5c\uc801\ud654<\/strong>: \ub370\uc774\ud130\ubca0\uc774\uc2a4, \uce90\uc2f1, \ube44\ub3d9\uae30 \ucc98\ub9ac<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">\ud83d\udd27 \uc6b4\uc601 \ub2e8\uacc4<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li>[ ] <strong>\uc7a5\uc560 \ub300\uc751<\/strong>: \ube60\ub978 \uac10\uc9c0\uc640 \ubcf5\uad6c \uc2dc\uc2a4\ud15c<\/li>\n\n\n\n<li>[ ] <strong>\ud655\uc7a5 \uacc4\ud68d<\/strong>: \ud2b8\ub798\ud53d \uc99d\uac00\uc5d0 \ub300\ud55c \ub300\uc751 \ubc29\uc548<\/li>\n\n\n\n<li>[ ] <strong>\ubcf4\uc548 \uc720\uc9c0<\/strong>: \uc815\uae30\uc801\uc778 \ubcf4\uc548 \uc810\uac80\uacfc \uc5c5\ub370\uc774\ud2b8<\/li>\n\n\n\n<li>[ ] <strong>\ube44\uc6a9 \ucd5c\uc801\ud654<\/strong>: \ub9ac\uc18c\uc2a4 \uc0ac\uc6a9\ub7c9 \ubaa8\ub2c8\ud130\ub9c1\uacfc \ucd5c\uc801\ud654<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\ub2e8\uacc4\ubcc4 \uc131\uc7a5 \ub85c\ub4dc\ub9f5<\/h3>\n\n\n\n<p><strong>\uc8fc\ub2c8\uc5b4 \uac1c\ubc1c\uc790 (1\ub144\ucc28)<\/strong>:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\ub2e8\uc77c \uc11c\ubc84 \ubc30\ud3ec \uacbd\ud5d8\ud558\uae30<\/li>\n\n\n\n<li>\uae30\ubcf8\uc801\uc778 \ubaa8\ub2c8\ud130\ub9c1 \uc124\uc815\ud558\uae30<\/li>\n\n\n\n<li>\uc218\ub3d9 \ubc30\ud3ec \ud504\ub85c\uc138\uc2a4 \uc774\ud574\ud558\uae30<\/li>\n<\/ol>\n\n\n\n<p><strong>\uc911\uae09 \uac1c\ubc1c\uc790 (2-3\ub144\ucc28)<\/strong>:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\ub85c\ub4dc \ubc38\ub7f0\uc11c\uc640 \ub2e4\uc911 \uc11c\ubc84 \uad6c\uc131<\/li>\n\n\n\n<li>CI\/CD \ud30c\uc774\ud504\ub77c\uc778 \uad6c\ucd95<\/li>\n\n\n\n<li>\ub370\uc774\ud130\ubca0\uc774\uc2a4 \ucd5c\uc801\ud654 \uacbd\ud5d8<\/li>\n<\/ol>\n\n\n\n<p><strong>\uc2dc\ub2c8\uc5b4 \uac1c\ubc1c\uc790 (4\ub144\ucc28+)<\/strong>:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\ub9c8\uc774\ud06c\ub85c\uc11c\ube44\uc2a4 \uc544\ud0a4\ud14d\ucc98 \uc124\uacc4<\/li>\n\n\n\n<li>\ud074\ub77c\uc6b0\ub4dc \ub124\uc774\ud2f0\ube0c \ud658\uacbd \uad6c\ucd95<\/li>\n\n\n\n<li>\ub300\uaddc\ubaa8 \ud2b8\ub798\ud53d \ucc98\ub9ac \uacbd\ud5d8<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">\ub9c8\uc9c0\ub9c9 \uc870\uc5b8<\/h3>\n\n\n\n<p><strong>\uc644\ubcbd\ud55c \uc544\ud0a4\ud14d\ucc98\ub294 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.<\/strong> \ud604\uc7ac \uc694\uad6c\uc0ac\ud56d\uc5d0 \ub9de\ub294 <strong>&#8220;\ucda9\ubd84\ud788 \uc88b\uc740&#8221;<\/strong> \uc124\uacc4\ub97c \ud558\uace0, \ud544\uc694\uc5d0 \ub530\ub77c <strong>\uc810\uc9c4\uc801\uc73c\ub85c \ubc1c\uc804<\/strong>\uc2dc\ucf1c \ub098\uac00\ub294 \uac83\uc774 \uc911\uc694\ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<p>\uc2dc\uc791\uc740 \uc791\uac8c, \ud558\uc9c0\ub9cc \ud655\uc7a5 \uac00\ub2a5\ud558\uac8c. \uadf8\ub9ac\uace0 \ud56d\uc0c1 <strong>\uc0ac\uc6a9\uc790 \uacbd\ud5d8<\/strong>\uc744 \ucd5c\uc6b0\uc120\uc73c\ub85c \uc0dd\uac01\ud558\uc138\uc694! \ud83c\udfaf<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><em>\ub2e4\uc74c\uc5d0\ub294 &#8220;CI\/CD\uc640 \uc790\ub3d9\ud654 \ubc30\ud3ec \uc2e4\uc804 \uac00\uc774\ub4dc&#8221;\ub85c \ub354 \uae4a\uc774 \uc788\ub294 DevOps \uc5ec\uc815\uc744 \ud568\uaed8 \ud574\ubcf4\uaca0\uc2b5\ub2c8\ub2e4. \uae30\ub300\ud574 \uc8fc\uc138\uc694!<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\uc548\ub155\ud558\uc138\uc694, \uc131\uc7a5\ud558\ub294 \uac1c\ubc1c\uc790 \uc5ec\ub7ec\ubd84! &#8220;\uac1c\ubc1c\uc740 \uc644\ub8cc\ud588\ub294\ub370&#8230; \uc774\uc81c \uc5b4\ub5bb\uac8c \uc11c\ube44\uc2a4\ub97c \uc6b4\uc601\ud558\uc9c0?&#8221; \ud83e\udd14 \ub9ce\uc740 \uc8fc\ub2c8\uc5b4 \uac1c\ubc1c\uc790\ub4e4\uc774 \ucf54\ub4dc \uc791\uc131\uc5d0\ub294 \uc775\uc219\ud558\uc9c0\ub9cc, \uc2e4\uc81c \uc11c\ube44\uc2a4\ub97c Production \ud658\uacbd\uc5d0 \ubc30\ud3ec\ud558\uace0 \uc6b4\uc601\ud558\ub294 \uac83\uc740 \ub610 \ub2e4\ub978 \uc138\uacc4\uc785\ub2c8\ub2e4. \uc624\ub298\uc740 \uc81c\uac00 \uc2e4\ubb34\uc5d0\uc11c \uacaa\uc740 \ub2e4\uc591\ud55c \ud504\ub85c\uc81d\ud2b8\ub97c \ud1b5\ud574 \ubc30\uc6b4 Production \ud658\uacbd \uc124\uacc4\uc758 \ubaa8\ub4e0 \uac83\uc744 \uc815\ub9ac\ud574 \ub4dc\ub9ac\uaca0\uc2b5\ub2c8\ub2e4. \ub2e8\uc21c\ud788 &#8220;\uc11c\ubc84 \ud558\ub098\uc5d0 \uc62c\ub824\uc11c \ub3cc\ub9ac\uba74 \ub418\uc9c0 \uc54a\ub098?&#8221;\ub77c\uace0 \uc0dd\uac01\ud558\uc168\ub2e4\uba74, \uc774 \uae00\uc744 \ub05d\uae4c\uc9c0 \uc77d\uc5b4\ubcf4\uc138\uc694. \uc2e4\uc81c [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[91],"tags":[90,89,88],"class_list":["post-35","post","type-post","status-publish","format-standard","hentry","category-architecture","tag-production-","tag-89","tag-88"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/hed-g.me\/index.php?rest_route=\/wp\/v2\/posts\/35","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hed-g.me\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hed-g.me\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hed-g.me\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/hed-g.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=35"}],"version-history":[{"count":2,"href":"https:\/\/hed-g.me\/index.php?rest_route=\/wp\/v2\/posts\/35\/revisions"}],"predecessor-version":[{"id":70,"href":"https:\/\/hed-g.me\/index.php?rest_route=\/wp\/v2\/posts\/35\/revisions\/70"}],"wp:attachment":[{"href":"https:\/\/hed-g.me\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=35"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hed-g.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=35"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hed-g.me\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=35"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}