KiCad의 기본 라이브러리 정책으로는 한계를 느껴서, Database 라이브러리(DBLib)를 좀 더 편하게 관리하기 위해 제작중인 프로젝트임.

여러 사람(또는 소규모 그룹)에서 쓰고, 버전관리 및, 오프라인 환경에서도 동작하도록 만드는 것이 목표.

이 프로젝트를 3-4번 정도 갈아엎으면서 해멨는데, AI 도움을 받으면서 처음 접해보는 App, DB쪽 개발을 하면서 방향을 잡고, “왜 이런 선택을 했는가"를 중점적으로 적은 글.

이 글 작성 시점으로, 라이프사이클 관리 등 버전관리쪽에 중점적으로 설계에 초점을 두고 있음. 도중에 문서를 남기면 좋겠다라는 생각이 들어, 이 글을 작성하게 됨.

배경

첫 취업, 관련 전공 나온 사람도 없고, 사수도 없었지만, 지금 다시 돌아봐도 큰 후회는 안함. 회사에 당연히 CAD가 있을리가, 바로 KiCad부터 설치했음.

KiCad는 기본 라이브러리가 있어서 빠르게 프로토타입을 만드는데 크게 도움이 됨. 기본적인 심볼, 메이저 IC, IPC 기반 풋프린트, 커넥터 등등..

디지키, 마우저 찾아가면서 부품 확인하고, 제조사 디자인 애플리케이션 찾아보면서 부품 찾아보고, 메일도 써보다가 유통업체랑 미팅하면서 망신도 당해보고,

메디컬 전용으로 나오는 부품이 있는데, 공식 홈페이지, 유통업체에도 있었는데, 부품명 한줄만 있고 설명이 없었음.

여러 부품을 찾아보고, 다운로드 받고, KiCad에 점점 “나만의 라이브러리”가 쌓여가기 시작함.

회사를 다니면서 학교 동창 친구도 같이 합류하게 되었음. 혼자 개발하다가, 두명이서 개발하니 큰 문제가 생겼다. 혼자서는 아무런 문제가 없던 프로젝트 공유, 라이브러리 공유 방법 등에서 문제가 생긴 것이다. 우선 KiCad 라이브러리 경로를 NAS 공유폴더로 지정해서 해결했었다.

시간이 지나 과제비로 Altium도 구매해보고, 라이브러리 공유 방법을 검색해보면서, 더 나은 해결책을 찾고 있었다. 우선, Altium은 KiCad랑 다르게 구성요소(Symbol) 기반 라이브러리가 아닌, Part 기반 라이브러리로 동작한다. (물론, KiCad도 Part 기반을 지원하고, Altium도 구성요소 기반 라이브러리를 지원한다)

KiCad도 Part 기반 라이브러리(DB)를 지원하지만, DB와 연결하기 위해 외부 프로그램 및 시스템이 필요하다. 직접 데이터베이스를 구축해야함.

예를 들어: 표준 Symbol/Footprint 기반이 아닌, Part에 맞는 Symbol/Footprint 즉, 커패시터 하나를 쓰려면 KiCad에서는 Cap 심볼 하나 배치하는 것이 아닌, 커패시터 부품 하나를(CC0603KRX7R9BB104) 배치하는 방식이다. symbol_vs_part 어찌저찌 다니지만, 회사에 사수도 없고, 시스템도 없고, 목표도 없었고, 회의감에 퇴사하게 되었다..

동기

퇴사 후 개인 프로젝트 만들면서 시간 보내자 하면서, 재미있는 부품 없나 하고 디지키/마우저 찾아보고. KiCad를 다시 키니까 전 직장의 시스템 부재가 아른거림. (사실 제일 먼저 든 생각은 라이브러리 관리하기 귀찮다)

그래서, KiCad 평생 쓸꺼, 라이브러리 시스템을 구축해두자(라이브러리도 내 자산이다) 구성요소 기반 라이브러리 써도 결국 디지키, 마우저, LCSC에서 사용할 부품 다 찾아봐야한다. Part 기반 라이브러리 구축해두자.

혹시나, 나중에 회사가서도 전에 있었던 일 일어나지 않고, 포트폴리오도 될 겸 시스템 구축 진행해보자.

이런 이유로 프로젝트를 시작하게 되었음.

목표 및 계획

핵심 목표

  1. 오프라인 상태에서도 사용 가능
  2. 추적 및 롤백 가능 (즉, 버전관리)
  3. 동시에 여러 사람이 사용 가능하고, 동기화되어야 함.
  4. 최소설치, 최소설정 (흔히 말하는 “딸깍”)

신뢰 가능한 소규모 환경에서 이용.

  • 모든 이용자가 관리자.

1차 구상

부품 기반으로 라이브러리를 구축하자.

동기에서 짧게 적었지만, 구성요소 기반으로 라이브러리를 구축하더라도, 그거에 맞는 실제 부품을 찾는 과정은 많이 힘들었음.

전용 라이브러리를 만들어도, 따로 폴더 만들어야 하고, 구성요소 기반 라이브러리랑 관리 방식이 다른 것이 불편했음.

  • 이렇게 된거 시간 걸리더라도 모든 라이브러리를 부품 기반으로 정리해두자.
  • 관리를 잘 하면 커패시터라도 높이가 제각각 다른 패키징을 3D 뷰어에서 볼 수 있다!

부품 기반으로 라이브러리를 구축하려면, 크게 두가지 방법이 있음.

DB 라이브러리(.kicad_dbl)와 HTTP 라이브러리(.kicad_httplib)

  • 결국 ODBC 연결이냐, localhost 연결이냐의 차이.
  • HTTP 라이브러리로 연결하려면, 로컬 서버가 켜져있다는 것도 부담(아무리 정적 서버지만). 오프라인에서도 사용하기 용이하고, 자료도 많은(그나마) DB 라이브러리로 선택하게 됨.
