스도쿠 판을 채우는 경우의 수

힌트 없는 스도쿠

최근 페이스북 타임라인에서 다음과 같은 스도쿠 퍼즐을 보았다.

  • 변끼리 인접한 두 칸에는 연속된 수가 올 수 없음.
  • 날일자로 떨어진 칸에는 같은 수가 올 수 없음.
  • 꼭지점끼리 인접한 두 칸에는 같은 수가 올 수 없음.
  • 스도쿠에 그려진 온도계에는, 볼록한 부분부터 수가 증가하는 순서로 적혀 있음.

(by Wei-Hwa Huang)

어떻게 풀지 감이 안 오는 이 퍼즐을 어떻게 만들었을까? 포스트의 댓글에 의하면, 다음과 같은 방법으로 위 퍼즐을 만들었다고 한다.

  • 날일자 조건과 꼭지점 조건을 만족하는 스도쿠는 사실상 유일함.
    • 대칭과 글자 치환을 생각하지 않으면 \(4 \times (9!)\) 가지 경우의 수가 존재함.
  • 연속된 수 조건을 추가하면 위의 9!가 18이 되어, 총 \(4 \times 18 = 72\) 가지 경우의 수가 존재함.
  • 남은 경우의 수를 1로 만들기 위해, 온도계를 추가함.

직접 풀어보기

퍼즐을 만든 방법을 이용하여, 다음과 같은 방법을 펜과 종이만 이용해 직접 풀 수 있을 것이다.

  • 첫 줄을 ABCDEFGHI로 고정하고, 날일자 조건과 꼭지점 조건을 만족하는 스도쿠 퍼즐을 풀어본다.
  • 변끼리 인접한 문자에는 연속된 수가 배정될 수 없다는 점과 온도계 조건을 이용해, 가능한 숫자 대입을 줄인다.

하지만 펜과 종이를 써서 직접 풀기에는 컴퓨터가 아까웠다(?). 그래서 한번 직접 스도쿠를 푸는 프로그램을 작성해 보았다. 여담으로, 나는 전에 간단하게 짜 볼 일이 한 번 있었다.

이 힌트 없는 스도쿠를 풀면서, 문득 이런 생각들을 하게 됐다.

  • 온도계가 좀 거추장스러운데…
  • 온도계 말고 다른 제약 조건을 넣어서, 깔끔한 힌트 없는 스도쿠를 만들 수 없을까?
  • 어떻게 하던지간에 프로그램을 돌려서 유일해라는 걸 증명해야겠지?
  • … 그러고보니 평범한 스도쿠는 유일해가 보장되려면 최소 17개의 힌트가 필요하다던데.

문득 2012년에 증명된, “스도쿠의 힌트가 최소 17개여야 유일해가 보장된다”라는 사실이 떠올랐다. 그리고 그걸 어떻게 증명할 수 있었는지 궁금해졌다.

힌트가 16개인 스도쿠

참고 논문: “There is no 16-Clue Sudoku: Solving the Sudoku Minimum Number of Clues Problem via Hitting Set Enumeration”

그 사실을 증명한 논문은 상당히 길었지만 읽을 만한 논문이였고, 대략적인 방법은 의외로 간단했다.

  1. 스도쿠 판을 채우는 모든 각각의 경우의 수에 대해,
  2. 힌트가 16개인 스도쿠 퍼즐을 만들고,
  3. 모든 퍼즐을 직접 풀어보아 해가 유일하지 않음을 증명한다.

위 작업을 하는데 대략 1년이 걸렸고, CPU 코어 하나로 모든 경우의 수를 살펴봤다면 대략 800년이 걸릴 정도의 연산을 했다고 한다. 또한, 스도쿠 판을 채우는 한 가지 경우에 대해 힌트가 16개인 모든 퍼즐을 살펴보는데 평균 3.6초가 걸렸다고 한다. 자연스럽게 두 가지 의문점이 생겼다.

  1. 스도쿠 판을 채우는 한 가지 경우에 대해, 대체 어떻게 3.6초만에 모든 퍼즐을 만들고 풀 수 있었을까?
    • 단순히 모든 퍼즐을 생성하는 경우의 수는 \( {81 \choose 16} \simeq 3.4 \times 10^{16} \)가지로, 지나치게 많다.
  2. 대체 어떻게 스도쿠 판을 채우는 모든 경우에 대해 조사를 했을까?

