<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://dongbum.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://dongbum.io/" rel="alternate" type="text/html" /><updated>2026-03-12T13:48:04+09:00</updated><id>https://dongbum.io/feed.xml</id><title type="html">DONGBUM on blog</title><subtitle>개발이야기</subtitle><author><name>DONGBUM KIM</name></author><entry><title type="html">MSSQL 프로시저 실행 중 데드락. 원인과 해결까지</title><link href="https://dongbum.io/2025/07/03/execute-mssql-procedure-on-jenkins" rel="alternate" type="text/html" title="MSSQL 프로시저 실행 중 데드락. 원인과 해결까지" /><published>2025-07-03T08:21:00+09:00</published><updated>2025-07-03T08:21:00+09:00</updated><id>https://dongbum.io/2025/07/03/execute-mssql-procedure-on-jenkins</id><content type="html" xml:base="https://dongbum.io/2025/07/03/execute-mssql-procedure-on-jenkins"><![CDATA[<h5 id="데드락-무슨-일이-있었나">데드락! 무슨 일이 있었나</h5>

<p>게임 패치를 위해 퍼블리셔에게 DB 패치 스크립트를 한데 묶어 전달했다.
서버 업데이트 당일, 모든 스크립트는 정상적으로 처리됐지만… 단 하나의 스크립트에서 모든 샤드에서 에러가 발생했다.</p>

<p>퍼블리셔 Tech PM이 전달해준 <strong>5천 줄짜리 로그</strong>를 샅샅이 뒤져보던 중, 눈에 띄는 메시지를 발견했다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># [tid:19][ERROR] (1205, b'Transaction (Process ID 155) was deadlocked on lock | communication buffer resources with another process and has been chosen as the deadlock victim. Rerun the transaction.DB-Lib error message 20018, severity 13:\nGeneral SQL Server error: Check messages from the SQL Server\n') 201703
Exception in thread Thread-13:
Traceback (most recent call last):
  File "src/pymssql.pyx", line 448, in pymssql.Cursor.execute
  File "src/_mssql.pyx", line 1064, in _mssql.MSSQLConnection.execute_query
  File "src/_mssql.pyx", line 1096, in _mssql.MSSQLConnection.execute_query
  File "src/_mssql.pyx", line 1294, in _mssql.MSSQLConnection.get_result
  File "src/_mssql.pyx", line 1639, in _mssql.check_cancel_and_raise
  File "src/_mssql.pyx", line 1683, in _mssql.maybe_raise_MSSQLDatabaseException
_mssql.MSSQLDatabaseException: (1205, b'Transaction (Process ID 148) was deadlocked on lock | communication buffer resources with another process and has been chosen as the deadlock victim. Rerun the transaction.DB-Lib error message 20018, severity 13:\nGeneral SQL Server error: Check messages from the SQL Server\n')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/infra/py/woodstock/execute_batch_sql/execute_batch_all_sql_use_thread_mssql.py", line 209, in process_restore
    p_db.cursor.execute(query)
  File "src/pymssql.pyx", line 468, in pymssql.Cursor.execute
pymssql.OperationalError: (1205, b'Transaction (Process ID 148) was deadlocked on lock | communication buffer resources with another process and has been chosen as the deadlock victim. Rerun the transaction.DB-Lib error message 20018, severity 13:\nGeneral SQL Server error: Check messages from the SQL Server\n')
</code></pre></div></div>

<p>클래식한 <strong>데드락 에러</strong>였다. 조금 더 자세히 보면 다음과 같은 <code class="language-plaintext highlighter-rouge">pymssql</code> 예외 트레이스백도 확인할 수 있었다.</p>

<h4 id="테스트할-땐-멀쩡했는데">테스트할 땐 멀쩡했는데?</h4>

<p>문제의 스크립트는 내부 QA 테스트에서는 아무런 문제가 없었다. 직접 SSMS에서 한 번에 실행했을 때는 에러도 없고, 처리 시간도 양호했다.</p>

<p>하지만 퍼블리셔 환경은 달랐다.<br />
그들은 <strong>젠킨스에서 파이썬 스크립트 (<code class="language-plaintext highlighter-rouge">pymssql</code>)로</strong>, <strong>멀티스레드</strong>로 동시에 각 샤드에 SQL을 실행하고 있었던 것 같다. (정확히는 확인할 수 없지만 정황상 꽤 유력하다.)</p>

<p>알고 보니 내가 작성한 쿼리는 다음과 같은 <strong>데드락 발생 조건</strong>을 잔뜩 갖추고 있었다:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">UPDATE ... JOIN</code> 구문</li>
  <li>여러 테이블을 동시에 수정</li>
  <li>UDF (사용자 정의 함수) 호출</li>
</ul>

<p>단일 세션에서는 잘 돌아가지만, 멀티세션/멀티스레드 상황에서는 충돌이 일어날 수밖에 없었다.</p>

<h5 id="해결책은-의외로-간단했다">해결책은 의외로 간단했다</h5>

<p>쿼리를 리팩터링해서 테이블 접근 순서를 맞추거나, 복잡한 힌트를 주는 방법도 고려했지만…</p>

<p>가장 간단하고 확실한 해결책은 <strong>단독 실행</strong>이었다.</p>

<p>퍼블리셔 측에 “이 쿼리만은 병렬 실행하지 말고 단독으로 실행해 달라”고 요청했다. 그리고 실제로 그렇게 하자 문제는 바로 해결되었다.</p>

<h4 id="마무리">마무리</h4>

<p>데드락은 언제나 예상치 못한 상황에서 등장한다. 특히 <strong>멀티스레드 + 복잡한 쿼리</strong> 조합은 사고가 나기 쉬운 구조다.</p>