DB 라이브러리HTTP 라이브러리
연결 방식ODBC (로컬/원격 DB)REST API (HTTP)
설정 파일.kicad_dbl (connection string, 테이블 매핑).kicad_httplib (URL, 토큰)
오프라인 사용로컬 DB 가능서버 필수
읽기/쓰기읽기 (KiCad 기본)읽기 전용
추가 설치ODBC 드라이버없음
DB 구조 인식KiCad가 직접 SQL 쿼리서버가 추상화하여 JSON 응답
지원 DBSQLite, CSV, MySQL, Postgres 등InvenTree 등 REST 서버

버전관리 및 동기화

공유 폴더

처음에는 라이브러리 공유폴더에 기존 버전관리 시스템 얹어 놓을까 생각했었음. (KiCad 라이브러리 경로를 공유폴더로 잡아놓기)

WebDAV+Syncthing 등등 공유 폴더 기반으로 생각해 보았지만, 오프라인 시 대책, 충돌 문제 등이 많아 폐기함.

SVN

사실 내부망에서만 이용하고(노트북 등 외부 고려X) 관리하려면 매우 효과적임. 파일 Lock 기능 있고, 처음 생각한 공유 폴더(중앙 관리형)에 매우 적합하다고 생각함.

문제가 생김. 결국 오프라인 상태에서도 사용 가능하려면 로컬에도 캐쉬가 필요한데, SVN을 사용하는 의미가 없어짐.

SVN 서버들도 지원하는 API들도 적고, 그래서 Git을 이용하기로 함.

GIT

결국 오프라인 상태에서도 라이브러리가 동작하려면, KiCad 라이브러리 경로는 로컬에 있어야 한다. 그러면 Git처럼 로컬 리포지토리에 라이브러리를 저장해두자.

라이브러리 특성상(물론 JSON 형식이지만) diff/merge 기능을 주로 활용하기 보다는 파일 추적 및 버전 태깅 용도로 활용하기로 함. Gitea, Gitlab 등 리모트 서버 구축 시 빵빵한 API들도 있고.

오프라인 요구를 충족하면서 API도 많은 Git이 가장 적합하다고 판단.

DB를 CSV로 구현한 이유

대부분 부품업체들이 csv로 export를 지원하니까, 엑셀로 편집하는게 가장 쉽다고 생각함. 다운로드 받은 파트리스트를 복사해서, CSV에 약간만 가공해서 붙여넣기.

당시 구현할 계획에서는 다른 DB를 쓰기에는 복잡도만 늘어난다고 판단했었음. dbeaver라든지, TablePlus라든지, 다른 프로그램으로 열어야 하고, 당시 스크립트 기반으로 구현했을 때에는 CSV가 가장 합리적인 선택이었다고 생각함.

심볼 1:1 대응 원칙

기본 KiCad 라이브러리는 심볼과 풋프린트 구조가 각자 다름. 심볼: *.kicad_sym 파일 안에 여러 심볼이 들어감(파일이 그룹이 됨) 풋프린트: *.pretty dir 안에 *.kicad_mod 개별 풋프린트가 들어감(dir이 그룹이 됨)

심볼 구조 예시

symbols/
  Capacitor.kicad_sym   //하나의 파일 안에 JSON으로 여러 데이터.
    ├ C                     
    ├ C_Polarized           
    ├ C_Small               
    └ ...                   
  Connector.kicad_sym
    ├ Conn_01x02
    ├ Conn_01x04
    └ ...

풋프린트 구조 예시

footprints/
  Capacitor_SMD.pretty/   //디렉토리가 그룹
    ├ C_0402.kicad_mod   //개별 파일이 풋프린트
    ├ C_0603.kicad_mod
    ├ C_0805.kicad_mod
    └ ...
  Connector_PinHeader.pretty/
    ├ PinHeader_1x02.kicad_mod
    ├ PinHeader_1x04.kicad_mod
    └ ...

기본 심볼처럼 한 심볼 파일 안에 여러 심볼을 넣을 경우

  • 버전관리, 충돌 등 문제(Git은 파일단위로 추적, 아니면 따로 내부 JSON에 버전관리 구축 필요)
  • diff/merge (물론 JSON 기반이라 가능은 하다..)
  • kicad_sym에 넣을 심볼 그룹 정책 정하고(어떤 심볼들끼리 집합할 건지, 예외는 어디에?), 파일명 바뀌면 참조된 DB도 다 바꿔줘야 하고..

그래서 하나의 *.kicad_sym 파일 안에는 하나의 심볼만 두기로 했다.

1:1 대응 시 구조 예시

symbols/
  CAP_GRM033.kicad_sym    //파일 하나 = 심볼 하나
  └ CAP_GRM033
  CAP_GRM155.kicad_sym
  └ CAP_GRM155
  NE555P.kicad_sym
  └ NE555P
  ...

1차 구현(스크립트 기반)

로컬 설치 항목 및 핵심 프로그램(KiCad는 당연히 설치)

  1. Git
  2. TortoiseGit
  3. CSV ODBC (Microsoft)
  4. 엑셀과 같은 CSV에디터

동작 순서

  1. git clone 한 라이브러리에서 수정을 함(sym/fp/csv 등등).
  2. tortoiseGit을 통해서 push를 함.
  3. post-push hook을 이용해서(tortoiseGit에만 있는 hook) 스케줄러 실행(추후 설명)
  4. gitlab runner가 push받으면, kicad_dbl,*-lib-table 를 생성해 artifacts로 만듬.
  5. 3번의 스케줄러는 폴링으로 Gitlab 감시하고 있다가, artifacts 발견 시 다운로드 받음. 동작순서

한계 및 문제점

  • 버전 관리 및 롤백 기능 어려움.
  • 라이브러리를 항상 수동으로 pull 받아야 함!!!!
  • 그냥 단순 kicad_dbl파일 생성 및 동기화 스크립트 뭉치
  • Push 또는 스크립트 수동 실행해야함.
  • 폴링으로 artifacts(kicad_dbl)를 다운로드 받는 방식은 매우 비효율적

결국 수동으로 모든 것을 다 진행해야 함.

피드백과 2차 구상

1차에서 얻은것들

오프라인 작업을 위해 로컬 기반 라이브러리는 확정.

  • 즉 충돌은 불가피하다. TortoiseGit와 같은 외부 App 의존성을 최대한 줄이자.

App 기반으로 전환한 이유