첫 번째 질문에 대한 답은 유일해가 존재할 가능성이 있는 퍼즐만을 고려해, 생성된 퍼즐의 개수를 최소화했다는 것이다. 구체적인 개수는 논문에 적혀있지 않지만, 논문에 사용된 스도쿠 솔버가 1초에 5만개의 퍼즐을 처리할 수 있었다고 언급되어 있으므로, 한 가지 스도쿠 해답에 대해 겨우 20만개 정도의 퍼즐을 만들었다는 것을 알 수 있다. 어떻게 하면 효율적으로 퍼즐을 생성할 수 있는지에 대해서는 나중에 살펴보기로 하자.

두 번째 질문에 대한 답은 간단한데, 스도쿠 판을 채우는 경우의 수 중에서 “필수적으로 고려해야 되는” 경우의 수는 생각보다 적기 때문이다. 논문에는 다음과 같은 사실이 명시되어 있다.

  • 스도쿠 판을 채우는 경우의 수는 총 \( 6,670,903,752,021,072,936,960 \simeq 6.7 \times 10^{21} \)가지이다.
  • 하지만 이 경우를 모두 살펴볼 필요는 없다. 스도쿠 퍼즐의 최소 힌트의 개수는 다음과 같은 변환을 해도 유지되기 때문이다.
    • 1-9의 숫자들을 다른 값으로 변경
    • 퍼즐을 뒤집거나 돌림
    • 3개씩 묶인 열/행에 대해, 묶음을 재배열
    • 각 묶음 안의 줄을 재배열
  • 이런 경우들을 제외하고, 필수로 살펴보아야 되는 스도쿠 해답의 경우의 수는 총 \( 5,472,730,538 \simeq 5.5 \times 10^{9} \)가지이다.
    • 모든 경우의 수를 압축하지 않고 저장하면 418GB정도 된다.
    • 적절한 압축을 하고 저장하면 6GB 미만의 공간을 차지한다.

저걸 보니, 저 숫자들은 또 어떻게 알아낸건지 궁금해졌고, 이 페이지에서 그 방법을 찾을 수 있었다. 이 블로그 포스트에서는 스도쿠 해답의 경우의 수인 \( 6.7 \times 10^{21} \)를 어떻게 계산할 수 있는지 알아보자. (여담인데, 노가다가 아닌 방법으로 경우의 수를 구하는 건 언제나 흥미롭다고 생각한다.)

스도쿠 판을 채우는 경우의 수

참고 논문: “Mathematics of Sudoku I”

만약 1초에 1억개의 스도쿠 해답을 본다면, 모든 스도쿠 해답을 보는 데에는 대략 210만년의 시간이 걸린다. 즉, 단순히 모든 해답을 살펴보는 방식으로는 스도쿠 해답의 경우의 수를 계산하는 것은 불가능하다는 뜻이다. 맨 왼쪽 위 블록을 고정한다고 해도, 6년이라는 시간이 걸리므로 이보다는 더 나은 방법이 사용했을 것이다.

논문에서 서술한, 스도쿠 판을 채우는 모든 경우의 수를 세는 방법은 생각보다는 의외로 간단했다.

  1. 맨 왼쪽 위 블록(3x3 칸 묶음)의 숫자를 고정한다. (경우의 수가 9!배 줄어든다.)
  2. 첫 세 줄을 채우는 경우의 수에 대해,
  3. 나머지 여섯 줄을 채우는 경우의 수를 구한다.

여기서 2번 과정과 3번 과정을 무식하게 바로 진행하면 사실상 (맨 왼쪽 위 블록이 고정된) 모든 경우의 수를 세어 보는 것과 같은데, 실제로는 조금 머리를 굴려 직접 세야 될 경우의 수를 줄일 수 있다.

첫 세 줄 채우기

우선 맨 왼쪽 위 블록을 다음과 같이 고정하자.

123|...|...
456|...|...
789|...|...