<p>테스트할 땐 멀쩡했는데 실서버에서만 문제가 생긴다면, “이 쿼리가 병렬 실행될 가능성은 없었을까?” 한 번쯤 되짚어보는 것도 좋은 습관이다.</p>]]></content><author><name>DONGBUM KIM</name></author><category term="Database/SQL" /><category term="DevStory" /><category term="database" /><category term="db" /><category term="sql" /><category term="mssql" /><category term="jenkins" /><category term="gameserver" /><summary type="html"><![CDATA[데드락! 무슨 일이 있었나]]></summary></entry><entry><title type="html">c++ 에서 병렬처리 정렬 알고리즘의 성능</title><link href="https://dongbum.io/2025/04/22/std-execution-par" rel="alternate" type="text/html" title="c++ 에서 병렬처리 정렬 알고리즘의 성능" /><published>2025-04-22T15:04:36+09:00</published><updated>2025-04-22T15:04:36+09:00</updated><id>https://dongbum.io/2025/04/22/std-execution-par</id><content type="html" xml:base="https://dongbum.io/2025/04/22/std-execution-par"><![CDATA[<p>서버 내 코드를 개선할게 없느라 가끔씩 지나가며 보는데 작은 알고리즘부터 수정 중이다.</p>

<p>병렬프로그래밍을 찾다보니 정렬알고리즘을 찾게 되었고 최적화할 수 있음을 알게되서 테스트해보았다.</p>

<p>기존의 코드는 std::sort 를 사용하고 있었다. 이 정렬 알고리즘은 concurrency::parallel_sort 로 바꿀 수 있었는데 PPL 의 특성상 비표준인데다가 윈도우즈에서만 작동가능한 코드였다. chatgpt 는 concurrency::parallel_sort 대신에 std::sort 와 std::execution::par 를 사용하는 것을 추천해주었다. (c++17 이상에서만 유효하다.) 이 알고리즘에 대한 설명은 chatgpt 를 참고하자.</p>

