PostgreSQL에서는 동시성 제어를 위해 여러가지 모드의 lock을 제공한다.

이런 lock에도 여러가지 종류가 있고, 명시적으로 사용되는 경우/묵시적으로 사용되는 경우가 있는데, 간단하게 알아보자

시작하기 전 3줄요약

  1. Lock이 미치는 범위를 level로 나눈다
  2. Lock 모드별로 충돌하는 관계가 존재한다
  3. Lock은 트랜잭션 종료 시 혹은 롤백시에 풀린다.

lock이 미치는 범위를 Level로 나누는데, Table-Level Lock, Row-Level Lock, Page-Level Lock, Database-Level Lock 까지 다양하게 존재한다.

Table-Level Lock

Table Level Lock은 테이블 수준에 락을 거는 방법이다.

만약 테이블 내에 100개의 로우가 있다고 하면 하나의 로우에 접근하는 동안 나머지 99개의 로우에 접근 할 수 없기 때문에, 다중 사용자 환경에서는 사용하지 않는 편이 좋다.

(보통 테이블 전체 로우의 변경이 있는 DDL 구문과 함께 사용됨. (ex) TRUNCATE, ALTER))

 

이런 Table Level Lock은 LOCK 명령어를 이용해서 명시적으로 걸어줄 수도 있지만,
우리가 특정 쿼리문을 사용할 때 마다 묵시적으로 걸리게 된다. 어느 상황에 어느 락이 걸리는지는 해당 문서를 보면 알 수 있다.

Row-Level Lock

Row-Level Lock은 row 수준에 락을 거는 방법이다.

SELECT ~ FOR SHARE과 같은 DML 구문과 함께 가장 자주 사용되는 Lock이다.


만약 이런 Lock의 종류가 하나밖에 없다면, 여러 종류의 트랜잭션에서 lock을 알맞게 사용하기 어려워지는데,
이런 상황을 처리하기 위해 Lock에는 Lock모드라는 것이 존재한다.

Lock 모드?

Lock 모드 별로 서로 충돌하는 Lock 모드가 있으며, 충돌한다면 해당 리소스(table, row)에 동시에 접근할 수 없게 된다다.

  • ACCESS SHARE락은 ACCESS EXCLUSIVE 락과 충돌한다,
  • ROW SHARE락은 EXCLUSIVE, ACCESS EXCLUSIVE 락과 충돌한다

Table-Level Lock에는ACCESS SHARE, ROW SHARE 등,
Row-Level Lock에는 FOR UPDATE, FOR KEY SHARE 등 여러가지 Lock 모드가 존재한다.
이런 모드들의 이름은 일반적으로 사용되는 경우를 나타내지만, 모드마다 기능적으로 다른 점은 없다.

각 모드마다 차이점은 오직 "어떤 모드의 lock과 충돌하는가" 이다.

이런 lock 모드별 충돌되는 경우는 아래의 표를 보고 확인할 수 있다.

Table-Level Lock Mode

Row-Level Lock Mode


Ref

Chapter 13. Concurrency Control

엘리스의 통합검색 기능을 개선하여 유저가 원하는 결과를 내도록 개선한 경험이다.

https://academy.elice.io 여기서 써볼수있다

유저가 겪는 문제

대부분의 유저는 이름이 "LangChain~"으로 시작하는 상품을 검색하기 위해 보통 "Lang"까지만 검색해본다.

하지만 우리의 통합검색 기능은 "Lang"을 입력했을때 "LangChain~" 상품을 보여주지 않았다.

 

왜 이런 현상이 일어나는 것일까? elasticsearch의 analyzer 동작 원리를 잠깐 살펴보고 가자.

 

elasticsearch analyzer 동작 원리

elasticsearch는 문자열이 들어오면 analyzer를 통해 character filter -> tokenizer -> token filter 순서로 전처리를 한다

 

예를 들어  "<strong>LangChain</strong>으로 나만의 업무" 라는 문장이 있다고 가정해보자

 

먼저 character filter는 토큰화 이전에 문자열을 가공하는 역할을 한다.