아직 할 일은 산더미지만, 동기화, 버전/라이프사이클 관리를 위해서 App은 선택이 아니라 필수일 것 같다.

동기화

모두가 업데이트 된 라이브러리를 사용하려면, 수동으로 새로고침을 하는 것이 아닌 자동화된 동기화 시스템이 필요하다. App을 통해서 수정/추가 시 자동으로 업데이트가 되게 구현

버전 관리

Gitlab, Git CLI를 통해서 버전/라이프사이클 관리는 이론적으로 가능하다. 하지만, 전자부품 전용 관리 시스템으로 나온 물건이 아니니, 사용하는데 불편함. App을 구현하면, 부품 전용으로 직관적이게 버전 및 히스토리를 볼 수 있음.

구현 시 기술 선택

왜 React?

처음에는 웹 대쉬보드를 통해서도 버전 및 라이프사이클을 쉽게 확인할 수 있도록 하고싶었다.(마치 Gitlab 사이트처럼)

MUI도 지원하니, MUI를 통해 빠르게 레이아웃을 구현할 수도 있다고 판단했다. klm_screenshot

작업중인 프로젝트 스크린샷.

오히려 좋은 선택인것 같다. 네이티브로 돌리는 것보다 추후 리눅스로 전환도 수월할 것이고, 자료도 많고, AI도 답변을 잘해준다.

SQLite 전환 이유

단순 데이터 저장 및 엑셀에서 편집 염두해 두면, CSV는 충분히 합리적인 선택임.

SQLite를 고른 이유?

  • App에 들어가는 DB로 최적이다라는 생각, 사실 크게 고민하지 않고 바로 선택했었음.

App으로 전환하면서 CSV로는 한계를 느낌

  • 데이터 필드의 column 추가 및 수정.
  • DB가 많아지면 CSV 파일도 많아져서 부담됨(SQLite 단일 파일)
  • 인덱스 등 성능
  • PK, FK 등 데이터 타입 지원
  • App에서 특정 Part를 참조하는 라이브러리 요소 List를 표시하는 기능도 구현하고 싶었음. CSV로 구현하기에는 한계.

DB 구조 구상

v2_db_schema

테이블 설명

db Table

ColumnType설명
idPK (UUIDv4)
nameTEXT
  • 단순한 이름을 가진 최상위 그룹

group Table

ColumnType설명
idPK (UUIDv4)
nameTEXT그룹명
noteTEXT비고
  • 여러 라이브러리가 attr_key(column) 스키마 공유.
  • Capacitance, Tolerance 와 같은 공통된 속성을 가진 라이브러리들은 같은 group를 사용.

library

ColumnType설명
idPK (UUIDv4)
db_idFK (db.id)어느 DB에 속하는지
group_idFK (group.id)어느 column 리스트를 사용하는지
nameTEXT라이브러리명

part

ColumnType설명
idPK (UUIDv4)
library_idFK (library.id)어느 Library에 속하는지
nameTEXT부품명
symbol_libTEXTKiCad 참조 심볼 라이브러리
symbol_nameTEXTKiCad 참조 심볼명
footprint_libTEXTKiCad 참조 풋프린트 라이브러리
footprint_nameTEXTKiCad 참조 풋프린트명
  • 심볼 1:1 정책으로 symbol_lib 와 symbol_name은 항상 같음.

part_attr

ColumnType설명
part_idFK (part.id)어느 부품의 속성인지
group_idFK (group.id)어느 그룹의 속성인지
key_idFK (attr_key.key_id)어느 column에 해당하는지
valueTEXT속성값
  • 부품이 어떤 column을 갖고, 어떤 값을 가지는지 정의.

attr_key

ColumnType설명
key_idPK (UUIDv4)
group_idFK (group.id)어느 그룹에 속하는지
nameTEXT속성명 (예: Capacitance, Tolerance)
sortINTEGERApp에서 테이블 표시 시 정렬 순서

kicad_export_key

ColumnType설명
key_idFK (attr_key.key_id)대상 속성
enableINTEGER내보내기 활성화 여부
col_nameTEXTKiCad .kicad_dbl에서 사용할 column명
  • KiCad의 .kicad_dbl 내보내기 전용 테이블.
  • 부품 속성들이 세로(EAV)로 저장되어 있는데, 가로(테이블)로 변환하여 생성.

데이터 예시

CC0603KRX7R9BB104, CC0603KRX7R9BB105
두 부품이 있다고 할 때, DB에 어떻게 저장되는 지 설명.

part

idlibrary_idname
UUID1UUID-LIBCC0603KRX7R9BB104
UUID2UUID-LIBCC0603KRX7R9BB105

attr_key

key_idgroup_idnamesort
UUID-KEY1UUID-GRPCapacitance0
UUID-KEY2UUID-GRPTolerance1

part_attr

part_idgroup_idkey_idvalue
UUID1UUID-GRPUUID-KEY1100nF
UUID1UUID-GRPUUID-KEY2±10%
UUID2UUID-GRPUUID-KEY11μF
UUID2UUID-GRPUUID-KEY2±10%

kicad_export.db에 출력되는 테이블

NameCapacitanceTolerance
CC0603KRX7R9BB104100nF±10%
CC0603KRX7R9BB1051uF±10%

설계 의도

attr_key 와 같은 파편화 된 방식(EAV 패턴)을 사용한 이유는?

  • 라이브러리마다 사용하는 column이 다름.
  • 물론 강제로 통일이 가능하겠지만, 언젠간 column에 맞지 않는 예외가 발생할꺼임.(공급업체 사이트만 봐도 통일되지 않은 요소들이 엄청나다!)
  • 유연한 라이브러리를 구축하기 위해서 part_attr-attr_key와 같은 구조로 설계했음.

ID들은 모두 UUIDv4로 생성함.

  • 여러 key와 table이 생성될 것을 고려하여, 충돌위험이 매우 낮은 UUID를 이용함.

유연한 라이브러리를 구축하려고 구조를 짜고 확인 결과, EAV 기반 데이터베이스로 구축하게 됨.

이 글을 쓰는 시점에 EAV 패턴을 알게 되었다, 더 열심히 배우고, 더 나은 구조를 만들기 위해 노력할 것이다