<h3 id="테스트코드">테스트코드</h3>
<p>기존의 std::sort 를 사용한 정렬과 std::sort + std::execution::par 를 사용한 코드를 비교해보았다.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define WIN32_LEAN_AND_MEAN
#include</span> <span class="cpf">&lt;Windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;vector&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;string&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;random&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;chrono&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;execution&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;locale&gt;</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span> <span class="kt">void</span> <span class="p">)</span>
<span class="p">{</span>
	<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">vec</span><span class="p">;</span>

	<span class="k">for</span> <span class="p">(</span> <span class="k">auto</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">1000000</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span> <span class="p">)</span>
		<span class="n">vec</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span> <span class="n">i</span> <span class="p">);</span>

	<span class="n">std</span><span class="o">::</span><span class="n">random_device</span> <span class="n">rd</span><span class="p">;</span>
	<span class="n">std</span><span class="o">::</span><span class="n">mt19937</span> <span class="n">engine</span><span class="p">(</span> <span class="n">rd</span><span class="p">()</span> <span class="p">);</span>
	<span class="n">std</span><span class="o">::</span><span class="n">shuffle</span><span class="p">(</span> <span class="n">vec</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">vec</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span> <span class="n">engine</span> <span class="p">);</span>

	<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">vec1</span> <span class="o">=</span> <span class="n">vec</span><span class="p">;</span>
	<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">vec2</span> <span class="o">=</span> <span class="n">vec</span><span class="p">;</span>

	<span class="p">{</span>
		<span class="c1">// 시간 측정 시작</span>
		<span class="k">auto</span> <span class="n">start</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">chrono</span><span class="o">::</span><span class="n">high_resolution_clock</span><span class="o">::</span><span class="n">now</span><span class="p">();</span>

		<span class="n">std</span><span class="o">::</span><span class="n">sort</span><span class="p">(</span> <span class="n">vec1</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">vec1</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span> <span class="p">[](</span> <span class="k">auto</span> <span class="n">lhs</span><span class="p">,</span> <span class="k">auto</span> <span class="n">rhs</span> <span class="p">)</span>
		<span class="p">{</span>
			<span class="k">return</span> <span class="n">lhs</span> <span class="o">&lt;</span> <span class="n">rhs</span><span class="p">;</span>
		<span class="p">}</span> <span class="p">);</span>

		<span class="c1">// 시간 측정 끝</span>
		<span class="k">auto</span> <span class="n">end</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">chrono</span><span class="o">::</span><span class="n">high_resolution_clock</span><span class="o">::</span><span class="n">now</span><span class="p">();</span>

		<span class="c1">// 경과 시간 계산 (단위: 마이크로초)</span>
		<span class="k">auto</span> <span class="n">duration</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">chrono</span><span class="o">::</span><span class="n">duration_cast</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">chrono</span><span class="o">::</span><span class="n">microseconds</span><span class="o">&gt;</span><span class="p">(</span> <span class="n">end</span> <span class="o">-</span> <span class="n">start</span> <span class="p">);</span>

		<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"실행 시간: "</span> <span class="o">&lt;&lt;</span> <span class="n">duration</span><span class="p">.</span><span class="n">count</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="s">" 마이크로초"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="p">{</span>
		<span class="c1">// 시간 측정 시작</span>
		<span class="k">auto</span> <span class="n">start</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">chrono</span><span class="o">::</span><span class="n">high_resolution_clock</span><span class="o">::</span><span class="n">now</span><span class="p">();</span>

		<span class="n">std</span><span class="o">::</span><span class="n">sort</span><span class="p">(</span> <span class="n">std</span><span class="o">::</span><span class="n">execution</span><span class="o">::</span><span class="n">par</span><span class="p">,</span> <span class="n">vec1</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">vec1</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span> <span class="p">[](</span> <span class="k">auto</span> <span class="n">lhs</span><span class="p">,</span> <span class="k">auto</span> <span class="n">rhs</span> <span class="p">)</span>
		<span class="p">{</span>
			<span class="k">return</span> <span class="n">lhs</span> <span class="o">&lt;</span> <span class="n">rhs</span><span class="p">;</span>
		<span class="p">}</span> <span class="p">);</span>

		<span class="c1">// 시간 측정 끝</span>
		<span class="k">auto</span> <span class="n">end</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">chrono</span><span class="o">::</span><span class="n">high_resolution_clock</span><span class="o">::</span><span class="n">now</span><span class="p">();</span>

		<span class="c1">// 경과 시간 계산 (단위: 마이크로초)</span>
		<span class="k">auto</span> <span class="n">duration</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">chrono</span><span class="o">::</span><span class="n">duration_cast</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">chrono</span><span class="o">::</span><span class="n">microseconds</span><span class="o">&gt;</span><span class="p">(</span> <span class="n">end</span> <span class="o">-</span> <span class="n">start</span> <span class="p">);</span>

		<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">"실행 시간: "</span> <span class="o">&lt;&lt;</span> <span class="n">duration</span><span class="p">.</span><span class="n">count</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="s">" 마이크로초"</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>배열에 int 값을 여러개넣고 섞은 후 다시 정렬하는데에 걸리는 시간을 측정하는 코드이다.</p>

<h3 id="테스트결과">테스트결과</h3>
<p>이 코드를 실행했을 때 다음과 같은 결과를 얻을 수 있었다. 단위는 ‘마이크로초’이다.</p>

<table>
  <tbody>
    <tr>
      <td>원소갯수</td>
      <td>std::sort</td>
      <td>std::sort+std::execution::par</td>
    </tr>
    <tr>
      <td>100</td>
      <td>20</td>
      <td>61</td>
    </tr>
    <tr>
      <td>300</td>
      <td>70</td>
      <td>98</td>
    </tr>
    <tr>
      <td>500</td>
      <td>111</td>
      <td>86</td>
    </tr>
    <tr>
      <td>1000</td>
      <td>258</td>
      <td>128</td>
    </tr>
    <tr>
      <td>10000</td>
      <td>3302</td>
      <td>461</td>
    </tr>
    <tr>
      <td>100000</td>
      <td>41760</td>
      <td>4022</td>
    </tr>
    <tr>
      <td>1000000</td>
      <td>509795</td>
      <td>48412</td>
    </tr>
    <tr>
      <td>10000000</td>
      <td>5024498</td>
      <td>564703</td>
    </tr>
    <tr>
      <td>100000000</td>
      <td>68011136</td>
      <td>5948772</td>
    </tr>
  </tbody>
</table>

<p>사용된 컴퓨터는 Intel Core i5-12400F 2.5GHz CPU, 64GB 메모리의 컴퓨터이며 Visual Studio 2019 x64 환경에서 실행하였다.</p>

<p>표를 본다면 원소갯수가 300개까지는 정렬전략을 고르지 않고 기본전략대로 사용하는 것이 더 좋을 것으로 보인다. 대략 원소갯수 500개 정도부터는 std::execution::par 를 설정하는 것이 검색시간을 줄이는 효과가 나타나는 것을 알 수 있었다. 1000개는 대략 1/2 로 감소시키는 효과가 있었고 10000개부터는 거의 1/10 으로 감소시킬 수 있었다.</p>

<h3 id="결론">결론</h3>
<p>원소갯수 300개 미만은 std::sort 를 사용하면 된다. 원소갯수 300개 이상은 std::sort + std::execution::par 를 사용하는 것이 좋다.</p>

<p>난 다음과 같은 템플릿함수를 만들어서 사용 중이다. std::sort 와 동일하게 작동할 수 있으면서 원소 갯수에 따라서 정렬알고리즘을 선택하게 하여 최고의 속도를 내게 만든다.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o">&lt;</span><span class="k">class</span> <span class="nc">RandomIt</span><span class="p">,</span> <span class="k">class</span> <span class="nc">Compare</span><span class="p">&gt;</span>
<span class="kt">void</span> <span class="nf">RzSort</span><span class="p">(</span> <span class="n">RandomIt</span> <span class="n">first</span><span class="p">,</span> <span class="n">RandomIt</span> <span class="n">last</span><span class="p">,</span> <span class="n">Compare</span> <span class="n">comp</span> <span class="p">)</span>
<span class="p">{</span>
	<span class="k">auto</span> <span class="n">size</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">distance</span><span class="p">(</span> <span class="n">first</span><span class="p">,</span> <span class="n">last</span> <span class="p">);</span>
	<span class="k">if</span> <span class="p">(</span> <span class="n">size</span> <span class="o">&lt;=</span> <span class="mi">0</span> <span class="p">)</span>
		<span class="k">return</span><span class="p">;</span>

	<span class="k">if</span> <span class="p">(</span> <span class="n">size</span> <span class="o">&lt;</span> <span class="mi">1000</span> <span class="p">)</span>
		<span class="n">std</span><span class="o">::</span><span class="n">sort</span><span class="p">(</span> <span class="n">first</span><span class="p">,</span> <span class="n">last</span><span class="p">,</span> <span class="n">comp</span> <span class="p">);</span>
	<span class="k">else</span>
		<span class="n">std</span><span class="o">::</span><span class="n">sort</span><span class="p">(</span> <span class="n">std</span><span class="o">::</span><span class="n">execution</span><span class="o">::</span><span class="n">par</span><span class="p">,</span> <span class="n">first</span><span class="p">,</span> <span class="n">last</span><span class="p">,</span> <span class="n">comp</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>]]></content><author><name>DONGBUM KIM</name></author><category term="c++" /><category term="c++" /><category term="jenkins" /><summary type="html"><![CDATA[서버 내 코드를 개선할게 없느라 가끔씩 지나가며 보는데 작은 알고리즘부터 수정 중이다.]]></summary></entry><entry><title type="html">퍼포스 서버에서 한번에 여러파일 정리하기</title><link href="https://dongbum.io/2025/04/03/perforce-server-obiliterate-multi-files" rel="alternate" type="text/html" title="퍼포스 서버에서 한번에 여러파일 정리하기" /><published>2025-04-03T13:02:36+09:00</published><updated>2025-04-03T13:02:36+09:00</updated><id>https://dongbum.io/2025/04/03/perforce-server-obiliterate-multi-files</id><content type="html" xml:base="https://dongbum.io/2025/04/03/perforce-server-obiliterate-multi-files"><![CDATA[<p>퍼포스 서버를 관리하다보면 대량의 바이너리 파일들로 인해 서버 디스크의 용량이 점점 가득차게 된다. 적절하게 관리해주지 않으면 퍼포스 서버가 위태로워질 수 있으니 주기적으로 상태를 봐가며 관리해줘야한다. 특히 사용되지 않고 있는 오래된 리비전의 파일은 삭제하면 디스크 공간 확보에 많은 도움이 된다.</p>

<p>퍼포스에는 이 기능을 하도록 Obliterate Files 라는 기능이 있는데, 문제는 이 기능이 다 수동으로 작동되어야 한다는 것이다. 특정 리비전에서 특정 리비전까지 삭제는 가능하지만 이것을 각 파일마다 일일히 하나씩 다 처리해줘야한다.</p>

<p>처음에는 이것을 파일 하나씩 수동으로 정성스럽게 했었지만 관리해야할 파일이 너무 많아짐에 따라 손이 너무 많이 가서 자동으로 처리하도록 배치파일로 만들었다.</p>

<p>files_program.txt 파일에 정리할 파일들의 목록을 넣어놓으면 된다. 가장 최근 5개 리비전만 남기게 된다.</p>

<p>퍼포스 쓰는 사람들은 잘 알겠지만 Obliterate 명령은 한번 실행되면 절대 되돌릴 수 없으므로 아래 배치파일을 실행하기 전에 충분히 코드를 파악하고 테스트 파일에 테스트를 해보고 실행해보길 바란다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@echo off
setlocal enabledelayedexpansion

rem 파일 목록이 저장된 파일 경로 설정
set FILE_LIST=files_program.txt

rem files.txt 파일을 한 줄씩 읽어서 처리한다.
for /f "delims=" %%a in (%FILE_LIST%) do (
	set "TARGET_FILE=%%a"
	
	rem echo !TARGET_FILE!
	
    rem 가장 마지막 리비전 번호를 구한다.
    for /f "tokens=3" %%b in ('p4 fstat -T headRev !TARGET_FILE! 2^&gt;nul ^| find "headRev"') do set LATEST_REVISION=%%b

    rem echo !LATEST_REVISION!
	
	rem 마지막 5개 리비전이 5 초과일 때만 처리한다.
	if !LATEST_REVISION! gtr 5 (
        
	    set /a LATEST_REVISION -= 5
	    
	    rem echo !LATEST_REVISION!
	    
		rem obliterate 명령 실행
	    rem echo "p4 obliterate -T !TARGET_FILE!#1,#!LATEST_REVISION!"
	    
	    p4 obliterate -y -T !TARGET_FILE!#1,#!LATEST_REVISION!
    )
)

endlocal
</code></pre></div></div>

<p>files_program.txt 파일은 아래와 같이 작성한다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//depot/Project/Server/TestServer1.pdb
//depot/Project/Server/TestServer2.pdb

//depot/Project/Server/TestServer3.pdb
//depot/Project/Server/TestServer4.pdb
</code></pre></div></div>]]></content><author><name>DONGBUM KIM</name></author><category term="Perforce" /><category term="perforce" /><category term="vcs" /><summary type="html"><![CDATA[퍼포스 서버를 관리하다보면 대량의 바이너리 파일들로 인해 서버 디스크의 용량이 점점 가득차게 된다. 적절하게 관리해주지 않으면 퍼포스 서버가 위태로워질 수 있으니 주기적으로 상태를 봐가며 관리해줘야한다. 특히 사용되지 않고 있는 오래된 리비전의 파일은 삭제하면 디스크 공간 확보에 많은 도움이 된다.]]></summary></entry><entry><title type="html">리눅스에서 SMART 실패</title><link href="https://dongbum.io/2023/12/06/linux-smart-failed" rel="alternate" type="text/html" title="리눅스에서 SMART 실패" /><published>2023-12-06T13:54:36+09:00</published><updated>2023-12-06T13:54:36+09:00</updated><id>https://dongbum.io/2023/12/06/linux-smart-failed</id><content type="html" xml:base="https://dongbum.io/2023/12/06/linux-smart-failed"><![CDATA[<p>드디어 8년간 사용하던 NAS의 하드디스크에서 에러메시지가 발생했다. 몰랐는데 매일매일 SMART 검사 에러메시지가 메일로 오고 있었다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Return-path: &lt;root@debian&gt;
Envelope-to: root@debian
Delivery-date: Sat, 25 Mar 2023 15:47:09 +0900
Received: from root by debian with local (Exim 4.94.2)
        (envelope-from &lt;root@debian&gt;)
        id 1pfxgD-00FRcJ-EF
        for root@debian; Sat, 25 Mar 2023 15:47:09 +0900
Subject: SMART error (CurrentPendingSector) detected on host: debian
To: &lt;root@debian&gt;
X-Mailer: mail (GNU Mailutils 3.10)
Message-Id: &lt;E1pfxgD-00FRcJ-EF@debian&gt;
From: root@debian
Date: Sat, 25 Mar 2023 15:47:09 +0900
X-UID: 60
Status: O

This message was generated by the smartd daemon running on:

   host name:  debian
   DNS domain: [Empty]

The following warning/error was logged by the smartd daemon:

Device: /dev/sdc [SAT], 4 Currently unreadable (pending) sectors

Device info:
SAMSUNG HD250HJ, S/N:S0UTJDWQ117986, WWN:5-0000f0-0db117986, FW:FH100-06, 250 GB

For details see host's SYSLOG.

You can also use the smartctl utility for further investigation.
The original message about this issue was sent at Fri Jan 27 14:47:09 2023 KST
Another message will be sent in 24 hours if the problem persists.
</code></pre></div></div>

<p>이 디스크는 2008년에 생산된 삼성의 250GB 하드디스크. 지금이 2023년 말이니 대략 15년간을 작동한 셈. 내가 나눔받아서 사용하게 된게 2015년 초였고 15년 중에 그 절반은 거의 24시간 켜져있었다. 이정도 사용하면 불량섹터가 서서히 생겨간다는걸 알 수 있었다.</p>

<p>집에와서 해당 디스크의 파일을 모두 지우고 NAS에서 분리했다.</p>]]></content><author><name>DONGBUM KIM</name></author><category term="MicroServer" /><category term="linux" /><category term="리눅스" /><summary type="html"><![CDATA[드디어 8년간 사용하던 NAS의 하드디스크에서 에러메시지가 발생했다. 몰랐는데 매일매일 SMART 검사 에러메시지가 메일로 오고 있었다.]]></summary></entry><entry><title type="html">AWS S3로 동기화 스크립트</title><link href="https://dongbum.io/2023/09/16/photo-backup-using-aws-s3" rel="alternate" type="text/html" title="AWS S3로 동기화 스크립트" /><published>2023-09-16T00:32:36+09:00</published><updated>2023-09-16T00:32:36+09:00</updated><id>https://dongbum.io/2023/09/16/photo-backup-using-aws-s3</id><content type="html" xml:base="https://dongbum.io/2023/09/16/photo-backup-using-aws-s3"><![CDATA[<p>NAS로 가족사진을 백업하며 사진들을 날려버리지는 않을까 항상 조마조마한 마음이 있었다. AWS S3에 가족들의 사진을 백업하게 되면서 이 불안함이 많이 사라졌다.</p>