character filter는 문자열을 어떻게 정리할 것인지에 따라 HTML 태그 제거, 패턴 기반 치환등 여러 종류가 있다.

만약 HTML 태그 제거 character filter를 사용한다면 "LangChain으로 나만의 업무" 와 같은 문자열로 변환된다.

 

다음으로 tokenizer는 문자열을 토큰화(분리)하는 역할을 한다.

tokenizer는 문장을 어떻게 나눌 것인지에 따라 공백 기반 분할, 패턴 기반 분할 등 여러 종류가 있다

공백 기반 Tokenizer를 사용한다면 ["LangChain으로", "나만의", "업무"] 와 같이 문자열을 분리한다.

 

마지막으로 token filter는 토큰화된 문자들을 다시 가공하는 역할을 한다.

token filter 또한 문자열을 어떻게 정리할 것인지에 따라 대소문자 변환, 중복 제거 등 여러 종류가 있다 

대소문자 변환 token filter를 사용한다면 ["langchain으로", "나만의", "업무"] 와 같이 가공한다.

 

이처럼 analyzer는 원본 텍스트를 검색엔진(elasticsearch)이 이해할 수 있는 형태로 변환하는 역할을 한다.

 

문제 원인

다시 유저가 겪던 문제로 돌아가서 원인을 파악해보았는데, 아래와 같았다

  • 현재 공백 기준으로 토큰화하는 tokenizer를 사용하고 있다 (standard tokenizer)
  • fuzziness 허용 편집거리는 1이다 (오타 1개까지 허용)
    • 즉 "Lang_" 까지 편집거리를 허용해주는데,
      실제 저장된 token인 "LangChain으로"는 편집거리가 훨씬 커서 매칭되지 않는다

해결방법

요구사항에 맞는 적절한 analyzer를 생성하여 해결하였다

{
  "analyzer": {
    "camel_analyzer": {
      "filter": ["lowercase"],
      "char_filter": ["camel_filter"],
      "tokenizer": "nori_mixed"
    }
  },
  "char_filter": {
    "camel_filter": {  // 1
      "pattern": "(?<=\\p{Lower})(?=\\p{Upper})",
      "type": "pattern_replace",
      "replacement": " "
    }
  },
  "tokenizer": {
    "nori_mixed": {  // 2
      "type": "nori_tokenizer",
      "decompound_mode": "mixed"
    }
  }
}
  1. CamelCase character filter 를 적용해 입력을 전처리 한다
    • ["LangChain으로 나만의 업무"] → ["lang chain으로 나만의 업무"]
  2. 전처리 결과에 nori_tokenizer를 적용해 조사를 분리한다
    • ["lang chain으로 나만의 업무"] → ["lang", "chain", "으로", "나", "만", "의", "업무"]

위 analyzer를 index에 적용하고, reindex한 후에 "lang"으로 검색하면,

["lang", "chain", "으로", "나", "만", "의", "업무"]에 lang이 포함되어 있으므로 가중치가 높게 계산될 것이다.

결과

의도한대로 "Lang"으로 검색하면 "LangChain~"이름을 가진 상품이 잘 나온다. 👍

 

 

Ref.

'DB' 카테고리의 다른 글

[PostgreSQL] Lock에 대해  (0) 2025.11.02
[PostgreSQL] 운영중인 DB 조용히 갈아끼우기  (0) 2025.07.27
[DB] MongoDB 장점  (0) 2022.06.01
[DB] MongoDB 자료구조  (0) 2022.06.01
[DB] Lock 이해하기  (0) 2022.05.20

 

회사의 모든 데이터베이스를 CNPG로 일원화하고자 하는 정책에 따라, 현재 운영 중인 Azure PostgreSQL 서비스를 자체 관리형 CloudNativePG(CNPG) 클러스터로 마이그레이션해야 하는 상황이 되었다.

 

만약 운영 서비스와 연결되지 않은 데이터베이스였다면 단순히 dump & restore 방식으로 쉽게 해결할 수 있었겠지만, 실제 서비스가 연결되어 있어 다른 접근 전략이 필요했다.

 

