<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Devlog</title>
    <link>https://storyofvector7.tistory.com/</link>
    <description>개발 관련 조각을 끼워넣는 곳입니다.</description>
    <language>ko</language>
    <pubDate>Thu, 27 Aug 2026 09:14:30 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>recoma</managingEditor>
    <image>
      <title>Devlog</title>
      <url>https://tistory1.daumcdn.net/tistory/4843990/attach/05d4e3b936614c12b8ee51462dc395d2</url>
      <link>https://storyofvector7.tistory.com</link>
    </image>
    <item>
      <title>[Database:MySQL] 정해진 시간에 작동하는 이벤트 스케줄러(Event Scheduler) 만들기</title>
      <link>https://storyofvector7.tistory.com/90</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;회사에서 특정 기간 동안 어느 이벤트에 대한 발급 포인트를 일정 기간 동안 2배로 올리라는 업무를 받았습니다. 이벤트 발급 포인트는 데이터베이스에 저장이 되어 있기 때문에 시간에 맞춰 자동으로 DB에 있는 데이터를 업데이트 해야 했습니다. 복잡한 SQL문을 재사용할 수 있는 FUNCTION과&amp;nbsp; PROCEDURE, 그리고 특정한 상황에 맞춰 작동하는 TRIGGER의 개념까지 알고 있었지만, Batch Process 마냥 특정 시간에 맞춰서 데이터베이스를 조작하는 방법은 전혀 몰랐습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;서버가 Python 계열로 개발이 되어 있기 때문에 CeleryBeat라는 Batch Process Tool을 사용해서 특정 시각에 업데이트를 하는 것을 생각해 봤지만, 이게 적용이 되려면 서버를 다시 배포를 해야 했기 때문에 이 방법을 차선책으로 미루고 다른 방법을 고안하게 되었습니다. 이때 찾은 방법이 이벤트 스케줄러(Event Scheduler) 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Event Scheduler&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이벤트 스케줄러는 Python 계열의 CeleryBeat 처럼 일정한 시간 또는 특정한 시간에 맞춰 SQL로직을 작동시키는 일종의 장치 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;특정 시간에 맞춰 딱 한번만 작동시키기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;market이라는 테이블이 있고 데이터는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 51px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;id(int)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;name(varchar)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;price(int)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;pencel&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1000&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;eraser&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2000&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2024년 7월 1일에 연필(pencel)의 가격을 1000원 올린다고 했을 때, 아래와 같이 이벤트를 등록해 주면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1715994329928&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;create event increase_price_of_pencel
on schedule at '2024-07-01 00:00:00'
do
    begin
        update market set value = 1000 where name = 'pencel';
end;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;특정 시간에 맞춰 딱 한번만 작동시키기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; 한번 말고 시간 간격에 맞춰 SQL문을 작동시킬 수도 있습니다. 이번에는 2024년 8월 1일 부터 연필(pencel)의 가격을 매일 00시 00분 00초 마다 1000원씩 인상(?) 하려고 합니다. 그렇다면 아래와 같이 이벤트를 등록하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716043630306&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;create event increase_price_of_pencel_per_a_day
on schedule
every 1 day
starts '2024-08-01 00:00:00'
do
    begin
        set @price = (select price from market where id = 1);
        update market set price = @price + 1000 where id = 1;