<p>S3 에 백업하려면 기본적인 S3 사용법을 알아야한다. 버킷의 개념… 등등.</p>

<p>항상 NAS의 사진 디렉토리와 S3의 버킷이 동기화할 수 있도록 다음의 스크립트를 작성해서 생각날 때마다 한번씩 돌려주고 있다. (NAS는 리눅스 서버이다.)</p>

<h3 id="기본-스크립트">기본 스크립트</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws s3 sync /disk3/BackupTargetDirectory s3://backup-bucket \
        --storage-class STANDARD_IA \
        --exclude "*" \
        --include "backup1/*" \
        --include "backup2/*" \
        --include "backup3/*"
</code></pre></div></div>

<p>이 스크립트의 가장 큰 목적은, 디렉토리내에 백업할 여러 자료들이 섞여있기 때문에 디렉토리내의 모든 디렉토리를 제외하고 백업하고자하는 특정디렉토리만을 선정하여 백업한다는 것이다.</p>

<h3 id="저장-수준">저장 수준</h3>

<p>자료들의 저장수준은 –storage-class 라는 옵션을 통하여 지정할 수 있다.</p>

<p>storage class 옵션에 대한 설명은 아래 참고자료의 링크에 있다.
한글페이지에는 아직 이 내용이 없는듯하다. 아래 링크의 메뉴얼에 대부분 옵션에 대한 설명이 되어있다.</p>