2차 구현 (App 기반)

로컬 설치 항목 및 핵심 프로그램

  1. Git
  2. SQLite ODBC
  3. Python

Tauri + (react+vite) + fastAPI

Tauri 사용 이유

나도 Tauri는 처음 들어보는 프레임워크였다. react를 App으로 돌릴만한 것을 찾아보다가, 이것을 채택했는데.

  • 매우 적은 메모리 사용량
  • 빌드 시 매우 작은 실행파일 용량(윈도우 웹뷰엔진으로 돌아감)
  • 빠른 초기 실행속도

chatGPT App이 electron으로 돌아갔었는데, 많이 버벅이는 경험으로 인해 Tauri로 구현해보기로 결정해보았다. (사실 chatGPT 자체의 최적화 문제다.)

데이터 저장 구조

JSON + Git

추적 및 롤백(버전관리)를 위해 Git을 최대한 활용하기로 했음. 파일 추적 기반이니, 위에서 구상한 데이터들을 모두 JSON 형식으로 저장하고, 추적/롤백 구현이 수월했음. 변경/작성자/시간/커밋 메세지 등은 Git으로 모두 관리가 가능함.

JSON 데이터는 Git repo에 모두 저장

Repo 구조는

repo/
  db/{uuid}.json
  library/{uuid}.json
  part/{uuid}.json
  symbol/
  footprint/

JSON 안에는
id(상위 그룹에 속하는 id도 포함), name, note, sha, part..
각 테이블에 들어가는 스키마 내용이 들어감

JSON만으로도 다시 완벽한 DB를 재구축 가능

SQLite 역할

JSON은 원본(Git 추적, 버전관리), SQLite는 kicad_dbl(ODBC)와 연결하기 위한 캐시 역할 App은 JSON을 불러와서 SQLite를 재구축하는 방식임. v2_data_flow

서버 구축 및 동기화

DB 서버 구축 이유

동기화, 점유, 충돌 등을 구현하려고 DB 서버 구현했었음

  • 동기화 기능까지만 구현하고 다시 계획 진행.

GitLab API만으로는 마지막 동기화 이후에 어떤 파일이 변경되었는지 확인하는 작업을 구현하기가 어려웠고(아니면 전체 JSON을 다시 동기화해야한다..), 커서 기반으로 변경 내용을 추적하기로 했음.

Gitlab JSON 데이터와 DB 서버 데이터가 섞여있는 과도기라고 보면 될 거 같다.

커서(마지막 변경 ID)로 변경된 파일 추적 후, 변경된 JSON 파일만 패치해서 최소한의 동기화를 구현하려고 함.

오프라인 작업내용 동기화 구현

오프라인에서 작업한 파일들 pending_ops.json에 기록. 온라인으로 전환 후 동기화 시 큐에 있던 내용 Git에 커밋.

동작 순서

v2_process

초기 설정

  1. App 설치(현재는 직접 빌드해야함, 아직 exe로 만들진 않았음.)
  2. ODBC 설치 및 연결
  3. Python 설치
  4. Gitlab 및, 서버 구축
  5. App에 Gitlab 토큰, 경로 등 등록 klm_screenshot_setting

사용 흐름

  1. App 실행 후, Vault Password 입력(Gitlab 토큰 저장 비밀번호)
  2. DB 추가 및 수정
  3. 헤더의 SAVE 버튼 누르면 동기화 및 KiCad 반영.

한계 및 문제점

JSON-SQLite 이중 관리

  • JSON 편집과 SQLite 편집, App에서 한번 변경이 있을 때마다 두번씩 수정해야 함.

Part 속성 수정 시:

  1. 로컬 JSON 파일 수정
  2. SQLite 수정

동기화 시에도:

  1. JSON -> Gitlab 커밋
  2. JSON 변경사항 -> Postgre 서버 기록
  3. Gitlab-Postgre 서버 무결성 검증(계획만 했었음)

만약, 한쪽 실패하거나 불일치 발생 시, 검증 과정도 거쳐야 하는 복잡한 구조였음

방대한 JSON 데이터

JSON 데이터가 늘어나니, 파일 각각 매번 SHA를 비교하고, SQLite 캐쉬를 다시 만드는 작업이 매우 비효율적이게 됨.

동기화 시 JSON 데이터가 부적합하고, SQLite와 JSON 두가지 동시에 관리를 해야하는 복잡한 구조였음.

JSON 저장방식 문제점

최소한의 JSON 파일을 읽기 위해서 커밋 SHA를 기록하고, 구조를 짜보았지만, 결국 동기화를 하기 위해서 전체 JSON 파일을 불러와서 DB로 재구축을 해야한다.

이때, 수백개(나중에는 수만개가 되겠지)의 JSON을 하나하나 읽어들인다는 생각이 드니까, 매우 비효율적이라는 생각이 들었다.

피드백 및 3차 구상

2차 구현을 통해 얻은것들

JSON 기반 데이터 저장은 비효율적이다.

  • DB 서버에 데이터를 원본으로 두는 것이 합리적이다.
  • 로컬 DB(SQLite) 재생성 시 문제 발생
  • 동기화 구조 재설계 필요

DB 구조 재구성

v3_db_schema

UUIDv4 -> UUIDv7 변경

추후 기능확장 시, 시간순 정렬이 필요한 경우을 대비하여, 타임스템프 기반 UUIDv7로 변경함.

UUIDv4UUIDv7
생성 방식완전 랜덤타임스탬프 + 랜덤
시간순 정렬불가가능
DB 인덱스 성능랜덤 삽입으로 비효율적순차 삽입으로 효율적

group 제거 이유

미리 Attr_key(Colume) 프리셋을 만들어서 한번만 만들어도 되게 설계하려고 했음

  • 라이브러리 생성 시 직접 만들어 둔 group에 연결해야하는 번거로움
  • 기존 그룹을 변경할 경우, 다른 라이브러리에도 영향을 줌.
  • 중간에 group이라는 테이블이 추가가 되어 이득 대비 손해가 크다고 판단

db, part table은 큰 변경 없음 library table

  • name key 추가 (kicad_dbl에서 출력할 부품명을 선택 가능하게)