팀 내에서도 실시간으로 운영 중인 데이터베이스를 마이그레이션해본 경험이 없었기 때문에 팀원들과 충분히 논의하며 마이그레이션 계획을 수립했다.

 

검토 결과 두 가지 선택지가 있었다.

  • 무중단 마이그레이션 (복제 지연 위험 존재)
  • 다운타임 허용 마이그레이션 (데이터 정합성 보장)

우리 서비스의 특성상 교육 시간대만 피하고 사전 공지를 한다면 짧은 다운타임이 발생해도 비즈니스에 큰 영향이 없었기 때문에, 데이터 정합성을 우선시하는 방향으로 결정했다.

마이그레이션 순서

다운타임 허용 마이그레이션 순서는 아래와 같이 정리했다

  • DB 실시간 복제 실행 → 기존 앱 중단 → 데이터 정합성 확인 → 새로운 앱 배포

마이그레이션 방식 선택

마이그레이션 방식으로는 CDC와 Logical Replication 두 가지를 검토했다.

두 방식 모두 내부적으로 WAL(Write Ahead Logging)을 사용한다는 공통점이 있지만, CDC는 Kafka와 같은 추가 이벤트 스트림 컴포넌트가 필요하다는 차이점이 있다.

  • CDC: Source DB → WAL → Debezium → Kafka → Target(PostgreSQL, Elasticsearch 등)
  • Logical Replication: Source DB → WAL → Target DB

이번 케이스에서는 Debezium, Kafka 등의 추가 컴포넌트 설정이 불필요하고, 동일한 PostgreSQL 간의 단순 마이그레이션이므로 Logical Replication을 선택했다.

도구 선택

Logical Replication 구현을 위해 별도의 서드파티 도구 없이 PostgreSQL 내장 기능만으로 동작하는 pgcopydb(https://github.com/dimitri/pgcopydb)를 선택했다.


테스트 환경 세팅

가장 먼저 개발서버에서 사전 테스트를 진행하기 위해 운영환경과 동일한 환경을 세팅하였다

docker compose로 진행했으며, source/target DB에 대해 postgresql.conf를 적절히 작성하였다

version: '3'
services:
    postgres_source:
        image: postgres:13
        environment:
            - POSTGRES_DB=source-db
            - POSTGRES_USER=hanbin
            - POSTGRES_PASSWORD=thisispassword
        volumes:
            - postgres_13-data:/var/lib/postgresql/data
            - ./postgres/postgresql.conf:/etc/postgresql/postgresql.conf:ro
        ports:
            - "127.0.0.1:5432:5432"
        shm_size: 1g
        command: [
            "-c", "config_file=/etc/postgresql/postgresql.conf",
         ]
    postgres_target:
        image: postgres:16
        environment:
            - POSTGRES_DB=target-db
            - POSTGRES_USER=hanbin
            - POSTGRES_PASSWORD=thisispassword
        volumes:
            - postgres_16-data:/var/lib/postgresql/data
            - ./postgres/postgresql.conf:/etc/postgresql/postgresql.conf:ro
        ports:
            - "127.0.0.1:5433:5432"
        shm_size: 1g
        command: [
            "-c", "config_file=/etc/postgresql/postgresql.conf",
        ]
volumes:
    postgres_source-data:
        driver: local
    postgres_target-data:
        driver: local

 


테스트 순서

자세한 순서는 아래와 같다

1. Bastion VM pgcopydb 설치 및 연결 테스트
2. Source DB유저에게 모든 sequence 권한 부여
3. Target DB유저에게 임시로 superuser 권한 부여
4. DB 동기화
5. 복제 상황 모니터링
6. 복제 지연 최소화 대기 및 데이터 검증
7. api, worker, cron pod 제거
8. 복제 지연 없음 확인
9. pgcopydb 프로세스 종료
10. replication slot, origin 삭제
11. target DB유저 superuser 권한 제거
12. api, worker, cron 재배포

순서대로 차근차근 진행해보자

1. Bastion VM pgcopydb 설치 및 연결 테스트
sudo apt update
sudo apt install pgcopydb

mkdir -p pgcopydb-migration
cd pgcopydb-migration

export PGCOPYDB_SOURCE_PGURI="postgres://source-db:thisispassword@127.0.0.1:5432/source-db"
export PGCOPYDB_TARGET_PGURI="postgres://target-db:thisispassword@127.0.0.1:5433/target-db"

pgcopydb ping --source "$PGCOPYDB_SOURCE_PGURI" --target "$PGCOPYDB_TARGET_PGURI"
# 기대 출력
## INFO Successfully could connect to source database
## INFO Successfully could connect to target database
2. Source DB유저에게 모든 sequence 권한 부여
-- source-db 유저에게 모든 sequence 권한 부여
GRANT SELECT, USAGE ON ALL SEQUENCES IN SCHEMA public TO "source-db";
3. Target DB유저에게 임시로 superuser 권한 부여
-- 임시로 superuser 권한 부여
ALTER USER "target-db" WITH SUPERUSER;
4. DB 동기화
nohup pgcopydb clone --follow \
  --source "$PGCOPYDB_SOURCE_PGURI" \
  --target "$PGCOPYDB_TARGET_PGURI" \
  --no-owner \
  --no-acl \
  --table-jobs 1 \
  --index-jobs 1 \
  --verbose \
  --dir . \
  > ./pgcopydb-migration.log 2>&1 &

# 프로세스 ID 저장
echo $! > ./pgcopydb-migration.pid

# 실시간 로그 확인
tail -f ./pgcopydb-migration.log

# 진행 상황 확인
./replication_monitoring.sh
6. 복제 지연 최소화 대기 및 데이터 검증
복제지연 확인
SELECT
    slot_name,
    active,
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) as lag_size,
    pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) as lag_bytes