첫 세 줄을 채우는 경우의 수는, 다음과 같이 계산할 수 있다.

  • 가운데 블록의 첫 줄 세 숫자와, 오른쪽 블록의 첫 줄 세 숫자를 (순서 고려하지 않고) 정하는 경우의 수는 \( {6 \choose 3} = 20 \)가지이다.
  • 가운데 블록의 첫 줄 세 숫자가 456인 경우, 나머지 칸의 숫자는 (같은 블록과 줄 안에서는 순서를 고려하지 않고) 다음과 같이 정해진다.
    • 가운데 블록의 마지막 줄은 123이며, 가운데 줄은 789이다.
    • 오른쪽 블록의 세 줄의 숫자들은 위부터 789, 123, 456이다.
    • 순서를 고려하게 되면,두 블록을 채워넣는 경우의 수는 \( (3!)^6 \)가지이다.
    • 가운데 블록의 첫 줄 세 숫자가 789인 경우에도 경우의 수는 동일하다.
  • 가운데 블록의 첫 줄 세 숫자가 457인 경우, 나머지 칸의 숫자는 (역시 같은 블록과 줄 안에서는 순서를 고려하지 않고) 다음과 같이 정해진다.
    • 가운데 블록에서 6은 마지막 줄에, 8과 9는 가운데 줄에 들어갈 수 있다.
    • 가운데 블록 가운데 줄의 나머지 숫자를 a라고 하자. (a는 1, 2, 3 중 하나가 될 수 있다.)
    • 가운데 블록의 가운데 줄은 89a, 마지막 줄은 6bc이다. (b, c는 1, 2, 3중 a가 아닌 다른 두 숫자이다.)
    • 오른쪽 블록의 세 줄의 숫자들은 위부터 689, 7bc, 45a이다.
    • 순서를 고려하게 되면, a가 정해져 있을 때 두 블록을 채워넣는 경우의 수는 \( (3!)^6 \)가지이다.
    • a를 고르는 경우의 수는 3이므로, 두 블록을 채워놓는 총 경우의 수는 \( 3 \times (3!)^6 \)가지이다.
    • 이 경우의 수는 가운데 블록의 첫 줄 세 숫자중 한 개 혹은 두 개가 456 중에 있고, 나머지 숫자가 789 중에 있을 때에는 같다.

즉, 왼쪽 위 블록을 고정한 상태로 스도쿠 퍼즐의 첫 세 줄을 채우는 총 경우의 수는 다음과 같다. \[ 2 \times (3!)^6 + 18 \times (3 \times (3!)^6) = 2612736 \]

왼쪽 위 블록이 고정되지 않았다면, 위 숫자에 9!을 곱한 948109639680가지 경우의 수가 가능하다.

260여만 가지 경우의 수를 단순히 돌아보는 것은 쉽게 가능하지만, 각각의 경우에 대해 나머지 여섯 줄을 채우는 경우의 수를 구하기에는 너무 많다. 다만, 다음과 같은 경우 나머지 여섯 줄을 채우는 경우의 수가 변하지 않기 때문에 이런 경우 굳이 다시 셀 필요는 없다는 것을 알 수 있다.

  • 가운데 블록과 오른쪽 블록을 바꾼다.
  • 가운데 블록, 또는 오른쪽 블록의 각 세 열을 교환한다.

이런 식으로 경우의 수를 굳이 다시 고려하지 않아도 되는 경우를 추려내보자.

첫 번째 줄 정렬하기

우선 방금 언급한 것을 고려하여 다음과 같은 경우만 고려한다 해 보자.

  • 가운데 블록의 왼쪽 위 숫자가 오른쪽 블록의 왼쪽 위 숫자보다 작아야 된다. (그렇지 않다면 두 블록을 바꾼다.)
  • 각 블록에 대해, 첫 줄은 오름차순으로 정렬되어 있어야 한다. (그렇지 않다면 열들을 재배열한다.)

이렇게 정렬하는 경우, 총 경우의 수는 \( 2\times(3!)^2 = 72 \)배만큼 줄어들어 36288가지가 된다. 이 정도면 각 경우에 대해 스도쿠 판을 채우는 경우의 수를 구해볼 수도 있을 것 같다.

한 번, 36288가지 경우를 나열하고 그 중 한 가지 경우에 대해 스도쿠 판을 채우는 경우의 수를 구해보자.