group_id를 참조하던 것들을 library_id로 변경함.

  • 다른 라이브러리에 영향 없이 수정/삭제

version/server_ver table

  • 새로 구조를 짜면서 V2 개발 마지막에 추가했던 DB 서버 기반으로 커서 기반 변경추적

version/server_ver 추가

version, 로컬에서 수정한 버전 server_ver, 마지막으로 수정된 서버 버전

db, library, part 테이블에 추가함. 이전 버전의 전체 스캔 기반에서, 두개의 컬럼으로 변경 유무 확인

오프라인 큐 재설계

파일 데이터, 라이브러리 파일 변경 내용을 JSON에 저장해뒀다가, 온라인 상태에서 동기화 시 큐의 내용을 전송하는 방식.

파일 데이터(pending_metadata_ops.json)

  • Part/Library/Db/AttrKey (Postgre 서버쪽)

라이브러리 데이터(pending_ops.json)

  • Symbol/footprint/3d (Gitlab 서버)

같은 파일을 여러번 수정 시 큐가 중복으로 쌓이는 것을 방지하기 위해 하나의 파일에는 하나의 ops만. 동기화 전, 큐 정렬을 통해 DB 서버 오류 방지(예를 들어, Part를 먼저 생성하고, Library 생성 시 FK 에러.)

라이브러리 파일과 DB(파일) 데이터를 분리하면서, pending_metadata_ops.json 큐를 새로 추가함. DB에서 바뀐 내용만 더 효율적으로 전송 가능.

큐 파일대상 데이터전송 서버
pending_metadata_ops.jsonPart / Library / Db / AttrKeyPostgres 서버
pending_ops.jsonSymbol / Footprint / 3DGitLab 서버(Postgres 경유하여)

3차 구현

v3_process

항상 SAVE를 누를 시 큐에 먼저 저장된다, 온라인 상태일 경우, 큐의 내용을 즉시 동기화.

로컬 설치 항목 및 핵심 프로그램

  1. SQLite ODBC
  2. Python

electron + (react+vite)

Electron 전환

어떤 방법을 쓰더라도 App 반응이 너무 느리다.(Tree 열기, 스크롤 등 모든게) F12 눌러가면서 캡쳐하고, 콘솔에 타이머 로그 넣어서 느린부분 해결해보려고 해도 개선이 되지 않았다.

Tauri V2에서 개선이 되었다고는 하지만, 내부 IPC 문제가 아니었을까 한다.

  • JS쪽 병목은 없고, IPC 대기시간만 길게 나옴.

그래서 electron으로 전환하기로 한다. Rust로 짜여져 있던 AI가 코드를 빠르게 마이그레이션해줬다!(만약 AI 없이 진행하던 프로젝트였다면..) 전환하면서, 백그라운드로 돌아가던 fastAPI도 불필요하다고 생각이 들어 electron IPC에 통합하기로 했다.

  • 사실 이전 버전에서 로컬에서도 fastAPI를 사용한 이유는 설계 및 기획단계에서의 누락이다.

DB/Lib 분리

KiCad가 ODBC로 SQLite를 연결하는데, App에서 동시에 같은 SQLite 쓰기가 되지 않는 문제 발생.

App 전용 DB(dev_local.db), KiCad 출력용 DB(kicad_export.db(읽기 전용)) 두개의 DB 구조로 변경함.

DB용도접근쓰기
dev_local.dbApp 전용 (편집, 동기화, 버전관리)AppO
kicad_export.dbKiCad ODBC 출력용KiCad (ODBC)X (읽기 전용)

동기화 기능 개선

동기화 기능 구현하면서 여러 시행착오를 겪었는데, 그 중 내용을 조금 적어보자면,

파일 다운로드 개선

  • 속도도 느리고, 로그에 API가 수백번씩 호출됨…
  • 동기화 해야하는 파일만 압축을 통해 한번에 전송

사실 당연히 필요한 기능이라고 생각한다.

서버-로컬 불일치 문제

  1. DB 초기화 시(DB 커서 0) App에서 동기화를 하지 않던 문제
  • 로컬 커서가 더 클 경우 전체 파일 재동기화로 해결
  1. 참조하는 대상이 서버에 없음(FK 에러)
  • Part를 만들어서 서버에 보내는데, Attr_Key가 서버에 없음(FK 에러 발생)
  • 없을 경우, 서버쪽에서 생성.
  1. 상위 노드 삭제 후, 오프라인 큐 잔존 문제
  • 라이브러리를 삭제했는데, 큐에 하위 Part ops가 남아있음.
  • 상위 노드 삭제 시 하위 ops도 자동으로 제거되도록.

오프라인 상태에서도 작업이 가능한 것이 이번 프로젝트에서의 핵심이니까, 서버와 로컬간 상태 불일치는 불가피하다고 생각함. 그래서 그것을 감지하고 복구하는 쪽에 시간을 많이 들였음. v3_queue_process

SSE 무한 루프

내가 push한 SSE를 수신해서 다시 pull.. 다시 push…

  • 자기가 push한 커서 ID를 기억하고, SSE로 수신한 ID를 확인하는 과정 추가..

충돌 해결

기본 충돌 정책은 낙관적 잠금(Optimistic Locking)으로 결정했음.

낙관적 잠금(소프트락) 선택 이유

  • DB가 분산되어 있어서 중앙 DB에서 Lock이 어려움(로컬 SQLite)
  • 중앙 DB로 Lock을 하면, 오프라인에서 작업을 하는것이 불가능.

하드락을 걸게되면 오프라인 상태에서 편집이 불가능하고, 해제 시, 하드락 의의가 사라지기 때문에 소프트락으로 결정하게 됨.

충돌 시나리오와 대책

온라인 상태 (후술할 Lock 기능으로 가능성은 낮음.)

A와 B가 같은 Part를 수정 후 Push, A가 먼저 도착. B가 도착 시, 서버는 409 Conflict

오프라인 작업 후 동기화 (사실 이걸 상정하고 만든 기능.) A가 오프라인 상태에서 작업 B도 온라인 상태에서 같은 내용 작업 후 반영