<ul>
  <li>STANDARD</li>
  <li>REDUCED_REDUNDANCY</li>
  <li>STANDARD_IA</li>
  <li>ONEZONE_IA</li>
  <li>INTELLIGENT_TIERING</li>
  <li>GLACIER</li>
  <li>DEEP_ARCHIVE</li>
  <li>GLACIER_IR</li>
</ul>

<h3 id="참고자료">참고자료</h3>

<ul>
  <li><a href="https://docs.aws.amazon.com/cli/latest/reference/s3/sync.html">https://docs.aws.amazon.com/cli/latest/reference/s3/sync.html</a></li>
</ul>]]></content><author><name>DONGBUM KIM</name></author><category term="AWS" /><category term="Amazon" /><category term="AWS" /><category term="S3" /><category term="Cloud" /><summary type="html"><![CDATA[NAS로 가족사진을 백업하며 사진들을 날려버리지는 않을까 항상 조마조마한 마음이 있었다. AWS S3에 가족들의 사진을 백업하게 되면서 이 불안함이 많이 사라졌다.]]></summary></entry><entry><title type="html">이케아에서 아이가 가지고 놀고 있던 장난감을 보며 드는 생각들</title><link href="https://dongbum.io/2023/05/01/ikea-uppsta" rel="alternate" type="text/html" title="이케아에서 아이가 가지고 놀고 있던 장난감을 보며 드는 생각들" /><published>2023-05-01T00:10:36+09:00</published><updated>2023-05-01T00:10:36+09:00</updated><id>https://dongbum.io/2023/05/01/ikea-uppsta</id><content type="html" xml:base="https://dongbum.io/2023/05/01/ikea-uppsta"><![CDATA[<p>얼마전 이케아에 갔다가 아이가 가지고 놀고 있는 장난감을 보며 문득 웃음이 나왔다.</p>