그런데, 한 가지 경우에 대해 5분이 걸린다면, 36288가지 경우에 대한 경우의 수를 모두 계산하려면 대략 4달이 걸리게 된다. 계산을 하기에 불가능한 시간은 아니지만 여전히 너무 긴데, 어떻게 하면 이 시간을 줄일 수 있을까?

행과 열 재배열하기

3x9를 채우는 36288가지 경우의 수에 대해, 다음과 같은 변환을 거쳐도 나머지 6x9를 채우는 경우의 수는 변하지 않는다.

  1. 1-9까지 숫자를 다르게 적기
  2. 세 블록의 순서 바꾸기
  3. 세 블록 안 각각의 행을 재배치하기
  4. 세 행을 재배치하기

이 중에서 왼쪽 블록을 건드리지 않는 변환은 이미 위에서 다뤘는데, 왼쪽 블록을 건드리는 변환도 사실 유용하게 쓸 수 있다. 논문에 있는 예제를 살펴보자.

123|458|679
456|179|238
789|236|145

여기서 첫 번째 블록의 열의 순서를 바꿨다고 해 보자.

213|458|679
546|179|238
879|236|145

이 다음 왼쪽 블록의 숫자를 다시 1-9로 치환한다.

123|547|689
456|289|137
789|136|254

이렇게 하면 첫 번째 블록은 원래대로 바뀌었긴 했지만, 첫 번째 행이 정리되지 않았기 때문에 앞서 고려했던 36288가지 경우의 수에는 들어가지 않는다. 열들을 재배치하여 오름차순이 되게 해 보자. (이 과정을 “정규화”한다라고 하자.)

123|457|689
456|829|137
789|316|254

처음 상태와 다른 상태가 되었지만 해답의 개수는 변하지 않았다. 즉, 처음 상태의 해답의 개수를 계산했다면 이 상태의 해답의 개수는 굳이 계산하지 않아도 되는 것이다.

한번 이런 식으로 모든 행/열 재배치를 고려하게 되면 경우의 수가 얼마나 줄어드는지 알아보는 코드를 짜 보자.

이렇게 하면 고려할 경우의 수가 줄어드는 것을 확인할 수 있는데, 논문과 일치하지 않는 숫자가 출력된다.

  • 블록 재배열을 고려하는 경우, 논문에서는 2051가지 경우의 수로 줄어든다고 적혀 있지만, 실제로 계산된 값은 2081이다.
  • 행 재배열까지 고려하는 경우, 논문에서는 416가지 경우의 수로 줄어든다고 적혀 있지만, 실제로 계산된 값은 426이다.

논문의 오타거나 코드가 잘못됐거나 둘 중 한 가지일텐데, 우선 스도쿠 판을 채울 수 있는 총 경우의 수에는 영향이 없다 가정해 보자. 426가지 경우 각각에 대해 스도쿠를 채울 수 있는 경우의 수를 모두 구하면 대략 하루하고도 반나절 정도 걸린다. 이 시간을 더 줄일 수 있을까?

각 숫자의 열 분포 고려하기

스도쿠의 위 세 줄을 채웠을 때, 아래 여섯 줄을 채우는 경우의 수는 단순히 말하면 단 한가지에 달려 있다. “각 열에 어떤 숫자들이 있는가?” 그렇다면, 다음과 같은 알고리즘으로 해답의 개수가 동일한 경우를 더 찾을 수 있을 것이다.

  1. 위에서 구한 426가지 경우 각각에 대해,
  2. 블록 및 블록 안의 열들을 재배치하고,
  3. 숫자들을 다른 숫자들로 치환하여,
  4. 다른 425가지 경우와 각 열에 오는 수가 같은 경우가 존재하는지 확인한다.

2번 과정의 경우의 수는 \( (3!)^4 = 1296 \)가지밖에 되지 않지만, 3번 과정의 경우의 수는 \( 9! = 362880 \)가지로 조금 많은 듯 하다. 이 때문인지, 논문에서는 이렇게 하는 대신 특정한 직사각형 패턴을 찾아 변경하는 방식으로 동일한 경우를 찾았다.