A가 온라인 상태로 복귀 후, push 시 서버는 409 Conflict

409 Conflict시 ConflictDialog 라는 충돌 관리 창을 구성함. 로컬 버전과 서버 버전을 보여주고, 둘 중 하나를 선택하는 방식.

  • 로컬 버전 선택 시, 큐에 등록 후, 나중에 서버에 재전송.

v3_Conflict

충돌된 요소는 SSE로 받더라도, 서버 버전으로 자동 적용되지 않는다. (수동으로 충돌된 요소 해결)

아직 라이프사이클 시스템은 갖춰지지 않았지만, 기본적인 쓰기, 실시간 동기화, 오프라인 작업, 충돌 해결까지는 완료.

Lock(점유) 기능

이제, 오프라인 상태에서 작업 및 충돌된 내용까지 처리가 가능해졌다. 오프라인 상태에서 작업한 내용 충돌은 불가피하고, 온라인 상태에서 충돌을 최소화 하기위해, 여려명이서 작업할 때 Lock 기능을 구현할 차례다. (SVN의 파일 Lock 기능과 유사)

TTL 기반 소프트락, heartbeat로 연장 상위 노드 선택 시 하위 노드까지 잠김(Library 선택 시 Part까지) Lock 강탈 가능

변화 시 서버에서 SSE로 다른 App에도 전파.

API역할비고
POST /lock/acquireLock 점유scopeType, scopeId, ttlSec
POST /lock/force-acquireLock 강제 점유 (강탈)다른 사용자의 Lock을 빼앗음
POST /lock/releaseLock 해제
POST /lock/heartbeatTTL 연장편집 중 자동 갱신
GET /locks현재 Lock 목록 조회

Git 의존성 제거

이전에는 로컬에서 직접 Git pull을 요청했었는데 pull 속도가 생각보다 많이 느리고, 로컬에 Git을 설치해야하는 번거로움이 있었다.

이제는 App에서 postgres 서버에 API만 날리고, postgres 서버에서 Gitlab 서버로 통신. (App과 Gitlab는 서로 직접 통신하지 않음)

보안

프로젝트 배포 시에 .env 파일을 올린다는지, AI에게 모든 권한을 준다던지, 요즘 이야기가 많이 나오는 것 같다.

물론 나도 chatGPT에 전체 로그 올렸다가 토큰도 같이 올린적이 몇번 있다..

  • 물론, 누구도 개발용 임시 토큰을 실제 배포시까지 쓸 거라고 생각하지는 않는다.

V2에서 해쉬와 패스워드를 같은 폴더에 저장하는 속된말로 “찐빠"도 저질러보고

불편하지만, 패스워드 저장 기능은 구현하지 않고, vault에 Gitlab 토큰을 저장해두고 vault Password를 입력해야 열리는 구조로 해놨다.

Electron safeStorage + vault 구조

  • Electron safeStorage: OS단으로 암호화.
  • App 실행 시 vault 비밀번호 입력해야 토큰 복호화 가능

SSE 토큰과 서버 인증

  • App -> GitLab PAT로 서버에 인증(Gitlab 서버가 아님) -> 서버가 세션 토큰 발급
  • SSE 전용토큰 별도 (TTL 5분, 자동갱신 4분)
  • SSE는 장시간 연결되기 때문에, SSE가 끊기지 않도록 따로 분리해둠.
단계동작비고
1App에서 Vault Password 입력Electron safeStorage로 GitLab PAT 복호화
2App -> Postgres 서버에 GitLab PAT 전송POST /auth/gitlab
3서버가 GitLab PAT로 사용자 검증서버가 GitLab API를 호출하여 확인
4서버가 세션 토큰 발급 -> App에 반환이후 모든 API 요청에 세션 토큰 사용

App은 GitLab 서버와 직접 통신하지 않음. 모든 인증은 Postgres 서버를 경유.

처음 설명처럼 SSE 토큰과 세션 토큰 두가지가 있음.

항목설명
세션 토큰일반 API 요청용
SSE 전용 토큰SSE 스트림 연결 전용

SSE는 장시간 HTTP 연결을 유지하기 때문에, 세션 토큰과 분리하여 별도 TTL로 관리.
세션 토큰이 탈취되더라도 SSE 스트림에 직접 접근할 수 없고,
SSE 토큰이 탈취되더라도 5분 후 만료됨.

SSE 연결 방식

  • Electron 메인 프로세스에서 Node.js HTTP 스트림으로 연결
  • 서버에서 15초 간격 heartbeat
  • 연결 끊김 시 App에서 자동 재연결 + SSE 토큰 재발급
  • 수신한 이벤트는 IPC를 통해 React에 전달

한계 및 문제점

심볼/풋프린트 편집 기능 부재

심볼과 라이브러리는 아직 뷰어 역할밖에 하지 못함.

극히 제한적인 편집만 가능.

리포지토리에 있는 심볼과, 라이브러리를 동기화하는 기능밖에 수행하지 못함.

심볼/라이브러리도 App을 통해 생성하여 추적할 수 있어야 한다고 판단.

  • 처음 설명처럼 부품은 라이브러리, 참조 데이터 두가지로 구성되어있음.
  • 그 중 참조 데이터만 App에서 관리가 가능한 상태.
  • 라이브러리도 Git을 통해 버전관리 및 추적이 가능하지만, 라이프사이클 및 고도의 잠금 구현이 어려움.

4차 계획 및 구현

이 글을 작성하는 시점, 구상 및 구현 중에 있음.

라이프사이클

라이프사이클은 다음과 같이 구상하기로 함(Altium의 영향을 많이 받은 감이 있다).

  • Design (rev 0)
    • 작업, 아직 완성(설계)하지 못한 부품 단계.
    • KiCad 부품 리스트에 표시되지 않음(완성되지 않은 부품 배치 방지).
    • 편집 보호되어있지 않음.
  • Prototype
    • 테스트, 검증 중
    • 아마 가장 많은 부품이 여기에 속할 것 같음.
  • Released
    • 검증 완료된 부품들
  • Obsolete
    • 단종된 부품들
    • 향후 KiCad DRC와 연동 가능하다면 연동할 계획
  • dispose(추가 계획)
    • 여기에 있는 부품만 “완전 삭제” 구현하여 부품 임의 삭제를 일차적으로 방지.
    • Design, Prototype 단계에 있는 부품들을 바로 Obsolete에 넣기에는 완성되지 않은 부품.
    • 단종 보다는 폐기 사이클이 자연스러운 흐름으로 보임.