<p><img src="/assets/images/ikea_uppsta.jpg" alt="" /></p>

<p>이 제품은 IKEA의 Uppsta 라는 제품이다. (https://www.ikea.com/kr/ko/p/uppsta-stacking-rings-multicolour-80513893/)</p>

<p>프로그래머, 개발자들이라면 저 사진의 장난감이 ‘하노이의 탑’ 문제와 같다는 것을 한눈에 보고 알 것이다.</p>

<p>영유아 장난감 만으로도 알고리즘을 얘기할 수 있고 재미있는 문제를 생각해볼 수 있는데 굳이 코딩학원이니 코딩교육이니 이런게 필요한건가 하는 생각이 든다. 그런건 고등학교, 대학교에서 해도 늦지 않다고 본다. 아이 때에는 흔하고 쉬운 장난감 등을 통해 문제들을 생각해보며 코딩이라기보다는 문제 풀이라는 개념 자체에 접근해나가는게 올바른게 아닌가 하는 생각이 든다.</p>]]></content><author><name>DONGBUM KIM</name></author><category term="programing" /><category term="programing" /><summary type="html"><![CDATA[얼마전 이케아에 갔다가 아이가 가지고 놀고 있는 장난감을 보며 문득 웃음이 나왔다.]]></summary></entry><entry><title type="html">워드프레스를 완전히 떠나며</title><link href="https://dongbum.io/2022/10/17/goodbye-wordpress" rel="alternate" type="text/html" title="워드프레스를 완전히 떠나며" /><published>2022-10-17T13:54:36+09:00</published><updated>2022-10-17T13:54:36+09:00</updated><id>https://dongbum.io/2022/10/17/goodbye-wordpress</id><content type="html" xml:base="https://dongbum.io/2022/10/17/goodbye-wordpress"><![CDATA[<h3 id="워드프레스와-15년">워드프레스와 15년</h3>

<p><img src="/assets/images/wordpress-logo.png" alt="" /></p>

<p>워드프레스를 쓰기 시작했던건 대략 15년 정도 된것 같습니다. 사실 언제였었는지 정확히 기억이 잘 안납니다. 처음에는 태터툴즈라는 설치형 블로그(지금의 티스토리의 전신)를 썼었고, 그 이후에는 무버블타입(aka. MT3)이라는 블로그툴을 쓸까 하다가 soojung 이라는 파일기반 블로그툴을 쓸까…하다가 고민 끝에 많이 사용되어지는 워드프레스를 사용하게 되었습니다. 그렇게 10여년이 훌쩍 흘렀습니다.</p>

<h3 id="단점과-해킹">단점과 해킹</h3>

<p>PHP로 만들어진 워드프레스는 막강한 기능을 자랑했는데 단점은 이 막강한 기능 때문에 너무나 복잡했습니다. 한군데라도 잘못 건드렸다가는 사이트가 먹통되기 일쑤여서 뭐 하나 마음대로 건드려보기가 망설여지는 그런 시스템이었습니다. 프로그램이라는게 뭐 다 그렇긴하지만.</p>

<p>최근의 트렌드는 HTML 기반으로 구성되는 static한 사이트들이 많아지고 나도 곰곰히 생각하다가 내 사이트부터 그렇게 바꿔보았습니다. PHP를 쓰지 않아보니 정말 관리하기가 편했습니다. 관리자모드라는 것도 없어졌고 Github + Netlify 를 통한 편리한 시스템이 정말 마음에 들었습니다.</p>

<p>아내의 비즈니스용 웹사이트는 워드프레스를 이용하여 구축되었고 AWS Lightsail 위에서 작동하고 있었는데 얼마전 들어가봤더니 해킹당한 것을 알게 되었습니다.</p>

<p><img src="/assets/images/hacking_1.png" alt="" /></p>

<p>구글 서치콘솔에 가보니 알 수 없는 35만개의 페이지가 생성되어 있었습니다.</p>

<p><img src="/assets/images/hacking_2.png" alt="" /></p>

<p>URL을 보니 PHP의 취약점 등을 이용하여 자동으로 생성된 페이지 같습니다.</p>

<p>사이트는 아예 접속이 되지 않고 있었는데 다행히 관리자모드는 들어갈 수 있어서 자료들만은 살릴 수 있었습니다. 어차피 static한 자료를 관리하는 사이트라 PHP를 더이상 쓸 필요가 없다고 생각되었습니다. 자료를 백업하고 다시 Jekyll 과 Netlify 기반의 사이트로 재구축하고 기존 자료를 다시 입력하는데 3일이 걸렸습니다.</p>

<h3 id="netlify">Netlify</h3>

<p><img src="/assets/images/netlify-logo.png" alt="" /></p>

<p>위에 얘기했지만, Github + Netlify 의 간단하고 편리한 사이트 제네레이팅/빌드 시스템은 너무나 마음에 듭니다.</p>