FROM pg_replication_slots
WHERE slot_name = 'pgcopydb';

-- Result --
 slot_name | active | lag_size | lag_bytes
-----------+--------+----------+-----------
 pgcopydb  | t      | 541 kB   |    553920
(1 row)
데이터 일관성 검증
pgcopydb compare schema --verbose
pgcopydb compare data --verbose

약 541KB 만큼의 복제 지연이 있다는 의미이다.

# 데이터 정합성 확인
pgcopydb compare data

# Output
16:11:44.270 162208 INFO   Running pgcopydb version 0.17-1.pgdg22.04+1 from "/usr/bin/pgcopydb"
16:11:44.357 162208 INFO   Using work dir "/tmp/pgcopydb"
16:11:44.358 162208 INFO   SOURCE: Connecting to "postgres://target-db@127.0.0.1:5432/target-db?keepalives=1&keepalives_idle=10&keepalives_interval=10&keepalives_count=60"
16:11:44.361 162208 INFO   Re-using catalog caches
16:11:44.365 162208 INFO   Starting 4 table compare processes
                    Table Name | ! |                      Source Checksum |                      Target Checksum
-------------------------------+---+--------------------------------------+-------------------------------------
                 public.user |   |  9136aaf4-0f32-cd2b-850f-49270ff7c03 |  9136aaf4-0f32-cd2b-850f-49270ff7c03

만약 두 데이터가 다른 경우 Checksum이 다르고, 두번째 컬럼에 !가 나타난다

7. api, worker, cron pod 제거

사용중인 CD tool에서 직접 내렸다

8. 복제 지연 없음 확인

6번 과정에서 진행한 작업을 반복하여 확인한다

9. replication slot, origin 삭제
-- SOURCE DB에서
SELECT pg_drop_replication_slot('pgcopydb');

-- TARGET DB에서
SELECT pg_replication_origin_drop('pgcopydb');
10. target DB유저 superuser 권한 제거
-- superuser 권한 제거
ALTER USER "target-db" WITH NOSUPERUSER;
11. api, worker, cron 재배포

사용중인 CD tool에서 직접 다시 올렸다

 


실제로 진행할때는 개발환경에서 권한과 postgresql 버전 등등을 정확히 동일하게 세팅 하지 않았어서 운영환경에 반영할때 잡음이 많았다.