part_lifecycle

released Dir를 만들어서 Design 단계의 부품을 제외하고 모두 복사하여 넣음.

  • KiCad 부품 브라우저에는 필터 기능이 없으므로 Dir 단에서 차단하여 구현

부품 수정 시 모두 Design 단계로 강등됨.

  • 기존 버전은 스냅샷 유지.
    • released/ 폴더에 그대로 있으니 계속 사용 가능.
  • 새로운 브랜치 생성(자세한 내용은 후술)

브랜치 설계

부품 수정(Prototype 이상) 시 브랜치가 생성된다. 기존 부품은 KiCad에 그대로 표시되고, Design 단계로 강등된 부품이 생성되는 방식.

KiCad는 Altium과 다르게 여러 버전을 선택하는 기능을 지원하지 않기 때문에, 맨 처음 심볼 1:1 대응 정책을 변경할 때가 온 것 같다.

심볼 1:1 정책 변경 이유

예를 들어, Released Part를 강등하면 Design, Design을 Prototype 으로 승격시키면,
라이프사이클 관리에 문제가 발생함.

  • Prototype Part를 강등시킨다면 버전 업하면서 병합시킨다고 쳐도, Released 단계에서 강등 후 버전 업은 해결하기 어려움.

심볼 파일(kicad_sym)에 하나의 심볼만 넣는것은 유지하되, 여러 리비전을 넣는 것이다.

lifecycle_example

위 그림을 예시로 들자면,

NE555P.kicad_sym
  ├ Rev_1.0                     
  ├ Rev_2.0           
  ├ Rev_3.0               
  ├ Rev_4.0           
  ├ Rev_5.0               
  └ Rev_6.0       

이런 식으로 구성한다면 해결 할 수 있을 것이다.

KiCad에서는 하나의 라이브러리에서 여러 버전을 선택 할 수 있는 기능이 없음.

로컬 전용 선택 저장 기능도 구현 필요.

대부분 상황에서 최신 리비전을 사용하겠지만, 만약의 상황을 방지하기 위해 App에서 설정 가능하게.
위 그림에서 원하는 Revision을 선택하여 배치(Design 버전 제외)

리비전 규칙

major.minor (X.X)

  • 심볼 및 풋프린트 변경 시 major 버전 증가
  • DB 데이터(메타데이터), 3D 모델 요소 변경 시 minor 버전 증가.
  • 라이프사이클 변경 시 major 버전 증가.

KiCad 라이브러리 새로고침 문제 해결

KiCad를 재시작 해야만 라이브러리가 새로고침 되는 문제가 있었다. 이 문제는 사용성 측면에서 매우 큰 걸림돌이었다.
다행이도, 이 문제는 선구자가 있었다.

https://forum.kicad.info/t/database-symbol-library-reloading/52554

요약하면, 테이블을 DROP해서 ODBC 드라이버가 캐시를 다시 만드는 구조.

KiCad는 처음 라이브러리를 불러올 때 캐시를 생성하는데,
UPDATE, INSERT로 데이터 수정 시 기존 캐시된 데이터를 계속 보여주지만,
DROP 후 다시 CREATE하여 캐시를 강제로 재생성하도록 회피

심볼/풋프린트 편집기 standalone 실행

사실상 이 프로그램의 제일 큰 문제였다. (해결했다. 아마 처음으로?) Symbols, Footprint 같은 요소는 결국 KiCad를 통해서 수정을 해야하는 문제였다.
(현재 라이프사이클 구조로는 Design 단계를 수정할 방법이 없다!)

버전관리 및 의도치 않은 수정을 통한 “참사”를 막기 위해 App을 통해서만 수정을 하도록 제한하고 싶었다.

KiCad를 통해 수정할 경우, App이 켜져있지 않다면 수정 트래킹이 되지 않는다.
또는, 파일 하나를 수정하기 위해 App->파일 선택->잠금 해제->저장->파일잠금 과 같은 골치 아픈 사용성을 가질것이다.

그래서 심볼, 풋프린트 편집기를 standalone으로 실행하려고 찾아봤지만, 파일이 없었다. 공식 Gitlab도 3년 전 이 기능 추가해주세요가 마지막.

KiCad Python API, KiCad IPC, CLI 등등 지원은 하지 않았지만, kicad, eeschema, pcbnew 세군데 모두 열리는 것을 보면 분명 독립적으로 파일이 있을 것이라고 생각했다. 찾아본 결과(AI가) .DLL 파일 안에 편집기 프레임 여는 코드가 있었고, 그걸 독립적으로 실행시켜서 구동시키기로 했다.

호출 방법(사실 나도 모름)

단계동작상세
1DLL 로드wxWidgets DLL, kicommon.dll을 ctypes로 로드
2C++ 함수 resolveMSVC mangled name으로 내부 함수 접근
3wxApp 생성wxWidgets GUI 프레임워크 초기화 (wxEntryStart)
4PGM_BASE 초기화KiCad 전역 프로그램 매니저 생성
5KIWAY + KIFACE 로드심볼: _eeschema.dll / 풋프린트: _pcbnew.dll
6OnKifaceStartvtable에서 함수 포인터 직접 접근하여 호출
7KIWAY::Player()편집기 프레임 생성 + 표시
8MainLoopwxApp 이벤트 루프 진입

요약하면, _eeschema.dll / _pcbnew.dll에서 KIFACE 인터페이스를 찾아 독립 실행 KIWAY, KIFACE은 자체 C++ 코드.

KiCad에서는 심볼/풋프린트를 읽기 모드로 설정해두고, App에서 편집기를 직접 열어서 수정 후 저장과 동기화, 버전 카운트를 동시에 할 계획이다.

향후 계획

Python 의존성 제거

Python 의존성 제거를 고민중이다.

  • 심볼/풋프린트 편집기 standalone 실행 시 ffi-napi 고려.
  • DB 재구축 python 스크립트 마이그레이션