하지만 조금만 생각해 보면 사실 3번 과정 대신 훨씬 간단한 방식으로 위 알고리즘을 만들 수 있다는 것을 알 수 있게 된다. 즉, 이론상으로도 구현상으로도 논문에서 언급된 방식보다 간단한 방식이 있는 셈이다. 3번 과정을 어떻게 하면 피할 수 있을까?

그 방법은 바로 “각 행에 어느 숫자가 있는지” 대신 “각 숫자가 어느 행에 있는지”를 고려하는 것이다. 예를 들어 다음 세 행에 대해 해답의 개수가 동일한 경우를 더 찾는다고 해 보자.

123|458|679
456|179|238
789|236|145

여기서 각 숫자가 어느 행에 있는지, 1부터 9까지의 숫자에 대해 적으면 다음과 같다.

147 247 358 148 259 367 158 269 369

여기서 첫 세 행의 1과 5를 바꿔 적는다고 하면, 위에서 147259가 뒤바뀌게 된다. 하지만 나머지에는 영향을 주지 않으며, 따라서 전체 집합에는 변화가 없다.

집합이 동일한 경우라면 나머지 6x9 칸을 채우는 경우의 수가 동일함을 쉽게 확인할 수 있다. 즉, 3x9를 채워넣는 두 경우의 수에 대해, “숫자를 치환했을 때 각 행에 오게 되는 숫자를 동일하게 만들 수 있는지”를 판단하기 위해서는, 단순히 두 경우에 대해 위와 같이 각 숫자가 어느 행에 있는지에 대한 집합을 만든 다음, 두 집합이 동일한지 판단하면 된다.

다만 블록이나 블록 안의 열들을 재배치 하는 경우에는 위 집합이 달라지지만 나머지 6x9를 채우는 경우의 수에는 변함이 없다. 즉, 위 알고리즘의 2번 과정은 유지한 채로 3번 과정 대신 위와 같은 집합을 만드는 과정을 넣어야 한다.

한 번 구현해 보자.

구현 결과, 논문과 동일하게 경우의 수가 44가지로 줄어들고, 각 경우에 대응되는 원래 경우의 수들이 일치하는 것을 확인할 수 있다. 이 44가지 경우에 대해 스도쿠 판의 나머지를 채우는 경우의 수를 구하면 이제 4시간 좀 안 되는 시간이 걸리게 된다.

잠깐만, 그런데 이 아이디어를 6x9의 왼쪽 두 블록을 채울 때에도 적용할 수 있지 않을까? 아이디어는 괜찮지만, 불행히도 경우의 수가 단순히 44가지로 줄어들지는 않는다.

왼쪽 두 블록을 채우는 한 가지 경우에 대해 주대각선(왼쪽 위에서 오른쪽 아래로 향하는 대각선)을 따라 뒤집어 보자. 그 결과는 위쪽 세 블록을 채우는 한 가지 경우가 되지만, 왼쪽 세 열 전체(9x3)가 이미 채워져 있다는 중요한 차이가 있다.

즉, 다음과 같은 변환은 왼쪽 두 블록을 채우는 경우의 수를 줄일 때 적용할 수 없다.

  • 숫자를 다른 숫자로 치환하기
  • 왼쪽 블록을 건드리면서 열 또는 블록을 재배치하기

위 경우를 제외해도 왼쪽 두 블록을 채우는 경우의 수가 줄어들기는 하지만, 불행히도 많이 줄어들지는 못한다. 얼마나 줄어들게 되는지 직접 확인해 보자.

즉, 왼쪽 두 블록을 채우는 경우의 수가 36288가지에서 22266가지로 조금 줄어들 뿐이다. 다만 스도쿠 퍼즐을 푸는 경우의 수를 구하는 데 걸리는 시간이 2시간에서 조금 더 되는 정도로 줄어들게 되긴 한다.

논문에서도 Guenter Stertenbrink, Kjell Fredrik Pettersen 두 사람이 전체 경우의 수를 1초 안에 셀 수 있는 방법을 개발했다고 서술되어 있다. 어떻게 하면 여기서 계산 시간을 더 줄일 수 있을까?

다섯 블록 한 번에 고려하기

위에서 언급한 숫자를 잘 생각해 보면, 다섯 블록을 채우는 경우의 수가 \( 44 \times 22266 = 979704 \)가지 있다는 사실을 깨달을 수 있다. 간단하게 얻을 수 있는 경우의 수인 \( 36288^2 = 1316818944 \)가지보다 훨씬 적은 값이 된 것이다.