좀더 꼼꼼하게 개발환경을 세팅했으면 좋았을텐데 아쉬웠다.

 

팀 내에서 경험이 없던 작업이라 문서화에 신경을 많이 썼는데, 문서화가 제일 피곤했다

 

데이터 정합성과 다운타임을 모두 잡는 해결책이 있을지 궁금해졌다.

'DB' 카테고리의 다른 글

[PostgreSQL] Lock에 대해  (0) 2025.11.02
[Elasticsearch] 유저가 원하는 검색결과 보여주기 (analyzer)  (1) 2025.07.31
[DB] MongoDB 장점  (0) 2022.06.01
[DB] MongoDB 자료구조  (0) 2022.06.01
[DB] Lock 이해하기  (0) 2022.05.20

유저: 실습 기능이 너무 느린것 같아요.
개발팀: 저희는 재연이 안되어서... 혹시 어떻게 하셨나요?

 

이런 일이 벌써 몇 번째였다. 항상 유저가 먼저 문제를 발견하고, 리포트해주기를 기다리는 수동적인 상황에 놓여있었다. 서비스에 문제가 생겨도 개발자는 알 수 없는 구조였다.

그러다 보니 자연스레 유저가 서비스를 신뢰하지 못하게 되고, 개발자들은 예측이 안되는 이슈에 예정된 일을 내팽개치고 달려나와 해결해야 하는 상황에 놓이게 되었다.

더 큰 문제는 개선 작업 후에도 마찬가지였다. 데이터베이스 쿼리를 최적화하고, 캐시를 도입해도 "정말 빨라졌나?"라는 의문만 남긴채 쌓인 이슈를 처리하러 갈 뿐이었다.

개선 효과를 수치적으로 증명할 방법이 없기 때문이었는데, 이는 팀 내 개발자들의 동기부여에도 악영향을 미치고 있었다.

 


메트릭부터, 빠르게 구축하자

APM(Application Performance Monitoring) 시스템을 한번에 도입하려 했지만, 복잡한 인프라 세팅과 러닝 커브가 부담스러웠다. 우리에게 당장 필요한 현재 서버 상태를 대략적으로라도 파악할 수 있는 메트릭부터 구축하기로 결정했다.

메트릭만 제대로 도입해도 위에서 설명한 문제들을 해결 할 수 있다

  • 실시간 서비스 상태 파악: 개발자가 먼저 서비스 이상을 탐지할 수 있음
  • 성능 개선 효과 측정: 수치로 증명 가능한 개선 결과
  • 문제 구간 식별: 어떤 엔드포인트에서 문제가 발생하는지 즉시 확인

 


수집하기

아래 두가지 메트릭만 우선 수집하기로 결정했다.

http_requests_total (요청 수)

  • 라벨: path, method, status_code
  • 엔드포인트별 트래픽 패턴과 에러율 추적

http_duration (요청 시간)

  • 라벨: path, method
  • 각 요청의 처리 시간 분포 측정

현재 우리의 대부분 MSA는 FastAPI로 구현되어 있는데, http 요청 메트릭을 기록하는 Middleware를 구현하였다

def setup_apm(app: FastAPI, service_name: str, version: str) -> MetricsManager:
    middleware = APMMiddleware(service_name, version)
    app.add_middleware(BaseHTTPMiddleware, dispatch=middleware)

    @app.get(
        "/metrics",
        include_in_schema=False,
        response_class=PlainTextResponse,
        tags=["monitoring"],
    )
    async def metrics_endpoint() -> PlainTextResponse:
        return PlainTextResponse(
            middleware.metrics.generate_metrics(),
            media_type="text/plain; version=0.0.4; charset=utf-8",
        )

    return middleware.metrics

 


 

시각화 (대시보드 구축)

시각화는 grafana를 이용했다.
grafana는 JSON 기반 대시보드 설정이 가능해서, claude와 대화 몇번 주고받으면서 대시보드를 완성시켰다

 

Request Per Seconds.
서비스 전체의 트래픽 패턴을 모니터링한다. 어느 기능에 요청이 몰리는지 파악할 수 있다.