<p>HTML 기반의 사이트로 변경되었으니 이제 해킹 당할 위험은 거의 없을 것 같습니다. 워드프레스도 나쁘지 않았지만 편안하게 쓰기에는 너무 복잡했습니다. 이런 해킹 위험까지 감수하며 사용할만한 프로그램도 아니었고.</p>

<p>Netlify 유료 플랜을 생각해볼 차례입니다.</p>]]></content><author><name>DONGBUM KIM</name></author><category term="wordpress" /><category term="wordpress" /><category term="php" /><summary type="html"><![CDATA[워드프레스와 15년]]></summary></entry><entry><title type="html">GIT에서 index lock 에러 메시지</title><link href="https://dongbum.io/2022/05/01/git-index-lock-error" rel="alternate" type="text/html" title="GIT에서 index lock 에러 메시지" /><published>2022-05-01T01:00:36+09:00</published><updated>2022-05-01T01:00:36+09:00</updated><id>https://dongbum.io/2022/05/01/git-index-lock-error</id><content type="html" xml:base="https://dongbum.io/2022/05/01/git-index-lock-error"><![CDATA[<p>젠킨스에서 빌드를 하고 있는데 이때 Perforce 에서 소스코드를 가져오고 빌드를 한 다음 git 으로 commin 하고 push 하는 과정을 거치고 있습니다.</p>

<p>하루는 빌드에 계속 문제가 생겨서 살펴보던 중 젠킨스에서 다음의 로그를 발견하였습니다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Locking support detected on remote "origin". Consider enabling it with:
  $ git config lfs.https://aaa.com/project.git/info/lfs.locksverify true
To https://aaa.com/project.git/server.git
 * [new tag]           20220412-01 -&gt; 20220412-01
fatal: Unable to create 'D:/aaa/git/server/.git/index.lock': File exists.

Another git process seems to be running in this repository, e.g.
an editor opened by 'git commit'. Please make sure all processes
are terminated then try again. If it still fails, a git process
may have crashed in this repository earlier:
remove the file manually to continue.
fatal: Unable to create 'D:/aaa/git/server/.git/index.lock': File exists.

Another git process seems to be running in this repository, e.g.
an editor opened by 'git commit'. Please make sure all processes
are terminated then try again. If it still fails, a git process
may have crashed in this repository earlier:
remove the file manually to continue.
Everything up-to-date
</code></pre></div></div>

<p>그런데 이렇게 에러가 난다해도 젠킨스의 프로젝트 빌드 결과는 Success 이기 때문에 에러라고 알아차릴 수 없었기에 문제 상황을 알아내는데 시간이 꽤 걸렸습니다.</p>

<p>여튼 위처럼 에러메시지가 나올 때는 index.lock 파일을 삭제해서 해결할 수 있습니다.</p>

<h3 id="참고자료">참고자료</h3>
<ul>
  <li><a href="https://jinseongsoft.tistory.com/151">https://jinseongsoft.tistory.com/151</a></li>
</ul>]]></content><author><name>DONGBUM KIM</name></author><category term="GIT" /><category term="GIT" /><category term="Jenkins" /><summary type="html"><![CDATA[젠킨스에서 빌드를 하고 있는데 이때 Perforce 에서 소스코드를 가져오고 빌드를 한 다음 git 으로 commin 하고 push 하는 과정을 거치고 있습니다.]]></summary></entry><entry><title type="html">과유불급의 보안정책</title><link href="https://dongbum.io/2021/11/10/stupid-security-policy" rel="alternate" type="text/html" title="과유불급의 보안정책" /><published>2021-11-10T23:09:36+09:00</published><updated>2021-11-10T23:09:36+09:00</updated><id>https://dongbum.io/2021/11/10/stupid-security-policy</id><content type="html" xml:base="https://dongbum.io/2021/11/10/stupid-security-policy"><![CDATA[<h3 id="강력한-비밀번호-정책">강력한 비밀번호 정책</h3>

<p>제가 겪었던 어떤 회사는 비밀번호 정책이 매우 강력했습니다.</p>

<p>당연하게도, 특수문자, 영어 대/소문자, 숫자를 섞어서 비밀번호를 만들어야 했습니다. 그리고 이 비밀번호는 대략 한달 정도 지나면 다른 비밀번호로 교체를 요구했습니다. 비밀번호 교체를 할 때에는 이전에 바꿨던 3개의 비밀번호는 사용할 수 없었습니다. 만약 비밀번호 입력시 다섯번이 틀리면 계정이 잠겨버리고 복잡한 비밀번호 초기화 작업을 해야했습니다.</p>

<p>이런 복잡한 비밀번호 규칙 때문에 대부분의 사람들은 비밀번호를 크롬의 비밀번호 저장 기능을 통해 저장해서 사용했고 그냥 대충대충 저장하고 까먹는 일이 다반사였구요.</p>

<p>하지만 크롬의 비밀번호 저장기능이 만능인 것도 아닌게, 사이트의 도메인이 엇비슷하고 리다이렉트 기능이 난무하다보니 이 비밀번호 기능도 사이트를 헷갈려하기 시작합니다. A 사이트인데 B 사이트의 비밀번호를 자동대입해주는 것이지요.</p>

<p>이렇게 비밀번호를 다섯번 틀려버리면 또 비밀번호 초기화 정책에 의하여 비밀번호를 다시 설정하라고 합니다. 그리고 저 복잡한 비밀번호 규칙을 지켜야하며, 직전 3개의 비밀번호는 또 쓸 수 없었습니다.</p>

<p>이렇게 복잡한 비밀번호 규칙을 통하여 과연 보안이 강력해졌을까?</p>

