전체 글257 [Architecture] 엣지 서버의 로컬 ID와 전역 ID 충돌을 피하는 방법 하나의 엣지 서버가 자기 데이터와 중앙 플랫폼의 다른 운영 단위 데이터를 함께 조회할 때, 같은 숫자를 가진 로컬 ID와 전역 ID를 API 경계에서 분리한 과정을 정리했다.시작하며엣지 서버는 인터넷 연결이 끊겨도 자기 데이터를 조회할 수 있도록 로컬 MySQL을 가지고 있다. 온라인일 때는 중앙 플랫폼을 통해 다른 운영 단위의 데이터도 조회한다.처음에는 요청의 site_id가 로컬 DB의 site.id와 같으면 자기 데이터, 다르면 다른 운영 단위의 데이터라고 판단했다.site_id == local site.id → 로컬 조회site_id != local site.id → 중앙에서 위치 확인 후 원격 조회단일 서버만 볼 때는 자연스러운 규칙처럼 보였다. 그러나 온라인 조회를 붙이면서 이 방식에는 식별자.. 2026. 9. 20. [Server] 사용자 JWT와 장비 Token의 인증 경계를 분리하기 사람과 장비는 같은 API Gateway를 호출하지만 Credential의 발급 방식과 수명주기, 접근 범위는 다르다. 사용자 JWT와 장비 Token을 별도의 Guard로 나누고, 인증 결과에서 자원 범위를 파생한 과정을 정리했다.시작하며처음에는 외부 요청을 모두 Gateway의 사용자 인증으로 처리하는 구성이 단순해 보였다. 브라우저 요청은 Access Token을 검증하고, 필요하면 Refresh Token으로 재발급한 뒤 내부 서비스에 사용자 정보를 전달하면 된다.하지만 외부 호출 주체가 사람만 있는 것은 아니었다. 로그인 화면을 사용할 수 없는 무인 장비와 자동화 프로그램도 API를 호출해야 했다.장비에 사용자 계정을 발급해 JWT를 사용하게 만들 수도 있다. 그러나 사용자 JWT는 로그인, 로.. 2026. 9. 19. [Refactoring] 레거시 API를 이식할 때 계약과 버그를 구분하는 방법 레거시 API를 새로운 서버로 옮기면서 외부 계약은 보존하고, 내부 구현은 재구성하며, 명확한 버그는 회귀 테스트와 함께 수정한 기준을 정리했다.시작하며기존 앱이 사용하는 API를 새 DB에 연결하는 호환 서버를 만들고, 레거시의 설정 관리 기능을 새 API로 옮겼다. 두 작업 모두 기존 코드를 읽는 것에서 출발했지만, 바꿀 수 있는 범위는 달랐다.기존 앱을 그대로 유지하는 경로는 요청과 응답을 보존해야 했다. 호출자도 함께 바꾸는 설정 관리 API는 엔드포인트를 역할별로 나눌 수 있었다. 이 차이를 먼저 정하지 않으면 같은 변경을 두고도 한쪽에서는 개선, 다른 쪽에서는 장애가 된다.이식 범위유지해야 하는 것바꿀 수 있는 것기존 앱을 유지하는 호환 서버앱이 보내는 요청, 읽는 필드와 타입, 기대하는 결과.. 2026. 9. 13. [Server] 대용량 ZIP을 생성하면서 API Gateway 너머로 스트리밍하기 여러 파일과 동적으로 생성한 데이터를 ZIP으로 묶되 완성된 압축 파일 전체를 메모리에 올리지 않고, 내부 서비스에서 API Gateway를 거쳐 Client까지 Stream으로 전달한 과정을 정리했다.시작하며운영 데이터를 외부에서 사용할 수 있도록 CSV, DB에서 만든 JSON, 데이터 노드의 이미지를 하나의 ZIP으로 내려주는 Export API를 구현했다. 다운로드는 인증을 담당하는 API Gateway를 거쳐야 했다.구현하면서 수정한 곳은 Gateway의 응답 처리였다. 기존 관리자 API의 일반 JSON 프록시는 ZIP 바이너리를 문자열로 처리하는 문제가 있었다. 스트리밍 전용 프록시를 추가하고, Controller가 상태 코드와 헤더를 옮긴 뒤 응답 스트림을 직접 연결하도록 변경했다.왜 이 .. 2026. 9. 12. [Database] 재실행 가능한 백필과 데이터 교정은 왜 분리해야 할까 실시간 전송에서 빠진 데이터를 다시 채우는 백필을 구현하고, 보관본을 재실행해 보면서 누락 보충과 기존 값 교정이 서로 다른 작업이라는 사실을 확인한 과정을 정리했다.시작하며데이터는 평소 실시간 경로로 서버에 전달됐다. 하지만 네트워크가 끊기거나 전송 프로세스가 멈추면 일부 구간이 로컬에만 남을 수 있었다. 이 누락을 복구하기 위해 로컬에서 만든 일별 CSV를 서버에 보내 빈 구간을 채우는 백필 API를 구현했다.실시간 전송수집 장치 ──────────────▶ 중앙 저장소 일부 구간 누락백필수집 장치의 일별 CSV ───▶ 누락된 행 보충백필에서 가장 중요하게 본 조건은 재실행 가능성이었다. 전송하는 쪽은 중앙 저장소에 어떤 행이 이미 들어갔는지 알 수 없었고, 실패 지점도 정확히 알기.. 2026. 9. 6. [Data] 서로 다른 저장소의 시계열 데이터를 외부 JSON 계약으로 만들기 메타데이터와 시계열 값이 서로 다른 저장소에 있을 때, 내부 식별자로 데이터를 결합하되 외부에는 필요한 의미만 남기는 JSON 계약을 만든 과정을 정리했다.시작하며외부 시스템에 하루치 시계열 데이터를 파일로 전달해야 했다. 처음에는 필요한 행을 조회해 파일로 저장하면 되는 작업처럼 보였다.하지만 실제 데이터는 한곳에 있지 않았다.항목의 이름, 단위와 활성 상태는 신규 메타데이터 저장소에 있었다.시각별 값은 기존 시계열 저장소에 있었다.시계열 저장 영역은 대상과 날짜에 따라 나뉘어 있었다.두 저장소를 연결하는 내부 ID는 외부 사용자가 알 필요가 없었다.값이 없는 항목도 “항목은 있지만 그날 값은 없음”으로 표현해야 했다.생성된 JSON은 이후 다른 파일과 함께 묶여 외부로 전달됐다.내부 테이블 구조를 그.. 2026. 9. 5. [Trino] 같은 서버의 MySQL인데 왜 연결이 타임아웃됐을까 여러 서비스가 정상 실행 중인데도 대시보드 요청이 60초 뒤 503으로 끝난 상황에서, Gateway부터 Trino Catalog와 서버 방화벽까지 연결 경로를 좁혀 간 과정을 정리했다. 같은 서버의 MySQL을 공인 IP로 호출하던 JDBC 주소를 loopback으로 바꾼 뒤 동일한 요청이 정상화된 결과까지 다룬다.시작하며같은 시각에 서로 다른 대시보드 위젯 두 개가 모두 503을 반환했다.GET /dashboard/widgets/metric-aGET /dashboard/widgets/metric-bHTTP 503 Service Unavailable요청 전달에 실패했습니다.조회 서비스에 연결하지 못했습니다: 요청 시간이 초과되었습니다.처음에는 조회 API가 종료됐거나 Trino가 멈췄다고 생각했다. 하.. 2026. 9. 4. [Trino] MySQL은 연결되는데 Trino Worker는 연결되지 않았던 이유 데이터베이스 연결 문제로 보였던 Trino 장애에서 타임존이라는 가설을 거쳐, 실제 원인이 서로 다른 VPC 사이의 Worker 통신이라는 것을 확인한 과정을 정리했다.시작하며여러 MySQL을 Trino Catalog로 연결하는 과정에서 일부 데이터 소스의 조회가 실패했다. 대상 MySQL에는 CLI로 바로 접속할 수 있었고 계정과 비밀번호도 정상이었다.mysql -h DB_HOST -u READ_ONLY_USER -p처음에는 JDBC Driver와 MySQL의 타임존 협상을 의심했다. JVM 타임존과 Trino 세션 타임존을 확인하고 설정도 변경했지만 장애는 해결되지 않았다.결국 문제는 MySQL 연결보다 앞선 곳에 있었다. 다른 VPC에 설치한 Trino Worker가 자신의 사설 IP로 등록됐고, .. 2026. 8. 30. [Trino] MySQL SQL을 Trino로 옮길 때 문법보다 의미가 어려웠던 이유 MySQL 조회문을 Trino SQL로 변환하면서 단순한 Dialect 변환만으로 해결되지 않았던 시간대, 주차 계산, 사용자 지정 정렬 문제를 정리했다.시작하며여러 MySQL 데이터 소스를 Trino에서 통합 조회하려면 기존에 저장된 MySQL SQL도 Trino가 실행할 수 있는 형태로 바꿔야 했다.처음에는 이 작업을 문법 변환 문제로 생각했다.MySQL SQL → MySQL 문법으로 Parse → Trino Dialect로 출력 → Trino에서 실행실제로 identifier quoting이나 일부 함수명처럼 SQL Dialect만 지정해도 변환되는 부분이 있었다. 하지만 변환된 SQL이 문법적으로 유효하다고 해서 원래 SQL과 같은 결과를 반환하는 것은 아니었다.다음과 같은 문제가 남았다.같.. 2026. 8. 29. [Trino] 여러 MySQL 데이터 소스를 Trino로 통합 조회하기 여러 MySQL 서버에 분산된 데이터를 Catalog로 추상화하고, 기존 MySQL 조회문을 별도 변환 서비스와 라우팅 로직을 거쳐 Trino SQL로 바꾼 과정을 정리했다.시작하며서비스가 처음 만들어졌을 때는 하나의 MySQL 연결만으로 필요한 데이터를 조회할 수 있었다. 이후 데이터 규모와 운영 단위가 늘어나면서 데이터가 여러 MySQL 서버로 나뉘었다.여러 운영 단위의 데이터 → 서로 다른 MySQL 서버에 분산 저장 → Trino를 통해 통합 조회애플리케이션이 각 MySQL에 직접 연결하도록 만들면 조회할 때마다 다음 작업이 필요하다.요청 대상의 데이터 위치를 확인한다.해당 서버의 연결 풀을 선택한다.쿼리를 실행한다.여러 서버에 걸친 결과를 애플리케이션에서 합친다.데이터 소스가 추가되면 연결 .. 2026. 8. 29. 이전 1 2 3 4 ··· 26 다음