ODBC 하나만 필요한 것이 목표(App 설치 시 내장)

만약 파이썬을 제거함으로 얻는 손해가 더 크다면, python(w).exe 만 따로 bin 폴더 구축 후 넣어 둘 계획이다.

  • 또는 KiCad 번들 Python 이용.

이미 KiCad 설치 시 Python도 내장되기 때문에, 외부 Python 설치 의존성은 제거할 것이다.

초기 설정 편의성

앱 초기 설정 자동화 (현재는 스크립트 파일 실행해줘야함.)

  • KiCad 환경변수 등록
  • *-lib-table 파일 소프트링크
  • ODBC 자동 등록

블록 다이어그램 지원

KiCad 버전 9.0, 10.0(글 쓴 시점에 출시)에서 블록 다이어그램 기능을 강화해주고 있다. 10.0 버전에서는 아트워크까지 설계 후, 블록으로 저장 가능한 것으로 알고 있다.

아직 블록 다이어그램 기능을 사용해본적은 없지만, 풋프린트 라이브러리와 비슷한 구조의 구성을 가지고 있다.

블록 다이어그램 리스트의 경우 design-block-lib-table 파일.
블록은 *.kicad_blocks dir에 저장되며, *.kicad_block 파일로 구성되어 있음.

결국, 회로 설계도 모듈 기반 설계로 변화하고 있다고 생각하는 입장이라, 모듈도 라이브러리화 하여, 버전관리 및 라이프사이클 기능을 추가 할 계획이다.

각 모듈에 들어가는 부품 추적.

  • 모듈 회로 수정시, 아트워크 수정시, 부품 개정시 리비전 계획.
  • 라이프사이클 계획.
  • 대체부품 기능을 통한 Part Revision(BOM에 따라 달라지는 rev)

블록 다이어그램 버전관리까지 구현하게 된다면, 모듈화된 회로설계로 얻는 장점을 극대화 할 수 있을것이다.

  • 빠른 회로 설계 및 반복작업 최소화.
  • 관리되고 검증된 회로를 통한 실수 최소화

여러 블록의 Part Revision을 가져와서 전체 BOM을 구성하는 기능과, 기존 Part, Library를 참조하는 이중 라이브러리 구조가 복잡해 보이지만, 구현하게 된다면, 매우 핵심 기능을 가진 프로그램이 될 거라 믿는다.

db_design_block

아직 기능 테스트는 해보지는 않았지만, DB 라이브러와 연결한다면, 다음과 같은 구조로 예상한다.

KiCad 10.0 마이그레이션

  • 9.0 버전 기준으로 개발 중 10.0 출시.
  • 심볼 라이브러리도 .kicad_symdir 폴더에 저장 (Footprint의 .prttey 폴더)
  • 심볼 1:1 정책 변경과 함께 디렉토리 및 파일 저장 구조 변경.

공급업체 API 지원(후순위)

현재 DB와 App에 Attr_key에 공급업체를 바로 등록 가능하게 구현은 해 둔 상태이다. App 테이블 편집창에서 하드코딩된 공급업체 선택 가능.

  • 현재는 Part Number 적는 칸만 구현해 둠.
  • 물론 Custom 공급업체도 가능.

미리 구성해둔 공급업체의 API를 이용해서, Part Number 조회 후, 부품 가격과 MOQ 연동하여 출력까지 계획하고 있다.

현재 part_attr 테이블에 supplier_id key를 만들어 둔 상태임.
사전구성된 supplier_id의 공급업체 API를 통해 Part Number를 조회해서 부품 가격 조회할 계획.

만드면서 느낀거

AI의 한계점과 활용.

활용 방법

처음으로 AI를 활용하여 만드는 프로젝트, 내가 만드는 것이 무엇인지 알아야 AI도 안다고 판단했다. 세번째 구현 전까지는 일부러, chatGPT에다가 질문하고, 코드/오류 일부 올리고 코드 다운로드 받고. 수동으로 계속 작업했었음.

구현하기 위한 일부를 제외하면, 대부분은 질문 위주였음. 그리고, 이 대화 프롬포트가 효율적이었던 것 같음. “구현을 하려고 하는것이 아닌, 대화를 통해 구체적인 설계를 하려고 하는것이다. 코드 작성은 자제”

물론 코드 작성은 cursor, claude code 등 여러 전문 툴이 있었지만, 코드 올리고, 다운로드 받고 한 과정은 비효율적이었다고 생각함.

지금 생각해보니 AI를 사용하면서 어떤 기능이 필요한건지 생각해보고, 그거에 대한 해결 방법을 찾아나가는 방법을 얻게 되었다고 생각이 듬.

한계점

AI는 기획을 해주지 않는다. (그래서 그 다음에 뭐하지? 라는거는 절대 안해준다.)

이 기능을 구현하기 위해서 이런 것이 필요하고, 이런 방법이 있는데, “그 방법을 구현해줘”는 매우 잘 해준다.
(역으로, 이런 방법으로 이것을 구현하면 왜 비효율적인지 설명해달라는 것도 잘해준다)

물론 방법을 안알려주면 지멋대로 만든다.

인터넷에서 본 글이 딱 알맞는 것 같다.

쓰레기 입력을 넣으면 쓰레기 출력을 만들어주는 생성기

아쉬운 점과 드는 생각들.

글을 작성하면서 문서화 및 스냅샷을 많이 남겨둘껄이라는 아쉬움도 남는다.

특히 문서화 기능이 뛰어났는데도, 여러 문서를 만들어두고 기록해두고, AI에게 확인시키는 과정을 하지 않은 것이 지금 생각하면 많이 아쉽다.

“AI 딸깍 이거 해줘” 라는 말은 누가 만든 말일까.. 심볼/풋프린트 편집기 standalone 실행 구현만 해도,
“ㅇㅇ 이거 구현 불가능함, 다른 방법 찾아라” 라는 말만 수십번은 들은 거 같다.

결국 한계점에서 말했듯이, 다른 방법으로 접근해야 한다면, 구상하고 기획해야하는 주체는 사람이다.