Request Duration (99p/90p/50p)
응답 시간의 백분위수를 추적하는 가장 중요한 메트릭이다

  • 50p (median): 일반적인 사용자 경험
  • 90p: 대부분의 사용자 경험
  • 99p: 최악의 사용자 경험

Slowest Endpoint
가장 느린 엔드포인트가 무엇인지 알 수 있다. 성능 개선이 필요한 기능의 우선순위를 정하는 데 도움이 된다.


배포 하자마자 문제발견

배포하고 1시간만에 안되는 조회 Endpoint에서 99p와 90p, 50p의 격차가 넓은것을 확인했다.
외부호출이 있는 엔드포인트 였는데 특정 상황에 지연이 발생하고 있어서, 내일 해결해봐야 겠다.


느낀점

  • 이걸 왜 이제 했을까..
  • 평균은 문제를 숨긴다
    • 대시보드를 세팅할때 처음에는 Request Duration의 평균값만 기록하였는데, 모든 기능의 지연시간이 고만고만해보여서 아무런 문제가 없는줄 알았다.
    • 의심스러워서 99p/90p/50p등의 백분위수를 도입하고 다니 평균에 가려져있던 문제들이 드러나게 되었다.
  • 수치화는 개발자에게 동기부여를 준다
    • 대부분의 개발자는 목표지향적인 성격을 가지고 있다고 생각한다
    • 그런 그들에게 OO기능 50% 성능 개선과 같은 정량적 목표를 부여한다면 달성했을때 더 큰 보람을 스스로 느낄 것이다 

콩쥐야.. 조때써

 

“xxx-api CPU가 계속 스파이크를 찍네요, 이유가 무엇인가요?”

 

위 질문에 답변을 못한다는 것을 장애가 터지고 깨달았다, 사실 인지는 했지만 외면해두고 있었다

최근 배포한 기능들을 뒤져보며 문제가 될 만한 부분을 찾아 급한 불은 우선 껐지만, 현 구조가 장애 대응 능력이 부족하다는 것이 느껴졌다.

또한 사용자와 서비스 규모가 늘어나면서, 장애 상황에서의 빠른 대응이 팀의 우선순위가 되었다.

“무엇이 원인인지“ 더욱 빠르게 파악할수 있는 해결책이 필요하다.


 

현재 우리의 서버는 아래 로깅을 지원하고 있다

  • 서버 예외 sentry 로그
  • k8s pod 시스템 성능 지표 (CPU, MEM, Net I/O, …)
  • db 시스템 성능 지표 (CPU, MEM, Net I/O, …)
  • nginx http 로그

시스템 레벨의 지표는 수집하고 있지만, 애플리케이션 레벨의 관찰 가능성이 부족하여 “어떤 코드에서 발생했는지" 파악할 수 없는 상황이다.

따라서 다음과 같은 요소들이 추가로 필요하다:

Metric

어플리케이션의 상태 트렌드를 파악하기 위해 필요하다.

  • API 엔드포인트별 응답시간, 처리량(TPS), 에러율
  • 비즈니스 로직별 실행시간 (예: 결제 처리, 데이터 조회 등)

Logging

metric에서 트렌드를 파악하고, 구체적으로 어떤 문제인지 파악하기 위해 필요하다

  • nginx http 로그
  • 애플리케이션 에러 로그 (stack trace, 예외 상황)

Trace

하나의 요청이 여러 서비스를 거쳐가는 과정을 추적하여, 어느 구간에서 지연이나 문제가 발생했는지 파악하기 위해 필요하다

  • 각 API 호출의 상세 실행 과정 로깅
  • 마이크로서비스 간 호출 관계와 소요시간

Alert

문제 발생 시 즉시 인지하고 빠른 대응을 위해 필요하다

  • 임계값 기반 실시간 알림 (응답시간 >3초, 에러율 >5% 등)
  • 일정 시간 내 미응답 시 상위 담당자에게 자동 확대 알림

현재 logging은 loki 기반으로 nginx http 로그를 수집하고 있어 어느정도 갖춰져 있는 상태이다.