end;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Database/이론</category>
      <category>MYSQL</category>
      <category>데이터베이스</category>
      <author>recoma</author>
      <guid isPermaLink="true">https://storyofvector7.tistory.com/90</guid>
      <comments>https://storyofvector7.tistory.com/90#entry90comment</comments>
      <pubDate>Sat, 18 May 2024 23:47:44 +0900</pubDate>
    </item>
    <item>
      <title>[PS:Leetcode] Factorial Trailing Zeroes</title>
      <link>https://storyofvector7.tistory.com/89</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;거의 2년만에 해보는 PS 포스팅...&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;난이도: &lt;span style=&quot;color: #f89009;&quot;&gt;medium&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제 링크: &lt;a href=&quot;https://leetcode.com/problems/factorial-trailing-zeroes/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/factorial-trailing-zeroes/description/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Given an integer&amp;nbsp;n, return&amp;nbsp;the number of trailing zeroes in&amp;nbsp;n!.&lt;br /&gt;Note that&amp;nbsp;n! = n * (n - 1) * (n - 2) * ... * 3 * 2 * 1.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0과 10000사이의 수 n이 주어졌을 때 n의 팩토리얼인 n!에서 0이 몇개 들어가 있는지 그 갯수를 구하는 간단한 문제 입니다. (단 n = 0일대 0의 갯수는 0입니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Solution 1&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0의 갯수를 새는 방법은 간단합니다. n! = a * 10^b 라면 0의 갯수는 10의 제곱수인 &lt;b&gt;b&lt;/b&gt;가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 n!이 더이상 10으로 나누어 떨어질 때 까지 10을 계속 나누면서 카운팅을 해주면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1713802255671&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int solution(int n)
{
    int ans = 0;
    int x = 1;
    for(int i = 1; i &amp;lt;= n; i++)
        x *= i;
    while(x%10 == 0) {
        ans++;
        x /= 10;
    }
    return ans;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 n=10000일 때의 n!값은 int는 커녕 long long을 아득히 넘는 숫자이기 때문에 이런식으로 문제를 푸는 것을 사실상 불가능 합니다. 그렇기 때문에 n!값을 계산하기 보다는 1부터 n까지 순회하면서 각 숫자가 2를 곱한 갯수와 5를 곱한 갯수를 구해 모두 취합 한 다음, 2를 곱한 갯수와 5를 곱한 갯수 중 작은 갯수를 정답으로 출력하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 4!이 있다고 한다면 1 * 2 * 3 * 4 * 5가 됩니다. 여기서 1은 아무것도 없고, 2는 2가 1개, 3도 아무것도 없고, 4는 2가 2개 있습니다(2^2=4) 그리고 마지막으로 5는 5가 하나 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 2의 갯수와 5의 갯수를 모두 취합하면, 2는 3개(2, 2^2), 5는 1개가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2는 3개나 있지만 5는 1개밖에 없으므로 여기서 10의 1제곱까지만 구할 수 있고, 제곱수는 곧 정답을 의미하므로&lt;b&gt; n=5일 때 정답은 1이 됩니다. &lt;/b&gt;실제로 5!은 120으로 0이 하나 있습니다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713802514388&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int solution(int n) {
    int cnt_2 = 0, cnt_5 = 0;

    for(int _k = 1; _k &amp;lt;= n; _k++) {
        int k = _k;
        
        while(k%2 == 0) {
            cnt_2++;
            k /= 2;
        }

        while(k%5 == 0) {
            cnt_5++;
            k /= 5;
        }
    }
    return min(cnt_2, cnt_5);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;n이라는 숫자가 있을 때 a라는 인자를 가지고 있는지 그리고 몇제곱 까지 있는지 구하는 방법은 간단합니다. &lt;b&gt;그냥 n이 a으로 나누어 떨어지지 않을 때 까지 계속 a로 나누면서 동시에 갯수를 카운팅 하면 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Solution 2 (최적화)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Solution 1의 방식은 어떻게 보면 가장 무식하지만 n의 범위가 작기 때문에 충분히 통과되는 코드 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 뭔가 찝찝합니다. 2중 반복문이 들어가 있으니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;n이 10000이니까 속도가 빨라 보일 뿐이지 n의 범위를 좀만 늘려도 시간이 오래 걸릴 께 눈에 선합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 이중 반복문을 사용하지 않고도 풀수 있는 방법이 또 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 서술했다시피 n!에서의 0의 갯수는 10의 제곱수와 같다고 했습니다. 또한 10 = 2 * 5가 되구요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 생각을 좀만 해보면 2는 n이 짝수가 될 때마다, 즉 2의 배수가 될 때마다 추가가 되지만, 5의 경우 5의 배수가 될 때마다 추가가 됩니다. x제곱으로 인해 5의 갯수가 x개로 갑자기 훅! 들어온다 치더라도 이미 이전에 2도 마찬가지로 x개가 훅! 들어왔을 것입니다. &lt;b&gt;즉, n!에서 2의 갯수는 5의 갯수보다 항상 더 많이 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713803568720&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int solution(int n) {
    int cnt_5 = 0;
    for(int _k = 1; _k &amp;lt;= n; _k++) {
        int k = _k;
        while(k%5 == 0) {
            cnt_5++;
            k /= 5;
        }
    }
    return cnt_5;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 위와 같이 2의 갯수를 세재 않고 5의 갯수만 세는 코드가 실제로 통과가 되기도 합니다. 더이상 2에 대해 따로 계산을 하지 않기 때문에 어느정도 최적화가 되어 있다고는 하지만 2중 반복문인 것은 여전합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다시 생각해 봅니다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아까 서술했다시피 n!에서의 2의 갯수는 더이상 의미가 없습니다. 5의 갯수만 생각하면 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 이 n은 5의 배수가 될 때마다 n!에서 5가 추가로 생겨납니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 79.7674%; height: 281px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 48.0066%; height: 20px;&quot;&gt;n의 범위&lt;/td&gt;
&lt;td style=&quot;width: 26.9934%; height: 20px;&quot;&gt;n!에서의 5의 제곱 갯수&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;n!에서의 0의 갯수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 48.0066%; height: 17px;&quot;&gt;1 &amp;lt;= n &amp;lt;= 4&lt;/td&gt;
&lt;td style=&quot;width: 26.9934%; height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 48.0066%; height: 17px;&quot;&gt;5 &amp;lt;= n &amp;lt;= 9&lt;/td&gt;
&lt;td style=&quot;width: 26.9934%; height: 17px;&quot;&gt;1 (0+1)&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 48.0066%; height: 17px;&quot;&gt;10 &amp;lt;= n &amp;lt;= 14&lt;/td&gt;
&lt;td style=&quot;width: 26.9934%; height: 17px;&quot;&gt;2 (1+1)&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 48.0066%; height: 17px;&quot;&gt;15 &amp;lt;= n &amp;lt;= 19&lt;/td&gt;
&lt;td style=&quot;width: 26.9934%; height: 17px;&quot;&gt;3 (2+1)&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 48.0066%; height: 17px;&quot;&gt;20 &amp;lt;= n &amp;lt;= 24&lt;/td&gt;
&lt;td style=&quot;width: 26.9934%; height: 17px;&quot;&gt;4 (3+1)&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 48.0066%; height: 17px;&quot;&gt;&lt;b&gt;25 &amp;lt;= n &amp;lt;= 30&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 26.9934%; height: 17px;&quot;&gt;6 (4+&lt;b&gt;2&lt;/b&gt;)&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;n = 1, 5, 10, 15, 20 까지 차례대로 1씩 카운팅 되어있습니다. 마찬가지로 0의 갯수도 1씩 카운팅 되어있겠죠&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 n=25일 때는 1이 아니라 2가 카운팅 되어있는데 그 이유는 &lt;b&gt;25는 5의 제곱이기 때문에 5가 2개가 추가로 들어갔기 때문입니다.&lt;/b&gt; 즉, n이 5의 배수가 될 때 단순히 1씩 카운팅 하는 것이 아니라 5의 제곱 갯수대로 카운팅을 하는 것입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가로 예를 들어 n=50이 되면 50=2*(5^2)이므로 2개가 추가가 되고, n=125면 125=5^3이므로 3개가 추가가 될 것입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713804662710&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int solution(int n) {
    int count = 0;
    int num = 5;
    while(n / num) {
        count += n/num;
        num *= 5;
    }
    return count;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 n!에서 5의 제곱 갯수는 n보다 작으면서 5의 제곱 수들을 차례대로 나눈 값의 합이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 n = 25의 경우 n을 5로 나누면 5가 되고 또한 n를 25로 나누면 1이 되므로 0의 갯수는 5+1=6이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 이중 반복문을 사용하지 않고도 최적화를 할 수 있게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;끗&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제는 어떠한 알고리즘 지식을 요구하지 않는 상당히 쉬운 문제였지만, 최적화를 시도한다면 나름 문제해결력을 요구하는 문제이기도 했습니다. 알고리즘을 활용하여 푸는 문제도 좋긴 하지만, 이렇게 순수 문제해결력을 요구하는 문제를 푸는 것도 PS공부에 도움이 될 것같다는 생각이 드네요.&lt;/p&gt;</description>
      <category>Problem Solving/코딩문제풀기</category>
      <category>PS</category>
      <category>리트코드</category>
      <category>알고리즘</category>
      <category>코딩테스트</category>
      <author>recoma</author>
      <guid isPermaLink="true">https://storyofvector7.tistory.com/89</guid>
      <comments>https://storyofvector7.tistory.com/89#entry89comment</comments>
      <pubDate>Tue, 23 Apr 2024 01:57:28 +0900</pubDate>
    </item>
    <item>
      <title>[Django Restframework] 두개 이상의 데이터베이스를 가지고 테스트하기</title>
      <link>https://storyofvector7.tistory.com/88</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 프레임워크들이 그렇듯, DJango기반의 서비스를 구현함에 있어 여기에 포함되는 데이터베이스는 DJango-ORM으로 관리되는 것이 통상적입니다. 그래야 추후 DB내용이 바뀔 때 ORM 자체 라이브러리 내에서 쉽게 해결을 할 수 있으니까요. 이렇게 ORM 하에 관리되는 Model들을 &lt;b&gt;Managed한 Model이라고 합니다.&lt;/b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;말 그대로 관리되고 있는 테이블아라고도 하죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 가끔식 서비스 외의 데이터베이스에 직접 접근을 해야 할 때가 있습니다. 관리자 페이자가 그 대표적인 예가 되겠군요. 물론 DJango 자체 내에 어드민 페이지 기능을 제공하지만, 이걸 사용하지 않고 따로 DJango로 A부터 Z까지 구축하게 된다면. 메인 데이터베이스는 관리자 페이지의 시점으로부터 외부 데이터베이스가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서비스 내 데이터베이스와는 달리 외부 데이터베이스의 성질들(대표적으로 테이블의 컬럼을 관리하는 것들)을 절대 건드려서는 안됩니다. &lt;/b&gt;이렇게 DJango 내에서 절대 건드려서는 안되는 Model들을 &lt;b&gt;Unmanged Model이라고 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Managed, Unmanaged의 개념&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Managed와 Unmanaged는 단순히 용어 같은 것이 아닌 실제로 django model에 지정할 수 있는 개념 입니다. &quot;class Meta&quot;에 &quot;managed=True&quot;를 적거나 아무것도 적지 않으면 해당 Model은 Managed하게 되고, 반대로 &quot;managed=False&quot;로 세팅을 하면 Unmanaged한 Model이 됩니다&lt;/p&gt;
&lt;pre id=&quot;code_1713429182022&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ExampleModel(models.Model):
	... 이하 생략 ...
    class Meta:
    	__tablename__ = &quot;example_model&quot;
        managed = False&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드 처럼 &lt;b&gt;managed = False로 세팅되어있는 모듈들은 migration 대상에서 제외됩니다.&lt;/b&gt; 즉, 테이블 성질의 변경으로부터 보호가 되기 때문에, 외부 데이터베이스의 테이블들이 DJango의 Migration작업으로 인해 훼손되지 않게 돼죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까진 다 좋은데 문제가 하나 생깁니다. Unmanaged한 Model들은 유닛테스트를 하는 도중에 발목을 잡게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;왜 발목을 잡는가?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DJango에서는 유닛테스트를 시작하기 전에 개발자가 지정한 데이터베이스 안에. 테이블들을 마이그레이션을 합니다. SQLite로 세팅되었다면 SQLite 파일을 생성을 한 다음 마이그레이션을, MySQL이나 다른 외부 데이터베이스를 연동했다면, 접두사가 &quot;test_&quot;인 스키마를 생성한 다음, 그 안에 마이그레이션을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 아까 설명했듯이, Unmanaged한 Model들은 Migration대상에 포함되지 않습니다. 그렇기 때문에 DJango는 Unmanaged한 Model모델들을 가져오지 못해 문제가 발생하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;  &lt;/span&gt;해결법 1(무식한 방법): django.settings를 활용해 테스트 할 때만 managed=True로 변경하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. settings.py에 TEST를 진행중인지에 대한 여부를 나타내는 환경변수를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1713430308944&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UNDER_TEST = (len(sys.argv) &amp;gt; 1 and sys.argv[1] == 'test')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. managed=False상태인 모든 모델의 메타 클래스의 managed를 전부다 아래와 같이 변경합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1713430491776&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Meta:
    db_table = &quot;event_banner_image&quot;
    managed = getattr(settings, &quot;UNDER_TEST&quot;, False)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 대상 DB모델을 호출하는 테스트 스크립트를 작성해서 정상적으로 테스트가 돌아가는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 단순하지만 클린코드가 아닌 더티코드로 가는 지름길 입니다. 각 Model.Meta 클래스 마다 저렇게 똑같이 작성해야 하기 때문이지요. 코드 가시성이 떨어지고 유지보수에 문제가 생깁니다. 이보다 더 나은 방법이 존재합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;&lt;b&gt; ⭐️ Test Runner 활용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 방법과 마찬가지로 managed = True로 바꿔야 한다는 방향은 일치하지만, 위의 방법에 비해 기술부채가 일어나지 않고 또 클린코드하게 할 수 있는 해결방안 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DiscoverRunner를 상속받아서 테스트가 맨 처음에 시작했을 때 managed를 True로 바꾸고, 끝나면 다시. 원래대로 돌려놓게 함수를 오버라이딩 함으로써 Model 클래스에 위와 같이 똑같은 코드 작성 필요없이 깔끔하게 해결할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;TestRunner&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;A test runner is a class defining a&amp;nbsp; run_tests() method. Django ships with a DiscoverRunner class that defines the default Django testing behavior. This class defines the run_tests() entry point, plus a selection of other methods that are used by run_tests() to set up, execute and tear down the test suite.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TestRunner는 유닛테스트의 작동 방식을 정의하며. 각 TestCase 마다 작동 방식을 정의하는 함수들(setUpClass, setUp 등&amp;hellip;)과는 &lt;span data-token-index=&quot;1&quot;&gt;다르게 전체적인 작동 방식을 정의합니다.&lt;/span&gt; 예를 들어, 모든 테스트 케이스에서 공통적으로 사용할 수 있는 환경변수를 정의할 수도 있고, 데이터베이스 모델 정보도 변경할 수 있으며, 마이그레이션 없이 테스트를 진행하게 수정할 수 있습니다. 즉, &lt;b&gt;&lt;span data-token-index=&quot;3&quot;&gt;TestCase에서는 할 수 없는 정밀한 작업 프로세스를 여기서 구현할 수 있습니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 django.test.runner.DiscoverRunner 를 상속받아서 사용합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;오버라이딩 할 수 있는 함수들&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;setup_test_environment()&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;막 테스트가 시작될 때 작동하는 함수, 모델들을 managed=True로 변경하는 로직을 여기서 구현합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;teardown_test_environment()&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 테스트 케이스가 끝나면 작동되는 함수, managed정보가 변경된 모델들을 원상복귀할 때 사용됩니다..&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;setup_databases()&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트용 데이터베이스를 생성하고, 마이그레이션을 하는 등, 테스트에 사용되는 데이터베이스들을 세팅하는 데 사용합니다..&lt;/li&gt;
&lt;li&gt;테스트용 데이터베이스를 생성하는 것을 원하지 않는다면 함수내용을 비우는 방향으로 오버라이딩을 하면 됩니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;teardown_databases()&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트용 데이터베이스를 거두는 등, DB 환경을 원상복귀 시키는데 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;run_tests()&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 DJango 프로젝트 안의 모든 테스트 파일들이 돌아갈 수 있었던 이유는 매 테스트를 실행할 때마다 run_tests()함수가 작동되기 때문입니다. run_tests()의 로직은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;테스트가 시작되기 전 전처리 수행 setup_test_environment()&lt;/li&gt;
&lt;li&gt;테스트 케이스 수집 build_suite()&lt;/li&gt;
&lt;li&gt;테스트용 데이터베이스 수집 및 세팅 get_databases(), setup_databases()&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 수행&lt;/b&gt; run_suite()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;test실패 (assert)시 exception 호출&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;테스트 종료 후 프로세스 수행 teardown_databases() teardown_test_environment()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트실패 여부 상관업싱 해당 로직은 수행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;run_tests() 코드 전문&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1713482155412&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    def run_tests(self, test_labels, extra_tests=None, **kwargs):
        &quot;&quot;&quot;
        Run the unit tests for all the test labels in the provided list.

        Test labels should be dotted Python paths to test modules, test
        classes, or test methods.

        Return the number of tests that failed.
        &quot;&quot;&quot;
        if extra_tests is not None:
            warnings.warn(
                &quot;The extra_tests argument is deprecated.&quot;,
                RemovedInDjango50Warning,
                stacklevel=2,
            )
        self.setup_test_environment()
        suite = self.build_suite(test_labels, extra_tests)
        databases = self.get_databases(suite)
        suite.serialized_aliases = set(
            alias for alias, serialize in databases.items() if serialize
        )
        with self.time_keeper.timed(&quot;Total database setup&quot;):
            old_config = self.setup_databases(
                aliases=databases,
                serialized_aliases=suite.serialized_aliases,
            )
        run_failed = False
        try:
            self.run_checks(databases)
            result = self.run_suite(suite)
        except Exception:
            run_failed = True
            raise
        finally:
            try:
                with self.time_keeper.timed(&quot;Total database teardown&quot;):
                    self.teardown_databases(old_config)
                self.teardown_test_environment()
            except Exception:
                # Silence teardown exceptions if an exception was raised during
                # runs to avoid shadowing it.
                if not run_failed:
                    raise
        self.time_keeper.print_results()
        return self.suite_result(suite, result)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사용 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. DiscoverRunner를 상속받는 모듈을 작성합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;setup_test_environment
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;테스트가 본격적으로 시작하기 전에 딱 한번 실행되는 함수&lt;/b&gt;로 TestCase가 처음 실행될 때 작동하는 setUpClass나 setUpTestData 와는 결이 다르다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;teardown_test_environment
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;모든 테스트 케이스가 다 끝나는 순간에 작동되는 함수&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1713483451305&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from typing import Set

from django.test.runner import DiscoverRunner
from django.apps import apps


class UnManagedModelTestRunner(DiscoverRunner):
    un_managed_models: Set[str]

    def __init__(self, *args, **kwargs):
        self.un_managed_models = set()
        super().__init__(*args, **kwargs)

    def setup_test_environment(self, **kwargs):
        for model in apps.get_models():
            model_name = model._meta.model_name
            if not model._meta.managed:
                self.un_managed_models.add(model_name)
                model._meta.managed = True

        super(UnManagedModelTestRunner, self).setup_test_environment()

    def teardown_test_environment(self, **kwargs):
        for model in apps.get_models():
            model_name = model._meta.model_name
            if model_name in self.un_managed_models:
                model._meta.managed = False

        super(UnManagedModelTestRunner, self).teardown_test_environment()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.settings.py에 TEST_RUNNER 변수를 추가합니다&lt;/p&gt;
&lt;pre id=&quot;code_1713483478300&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# settings.py

TEST_RUNNER = &quot;api.tests.runner.UnManagedModelTestRunner&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 유닛테스트를 실행해 제대로 작동되는 지 확인합니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;유의 사항&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Auth User Model 관련&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저를 담당하는 테이블을 AUTH_USER_MODEL로 설정을 했다면 아래와 같은 에러가 발생할 수 있습니다&lt;/p&gt;
&lt;pre id=&quot;code_1713485162048&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;django.db.utils.IntegrityError: (1215, 'Cannot add foreign key constraint')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유닛테스트를 진행하기 전에 DJango에서는 테스트 전용 데이터베이스들을 생성하고 그 데이터베이스 위에 개발자가 작성한 모델들을 토대로 Migration을 진행하게 됩니다. 이때 생성되는 모델들 중에 유저가 정의한 모델들 말고 DJango 내장의 모델들도 같이 Migration을 하게 되는데, 이 중 django_admin_log라는 테이블이 생성되면서 개발자가 정의한 유저 모델과 relation 작업을 하게 됩니다. 그러나 유저 모델이 없는 데이터베이스에서는 django_admin_log와 relation을 진행할 수 없기 때문에 위의 에러가 발생하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 방법은 간단합니다. INSTALLED_APPS에 'django_contrib_admin'을 주석처리 또는 제거하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 직접 정의한 모델을 마이그레이션을 하기 위해 INSTALLED APPS에 해당 모델과 관련된 앱 이름을 작성하게 됩니다. python manage.py 을 수행할 때, 내가 직접 정의한 모델 말고도 장고 내장 테이블도 같이 마이그레이션 되는데, &lt;span data-token-index=&quot;7&quot;&gt;이는 직접 하드코딩 된 것이 아니라 장고 내장 테이블을 포함하고 있는 모듈이 INSTALLED_APPS&lt;/span&gt;&lt;span data-token-index=&quot;9&quot;&gt;에 선언되어 있기 때문에 저절로 같이 마이그레이션 된 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;django_admin_log도 마찬가지로 django.contrib.admin 안에 포함되어 있기 때문에 마이그레이션이 된 것입니다. 따라서 반대로 django.contrib.admin을 주석처리 및 제외를 하게 되면 django_admin_log를 생성하지 않게 될 것이고 마찬가지로 외래키 문제도 더이상 발생하지 않게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, DJango 앱에 어드민 페이지가 반드시 필요하다고 한다면, 단순히 제거하는 것이 아니라 test할대만 제거하는 방향으로 전략을 짜야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1713485642108&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSTALLED_APPS = [
    # 'django.contrib.admin'
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;References&lt;/b&gt;&lt;/h2&gt;
&lt;figure id=&quot;og_1713483516912&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Django Database Testing Unmanaged Tables with Migrations&quot; data-og-description=&quot;The answer is here: Models inside tests &amp;ndash; Django 1.7 issue. The problem is that you have some tables that are not managed by Django. (They get called &amp;ldquo;legacy&amp;rdquo;, but in my case, the&amp;hellip;&quot; data-og-host=&quot;technote.fyi&quot; data-og-source-url=&quot;https://technote.fyi/programming/django/django-database-testing-unmanaged-tables-with-migrations/&quot; data-og-url=&quot;https://technote.fyi/programming/django/django-database-testing-unmanaged-tables-with-migrations/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hM9QW/hyVS6I7cLJ/ZQ4G4i5OsYiJzpBNGx9xgk/img.jpg?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200&quot;&gt;&lt;a href=&quot;https://technote.fyi/programming/django/django-database-testing-unmanaged-tables-with-migrations/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://technote.fyi/programming/django/django-database-testing-unmanaged-tables-with-migrations/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hM9QW/hyVS6I7cLJ/ZQ4G4i5OsYiJzpBNGx9xgk/img.jpg?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Django Database Testing Unmanaged Tables with Migrations&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The answer is here: Models inside tests &amp;ndash; Django 1.7 issue. The problem is that you have some tables that are not managed by Django. (They get called &amp;ldquo;legacy&amp;rdquo;, but in my case, the&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;technote.fyi&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1713483530689&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Applications | Django documentation&quot; data-og-description=&quot;The web framework for perfectionists with deadlines.&quot; data-og-host=&quot;docs.djangoproject.com&quot; data-og-source-url=&quot;https://docs.djangoproject.com/en/5.0/ref/applications/&quot; data-og-url=&quot;https://docs.djangoproject.com/en/5.0/ref/applications/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cCpeF8/hyVPTkmyWi/BEYh7eCkBKgjSutSknPmN0/img.png?width=1200&amp;amp;height=546&amp;amp;face=0_0_1200_546&quot;&gt;&lt;a href=&quot;https://docs.djangoproject.com/en/5.0/ref/applications/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.djangoproject.com/en/5.0/ref/applications/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cCpeF8/hyVPTkmyWi/BEYh7eCkBKgjSutSknPmN0/img.png?width=1200&amp;amp;height=546&amp;amp;face=0_0_1200_546');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Applications | Django documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The web framework for perfectionists with deadlines.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.djangoproject.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/DJango</category>
      <category>Django</category>
      <category>Django RestFramework</category>
      <category>ORM</category>
      <category>유닛테스트</category>
      <author>recoma</author>
      <guid isPermaLink="true">https://storyofvector7.tistory.com/88</guid>
      <comments>https://storyofvector7.tistory.com/88#entry88comment</comments>
      <pubDate>Fri, 19 Apr 2024 09:15:32 +0900</pubDate>
    </item>
    <item>
      <title>[React] useEffect 첫 렌더링 때 작동 못하게 하기</title>
      <link>https://storyofvector7.tistory.com/87</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect를 사용하개 되면 state값 (useState로 선언된 변수들)이 바뀔 대 마다 이에 대한 대응을 할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 장바구니에서 내가 구매하기 위해 선택한 항목들을 다시 프로그램을 켜도 유지하고 싶다고 한다면, 항목이 변경될 때마다 항목 리스트를 업데이트 하는 API를 호출해야 합니다. 이때 useEffect를 사용하게 되면 해당 요구사항을 해결할 수 있겠죠&lt;/p&gt;
&lt;pre id=&quot;code_1711875012578&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [selectedItems, setSelectedItems] = useState([]);

useEffect(() =&amp;gt; {
	// 대충 항목리스트 업데이트하는 API 호출
	axios.post(&quot;/url&quot;, data=selectedItems...)
}, [selectedItems]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 useEffect는 state값이 변경되는 경우 말고도 처음 웹이 렌더링이 되었을 때도 작동이 되는데, 이를 이용해 state 변수 리스트를 가지는 deps라는 인자값을 빈배열로 선언하게 되면, useEffect에 등록된 콜백 함수는 오로지 첫 렌더링 때만 작동하게 됩니다.이를 이용해 오직 한번만 실행해야 하는 로직들을 작성할때 자주 사용되곤 합니다. 예를 들어 보통 서버에서 데이터를 가져오는 프로세스는 오직 한번만 작동해야 하는 경우가 많기 때문에 해당 방법을 사용하게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1711875377649&quot; class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
	// 대충 서버로부터 데이터를 가져오는 로직
}, []); // deps는 빈배열&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 가끔씩 이 useEffect를 처음 렌더링 할 때만 적용이 되지 않기를 바랄 때도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect의 콜백 함수에서 처음 렌더링 했을 때 로직이 작동되지 않도록 분기점을 만드는 전략을 세울 수 있지만, 여러군데에서 사용할 경우 해당 기능을 모듈화할 필요가 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1711876103560&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const useDidMountEffect = (func, deps) =&amp;gt; {
    const didMount = useRef&amp;lt;boolean&amp;gt;(false);

    useEffect(() =&amp;gt; {
        if (didMount.current) {
            func();
        } else {
            didMount.current = true;
        }
    }, deps);
}

// 활용
useDidMountEffect(() =&amp;gt; {
	// 대충 뭔갈 작업하는 로직
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;didMount를 false로 초기화를 했습니다. 즉, 처음 렌더링 할 때는 didMount가 false이기 때문에 콜백함수 func()를 실행하지 않고 didMount를 true로 전환합니다. 그리고 다음 렌더링 때는 didMount가 false가 아닌 true이므로 이때 콜백함수를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;didMount는 useState가 아닌 useRef로 선언이 되었는데, useRef는 useState와 달리 값이 변경되었다고 해서 Rerendering을 하지 않습니다. 만약에 didMount를 useState로 선언을 했다면, didMount.current = true 시점에서 개발자가 원하지 않음에도 불구하고 Rerendering 발생을 했을 것입니다. 하지만 useRef를 사용함으로써 그런 일은 일어나지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 첫 렌더링시 useEffect 콜백 함수가 작동하지 않게 할 수 있게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;틀린 부분이 있으면 지적 부탁드려요!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: &lt;a href=&quot;https://velog.io/@dlruddms5619/React-useEffect-%EC%B2%AB-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%8B%9C-%ED%95%A8%EC%88%98-%EC%8B%A4%ED%96%89-%EB%A7%89%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@dlruddms5619/React-useEffect-%EC%B2%AB-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%8B%9C-%ED%95%A8%EC%88%98-%EC%8B%A4%ED%96%89-%EB%A7%89%EA%B8%B0&lt;/a&gt;&lt;/p&gt;</description>
      <category>FrontEnd/React</category>
      <category>React</category>
      <category>reactjs</category>
      <category>useEffect</category>
      <category>리액트</category>
      <author>recoma</author>
      <guid isPermaLink="true">https://storyofvector7.tistory.com/87</guid>
      <comments>https://storyofvector7.tistory.com/87#entry87comment</comments>
      <pubDate>Sun, 31 Mar 2024 18:47:13 +0900</pubDate>
    </item>
    <item>
      <title>[Linux] 특정 service의 로그를 보는 방법</title>
      <link>https://storyofvector7.tistory.com/86</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;계기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 또 다른 개발 블로그(프론트엔드 포트폴리오용)에 소개 페이지를 추가하고 systemctl을 이용해서 재배포를 했더니&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKaDu2/btsFf3XzVuu/cB9FMOwYfgxrRkWhUTMXfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKaDu2/btsFf3XzVuu/cB9FMOwYfgxrRkWhUTMXfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKaDu2/btsFf3XzVuu/cB9FMOwYfgxrRkWhUTMXfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKaDu2%2FbtsFf3XzVuu%2FcB9FMOwYfgxrRkWhUTMXfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;836&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분조장을 유발하는 500에러가 터졌습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 얼탱이없는 사고가 왜 났는지 파악하기 위해 systemctl status명령어를 사용했습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JD0do/btsFhqEsVj9/yL8hi9clwEtfqdCeZNetb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JD0do/btsFhqEsVj9/yL8hi9clwEtfqdCeZNetb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JD0do/btsFhqEsVj9/yL8hi9clwEtfqdCeZNetb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJD0do%2FbtsFhqEsVj9%2FyL8hi9clwEtfqdCeZNetb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1246&quot; height=&quot;448&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파란 불이 켜저있지만 밑에 로그는 빨간불인 것처럼 보이는군요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;listOnTimeout 이라는 내용이 있는 걸 보니 뭔가 무한루프를 도는 것 같습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이거 가지고는 원인을 파악할 수가 없죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그의 전체 내용을 보아야 할 필요가 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 어떤 명령어를 입력해야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정답은 jouralctl 이라는 명령어를 사용하면 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;사용법&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;$ sudo journalctl -u [서비스 이름]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 명령어를 입력해먼 해당 서비스에 대한 로그를 처음부터 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcnMka/btsFh8XRJ6o/8vpp0HrWy3ojGuVd7PL8e0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcnMka/btsFh8XRJ6o/8vpp0HrWy3ojGuVd7PL8e0/img.png&quot; data-alt=&quot;'node' no such file or directory라는 에러 문구가 보이네요. 여기에서 문제가 터졌나 봅니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcnMka/btsFh8XRJ6o/8vpp0HrWy3ojGuVd7PL8e0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcnMka%2FbtsFh8XRJ6o%2F8vpp0HrWy3ojGuVd7PL8e0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1134&quot; height=&quot;693&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;'node' no such file or directory라는 에러 문구가 보이네요. 여기에서 문제가 터졌나 봅니다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;특정 조건을 만족하는 로그 내역 가져오기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 전에는 특정 서비스에 대해 로그를 가져왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 특정 서비스 말고도 작동되고 있는 service의 전체 로그를 가져올 수 있는 방법이 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-u [서비스 이름] 만 빼면 모든 서비스의 로그를 가져옵니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sudo journalctl&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 시간의 로그를 확인하려면 --since와 --until을 추가합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2024년 2월 24일 10시 부터 10시 10분 사이의 로그를 보고 싶은 경우 아래와 같이 입력하면 됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sudo journalctl --since &quot;2024-02-24 10:00:00&quot; --until &quot;2024-02-24 10:10:00&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;날짜를 따로 지정하는 것은 매우 귀찮은 일입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 journalctl은 최근 N개의 로그만 가져오는 기능도 제공합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sudo journalctl -n [로그 갯수]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;갯수 관심 없고 그냥 맨 끝으로 이동하고 싶다! 하면 -e를 추가합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sudo journalctl -e&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 로그를 실시간으로 볼 수 있는 기능도 제공합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;service에서 뭔가 메세지를 보내면 콘솔에서도 실시간으로 출력되는 기능이죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-f를 사용합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sudo journalctl -f&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-02-24104750-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;659&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c74Jgu/btsFeuavqPk/I0JMwLbwAjmRajOE0fz8A0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c74Jgu/btsFeuavqPk/I0JMwLbwAjmRajOE0fz8A0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c74Jgu/btsFeuavqPk/I0JMwLbwAjmRajOE0fz8A0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/c74Jgu/btsFeuavqPk/I0JMwLbwAjmRajOE0fz8A0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;659&quot; data-filename=&quot;2024-02-24104750-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;659&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;끗&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 특정 service의 로그를 확인하는 방법에 대해 알아보았습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이 기능을 통해 node 명령어를 제대로 찾을 수 없다는 원인을 파악했구요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 얘가 안돌아가는 문제를 해결하러 가보겠습니다 ㅂㅂ&lt;/p&gt;</description>
      <category>OS/생활리눅스</category>
      <category>linux</category>
      <category>log</category>
      <category>Service</category>
      <category>systemctl</category>
      <category>ubuntu</category>
      <category>리눅스</category>
      <category>우분투</category>
      <author>recoma</author>
      <guid isPermaLink="true">https://storyofvector7.tistory.com/86</guid>
      <comments>https://storyofvector7.tistory.com/86#entry86comment</comments>
      <pubDate>Sat, 24 Feb 2024 11:15:34 +0900</pubDate>
    </item>
    <item>
      <title>티스토리 다시 시작합니다.</title>
      <link>https://storyofvector7.tistory.com/notice/83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2023년 10월 까지 여기를 운영하다 &lt;a href=&quot;https://tech.skywinz.kr/posts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NextJS직접 구현한 개발 블로그&lt;/a&gt;를 따로 배포하면서 계속 방치해 왔었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 직접 개발한 개발 블로그는 기술 지식에 대해 깊이 다루는 내용을 적는 것이 목표이다 보니. 포스트를 업로드 하는 시기가 많이 늦습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 여기를 다시 한번 활용해 보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깊이 있는 내용 보다는, 실무 또는 사이드 프로젝트를 진행하면서 배운 짤막한 내용들이나, 개발자로서의 경험당 등이 여기에 올라올 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TIL처럼 매일 올리는 것은 못하지만, 그래도 1주일에 최소 한개 정도는 포스팅을 할 수 있도록 노력하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>recoma</author>
      <guid isPermaLink="true">https://storyofvector7.tistory.com/notice/83</guid>
      <pubDate>Thu, 4 Jan 2024 15:29:39 +0900</pubDate>
    </item>
    <item>
      <title>15개월차(2년차) 신입 개발자의 두번째 회고록</title>
      <link>https://storyofvector7.tistory.com/82</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이젠 티스토리를 블로그로서 사용을 안하려고 했는데, 어찌어찌하다 다시 여기로 돌아오게 되었네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에는 여러가지 이유가 있는데, 운영하고 있는 블로그들 중에 여기가 조회수가 가장 높은 것도 있고, 따로 이 글을 쓸만한 블로그 플랫폼이 없는 것도 있습니다. 네이버 블로그는 범위가 개발분야가 아니고, &lt;a href=&quot;https://tech.skywinz.kr/posts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NextJS로 직접 구현한 기술블로그&lt;/a&gt; 가 있기는 하지만 TIL이나 개발자로서의 경험담이 아닌 그냥 원론적인 기술관련 내용이 목적이기 때문에 저기에 작성을 하지 않고 여기를 다시 한번 더 활용해 보려고 합니다. (velog는 글쎄요... 이건 고민이 좀 많군요.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 가벼운 TIL이나, 그동안 개발자로 일을 해 오면서 느낀 점 같은 걸 쓸 것 같군요. 블로그 카테고리나 디자인도 다시 손보려고 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;잠깐 돌아보는 첫번 째 회고록&lt;/b&gt;&lt;/h2&gt;
&lt;figure id=&quot;og_1703776334690&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;6개월차 신입 개발자의 반성문과 회고록&quot; data-og-description=&quot;작년 여름이 끝나갈 즈음, 나는 이런 글을 썼다. 파이썬 백엔드 개발자 코테, 면접 후기 (2) (2022.07 ~ 2022.08) 파이썬 백엔드 개발자 코테, 면접 후기 (1) (2022.6 ~ 2022.7) 이번에 6월 ~ 7월 초에 7군데를 &quot; data-og-host=&quot;storyofvector7.tistory.com&quot; data-og-source-url=&quot;https://storyofvector7.tistory.com/76#comment18210245&quot; data-og-url=&quot;https://storyofvector7.tistory.com/76&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bVz4zX/hyUTLfT7jF/MSyyW90fKqcMBIvpBaY1G1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/WmTRB/hyUTBdhWkT/AKGp6tuw5178PCLnlJlL71/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://storyofvector7.tistory.com/76#comment18210245&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://storyofvector7.tistory.com/76#comment18210245&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bVz4zX/hyUTLfT7jF/MSyyW90fKqcMBIvpBaY1G1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/WmTRB/hyUTBdhWkT/AKGp6tuw5178PCLnlJlL71/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;6개월차 신입 개발자의 반성문과 회고록&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;작년 여름이 끝나갈 즈음, 나는 이런 글을 썼다. 파이썬 백엔드 개발자 코테, 면접 후기 (2) (2022.07 ~ 2022.08) 파이썬 백엔드 개발자 코테, 면접 후기 (1) (2022.6 ~ 2022.7) 이번에 6월 ~ 7월 초에 7군데를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;storyofvector7.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 쓴 시점으로부터 약 9개월 전, 저는 여기에 &quot;6개월차 신입 개발자의 반성문과 회고록&quot; 이라는 글을 작성한 적이 있었습니다. 말 그대로 반성문에 가까운 회고록인데요, 뭐 대충 요약하자면 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;모르면 물어봐라&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입사 초 때는 말하는 것을 두려워 해서 제대로 물어보지 못하고 혼자서 로직 파악한다고 낑낑댔는데 지금은 상사에게 잘 물어봅니다 ㅎㅎ... 물론 맨날 물어보면 상당히 귀찮아 할 테니, 가능한 내 선에서 해결 또는 파악이 가능한지 찾아보고 그래도 안되면 물어봅니다. 상사나 사수도 결국 일이 쌓여있는 바쁜 직장인인건 똑같으니까요.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;일을 미루게 만들지 말고, 애매모호 한게 있으면 뭐든지 물어봐라&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 내용이 &quot;모르면 물어봐라&quot;랑 똑같으니까 패스. 근데 &quot;이를 미루게 만드지 말고&quot;는 나도 할말이 있네요. 6개월차때의 저는 왜 이렇게 죄책감에 시달렸는지 모르겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;말은 끝까지 듣자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6개월차 때보다 더 심해졌습니다 ㅠㅠ. 아마 회사에서 긴장이 풀려서 예전 버릇 나오나 봅니다. 이건 다시 한번 더 반성을 하겠습니다 ㅠㅠ...&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;말은 똑바로 하자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금도 쉽지가 않네요. 말 더듬는거는 이제 더이상 하지 않게는 됐는데, 처세술에서 많이 밀리는 것 같습니다. 말을 잘 못하는 저이기에 노력을 좀더 해야겠군요.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;정상적이지 않은 생활루틴&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 당시 저는 야근을 밥먹듯이 해서 잠을 4시간 밑으로 자는 경우가 많아 다음날 업무때 집중을 제대로 못하는 일이 잦았습니다. 근데 이제는 야근을 거의 하지 않습니다. 바쁜게 아니면 10to7 칼퇴 조지고, 집가서 2~3시간 자기계발 한 다음에 6~7시간 정도 잡니다. 잠을 많이 자니까 확실히 정신 상태가 많이 맑아지긴 했네요. 꼴에 노오력 하겠다고 3~4시간 자는게 능사는 아닌가 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일 열심히 해서 돈 많이 버는 것도 좋지만, 이렇게 해서 몸 망가지는 것 보다는 그냥 먹고 살 수준 정도로만 벌고 몸 건강 챙기는 것도 나쁘지 않다고 가끔 생각이 들기는 합니다. 물론 제 생각이구요 ㅎㅎ 다른 분들의 생각은 다를 수 있겠죠.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;성격&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 대충 소심한 셩격을 버리자 인데... 몇달 전 까지 그래도 사람들하고 잘 부대끼다가 최근에 우울 증세가 다시 도지고 점심도 맨날 혼밥을 먹고 있습니다. 아무래도 제가 하고 있는 일에 대해서 요즘 권태기를 느끼나 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데, 사실 근본적인 셩격은 바꾸는 게 거의 불가능하다고 생각합니다. 단, 성격상 단점은 어떻게는 개선은 가능하겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여튼, 9개월 전의 회고록에서는 필자에 대한 반성이 주를 이뤘다면 이번 회고록은 근 1년동안 그동안 개발자로서 어떻게 지내왔는지 다시 한번 돌아보고, 오는 2024년에는 인생의 목표를 어떻게 잡고 커리어를 어떻게 관리를 해야 할지에 대한 계획이 주를 이루게 됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;다시 돌아보는 취준 시절&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 지금 컴퓨터 앞에 앉아서 여기 티스토리에 회고록을 쓰기 전에 약 1년 반 전에 여기에 썼던 코테/면접 후기를 다시 떠올려 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1703776646090&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;파이썬 백엔드 개발자 코테, 면접 후기 (1) (2022.6 ~ 2022.7)&quot; data-og-description=&quot;이번에 6월 ~ 7월 초에 7군데를 지원했고 이번에 봐야 할 면접들 전부 다 본 기념으로 후기 한번 적어보려고 한다. 일단 네카라쿠베나 중견/대기업은 일체 지원하지 않았고, 스타트 기업 기준으로&quot; data-og-host=&quot;storyofvector7.tistory.com&quot; data-og-source-url=&quot;https://storyofvector7.tistory.com/49?category=986213&quot; data-og-url=&quot;https://storyofvector7.tistory.com/49&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cCVrq8/hyUTynlQDj/q4gjWGDoKENXF3ReEb3wIK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c6oHJ9/hyUTwC4oxh/HfdqhJTzIp6CBVJdx95PLK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://storyofvector7.tistory.com/49?category=986213&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://storyofvector7.tistory.com/49?category=986213&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cCVrq8/hyUTynlQDj/q4gjWGDoKENXF3ReEb3wIK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c6oHJ9/hyUTwC4oxh/HfdqhJTzIp6CBVJdx95PLK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;파이썬 백엔드 개발자 코테, 면접 후기 (1) (2022.6 ~ 2022.7)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번에 6월 ~ 7월 초에 7군데를 지원했고 이번에 봐야 할 면접들 전부 다 본 기념으로 후기 한번 적어보려고 한다. 일단 네카라쿠베나 중견/대기업은 일체 지원하지 않았고, 스타트 기업 기준으로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;storyofvector7.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1703776664310&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;파이썬 백엔드 개발자 코테, 면접 후기 (2) (2022.07 ~ 2022.08)&quot; data-og-description=&quot;파이썬 백엔드 개발자 코테, 면접 후기 (1) (2022.6 ~ 2022.7) 이번에 6월 ~ 7월 초에 7군데를 지원했고 이번에 봐야 할 면접들 전부 다 본 기념으로 후기 한번 적어보려고 한다. 일단 네카라쿠베나 중견&quot; data-og-host=&quot;storyofvector7.tistory.com&quot; data-og-source-url=&quot;https://storyofvector7.tistory.com/66&quot; data-og-url=&quot;https://storyofvector7.tistory.com/66&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bz7euN/hyUTygz3MT/zAZK5kNXSHUC9mKScuMLbk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bk0b3c/hyUTKOQ1y8/N7PGLT6s0W2es7EPV8JYa1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://storyofvector7.tistory.com/66&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://storyofvector7.tistory.com/66&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bz7euN/hyUTygz3MT/zAZK5kNXSHUC9mKScuMLbk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bk0b3c/hyUTKOQ1y8/N7PGLT6s0W2es7EPV8JYa1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;파이썬 백엔드 개발자 코테, 면접 후기 (2) (2022.07 ~ 2022.08)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;파이썬 백엔드 개발자 코테, 면접 후기 (1) (2022.6 ~ 2022.7) 이번에 6월 ~ 7월 초에 7군데를 지원했고 이번에 봐야 할 면접들 전부 다 본 기념으로 후기 한번 적어보려고 한다. 일단 네카라쿠베나 중견&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;storyofvector7.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;취업 준비기간은 약 1년 정도 되었네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 졸업 시점에서 따지면 1년 6개월인데, 여기서 6개월은 취준을 안하고 그냥 하고싶은거 하면서 쉬었습니다. 이때 뭐했냐면 디자인에 관심이 있어서 이쪽으로 잠깐 공부를 했었고 &quot;컴퓨터그래픽스운용기능사&quot;라는 그래픽관련 자격증을 취득하기도 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 공백기간에 뭔갈 했었으니, 공백기간에 뭐했냐는 면접관의 질문에 당당하게 대답할수 있었습니다. 하하...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 면접 하나하나가 처절했고 절박했던 순간이었습니다. 또 그만큼 상처도 많이 받았었구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4년제 대학 컴퓨터공학과로 졸업을 한 전공자였고 30시간을 쉴틈 도 없이 코딩을 할수 있을 정도로 한때 개발에 열정적이었던 학부생은 졸작을 거치면서 현실의 벽을 느끼게 됩니다. 열정은 다 식어버렸고 개발에 대한 의욕과 실력은 바닥을 쳐버리게 되었죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬럼프가 시작되는 시점에서 취업 준비를 하다 보니 시작부터 정신적으로 많이 피폐해졌습니다. 네카라쿠베는 커녕 어디라도 좋으니 빨리 취업하고 밥빌어먹어야겠다 이 생각밖에 없었던 것 같습니다. 그래도 눈을 한참 낮춰서 그런지 이력서 딸랑 15개 들고 취업에 성공하긴 했네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서류는 어떻게 절반은 통과하고 코딩테스트와 과제는 전부 통과를 했지만 면접에서 허구헌날 낙방을 했었습니다. 졸작때의 충격으로 인간관계를 다 끊어놓고 1년 반동안 방구석에서 지낸 사람이, 처음 보는 사람을 상대로 제대로 말을 할 수 있을 리가 없었죠. 매번 저지르는 말실수로 무수히 불합격을 당하고 또 수치도 당했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 끝은 보이는 듯 이렇게 낙방을 여러번 한 끝에 어느 한 스타트업 면접에 합격을 하게 되고 작년 9월 입사를 하게 됩니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;취직했다고 끝이 아니다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 나도 밥값을 할 수 있다는 다행감과 나도 이제 개발자로서 일을 하게 된다는 기대감에 부풀어 있었지만. 직접 입사를 하고 백엔드 코드를 보니 앞날이 막막해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하고 있는 백엔드 프레임워크는 Flask. 애초에 채용공고에서도 Flask로 명시를 했었기 때문에 당연히 알고는 있었고 그당시 제 주력 프레임워크가 Flask와 비슷한 스타일인 FastAPI였기 때문에 그대로 금방 적응을 할 수 있을 것이라고 생각을 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Flask는 DJango나 Spring Boot보다 유연성이 훨씬 뛰어난 프레임워크 입니다. 즉, &lt;b&gt;개발자의 스타일에 따라 같은 Flask여도 시스템 구조는 어떻게 하냐에 따라 천차만별입니다.&lt;/b&gt; 저는 FastAPI를 자주 사용해 왔음에도 불구하고 이 점을 간과해 버렸습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 눈에 보인 Flask 코드들은, 제 개발 철학과는 상반되는 코드들이였습니다. 코드 뿐만이겠습니까, 개발 체계조차도 제 스타일과 전혀 상반되는 스타일이었죠. 하지만 낮선 스타일의 코드를 파악하고 이 코드들을 직접 건드는 것이야 말로 개발자의 역량이라고 생각을 했기에, 저는 불만을 갖기 않고 실무에 바로 투입이 될 정도로 인정을 받기 위해, 열심히 코드를 읽습니다. 또 코드를 읽고 로직을 노션 또는 직접 공책에다 적기도 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 모르겠다 싶으면 사수(現 팀장)에게 물어보기는 하는데, 사수는 오질라게 바쁩니다. 그도 그런 것이 백엔드 코드의 90%는 전부 이 사람이 작업을 했기 때문입니다. 그만큼 책임도, 일도 막중하다는 것이겠지요. 지금은 덜 하지만, 이때당시만 해도 사수가 정시퇴근을 하는 것을 본 적이 거의 없었습니다. 그렇기 때문에 행여나 내 질문 하나하나가 방해가 될까봐, 점점 질문하는 것도 꺼려지기 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 이때는 쉽게 주저않지 않았습니다. 입사한지 얼마 되지도 않았고, 이때는 그나마 열정이 가득했으니까요. 온보딩 마지막 때는, 백엔드 API중에 하나를 골라 최적화 해서 제안을 해 본적도 있습니다. pandas를 사용하던 것을 dict로 변형한게 다 이지만, 그래도 복잡한 연산은 줄었기 때문에, 조금이나마 성능이 개선이 되긴 했습니다. 물론 적용까진 못하긴 했지만요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약 1~2달 간의 온보딩이 끝나고 처음 실무에 투입이 됩니다. 그냥 간단한 API로직을 수정하는 것 뿐이었는데 실수를 남발합니다. 사람들은 이해를 해 주긴 했지만. 제 자신이 너무 쪽팔립니다. 업무 중에 정상적으로 완료된 업무들도 있었지만 또 그만큼 실패한 업무도 많았기에 시간이 지날 수록 자존감은 점점 바닥을 칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설령 API를 수정 또는 완성했다 치더라도 적용하기 까지의 시간이 너무 오래 걸려, 다른 팀으로부터 왜이렇게 오래 걸리냐는 얘기를 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말했듯이 백엔드 코드의 거의 대부분은 사수가 직접 개발했습니다. 그렇기 때문에 사수가 깆고 있는 책임은 막중합니다. 또 그만큼 권리도 상당히 많구요, 쉽게 얘기하자면 배포나 인프라 관리에 대한 모든 권한을 갖고 있다는 것입니다. 하지만 &lt;b&gt;저는 신입이었기에 책임을 별로 없지만, 그만큼 뭔가를 할 권리도 거의 없는 상태입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 개발 또는 수정한 API가 실서버 또는 개발서버에 적용하기 위해서는 사수에게 보고를 해야 합니다. 하지만 사수는 개 오질나게 바쁩니다. 커뮤니케이션이 제대로 될 리가 없죠. 변경 내용이 전달되는 데만 시간이 엄청나게 걸립니다. 1주면 끝날 일을, 2주 심하면 1달 까지 지연이 되는 경우도 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되다 보니 다른 쪽에서는 &quot;아직도 안끝냤냐&quot; 하는 얘기가 나오고 저는 할말이 없습니다. 개발 체계가 이렇게 되어 있다 보니 가끔 재취업 생각을 한 적도 가끔 있었지만, 지금 취업시장이 얼어붙어 있는 상태고 아직 다른 곳으로 재취업 하기에는 스펙이 모자르다 느껴 그냥 계속 다니게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그나마 다행인 것은 스타트업 특유의 자유롭고 수평적인 분위기로. 유튜브나 뉴스 기사에서 나오는 갑질 문화는 일절 없을 뿐더러. &lt;b&gt;직원 전원이 서로를 이해하고 배려하는 문화가 깊게 박혀있습니다.&lt;/b&gt; 그렇다 보니 일에 대한 스트레스는 있더라도 &lt;b&gt;인간관계로 인해 받은 스트레스는 다섯손가락에 꼽힙니다.&lt;/b&gt; 이것이 제가 지금까지 그만두지 않고 계속 다닐 수 있었던 이유입니다. 저같이 사회성이 심각하게 떨어진 사람에게는 여기만큼 사회성 튜토리얼도 없으니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겨울이 지나고 봄이 되자, 개발 팀에 경력직 개발자들이 들어오기 시작합니다. 백엔드팀에도 경력직 개발자 분이 한분 오셨습니다. 필자와 공통된 부분이 많아 금방 친해지면서, 형편도 점점 나아지기 시작합니다. 이벤트 관련 API개발을 같이 했었는데 다행이 기간 안에 끝낼 수 있었고 성과도 나름 좋은 편이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그 이후로 일이 잘 들어오지 않고, 들어와 봐야 관리자 페이지 기능 추가에 불가했습니다. 관리자 페이지 조차도 백엔드 API가 아니라 그냥 UI 변경이 주를 이뤘습니다. 즉, 백엔드 커리어와는 사실상 거의 관계가 없는 일이라고 보면 됩니다. 이 루틴을 다시 3~4달 지속을 하니, 내가 이 회사에서 내가 이정도로 회사에 쓸모가 없는 것 같다는 생각을 점점더 많이 하게 되면서 다시 스트레스가 쌓입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 영원히 이직할 때 까지 관리자 페이지나 만지면서 내 커리어도 망할까 심각하게 걱정하던 와중, 그해 여름, 회사생활의 분기점이 되는 소식을 듣게 됩니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;신사업 프로젝트&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;국책 사업으로 신사업 프로젝트를 진행하기 위해 스쿼드 팀을 하나 꾸리고 있었는데, 제가 그 팀으로 투입이 된 것입니다. 몇달 전부터 사업쪽 팀원으로부터 회사에서 새 프로젝트를 준비하고 있다는 얘기를 들었긴 했었지만, 제가 그곳으로 투입될 것이라고는 생각도 못했죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;걱정이 밀려옵니다. 신사업 프로젝트에 투입되어 있는 직원들은 기존에 있던 직원들이 아니라 새로 채용된 사람들이었습니다. 생판 처음 보는 사람들하고 작업을 해야 할 뿐만 아니라 전혀 다른 위치의 사무실에서 일을 해야 하니 근심이 앞을 가립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 걱정과는 달리 결과는 나름 좋았습니다. 같이 했던 사람들도 좋았고, 프로젝트 진행도 생각보다 스무스하게 진행이 되어 서비스 런칭에는 성공을 했습니다. 외부 그러니까 스쿼드 팀 밖에서도 반응이 나름 좋았습니다. 무사히 런칭을 했으니 성취감이 많이 드는 군요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 백엔드 프레임워크는 Flask가 아닌 DJango를 사용했습니다. 가능한 기존 서비스와의 연계를 위해 Flask사용하는 것을 생각해 봤지만. 프로젝트 기간이 촉박할 뿐만 아니라. 같은 백엔드팀의 사수(아까 위의 사수하고 다른 새로 채용된 사람)의 주력 프레임워크가 DJango라는 점, 그리고 제 자신도 DJango로 프로젝트를 진행하는 것을 원해서 DJango로 진행하는 것으로 결정을 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입사 하면서 가장 재미있었고 보람찼었던 때가 이때였던 것 같습니다. 같이 일하던 새로운 백엔드팀 사수는 저에게 가능한 여러가지를 알려주려고 노력을 했었고, 조언을 많이 주셨습니다. 저 역시도 사수로부터 단순 개발 뿐만 아니라 여러 부분에서도 많이 배웠기에 정말 감사하게 생각하고 있습니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;한치 앞을 모르는 2024년&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 제 2023년은 신사업 프로젝트 런칭 완료와 함께 저물었습니다. 하지만 런칭 완료 만으로 프로젝트가 아직 완료되지 않았습니다. 아직 3~6월 까지 해야 하는 잔업(?)이 남아있는 상태 입니다. 뿐만 아니라 기존 서비스 개발로 다시 돌아가야합니다. 기존 서비스 개발로 돌아가면 신사업 프로젝트 이전에 걱정이 많았던 생활로 돌아갈 까봐 걱정이 앞서네요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;두번 째 도약&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그렇기에 이제 저는 한번더 도약을 시도하려고 합니다.&lt;/b&gt; 그동안 신입의 입장에서 업무 경험을 해왔다면 오는 2024년에는 좀더 적극적이고 공격적으로 이 바닥에 다시 발을 들이려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탈 파이썬을 시도하려고 하고 있습니다. 그렇다고 파이썬을 완전히 접겠다는 뜻은 아니고, 알고리즘이나 분석용으로만 사용하고, 백엔드 언어는 파이썬이 아닌 다른 언어를 사용하려고 하고 있습니다. Kotlin/Spring Boot가 최우선순위로 공부하고 있고, 차선택으로 Go언어도 공부하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애초에 제가 파이썬을 사용했었던 목적은 백엔드 개발이 아니었습니다. &quot;백엔드&quot;라는 개념을 알기 전부 터 파이썬을 해 왔었고, 그 당시 목적은 영상처리(OpenCV)나 AI 신경망 구축이었습니다. 실제로 졸작 테마가 AI였었고 이때 파이썬으로 AI 모델을 구현을 한 적이 있었습니다. 하지만 2020년 하반기에 인턴을 거치면서 목표가 백엔드 개발자로 바뀌게 되었고, 언어는 그나마 자주 사용했었던 파이썬을 선택을 했었죠. 사실 이때 주력 언어가 파이썬이 아니라 코틀린이었는데 앱개발에만 코틀린을 사용했었지 이게 백엔드로도(Spring Boot) 사용될 거라고는 꿈에도 몰랐거든요 ㅎㅎ... 좀 일찍 알고 있었더라면... 젠장 ㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CS공부도 다시 차근차근 해 보려고 합니다. 아무리 프레임워크를 사용할 줄 안다 하더라도, 기초 컴퓨터과학 지식을 모른다면 제대로된 개발자가 될 수 없다고 생각합니다. 취업 전에도 이런 철학은 갖고 있었지만. &lt;b&gt;취업을 하고 API 개발을 해 오면서 이런 생각은 더 굳혀졌습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 컨퍼런스에도 적극적으로 참여를 해 보려고 합니다. 사실 작년에 파이콘2023에 참여를 해 본적이 있지만, 낮을 너무 가린 탓에 제대로 경험을 하지 못했습니다. 하지만 올해는 뒷생각 안하고 제대로 밀어붙여보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해는 좀 굵직한 사이드 프로젝트가 완성이 되어있을 예정입니다. 작년 2023년에는 작은 규모의 사이드 프로젝트만 완성했습니다. 예를 들어 &lt;a href=&quot;https://github.com/skywinz/dev-blog&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NextJS기반의 개발 블로그&lt;/a&gt; 가 그 예이지만, 아직 안정된 버전도 아니고 애초에 이건 백엔드 프로젝트가 아닌 프론트엔드 위주입니다. (사실 NextJS 자체가 풀스택 기반이라 실제로 서버 API도 구현되어 있고 SQLite를 사용해서 DB도 운영을 하고 있긴 하지만 어디까지나 블로그 포스트 정보를 Read하는 데만 사용할 뿐이지 Write는 하지 않습니다.)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 올해는 좀 다를 가능성이 높습니다. 굵직한 사이드 프로젝트 기획을 작년에 50%정도 진행을 했기 때문에 아마 좀 집중적으로 하면 올해 안에 끝나지 않을 까 싶습니다. 인맥이 없기에 프론트엔드 개발자를 구할 수 없는 상황이지만... 뭐 상관없습니다. 저는 백엔드 개발자 이지만, React도 어느정도 사용을 해 봤기에 프론트엔드 구현도 저 혼자 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제 슬럼프는 아직 끝나지 않았습니다.&lt;/b&gt; 개발 실력도 예전에 비해 못하다는 것도 사실이구요. 4년전 &lt;a href=&quot;https://github.com/recoma96/SKVS&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;C++로 2달 안에 캐시시스템&lt;/a&gt;을 구현했을 정도로 열정적이었던 학부생은, 지금은 파이썬 가지고도 개인 프로젝트 하나 만드는데 몇달이 걸리는 의욕없는 사람인 것이 현실입니다. 하지만 이렇게 영원히 주저앉을 수 없는 노릇입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실을 더이상 마주하지 않고 성장을 시도할 때, 반드시 성장통이 생기는 법입니다. 아마 올해 2024년에는 작년 보다 좀더 빡셀 것 같습니다. 하지만 영원히 이따위로 살 수가 없기에, 저는 새해에 한번 더 다짐을 하고 제 삶을 위해 한발짝 도약을 시도하려고 합니다.&lt;/p&gt;</description>
      <category>후기 &amp;amp; 리뷰</category>
      <category>개발자</category>
      <category>백엔드</category>
      <category>신입</category>
      <category>취업</category>
      <author>recoma</author>
      <guid isPermaLink="true">https://storyofvector7.tistory.com/82</guid>
      <comments>https://storyofvector7.tistory.com/82#entry82comment</comments>
      <pubDate>Mon, 1 Jan 2024 01:07:44 +0900</pubDate>
    </item>
    <item>
      <title>[SQLAlchemy] Alembic을 이용해서 DB버전관리를 해보자</title>
      <link>https://storyofvector7.tistory.com/80</link>
      <description>&lt;div id=&quot;SE-2ceed54e-f5da-42e8-b683-791b7da33cb3&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUJtix/btsmWl8P5qN/y99G4rAdQHLuFvmvfYO10k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUJtix/btsmWl8P5qN/y99G4rAdQHLuFvmvfYO10k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUJtix/btsmWl8P5qN/y99G4rAdQHLuFvmvfYO10k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUJtix%2FbtsmWl8P5qN%2Fy99G4rAdQHLuFvmvfYO10k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;966&quot; height=&quot;289&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-808f8b63-6c75-40d4-adb1-ddfe71a225b4&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p id=&quot;SE-c39647eb-e2c4-4d90-9365-5f0cfad8e4ff&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;DJango에서는 기본적으로 DB마이그레이션 기능이 존재합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-75cbf3fb-7cf9-4cf0-86ef-70b4ee4d7400&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #272822; color: #f8f8f2;&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-77c78fbe-eb54-4fa8-853f-f5b3c6e4274d&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1688893892346&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python manage.py makemigrations&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;SE-efa2afeb-fafd-43b0-9791-08a3643c17a0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;makemigrations 명령어로 DB변경 내역을 파이썬 파일로 자동 작성하고&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1688893900056&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python manage.py migrate&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&quot;SE-39e3ba87-6567-4408-80d1-8817074e8ad5&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;p id=&quot;SE-f473ca8f-81c5-4609-92cb-4356dd0640ec&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;migrate 명령어를 사용해서 DB내역을 기반으로 실제 데이터베이스에 마이그레이션을 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-2d8b5e25-2523-43c2-9c27-571b48adba77&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그렇다면 Flask또는 FastAPI에서 주로 사용하는 SQLAlchemy에서는 어떻게 마이그레이션을 해야 할 까요?&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-b2568214-4390-4c16-8fbf-74ea463744a6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;​&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-84da6aaf-5095-4219-969d-5d7957379aa8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여기도 마찬가지로 Alembic 모듈을 사용해서 DB버전관리를 할 수가 있습니다&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-035297ec-0d63-4b6f-ab37-6ad4f4817375&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번 포스트에서는 Alembic의 사용법을 간단하게 설명하려고 합니다&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-925afb53-101d-4a6f-bf7c-4d0b6409d57a&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이미 장고로 마이그레이션 기능을 사용한 경험이 있는 분들은 DJango마이그레이션과 Alembic작동 구조가 의외로 비슷해서&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-e6f2a51a-bcf7-4c5d-af56-e7809b667dbe&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;아마 어느정도 alembic 사용법을 익히는데 큰 부담이 없을 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마이그레이션 환경 세팅하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;alembic 모듈을 설치하기 전, 선행 모듈을 먼저 설치합니다&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688893944440&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ pip install sqlalchemy pymysql mysqlclient&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;성공적으로 설치했으면 alembic도 설치합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688893953685&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ pip install alembic&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;그리고 아래 명령어를 입력해서 DB마이그레이션에 필요한 alembic관련 파일들을 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688893965442&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;alembic init {원하는 이름}&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&quot;SE-05976aa5-97d8-40f5-b3e9-729a7ad668bd&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mLzvr/btsmRrBWlu1/t4e2jieMGoF06WTIEphoA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mLzvr/btsmRrBWlu1/t4e2jieMGoF06WTIEphoA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mLzvr/btsmRrBWlu1/t4e2jieMGoF06WTIEphoA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmLzvr%2FbtsmRrBWlu1%2Ft4e2jieMGoF06WTIEphoA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;308&quot; height=&quot;166&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-41f8b2c3-2697-4b23-9f19-2c34499a2697&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p id=&quot;SE-5633af84-7f5e-4591-bed3-b8777c310859&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;마이그레이션에 필요한 파일들을 생성했으니 데이터베이스를 연동하기 위해 세팅을 해야 합니다&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-f21abb0c-bf5f-40d7-93e5-d7b4162621a1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;alembic.ini 파일에 들어가서 sqlalchemy.url을 찾아서 수정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688893989910&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sqlalchemy.url = mysql+pymysql://root:1234@192.168.0.104/alembic&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Alembic으로 User 테이블 만들어보기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;로우 쿼리나 SQLAlchemy의 create_all 함수가 아닌 오직 alembic으로 유저 테이블을 만들어 봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-93bfe619-3abb-4997-bbbf-56cae35f1ab3&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;alembic의 revision 명령어를 입력해서 버전내역을 생성합니다&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-f698b1f1-8e02-4d44-a363-dc4a7697939c&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;저는 메세지에 &quot;add user table&quot;이라고 적었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688894011314&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ alembic revision -m &quot;{쓰고싶은 메세지}&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1688894020157&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# versions/eacbc10c2794_add_user_table.py
&quot;&quot;&quot;add user table

Revision ID: eacbc10c2794
Revises: 
Create Date: 2023-07-09 11:05:06.643433

&quot;&quot;&quot;
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'eacbc10c2794'
down_revision = None
branch_labels = None
depends_on = None


def upgrade() -&amp;gt; None:
    pass


def downgrade() -&amp;gt; None:
    pass&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 버전 파일들은 DJango의 migrations 디렉토리에 있는 DB버전로그 파이썬 파일과 그 역할이 동일하다고 보시면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-a17e4dc9-ee5a-4787-9d7b-b40fc74eac73&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;단, 이 버전 로그 내역을 DJango에서는 자동으로 만들어줬다면, 여기에서는 우리가 직접 내역을 작성해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-22501087-2029-4519-97c0-d18b8dca7153&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;upgrade() 함수에서는 버전 내역을 작성합니다. 예를 들어 지금처럼 유저 테이블을 생성한다고 하면, 유저 테이블을 생성하는 프로세스를 작성하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-abcd3070-b4db-4bf5-bbc8-f80dcbbdaa25&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;반대로 downgrade()는 말 그대로 적용한 부분을 다시 롤백한다는 뜻입니다. upgrade()에서 유저 테이블을 생성하는 프로세스를 작성했다면, downgrade()는 반대로 유저 테이블을 없애는 프로세스를 작성하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;upgrade()와 downgrade() 함수를 아래와 같이 입력합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-96f1a41e-2657-48b4-b436-58d79abe1424&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;SQLAlchemy를 기반으로 작동하기 때문에, 컬럼을 생성할 때 SQLAlchemy에서 사용하던 문법과 똑같이 사용하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688894657061&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def upgrade() -&amp;gt; None:
    op.create_table(
        'user',
        sa.Column('id', sa.Integer, primary_key=True, autoincrement=True),
        sa.Column('nickname', sa.String(16), nullable=False, unique=True),
        sa.Column('password', sa.String(16), nullable=False)
    )


def downgrade() -&amp;gt; None:
    op.drop_table('user')&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;아래의 upgrade() 명령어를 실행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688894669427&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ alembic upgrade head
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -&amp;gt; eacbc10c2794, add user table&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;head는 가장 최근의 버전로그를 의미합니다. 특정 버전로그로 업데이트를 하고 싶은 경우, 그 버전로그의 Revision ID를 입력하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688894680780&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ alembic upgrade eacbc10c279
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -&amp;gt; eacbc10c2794, add user table&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;SE-3f46dea9-9018-4065-a50b-b9c51dfaf5f8&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p id=&quot;SE-2026edb6-53ec-4bc8-ae77-849705380965&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이제 데이터베이스에 user 테이블이 생겼습니다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-6aeedd5a-9d00-4837-bb26-fc446d0b899a&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;286&quot; data-origin-height=&quot;221&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R2QEu/btsmQDCE0rI/7PZr4VBKi8pCZaLcKfKTP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R2QEu/btsmQDCE0rI/7PZr4VBKi8pCZaLcKfKTP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R2QEu/btsmQDCE0rI/7PZr4VBKi8pCZaLcKfKTP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR2QEu%2FbtsmQDCE0rI%2F7PZr4VBKi8pCZaLcKfKTP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;286&quot; height=&quot;221&quot; data-origin-width=&quot;286&quot; data-origin-height=&quot;221&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-220e8e62-e539-47ba-b163-b926e8b25553&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p id=&quot;SE-a0910976-3987-48f5-94cd-984c1113b587&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;​&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-5a58a3cb-830a-477e-a57b-ecbfd2275bfd&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 유저 테이블 말고도 다른 테이블도 생성이 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-74145c1c-7c95-46cc-b9bb-ded97414f5a8&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;197&quot; data-origin-height=&quot;60&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4dZ4f/btsmUHYCZDk/vkjZamRYUurKB2Jc1jMbkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4dZ4f/btsmUHYCZDk/vkjZamRYUurKB2Jc1jMbkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4dZ4f/btsmUHYCZDk/vkjZamRYUurKB2Jc1jMbkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4dZ4f%2FbtsmUHYCZDk%2FvkjZamRYUurKB2Jc1jMbkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;197&quot; height=&quot;60&quot; data-origin-width=&quot;197&quot; data-origin-height=&quot;60&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-f07eab8e-f083-4e4f-84df-e07bb2e8015b&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p id=&quot;SE-64e953e7-fd2f-40ba-97b8-d9b9e7183d08&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;alembic_version이라는 테이블이 생겼는데, version_name에서는 최근에 적용된 버전로그의 Revision ID가 저장이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;변경사항 롤백하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 반대로 upgrade() 프로세스를 잘못 작성하는 바람에 DB스키마를 잘못 생성할 때가 있습니다. 이때는 롤백을 해 줘야 하는데 upgrade가 아닌 downgrade를 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-6eae225a-0192-491a-8750-aa8bb44d22ce&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여기에서는 downgrade함수에 user테이블을 삭제하는 프로세스를 작성했기 때문에 downgrade를 수행하면 user테이블이 삭제가 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-1146d6d1-11a4-4bb6-b8c8-c67bc55c7f09&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;단 upgrade와는 달리 downgrade에서는 최근의 버전로그를 롤백할 때, head가 아닌 -1을 입력합니다. 아니면 최근 버전로그의 Revision ID를 입력합니다&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688894711682&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ alembic downgrade -1
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running downgrade eacbc10c2794 -&amp;gt; , add user table&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;컬럼 추가하기&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;테이블 생성 말고도 기존의 테이블에서 컬럼을 추가하거나 삭제할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-2aaaa2a1-ad6f-4bf9-ba8d-846ca8d08b32&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기존의 유저 테이블에서 이메일 컬럼을 추가하려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-ccff9901-21c8-400f-bf4b-22854c2c6826&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;버전 관리를 해야 하니 버전 파일을 하나 더 만들어 봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688894731892&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;alembic revision -m &quot;add email in user&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;새로 생성된 버전로그 파일에 아래와 같이 작성합니다&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-69fcd2de-4746-4b95-a816-4a8153079822&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;upgrade함수에는 email 컬럼을 생성하는 프로세스를&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-42d8d83d-c4ae-45d5-9cc3-35cac92378d1&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;downgrade함수에는 반대로 email컬럼을 삭제하는 프로세스를 작성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688894746279&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def upgrade() -&amp;gt; None:
    op.add_column('user', sa.Column('email', sa.String(128), nullable=False))


def downgrade() -&amp;gt; None:
    op.drop_column('user', 'email')&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1688894754103&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ alembic upgrade head
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade eacbc10c2794 -&amp;gt; c028cbc00cce, add email in user&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&quot;SE-b1c71453-dd69-4e74-90f0-a642d84d65f0&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;329&quot; data-origin-height=&quot;239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tdlZN/btsm2HXLC31/DDHzOy9hlCkTtDsvnnQQtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tdlZN/btsm2HXLC31/DDHzOy9hlCkTtDsvnnQQtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tdlZN/btsm2HXLC31/DDHzOy9hlCkTtDsvnnQQtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtdlZN%2Fbtsm2HXLC31%2FDDHzOy9hlCkTtDsvnnQQtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;329&quot; height=&quot;239&quot; data-origin-width=&quot;329&quot; data-origin-height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-ed545057-9696-4d04-b8ad-a84126ec15a3&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p id=&quot;SE-52b23fa8-bf3d-4526-8fb1-3892dd5ddc10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;email 컬럼을 추가하는 데 성공했습니다&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-a65ea754-aabe-41c7-b603-d155511440de&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;​&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-3445c164-667a-4f14-a0e2-27706585fd0d&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;컬럼 추가 말고도, 수정, 삭제 등, 데이터베이스 안에서의 거의 모든 작업들을&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-fdf3fd84-8a12-43da-b789-0aa26f6932a6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여기서 대부분 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;Model만 바꿔도 자동으로 버전로그 작성되게 하기&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p id=&quot;SE-49effb5f-206b-4d2b-9ba5-dc8b17784b94&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 이러한 방식은 버전로그를 직접 작성해야 한다는 단점이 있기 때문에 불편한 감이 없지 않아 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-fad49cf4-16a0-48f0-8156-286769ddcdf4&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;당장 DJango만 봐도 모델 클래스만 수정한 다음, makemigrations명령어만 입력하면 수정된 모델 정보에 대한&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-53924e62-b861-496e-b815-74474ffe3019&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;버전로그 파일을 자동으로 생성을 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;운이 좋게도 &quot;autoupgrade&quot;라는 옵션을 사용해 Alembic에서도 버전로그를 모델 클래스 변경에 따라 자동으로 버전로그 파일을 자동으로 작성하는 방법이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-31f99e67-0638-43f3-83f2-a474d5d00a0c&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;​&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-af3ee84c-99f8-4232-b338-20e7b0eaedbe&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;아까 전처럼 alembic init명령어로 세팅을 한 다음 model.py 파일을 생성하고 아래와 같이 적어줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688894803573&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String

Base = declarative_base()

class User(Base):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(16), unique=True, nullable=False)
    password = Column(String(16), nullable=False)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;그리고 env.py로 돌아가서 target_metadata = None를 아래와 같이 바꿔줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688894816721&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;target_metadata = Base.metadata&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;아래 명령어를 입력합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688894828025&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ alembic revision --autogenerate -m &quot;create user table&quot;
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'user'
  Generating /home/recoma/practice/alembic/migration/versions/e42d0e1b971d_create_user_table.py ...  done&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;model.py에서 새 모델이 추가되었음을 감지하고 (Detected added table 'user')&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-51b1cef7-d94b-4fd8-8594-b1a6904d968c&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이를 기반으로 버전로그 파일을 자동으로 생성했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-c202b0e1-8914-40f7-a082-18283bd40e4a&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;마치 DJango의 makemigrations와 유사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-6ad97c57-41f4-466e-8e1d-2caca168d36a&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;​&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-2d9a93cc-f6cb-4caf-92dd-e7e278f80189&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;생성된 버전로그 파일을 열어보면 유저 테이블을 생성하는 프로세스가 작성되어 있음을 알 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688894843658&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;&quot;&quot;create user table

Revision ID: e42d0e1b971d
Revises: 
Create Date: 2023-07-09 16:31:43.058976

&quot;&quot;&quot;
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'e42d0e1b971d'
down_revision = None
branch_labels = None
depends_on = None


def upgrade() -&amp;gt; None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('user',
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
    sa.Column('name', sa.String(length=16), nullable=False),
    sa.Column('password', sa.String(length=16), nullable=False),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('name')
    )
    # ### end Alembic commands ###


def downgrade() -&amp;gt; None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('user')
    # ### end Alembic commands ###&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;이제 기존 절차와 동일하게 upgrade명령어를 입력하면 유저 테이블이 생기게 됩니다. &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688894856574&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ alembic upgrade head&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;모델이 변경될 때도 자동으로 버전로그 파일을 만들어주는 지 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-aab4afdc-73a5-4cbc-9ed1-42210068d082&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기존 User 모델 클래스에 email 컬럼을 추가하고 아까와 똑같은 명령어를 입력합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688894868575&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;email = Column(String(16), unique=True, nullable=False)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1688894878454&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ alembic revision --autogenerate -m &quot;add email in user&quot;
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added column 'user.email'
INFO  [alembic.autogenerate.compare] Detected added unique constraint 'None' on '['email']'
  Generating /home/recoma/practice/alembic/migration/versions/2fd2f7d19dc7_add_email_in_user.py ...  done&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;이메일을 추가하는 버전로그 파일이 생성되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688894889829&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def upgrade() -&amp;gt; None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('user', sa.Column('email', sa.String(length=16), nullable=False))
    op.create_unique_constraint(None, 'user', ['email'])
    # ### end Alembic commands ###


def downgrade() -&amp;gt; None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_constraint(None, 'user', type_='unique')
    op.drop_column('user', 'email')
    # ### end Alembic commands ###&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&quot;SE-45ca8c6a-066a-4f2f-b500-d6b04c9b829a&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p id=&quot;SE-4f8f3a32-b642-4333-81e6-42f2a76f15b3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 상태에서 다시 'alembic upgrade head'를 입력하면 DB에 바로 마이그레이션이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-b2a143ba-8663-4a06-8499-0b22ad8ad6e8&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이렇게 해서 Flask또는 FastAPI에서도 SQLAlchemy와 Alembic으로 DJango처럼 자동으로 마이그레이션 파일을 만들고 이를 기반으로 DB에 마이그레이션을 할 수 있게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-b51dfc8d-511e-4964-87cc-735823cb9cd6&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;​&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-554ec429-bc4d-465e-a9ef-94c2e11c5add&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;단 몇가지 유의사항이 있는데 &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-15ae699d-45d6-4869-8a09-4b18c20a5483&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;바로 파일로 작성된 버전 로그들과 실제 데이터베이스의 구조가 동일, 즉 동기화 되어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-ba6b472d-832e-4b45-9bef-c570cf4af12b&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;구조가 서로 다르게 되면 Alembic이 제대로 작동이 되질 않기 때문에&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-255afb40-6cc4-4b61-a37a-a60eedd69166&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;프로젝트 구현 진행 시, Alembic을 중간에 도입하지 않고 처음 구현할 때부터 도입하는 것을 권장하고 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-1f02314e-6829-48b8-9469-c5664c9c2a15&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;​&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-5f81cffb-3896-4f07-8551-73ef57ad3826&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Flask, FastAPI의 경우 DJango처럼 DB마이그레이션 기능 자체가 없기 때문에,&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-3eae0e87-2489-4f26-977f-8cb14b9e3f88&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 프레임워크를 사용한지 얼마 안된 분들에게는 유지보수를 하는 데 부담이 클 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-8057ac7b-562f-4bfc-9466-e661a21f5fb6&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;저도 작년에 FastAPI로 프로젝트를 진행하고 유지보수를 했을 때, FastAPI코드에 작성된 모델과 실제 DB 구조를 맞추는데 애를 많이 먹은 적도 있었구요&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-42d6c7f6-82cb-46de-b22b-ca2d87536bdd&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 이런 환경에서도 다행이 Alembic이라는 모듈 덕분에, DJango가 아니어도 큰 부담 없이 DB마이그레이션을 쉽게 할 수 있게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-9b41c7bd-0673-4d4e-86c3-370f4b22060c&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;우리모두 Alembic으로 고민없은 행복한 코딩을 해 봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-d87c9d68-d60e-4579-a71f-075d39ee153c&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;​&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-cad3b38a-0484-4687-b4b8-828e27e96c03&quot; style=&quot;background-color: #ffffff; color: #555555; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Alembic의 자세한 설명은 공식 도큐먼트에서 보실 수 있습니다&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1688894914269&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Welcome to Alembic&amp;rsquo;s documentation! &amp;mdash; Alembic 1.11.1 documentation&quot; data-og-description=&quot;&quot; data-og-host=&quot;alembic.sqlalchemy.org&quot; data-og-source-url=&quot;https://alembic.sqlalchemy.org/en/latest/&quot; data-og-url=&quot;https://alembic.sqlalchemy.org/en/latest/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://alembic.sqlalchemy.org/en/latest/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://alembic.sqlalchemy.org/en/latest/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Welcome to Alembic&amp;rsquo;s documentation! &amp;mdash; Alembic 1.11.1 documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;alembic.sqlalchemy.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Database/SQLAlchemy</category>
      <category>alembic</category>
      <category>FastAPI</category>
      <category>flask</category>
      <category>python</category>
      <category>sqlalchemy</category>
      <category>데이터베이스</category>
      <category>백엔드</category>
      <author>recoma</author>
      <guid isPermaLink="true">https://storyofvector7.tistory.com/80</guid>
      <comments>https://storyofvector7.tistory.com/80#entry80comment</comments>
      <pubDate>Sun, 9 Jul 2023 18:30:31 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 이분 탐색의 변종들 (1) - lower/upper bound를 알아보자</title>
      <link>https://storyofvector7.tistory.com/78</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;이분탐색&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;알고리즘 공부를 어느정도 해봤다면 누구에게나 친숙한 알고리즘 입니다. 정렬된 배열에서 특정 값의 위치(인덱스 값)을 찾는 알고리즘이지요. 이분탐색을 구현하는 코드는 아래와 같습니다. 해당 값이 존재하면 그 값에 대한 인덱스 값을 출력하고, 없으면 -1을 출력합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1679740217985&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int process_binary_search(vector&amp;lt;int&amp;gt;&amp;amp; arr, int e)
{
    int n = (int)arr.size();
    int left = 0, right = n-1, mid;

    while(left &amp;lt;= right) {
        mid = (left + right) &amp;gt;&amp;gt; 1;
        if(arr[mid] == e)
            return mid;
        else if(arr[mid] &amp;gt; e)
            right = mid - 1;
        else
            left = mid + 1;
    }
    return -1;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이분탐색의 알고리즘은 선택된 값(arr[mid])이 찾고자 하는 값(e)과 일치하면 그 인덱스 값을 리턴하고(mid), 그렇지 않은 경우, 범위를 계속 좁혀가며 계속 선택된 값과 찾고자 하는 값이 일치한 지 확인합니다. left와 right가 일치하는 순간까지도 일치하는 값이 없다면, 찾고자 하는 값이 없는 것으로 간주하고 -1을 리턴합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이분 탐색은 조금만 생각을 잘 해도 금방 이해할 수 있고 또, 구현도 금방 할 수 있습니다. 하지만, 코딩 문제 또는 중급 이상의 알고리즘에서는 순수 이분탐색 보다는 &lt;b&gt;lower bound, upper bound&lt;/b&gt;라는 이분 탐색의 변종을 사용하는 경우가 많습니다. 그리고 이 친구들은 로직을 잘 이해하지 못하면 구현할 때 많이 헷갈릴 수 있습니다. 이번 포스트에서는 upper bound와 lower bound의 소개와 구현 방법, 그리고 이를 활용하는 예제 문제, 또는 알고리즘에 대해서 간단하게 설명하려고 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Upper Bound&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;test.drawio.png&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;131&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fihia/btr5Uj7ytff/u6RyxDwNPWfRqu3OjEPYU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fihia/btr5Uj7ytff/u6RyxDwNPWfRqu3OjEPYU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fihia/btr5Uj7ytff/u6RyxDwNPWfRqu3OjEPYU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFihia%2Fbtr5Uj7ytff%2Fu6RyxDwNPWfRqu3OjEPYU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;391&quot; height=&quot;131&quot; data-filename=&quot;test.drawio.png&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;131&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;upper_bound란 찾고자 하는 값 보다 더 큰 값의 첫 번 째 위치를 구하는 알고리즘 입니다. 예를 들어 [1,2,3,3,3,4,5,6]가 있으면, 3에 대해 upper_bound를 수행하면 4가 더 큰 값의 첫 번 째 위치해 있는 값이기 때문에 4의 인덱스 값인 5가 출력이 됩니다. 구현은 기존 이분 탐색에서 조금 만 바꿔주면 됩니다. 어떻게 바꿔줘야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 작성했던 이분 탐색의 코드를 다시 한번 보고 생각을 해 봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1679765356236&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    while(left &amp;lt;= right) {
        mid = (left + right) &amp;gt;&amp;gt; 1;
        if(arr[mid] == e)
            return mid;
        else if(arr[mid] &amp;gt; e)
            right = mid - 1;
        else
            left = mid + 1;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이분 탐색에서는 같으면 바로 인덱스 리턴, 크면 right를 mid-1 왼쪽으로 땡기고, 작으면 left를 mid+1으로 땡겼습니다. 이런 식으로 찾고자 하는 값에 대한 인덱스를 구했습니다. 하지만 upper bound에서는 찾고자 하는 값이 아닌 그 값보다 더 큰 첫번 째 값의 위치를 구해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 arr[mid]가 e보다 일치하다고 해서 바로 리턴해서는 안됩니다. &lt;b&gt;무조건 e보다 더 커야 하기 때문에 다음 arr[mid]는 이전 값보다 더 커야 합니다.&lt;/b&gt; 따라서 arr[mid] == e 일 때 left를 mid + 1으로 땡겨야 합니다. e보다 작을 때도 마찬가지로 left = mid + 1로 잡아놔야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;arr[mid]가 e보다 큰 경우를 생각해 봅시다. 이분 탐색에서는 이런 일이 일어나면 애초에 답이 아니기 때문에 arr[mid]보다 더 작은 값으로 접근할 수 있게 mid - 1로 땡겼습니다. &lt;b&gt;하지만 upper bound에서는 무조건 e보다 더 커야 하기 때문에 e보다 더 큰 arr[mid]값이 정답이 될 수도 있습니다.&lt;/b&gt; 따라서 이때 left를 mid + 1이 아닌 mid로 땡깁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1679767702934&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int process_upper_bound(vector&amp;lt;int&amp;gt;&amp;amp; arr, int e)
{
    int n = (int)arr.size();
    int left = 0, right = n-1, mid;

    while(left &amp;lt; right) {
        mid = (left + right) &amp;gt;&amp;gt; 1;
        if(arr[mid] &amp;gt; e) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    return right;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Lower Bound&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;test.drawio.png&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UdshZ/btr5PGpvdbf/8mKFwGbKKETArZJgbF9aa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UdshZ/btr5PGpvdbf/8mKFwGbKKETArZJgbF9aa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UdshZ/btr5PGpvdbf/8mKFwGbKKETArZJgbF9aa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUdshZ%2Fbtr5PGpvdbf%2F8mKFwGbKKETArZJgbF9aa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;391&quot; height=&quot;311&quot; data-filename=&quot;test.drawio.png&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lower bound는 찾고자 하는 값이 여러개 있을 때, 이 중 가장 맨 왼쪽에 있는 인덱스 값을 구하는 알고리즘 입니다. 찾고자 하는 값이 없다면 upper_bound처럼 해당 값보다 더 큰 첫번 째 값의 위치를 리턴합니다. upper_bound와 비슷한 접근 법으로 알고리즘을 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;찾고자 하는 값이 여러개 있을 때, 이 중 가장 맨 왼쪽에 있는 인덱스 값을 구하는 알고리즘&lt;/b&gt;이라는 말에 집중해 봅시다. 이 말은 즉슨, 같은 값을 찾았더라도 여기서 끝이 아니라 더 왼쪽에 같은 값이 있는 지 찾아야 한다는 말이 됩니다. 따라서 arr[mid] == e일 때 return하는 것이 아닌, 좀 더 왼쪽으로 가야 하므로 right를 mid로 땡겨야 합니다. &lt;b&gt;여기서 주의해야 할 점은 같은 값들 중 가잔 왼쪽에 있는 인덱스를 찾는 것이지 해당 값보다 더 작은 값의 위치를 찾는 것이 아니므로 right = mid - 1이 아닌 right = mid가 되어야 합니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;arr[mid] &amp;gt; e, 즉 찾고자 하는 값보다 arr[mid]가 더 큰 경우 입니다. 말 그대로 lower하기 때문에 right = mid - 1로 보일 수 도 있겠지만, &lt;b&gt;해당 값이 존재하지 않으면, 해당 값보다 더 큰 값이 정답이 됩니다.&lt;/b&gt; 따라서 큰 값이 답이 될 수도 있기 때문에 arr[mid] == e 처럼 right = mid로 잡아놔야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;arr[mid] &amp;lt; e 의 경우 즉, 찾고자 하는 값보다 arr[mid]가 더 작은 경우 입니다. e보다 더 작은 값을 찾으라는 말이 없기 때문에 left를 mid + 1로 땡깁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1679769716254&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int process_lower_bound(vector&amp;lt;int&amp;gt;&amp;amp; arr, int e)
{
    int n = (int)arr.size();
    int left = 0, right = n-1, mid;

    while(left &amp;lt; right) {
        mid = (left + right) &amp;gt;&amp;gt; 1;
        if(arr[mid] &amp;gt;= e) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    return left;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;끗!&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 포스트는 여기서 끝이지만, 아직 완전한 끝은 아닙니다. lower/upper bound 활용 문제와 알고리즘, 그리고 이분탐색과 비슷하지만 비슷하지 않은 것 같은 매개변수 탐색 (Parametric Search) 등, 아직 하고 싶은 말이 많지만, 지금 술 먹고 새벽 4시 까지 이 글을 쓰고 있기 때문에 너무 졸려서 이제 쉼표를 찍고 자려고 합니다. 다음 포스트에서는 매개변수 탐색 대해서 설명하려고 합니다. 그럼 ㅃㅃ&lt;/p&gt;</description>
      <category>Problem Solving/알고리즘</category>
      <category>Lower Bound</category>
      <category>Upper Bound</category>
      <category>매개변수 탐색</category>
      <category>알고리즘</category>
      <category>이분 탐색</category>
      <category>이분탐색</category>
      <author>recoma</author>
      <guid isPermaLink="true">https://storyofvector7.tistory.com/78</guid>
      <comments>https://storyofvector7.tistory.com/78#entry78comment</comments>
      <pubDate>Sun, 26 Mar 2023 04:12:22 +0900</pubDate>
    </item>
    <item>
      <title>6개월차 신입 개발자의 반성문과 회고록</title>
      <link>https://storyofvector7.tistory.com/76</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;작년 여름이 끝나갈 즈음, 나는 이런 글을 썼다.&lt;/p&gt;
&lt;figure id=&quot;og_1677931611567&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;파이썬 백엔드 개발자 코테, 면접 후기 (2) (2022.07 ~ 2022.08)&quot; data-og-description=&quot;파이썬 백엔드 개발자 코테, 면접 후기 (1) (2022.6 ~ 2022.7) 이번에 6월 ~ 7월 초에 7군데를 지원했고 이번에 봐야 할 면접들 전부 다 본 기념으로 후기 한번 적어보려고 한다. 일단 네카라쿠베나 중견&quot; data-og-host=&quot;storyofvector7.tistory.com&quot; data-og-source-url=&quot;https://storyofvector7.tistory.com/66&quot; data-og-url=&quot;https://storyofvector7.tistory.com/66&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/DY6P6/hyRQpMph19/URat7vSUp1sFe49HGL2p20/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/mJ5Hd/hyRORX3To3/Wkdq3Er2IJr1tme9KkTp11/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://storyofvector7.tistory.com/66&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://storyofvector7.tistory.com/66&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/DY6P6/hyRQpMph19/URat7vSUp1sFe49HGL2p20/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/mJ5Hd/hyRORX3To3/Wkdq3Er2IJr1tme9KkTp11/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;파이썬 백엔드 개발자 코테, 면접 후기 (2) (2022.07 ~ 2022.08)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;파이썬 백엔드 개발자 코테, 면접 후기 (1) (2022.6 ~ 2022.7) 이번에 6월 ~ 7월 초에 7군데를 지원했고 이번에 봐야 할 면접들 전부 다 본 기념으로 후기 한번 적어보려고 한다. 일단 네카라쿠베나 중견&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;storyofvector7.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접 망하고 술 진탕 먹고 쓴 거라 다음 아침에 지우려고 했는데, 나중에 취업하고 회사일로 힘들 때 위안 삼으려고 그냥 공개로 냅뒀다. 이 글을 쓰고 조상님이 불쌍하게 여겼는지 바로 3주 후에 취업에 성공하고 현재 5개월 째 정규직 개발자로 일을 하고 있다. 취업만 하면 모든 고민이 끝날 거라고 생각했지만, 지금 내 코앞엔 커다란 벽이 앞길을 가로막고 있고 이게 나를 힘들게 하고 있다. 너무 힘들어서 이것도 술쳐먹고 쓰고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신입은 신입이기 때문에 언젠가는 실수하기 마련이다. 하지만 내가 쓰고자 하는 반성문의 대상은 &quot;신입 개발자&quot;가 저지를 수 있는 실수가 아닌,&lt;b&gt; &quot;직장인&quot;이 하지 말아야 할 행동이다.&lt;/b&gt; 나는 이 행동을 5개월 동안 아무렇지 않게 저지르고 있었다. 회사 직원분과 임원진분이 워낙에 좋은신 분이라 지금까지 쓴소리를 들은 적이 한번도 없지만, &lt;b&gt;이때문에 나는 내가 무슨 잘못을 저지르고 있는지 깨닫는데 상당한 시간이 걸렸다.&lt;/b&gt; 아마 속으로 벼르고 있을 지도 모르겠다. 그리고 이 잘못은 그냥 방치해뒀다간 언젠간 한번 크게 사고칠 것 같아, 잊어버리지 말라고 여기에 나의 반성문을 적어보고자 한다. 내가 밑에 쓸 내용은 비단 개발자 뿐만이 아니라 모든 사회 초년생, 또는 신입 직장인들도 포함이 될 수도 있는 내용이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 성격은 내향적인 수준을 넘어서 사교성이 상당이 떨어지는 수준이다 (그렇다고 해서 대인기피증까지 갈 수준은 아니다). 게다가 나는 어떤 행동을 하기 위해 생각을 너무 많이 하는 탓에 행동이 굼뜨고 얼을 많이 탄다 군필자라면 흔히 말하는 폐급 정도로 생각하면 된다. 그래도 어떻게든 회사 사람들하고 잘 지내보려고 밥도 같이 먹고, 회식도 1차 까지는 참여도 자주 했지만 그래도 본성은 본성인지라 말을 잘 못하는 탓에 커뮤니케이션이 잘 되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하지만 기업이라는 조직은 어디까지가 이익을 내는 집단이기 때문에 개개인의 이런 단점을 이해해 주지 않는다. &lt;/b&gt;그러니까 학교나 유치원처럼 &quot;나 힘드러요 응애&quot;하고 징징대면 봐주는게 아니라 더이상 회사에 필요치 않는 인력으로 보고 언젠가는 쫒겨날 거라는 거다. 나도 지금이라도 내 버릇을 고치려는 노력을 하지 않으면 언젠간 쫒겨나겠지&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;다시는 이런 짓을 저지르지 말자&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;모르면 물어봐라&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연한 내용이다, 하지만 나는 이런 당연한 내용을 &quot;사수가 바빠서 방해하지 말아야 겠다&quot;라는 생각으로 모르는게 있으면 질문하게 전에 백엔드 코드를 밑바닥까지 뜯었다, 필요하면 프론트엔드쪽 코드까지 뜯어서 봤다. (나는 백엔드 포지션이지만 프론트엔드 포지션인 React에 대한 지식도 어느정도 있기에 프론트엔드 코드를 읽는데 어려움이 없었다). 그리고 코드를 통해 얻은 결과가 만족스럽고 완벽하다는 큰 착각을 하는 죄를 수많이 저질렀다. 그리고 그 죄값은 최근에 프로젝트를 진행하면서 수많은 실수로 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든것은 여기서부터 시작하며, 보이는 것으로만 판단하지 말고 모르는게 있으면 상사나 사수에게 물어봐서 확실히 알도록 하자, 이게 쌓이고 쌓여서 나중에 큰 도움이 될 것이다. 나는 그렇게 하지 못해서 지금 애를 많이 먹고 있는 중이니까&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;일을 미루게 만들지 말고, 애매모호 한게 있으면 뭐든지 물어보자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;일을 미루지 마라&quot;가 아니라 &quot;일을 미루게 만들지 마라&quot;다. 이건 아까 위의 &quot;모르면 물어봐라&quot;랑 연계되는 내용인데. 일을 하다 뭔가 부정확하거나 모르는게 있으면 끙끙대다 내일로 미루지 마라는 얘기다. 일감에 대해 뭔가 요구사항이 애매모호 하거나 모르는게 있으면 물어보자, 괜히 마구잡이로 코딩하다가 일정이 계속 미뤄질수 있다. 나같은 경우 3주 안에 끝날 것을 이거 때문에 2달 가까이 걸렸다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;말은 끝까지 듣자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간혹 나는 사람 말을 끝가지 듣지 않고 결론을 내버리는 습관이 있다. 그래서 중요한 내용을 놓치는 경우가 많아 다시 되물어 볼 때가 있다. 말을 반복하게 하는 것은 말하는 사람을 짜증나게 하는 것과 다름이 없다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;말을 똑바로 하자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본중에 기본인데 나는 최근에 들어서 이것마저 제대로 못하고 있다. 하루라도 빨리 고쳐야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 네가지에 대해 나는 정상적인 회사생활을 위해 하루라도 빨리 반드시 고쳐야 한다. 변명의 여지 없이 고쳐야 할 사항이지만, 굳이 핑계를 대자면 나는 이 버릇들을 고치는게 상당히 어렵다는 점이다. 두 가지의 이유가 있는데 아마 이 두 개의 문제가 해결되어야 나머지 것들도 해결할 수 있을 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;정상적이지 않은 생활루틴&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 바쁘다는 핑계로, 여유있는 주말 조차 운동을 하지 않은 데다, 인스턴스 식품을 자주 먹었고, 개인 프로젝트를 진행하기 위해 시간을 확보한다는 핑계로 잠도 5시간 이상을 자 본 적이 없다. 처음에는 나름 만족했다. 낮에는 일을 하고, 밤에는 개인 프로젝트를 진행했기 때문에, 나의 스펙을 어느정도 쌓을 수 있겠다고 생각했기 때문이다. 하지만 최근 2주 부터 급격히 몸이 안좋아지기 시작했고, 그때부터 일을 제대로 할 수가 없었으며, 실수 남발이 너무 잦았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 문제는 수면 시간을 2시간 늘림으로써 해결해 보려고 한다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;성격&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 위에서도 서술했듯이 내 성격하고 단체생활은 전혀 맞지가 않다. 거기다 나는 2020 졸작 실패 이후로 2년 넘게 슬럼프에 빠지면서 인간관계에 벽을 쳤었다. 하루에 입을 여는 날이 3번도 안되는 지경이었었고, 이런 생활을 2년넘게 한 탓에 지금도 어떻게 조리있게 말을 하지도 못해 다른 사람들이 내 말을 이해하지 못하고 있으며, 2년 넘게 사람 말을 들어 본 적이 거의없으니 사람들이 하는 말에 머리가 정리가 되지 못한다. 아까 위의 &quot;정상적인 생활루틴&quot;은 말 그대로 생활방식을 바꾸면 어느정도 해결되는 일이지만. 성격은 인간의 본성이라 이건 내가 평생 안고 가야 할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 언제까지 이러고 있을 수는 없다. 성격을 바꾸진 못하더라도, 커뮤니케이션 능력은 어떻게든 빨리 조금이라도 키워야 한다. 말을 잘 한다 정도까진 아니더라도, 기본적인 수준 까지는 어떻게 올릴수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사람 말을 제대로 듣기 위해 메모하는 습관을 가지고, 사람들에게 말을 똑바로 하기 위해, 뭔가 말하기 전에 글로 정리하는 습관을 가져보려고 한다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이 모든 문제의 원인은 컨디션 문제와 내 성격에 있었다. 사실 입사 초기부터 어느 정도 느낌은 왔었지만, 크케 문제되지 않아 개의치 않았었다. 그 결과 지금 이렇게 눈덩이가 되어 불은 것이다. 이 모든걸 방관한 내 잘못이다. 아마 겉으로는 표현되지 않았지만, 아마 내 생각에는 회사사람들이 날 많이 싫어할 것 같다. 이 싫어한다는 신호가 밖으로 나오기 전에 하루라도 빨리 버릇을 고쳐서 평판을 원래대로 돌려놔야겠다.&lt;/p&gt;</description>
      <category>후기 &amp;amp; 리뷰</category>
      <category>개발자</category>
      <category>신입</category>
      <category>취업</category>
      <category>회사생활</category>
      <author>recoma</author>
      <guid isPermaLink="true">https://storyofvector7.tistory.com/76</guid>
      <comments>https://storyofvector7.tistory.com/76#entry76comment</comments>
      <pubDate>Sun, 5 Mar 2023 03:04:25 +0900</pubDate>
    </item>
  </channel>
</rss>