여기서 한 가지 아이디어를 떠올릴 수 있다. 나머지 4개 블록을 채우는 경우의 수는, 6개 행과 열에서 사용된 숫자가 동일하다면 동일하다. 즉, 앞에서 행별 숫자를 고려했던 것처럼 5개 블록에 대해 행/열별 숫자 배치가 동일한 경우를 찾아 줄일 수 있지 않을까?

이렇게 바로 진행해 볼 수 있지만, 한 번 위에서 언급된 셈법도 같은 아이디어를 이용해 셌는지 궁금해서 찾아봤다. 조금 구글링을 해 보니 30ms만에 스도쿠 경우의 수를 구할 수 있는 방법에 대해 서술한 포스트를 찾을 수 있었다.

포스트에 적힌 방법을 요약하면 다음과 같다. 블록의 번호는 왼쪽 위부터 글을 읽는 순서대로 1, 2, 3…번이라고 하자.

  1. 2, 3번 블록의 열, 4, 7번 블록의 행에 어떤 숫자가 올지를 고정한다.
  2. 2, 3, 4, 7번 블록과 5, 6, 8, 9번 블록 배치를 독립적으로 생성한다.
  3. 생성된 두 배치를 합친다.

지금까지 진행한 방법에 어떻게 저 과정을 적용하면 될지 생각해보자.

  1. 1, 2, 3번 블록을 채우는 경우의 수 44가지를 찾는다.
  2. 1, 2, 3, 4, 7번 블록을 채우는 경우의 수 대략 백만 가지를 찾는다.
  3. 2에서 찾은 각 배치별로 4-9번째 행과 4-9번째 열에 어떤 숫자 조합이 올 수 있는지를 기록한다.
  4. 5, 6, 8, 9번 블록을 채우는 경우의 수 ??가지를 찾는다.
  5. 4에서 찾은 각 배치에 대해 2의 조합 중 남은 숫자 조합이 ‘호환되는’ 경우를 센다.

이 과정을 직접 구현해 볼 수도 있겠지만, 이것까지 구현하려면 블로그 글도 지나치게 길어질 것 같은데다가, 구현하는 데 걸리는 시간이 계산하는 데 걸리는 시간보다 더 길어질 것 같으므로 넘어가기로 하자.

결과

다음 코드를 실행시키면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
console.log("Step 1: 위 3x9, 왼쪽 9x3 미리 계산하기");
console.time('Step 1');
const topRowsList = Array.from(reducedFirstThreeRows(false));
const leftColsList = Array.from(reducedFirstThreeRows(true));
console.timeEnd('Step 1');

console.log(`줄어든 경우의 수: 위쪽 ${topRowsList.length}가지, 왼쪽 ${leftColsList.length}가지`);

console.log("Step 2: 첫 세 행을 채우는 모든 경우에 대해 나머지를 완성시키는 경우의 수 구하기");
console.time('Step 2');
let count = BigInt(0);
for(let [dup, arr] of topRowsList) {
const partialCount = BigInt(count6x9(arr, leftColsList));
console.log(`- 크기가 ${dup}인 동치류: ${partialCount}`);
count += BigInt(dup) * partialCount;
}

// 위 세 줄을 채우는 동일한 72*9!가지 경우의 수를 고려해야 한다.
count *= BigInt(72);
for(let i=2; i<=9; ++i) count *= BigInt(i);
console.timeEnd('Step 2');

console.log(`스도쿠 판을 완성시키는 경우의 수: ${count}`);

위 코드를 실행하면 대략 두 시간 뒤 스도쿠 판을 완성시키는 경우의 수가 6670903752021072936960가지라는 것을 계산할 수 있다.

다음 시간에는 이 경우의 수를 5472730538가지로 줄이는 방법에 대해 알아보자.

사이트 다시 만들기

요즘 0xF.kr 사이트를 다시 만들고 있다.

기존 웹 서버