우선 어플리케이션 레벨 metric을 수집하는 것에 초점을 맞춰보자

만든 이유

이번 대선 토론에서 각 후보들이 서로의 공약을 비판하는 것을 보았다.

그런데 내가 공약에 대해 아는것이 없고, 어떤 후보가 어떤 공약을 내새웠는지 자세히 알지 못했다.

자세히 알지 못하니 내 가치관을 투영하며 보질 못하니까 재미가 반감되었다.

그래서 https://policy.nec.go.kr 해당 링크에서 공약을 찾아보며 대선토론을 시청했다.

그러다 문득, "공약만 보고 비교하여 나에게 맞는 후보자를 찾는 사이트가 있으면 어떨까?" 생각이 들었다.

개발 과정

1. Project Rule 설정

claude 프로젝트를 만들고 간단한 Rule를 작성했다.

혹시나 큰일나면 안되니 선거법을 찾아보며 만들었다.

1. 정치적 중립성 유지
   - 특정 후보나 정당을 편향되게 지지하거나 비판하지 않기
   - 개인적 정치 성향이나 선호도 표현 금지

2. 사실 정확성 최우선
   - Github Repository에 명시된 중앙선관위 등록 공약만 사용

3. 선거법 준수
   - 선거 관련 법령 위반 요소 철저히 배제
   - 개인정보 수집이나 여론조작 요소 금지

4. 데이터 처리
   - 사용자 개인정보 수집 금지
   - 클라이언트 사이드에서만 동작
   - 결과 추적이나 분석 데이터 저장 금지

5. 면책 조항
   - 교육 목적임을 명시
   - 실제 투표 시 종합적 고려 권고

2. 데이터 사전 준비

Brave Search 등의 MCP가 존재하였지만, 정보의 형평성을 맞추기 위해 크롤링은 하지 않았다.