<h3 id="하지만-사람들은">하지만 사람들은</h3>

<p>결론적으로, 팀원들은 공용계정을 만들기로 했습니다. (물론 보안담당자는 공용계정을 허용하지 않습니다만 어차피 안 걸리면 그만입니다.)</p>

<p>모두가 쓰는 공용계정을 하나 만들고 비밀번호를 모두가 공유했습니다. 그리고 비밀번호는 한사람(보통 막내팀원이 맡겠죠.)이 전담해서 관리하고 모두가 비밀번호를 돌려씁니다.</p>

<p>보안 따위는 이제 없습니다. 누가 그 계정을 사용했는지 실제로 알 수도 없습니다.</p>

<p>뭐 문제가 터지면… 그때는 어떻게 되려나요? 하지만 다행히도 그런 일은 일어나진 않았습니다. 하하.</p>

<h3 id="과유불급">과유불급</h3>

<p>너무 과도한 보안정책은 사람들의 불편함을 초래합니다.</p>

<p>매일매일 사용해야하는 업무용 사이트를 한달에 한번씩 비밀번호를 바꾸게 하고 이전 비밀번호까지 계속 쓰지 못하게 하니 사람들이 계속 복잡한 비밀번호를 생성하여 외울 수는 없는 노릇입니다. 비밀번호를 잃어버리면 또 복잡한 절차가 기다리고 있죠.</p>

<p>적당한 비밀번호 정책을 만들어주고 IP 대역을 이용한 보안정책 등을 이용했어야 하는데 막무가내식으로 복잡하고 강력한 비밀번호 정책만 강요했습니다. (이 모든 사태가 보안인증 때문이라는 말도 있습니다.) 사람들은 그렇게 멍청하지 않습니다. 필요 이상의 복잡하고 불편한 것이 있다면 어떻게든 그것을 빠져나가고 부수려하지 거기에 순응하지 않기 마련입니다.</p>

<p>보안관리자는 저런 말도 안되는 정책을 받아들일 것으로 정말 기대했을까요?</p>]]></content><author><name>DONGBUM KIM</name></author><category term="DevStory" /><category term="Security" /><summary type="html"><![CDATA[강력한 비밀번호 정책]]></summary></entry><entry><title type="html">AWS Lightsail에서 PHP-FPM의 CPU 100% 폭주 현상</title><link href="https://dongbum.io/2021/03/24/aws-lightsail-php-fpm-overload" rel="alternate" type="text/html" title="AWS Lightsail에서 PHP-FPM의 CPU 100% 폭주 현상" /><published>2021-03-24T21:07:13+09:00</published><updated>2021-03-24T21:07:13+09:00</updated><id>https://dongbum.io/2021/03/24/aws-lightsail-php-fpm-overload</id><content type="html" xml:base="https://dongbum.io/2021/03/24/aws-lightsail-php-fpm-overload"><![CDATA[<p>신경 쓰지 않고 내버려두었던 AWS Lightsail에 구축해놓은 홈페이지 서버가 갑자기 마비가 되어버렸다. 접속이 아예 안되는 것은 아닌데 무지무지하게 느려졌다. SSH로 접속해보니 CPU가 100%인 상황.</p>

<p>CPU를 모두 사용하고 있는건 php-fpm이었다. 그런데 이상한건 접속자가 없는 상황에서도 php-fpm이 계속 CPU 점유율 100%를 쓰고 있었고 nginx를 꺼버렸는데도 계속 100%였다.</p>

<p>뭔가 이상한 상황. 서버를 재부팅해도 해결되지 않았고 인스턴스 자체를 restart해도 해결되지 않았다.</p>

<p>구글에 검색하다보니 다음의 문서를 발견했다.</p>

<ul>
  <li><a href="https://sarc.io/index.php/aws/1037-aws-lightsail-php-fpm-cpu">https://sarc.io/index.php/aws/1037-aws-lightsail-php-fpm-cpu</a></li>
</ul>

<p><strong>나와 동일한 케이스였는데 이 링크에서 설명한대로 Lightsail 콘솔에 들어가서 아예 stop하고 잠시 기다렸다가 start해주니 모든 것이 정상화되었다.</strong></p>

<p>이유도 알 수 없어서 조금 찝찝하지만 어쨌튼 완전히 종료하고 시작하는 것이 해결책이었다. 클라우드 환경에서는 이렇게 알 수 없는 문제들이 종종 생겨나고 단순히 인스턴스를 완전히 종료하고 재시작하는 것만으로 해결되는 경우가 종종 생겨나는 것 같다.</p>

<p><strong>2020년 4월 19일 추가</strong></p>

<p>위 재부팅만으로는 이 현상이 해결되지 않았다. 원인을 찾지 못한채 대략 일주일 정도의 시간이 흘렀고 어느날 갑자기 상태가 괜찮아졌다. AWS의 어떤 문제 때문에 생긴 일이었고 AWS에서 문제가 해결되었기 때문에 CPU 점유율이 정상적으로 돌아간 것으로 보여진다.</p>]]></content><author><name>DONGBUM KIM</name></author><category term="AWS" /><category term="Lightsail" /><category term="Amazon" /><category term="AWS" /><category term="Lightsail" /><category term="가상서버" /><category term="아마존" /><category term="클라우드" /><summary type="html"><![CDATA[신경 쓰지 않고 내버려두었던 AWS Lightsail에 구축해놓은 홈페이지 서버가 갑자기 마비가 되어버렸다. 접속이 아예 안되는 것은 아닌데 무지무지하게 느려졌다. SSH로 접속해보니 CPU가 100%인 상황.]]></summary></entry></feed>