나는 초등학생때부터 무료 웹 호스팅 업체를 통해 개인 홈페이지를 만들어 왔다. 하지만 무료에는 그만한 댓가가 따르기 마련이였다.

  • 대부분의 무료 웹 호스팅 업체에서는 용량이나 총 트래픽 제한을 걸어놓는다.
  • 제한이 널널한 외국 호스팅 업체들은 네트워크 속도가 느리기 일쑤였다.
  • 웹 서버에서 구동할 수 있는 프로그램이나, 설정 가능한 요소들이 제한되어 있다.

그래서, 지난 2012년인가 2013년 즈음에, 집에서 쓰던 오래 된 컴퓨터를 동아리방에 가져다 놓았다. 그리고 그 컴퓨터에 Debian을 깔고, NGINX 및 MariaDB등을 이용해 개인 홈페이지를 구축했다. 원하는 대로 서버를 굴리면서도 유지비용이 없어서 좋기는 한데, 서버가 낡아 성능이 좋지 못한것이 흠이였다. 뭐, 웹 호스팅에 그다지 높은 성능이 필요한 건 아니니까 큰 문제는 아니였고, 계속 시간이 날 때마다 홈페이지에 기능을 추가했다.

그러던 와중, 심심해서 만들어 보았던 끝말잇기 게임이 많은 인기를 끌게 되었고, 덕분에 서버가 매일 바쁘게 돌아갔다. 거의 항상 1초에 10건 정도의 요청을 처리하던 수준이였는데, 다행히도 서버의 다른 기능에 큰 영향을 끼치는 부하는 아니였다…라고 생각했었는데, 문제가 하나 생겼다. DB 데이터가 손상되어버린 것이다.

서버의 낡은 하드 디스크가 오랜 동작 끝에 수명을 다하기 시작했다는 신호였다. 비록 당장은 서버가 제 기능을 하더라도, 근 시일 내에 서버에 어떤 문제가 생길지도 모르는 것이였다. 하드디스크를 하나 사서 교체할 수도 있지만, 이왕 이렇게 된 거 새 컴퓨터를 하나 장만하기로 하고, 기존 서버를 내렸다. … 그리고 새 서버를 어떻게 장만해야 할 지 고민하다 몇 년이 지났고, 결국 다시 호스팅 업체를 이용하기로 결정했다. 웹 호스팅이 아닌 클라우드 서버 호스팅을.

새 웹 서버

이왕 클라우드 서버를 이용하게 됐으니, 기존 코드를 갈아엎고 새로 만들기로 했다.

  • 우선, 적당한 AWS 인스턴스 하나를 마련했다.
  • 다음과 같이 웹 백엔드를 구성했다.
    • NGINX로 라우팅을 한다.
    • 서버가 필요없는 웹 앱은 그냥 NGINX로 서빙한다.
      • 이 블로그의 경우 Hexo를 이용해 정적으로 구현했다.
    • 기존에 PHP로 짰던, 서버가 필요한 앱은 앱별로 별도의 백엔드를 띄웠다.
      • Express(또는 그냥 Node.js) 등을 이용해 서버를 만들고, NGINX 설정을 통해 노출시켰다.
  • (모양새를 갖추려고) CloudFlare를 앞에다 붙였다.

퍼시스턴스는 어떻게 해결할지 고민중인데, 당분간은 기존처럼 웹 서버가 돌아가는 인스턴스에 세팅해 두려 한다. 나중에 별도로 분리해야 될 때가 오면 그 때 하지 뭐.

기본적인 틀을 짠 뒤, 그동안 묵혀두고 있었던 메인 페이지 리뉴얼을 했다. 지금은 새 메인 페이지가 완성된 상태이지만, 아직 메뉴에 넣을 요소가 많이 없다는 것을 깨닫고 새 사이트에 채워넣을 것들을 만드는 중이다.

  • 해시 모음집 (완성): 기존의 MD5 해시 모음집을 확장했다.
  • 문자열 계산기 (제작중) : 한 번 배워보기 위해 Dart로 제작중이다.
  • 끝말잇기 게임 (구상중) : 기존 구현에 문제가 많았는데, 기존의 문제점들을 개선한 버전을 새로 만들어 보려 한다.
  • BPM 계산기 (제작중)
  • HEX 파일 뷰어 (구상중)

개개의 앱들에 대해서는 나중에 글을 써 볼 생각이다.