선관위 사이트(https://policy.nec.go.kr)에서 10대 공약 데이터를 추출하여 레포지토리에 미리 올려두었다

3. 개발

미리 올려둔 데이터를 기반으로 claude에게 개발해달라고 한다

실행까지 해준다

첫 프로토타입을 확인하고, 질문, 후보자 정보, 매칭 로직을 검토하여 점진적으로 개선해갔다

4. 배포

vercel로 배포했다.

주변에서 가장 많이 들어본게 vercel이고, 프로젝트가 next.js 기반이고, 후에 SEO 최적화도 쉽다고 들어서 선택했다.

후기

이렇게 1시간 만에 서비스를 만들고 배포까지 해보았다.

개발을 마치고 다음날 링크드인에 올리려 했는데, 거짓말처럼 누군가 비슷한 서비스를 아침에 올렸다.

그래서 그냥 주변 지인들끼리 재미로 해보는 용도가 되었다.

담부터 이런 서비스는 개발 마치면 무조건 먼저 올려야겠다

그래도 주변 지인들이 출근길에 한번씩 해줘서 만든 보람이 있었다.

링크

SpringBoot 3.0.0 환경에서 Swagger를 적용하고자 Spring fox 라이브러리를 사용했는데 아래와 같이 Whitelabel Error Page 가 나오며 매핑이 제대로 되지 않는 문제가 발생했다. 

 

build.gradle.kts는 아래와 같이 세팅해두었다.

...
implementation("io.springfox:springfox-boot-starter:3.0.0")
...

 

이 문제를 해결하기 위해 "spring fox not working in spring boot 3.0.0" 와 같은 키워드로 구글링을 해보았으나, 의미있는 결과를 찾기 힘들었다.

결국 울며 겨자먹기로 아래 두 방법을 찾았다.

 

1. Spring boot를 2.6.2 로 다운그레이드 한다.

2. springdoc-openapi 라이브러리를 사용한다.

 

나같은 경우 프로젝트 초기 세팅 단계라 1번 방법으로 해결하였다.

아직은 Spring boot3 레퍼런스가 부족하다는 것을 느껴 결정하게 되었다.

1년동안 잘 챙겨주신 사수분이 퇴사하신다.

내 인생 첫 사수고 정말 좋은 분이라 뭔가 재미있는 선물을 해드리고 싶다.

 

뭘 드리면 좋을까 고민해봤는데

크지 않고, 추억할 수 있고, 개발자를 위한 선물을 드리고 싶었다.

 

그러던 중 스쳐지나가면서 봤던 github skyline 이라는 사이트가 생각났고,

 

부끄러운 나의 커밋 수

이거를 3D 프린터로 뽑아서 드리면 그간의 노력도 볼 수 있고 되게 재미있을 것 같았다.

 

바로 실행에 옮겼다.

 

 


 

 

생각해보니 우리 회사는 gitlab을 써서 위 사이트를 이용할 수가 없었다. 그래서 아래 라이브러리를 찾아왔다.

https://github.com/felixgomez/gitlab-skyline

 

GitHub - felixgomez/gitlab-skyline: Generate a 3D Skyline in STL format and a OpenSCAD file from Gitlab contributions

Generate a 3D Skyline in STL format and a OpenSCAD file from Gitlab contributions - GitHub - felixgomez/gitlab-skyline: Generate a 3D Skyline in STL format and a OpenSCAD file from Gitlab contribut...

github.com

 

해당 라이브러리는 1년 단위로만 뽑을 수 있었는데, 사수의 2년이 모두 담길 수 있도록 코드를 조금 수정했다

 

2년은 역시 길다

 

회사 로고도 하나 박아주자

 

이쁘다

그리하여 출력할 stl 파일은 준비되었다.

 

 


 

 

다음으로 이를 프린팅 해줄 대행 업체를 찾아봤다.

 

 

3D프린터 장인 갓재석- 가장 저렴한 3D프린팅 / 실시간 비용 확인 - 3D프린터장인 갓재석

2021년 3D프린터 장인의 가장 저렴한 3D프린터 출력 서비스, 석고몰드, 비누몰드, 3D모델이 없어도 스케치,그림만으로 제작해드립니다. 다양한 재료와 후가공 서비스로 여러분의 꿈을 3d프린터 장

13.209.51.203

여러 사이트들 중에서 배송 기간이 명시되어 있고 가격도 적당한 갓재석이라는 사이트를 찾았고,

stl 파일을 업로드하여 소재 / 크기 / 색상 선택 후 출력 신청을 했다.

 

stl 뷰어도 제공한다

 

크기 18 × 2 × 2 cm, 플라스틱(PLA) 소재는 15000원 정도로 출력이 가능했다

 

아무튼 성공적으로 주문을 했고, 배송이 올때까지 기다리기로 하였다. (1~2주 소요)

 

 

 


 

 

!!!

느긋하게 배송을 기다리려 했는데, 사수의 퇴사일이 앞당겨졌다는 소식을 듣게되었다.

손가락 빨고있다간 퇴사일에 못 맞출 것 같아서, 퇴근하고 직접 가서 받아와야 했다.

판매자분께 사정 말씀드리니 감사하게도 밤 늦게까지 기다려주셨다.

 

또한, 플라스틱 재질로 주문했는데 피규어에 적합한 더 비싼 레진 재질로 출력해주시고, 마감처리까지 무료로 해주셨다.

개인 주문이라 번거로우실텐데도 신경써주셔서 너무 감사했다.

 

 


 

그렇게 힘들게 만든 결과물을 만날수 있었다.

 


 

두둥

 

2년간 열심히 일하신 기록을 한눈에 볼수있다. 가끔씩 주말에 잔디가 깔린걸 보면 눈물이난다.

 

잘 부러질까봐 걱정했는데. 레진 소재가 탄성과 무게감이 있어 튼튼했다.

 

보라색으로 페인팅까지 하면 좋았을텐데, 시간이 부족했다.

 

 


 

 

 

 

다같이 퇴사를 축하한 후, 사수의 손으로 무사히 전달되었다.

번거로운 짐이 되지 않을까 걱정했는데, 엄청 좋아하셔서 뿌듯했다.

 

 

 

^_ㅠ

 

 

+ Recent posts