Now Loading ...
-
바로그림 프로젝트 설계와 시행착오 정리
바로그림 프로젝트를 시작하면서 마인드맵을 만들고, 이미지 정보와 프롬프트를 어떻게 다룰지 정리했다. 이 글은 작업일지에서 프로젝트 방향과 시행착오가 확인되는 내용만 추려 압축한 회고다. 개인 정보, 서버 설정, 운영 로그, 핵심 프로젝트 이미지는 넣지 않았다.
설계에서 먼저 정리한 것
초기에는 기능을 마인드맵으로 나누고, ERD와 Flow Chart를 보완하려 했다. 태그 정보, 게시판 성격의 데이터, 인덱스 고려 사항을 메모했고, 이미지에서 얻을 수 있는 EXIF 정보도 기능 후보로 검토했다.
이 단계의 기록은 확정된 설계서가 아니라 기능과 데이터 관계를 정리하는 과정이었다. 그래서 어떤 테이블과 인덱스가 실제로 최종 반영되었는지는 이 회고에서 단정하지 않는다.
EXIF 수집 방식의 변경
초기 크롤러는 페이지를 넘기며 정보를 모으는 방식이었다. 페이지 번호가 제한된 화면에서 다음 구간으로 이동하는 방법을 고민했고, 동적 페이지와 iframe 요소를 처리하는 방법도 확인했다.
진행하면서 기존 방식이 수작업 성격이 강하고 오류가 발생하면 유지하기 어렵다고 판단했다. 이후 바이트 형태의 EXIF 정보를 디코딩하고, PIL을 활용해 추출하는 방향을 시도했다. 예외 처리와 모듈화, 데이터 수집 부분의 try except 적용도 보완 과제로 남겼다.
검색과 데이터 연동을 위한 검토
MySQL의 이미지 정보를 Elasticsearch에 가져오고, 프롬프트·부정 프롬프트·태그를 검색에 사용할 방법을 고민했다. 검색 로그와 태그를 시간 정보와 함께 쌓아 트렌드에 반영하는 안도 메모했다.
이 내용은 데이터 흐름과 검색 조건에 대한 구상이다. 원문에는 구현을 위한 SQL, query, 가중치에 대한 시도도 있지만, 모든 시도가 완료되거나 성능 개선으로 이어졌다고 볼 근거는 없었다. 그래서 결과가 확인되지 않은 사항은 계획과 검토 단계로 남겼다.
배포와 운영에서 남은 과제
Elasticsearch 설치 환경에서는 Windows에서 리소스 제한을 다루는 방법을 여러 방향으로 시도했다. 배포 환경으로는 Synology, AWS, goorm IDE, Azure, Docker 구성을 비교했고, 도메인과 SSL 인증서 발급 절차도 미리 조사했다.
Logstash는 일정 주기로 데이터를 가져오는 방식과 파이프라인을 분리해 실행하는 문제를 기록했다. 다만 당시 작업일지에는 환경별 설정, 내부 주소, 운영 로그가 섞여 있어 이 글에는 옮기지 않았다. 공개 가능한 회고로 남길 수 있는 사실은 문제를 발견하고 대안을 비교·검토했다는 과정까지다.
정리 기준
작업일지의 짧은 TODO, 강의별 메모, 중복된 검색 설명은 각각 따로 게시하지 않았다. Django와 Elasticsearch의 일반 개념은 별도 내용 정리 글로 합쳤고, 이 글에는 바로그림에서 어떤 문제를 고민하고 어떤 방향으로 바꾸려 했는지만 남겼다.
-
Elasticsearch 검색과 데이터 파이프라인 정리
바로그림 작업일지에는 MySQL의 이미지·프롬프트 정보를 Elasticsearch로 가져오고, 검색 로그를 바탕으로 트렌드를 살피려는 구상이 남아 있다. 이 글은 그 과정에서 반복된 개념을 하나로 묶고, 현재 공식 문서로 확인 가능한 내용만 남긴 정리다. 당시 메모의 배포·운영 설정과 미완료 계획은 결과로 표현하지 않았다.
검색용 필드 설계
프롬프트처럼 전문 검색할 내용과 태그처럼 정확히 분류·집계할 내용을 같은 필드 유형으로 다루지 않는 방향을 검토했다. Elasticsearch에서 text는 전문 검색에 쓰고, keyword는 정렬·집계·term 같은 term-level query에 주로 사용한다. keyword 필드를 전문 검색에 쓰는 것은 권장되지 않는다. Elasticsearch keyword field 문서
작업일지의 프롬프트, 부정 프롬프트, 태그와 검색 조건에 관한 기록은 이 구분을 어떤 데이터에 적용할지 고민한 메모다. 실제 매핑과 analyzer는 데이터 형태와 검색 요구를 확인한 뒤 결정해야 한다.
문구 검색과 term vectors
강의와 작업일지에서는 match_phrase와 term vectors를 살폈다. match_phrase query는 입력 텍스트를 분석한 뒤 phrase query를 만든다. 따라서 단어가 같은지만 보는 검색과 문구 순서·인접성을 고려해야 하는 검색은 구분해서 선택해야 한다. Elasticsearch match phrase query 문서
Term vectors API는 특정 문서의 필드에 대해 term 정보와 통계를 조회할 수 있다. 이 API 결과를 활용해 빈도를 계산하는 방식은 작업일지에 있었지만, 그 값만으로 검색 품질이나 사용자 선호를 확정할 수는 없다. Elasticsearch term vectors API 문서
MySQL에서 Elasticsearch로 가져오는 구상
MySQL의 그림 정보 테이블을 검색용 인덱스로 가져오는 방법과, 마지막으로 수집한 시간 이후의 데이터만 가져오는 방법을 검토했다. Logstash JDBC input은 JDBC 인터페이스가 있는 데이터베이스에서 데이터를 읽어 올 수 있으며, 조회 결과의 각 행을 event로 만든다. 주기 실행은 schedule, period, interval 중 하나로 설정할 수 있다. Logstash JDBC input 문서
증분 수집을 할 때는 마지막 실행 상태와 기준 열을 분리해 관리해야 한다. 여러 JDBC input이 sql_last_value를 쓴다면 각 input에 별도의 last_run_metadata_path가 필요하다. 이 점은 작업일지에서 파이프라인을 나누어 실행하려 했던 기록을 다시 확인하는 기준이 되었다.
Kibana과 트렌드 메모
Kibana에서는 data view가 하나 이상의 index, data stream, alias를 가리킬 수 있고 Discover 같은 분석 기능은 보통 이를 통해 데이터에 접근한다. Kibana Data views 문서
바로그림에서는 프롬프트 태그와 검색 로그를 시간 단위로 모아 순위·트렌드에 반영할 수 있을지 검토했다. 이는 구현 완료 결과가 아니라 데이터 기준, 기간 조건, 가중치가 더 필요했던 설계 메모다. 검색 로그를 쓴다고 해서 사용자 의도나 품질이 자동으로 보장되는 것은 아니므로, 실제 적용 전에는 수집 범위와 평가 기준을 따로 정해야 한다.
-
Django 학습 핵심 정리: 모델, 요청 처리, 템플릿
바로그림 프로젝트를 준비하며 Django 강의 1~20강을 따라 학습했다. 이 글은 당시 강의 노트에서 프로젝트와 연결되는 개념만 모아 다시 확인한 정리다. 강의에서 사용한 특정 환경 설정값, 개인 개발 경로, 인증 관련 예시는 포함하지 않았다.
데이터 모델과 화면에 전달할 데이터
프로젝트에서는 이미지, 프롬프트, 태그처럼 저장할 대상을 먼저 나누고 관계를 고민했다. Django에서 모델은 저장할 데이터의 필드와 동작을 정의하는 Python 클래스이며, 일반적으로 하나의 모델은 데이터베이스 테이블 하나에 대응한다. 모델의 필드는 데이터베이스 컬럼으로 연결되고 Django는 이를 바탕으로 데이터 접근 API를 제공한다. Django Models 문서
따라서 바로그림에서 이미지 정보와 프롬프트 정보를 어떤 단위로 저장할지, 태그 정보를 별도 데이터로 둘지 검토한 기록은 모델 설계 전에 필요한 고민이었다. 다만 당시 노트는 설계 검토와 학습 과정이며, 해당 구조가 최종 구현되었다는 뜻은 아니다.
URL, View, Template의 역할
강의에서는 URL 설정, view 함수, 템플릿 작성, 템플릿에서 반복문으로 데이터를 출력하는 흐름을 다뤘다. Django의 URL dispatcher는 요청 URL과 view를 연결하고, view는 요청을 받아 필요한 데이터를 준비해 응답을 만든다. 템플릿은 동적으로 넣을 데이터와 정적 HTML을 함께 표현하는 방식이다. Django HTTP 요청 처리 문서, Django Templates 문서
이 흐름을 기준으로 하면 목록 화면은 view에서 데이터를 가져오고, template에서 반복해 보여 주는 형태로 구성할 수 있다. 강의 노트에 있던 for 문과 base.html, header, footer 분리 기록은 이 역할을 나누는 연습이었다.
폼과 GET·POST
강의 후반에는 HTML form으로 값을 보내고 view에서 POST 데이터를 처리해 저장하는 실습을 했다. Django 공식 문서는 시스템 상태를 바꾸는 요청에는 POST를, 상태를 바꾸지 않는 조회·검색에는 GET을 사용하도록 안내한다. 내부 URL로 보내는 POST form에는 CSRF 보호도 필요하다. Django Forms 문서, Django CSRF 문서
바로그림의 검색처럼 URL로 조건을 공유해도 되는 기능과, 게시물·정보를 저장하는 기능은 같은 방식으로 처리하지 않아야 한다는 점을 학습 범위에서 확인했다.
정적 파일과 공통 화면
CSS, JavaScript, 이미지는 Django에서 static files로 관리할 수 있다. django.contrib.staticfiles를 사용하고 STATIC_URL을 설정한 뒤, 템플릿에서는 static 태그로 URL을 만들 수 있다. 개발 서버에서의 정적 파일 제공 방식은 운영 환경용 설정이 아니다. Django Static files 문서
강의 노트의 CSS 분리, base.html, header·footer 구성은 화면 공통 요소와 정적 자원을 나누는 연습으로 남겼다. 특정 폰트·스타일 값이나 강의 예제 코드는 이 글에서 반복하지 않았다.
Class-Based View
함수 기반 view를 학습한 뒤 Class-Based View와 CRUD를 살폈다. Django의 generic class-based views는 목록·상세·생성·수정처럼 반복되는 패턴을 줄이기 위한 기반을 제공한다. 예를 들어 CreateView, UpdateView, DeleteView는 편집 흐름에 사용할 수 있지만, 모델과 권한·검증 규칙을 프로젝트에 맞게 정해야 한다. Django Generic editing views 문서
이 글은 강의 학습 내용을 정리한 것이며, 강의 예제의 코드나 설정을 바로그림의 실제 운영 코드로 주장하지 않는다.
-
ELK스택 활용
Elasticsearch 로컬 설치
로컬 서버 컴퓨터에 포트를 열고 서버 컴퓨터에 설치된 elasticsearch에 연결시
https 관련 인증서 문제가 발생한다.
kibana 설치
server.host는 0.0.0.0 으로 설정
검색 알고리즘 설계
이미지 프롬프트 사용
검색에 사용하는 attribute 종류들
positive_prompt
negative_prompt
model_hash
cfg_scale
denoising_strength
sampler
match_phrase문 분석
#검색 쿼리를 위한 match_pharse문 생성
def match_phrase(self,positive,phrase):
actions = {
"match_phrase": {
positive: {
"query": phrase,
"slop": 1
}
}
}
if phrase == "":
actions = {
"match_phrase": {
positive: {
"query": "blank",
"slop": 1
}
}
}
if phrase == 0:
actions = {
"match_phrase": {
positive: {
"query": "blank",
"slop": 1
}
}
}
return actions
mtermvectors 도입
# mtermvectors API 실행
response = self.es.mtermvectors(
index=self.index_name,
body={
"docs": [
{
"_id": doc_id,
"fields": [str(prompt)]
}
for doc_id in recent_ids
]
}
)
elasticsearch의 mtermvectors API를 도입
# Elasticsearch에 mtermvectors 실행
if 'docs' in response:
for doc in response['docs']:
if 'term_vectors' in doc:
term_vectors = doc['term_vectors']
if str(prompt) in term_vectors:
# 역색인 정보를 사용하여 원하는 작업을 수행합니다.
for term, info in term_vectors[str(prompt)]['terms'].items():
if term in term_freq_dic:
temp = term_freq_dic.get(term)
term_freq_dic[term] = temp + info['term_freq']
else:
if isinstance(term,float) == True:
continue
elif isinstance(term, int) == True:
continue
else:
term_freq_dic[term] = info['term_freq']
else:
print(f"No term vectors found for Document ID: {doc['_id']}")
else:
print("No documents found.")
no_dic = ['detailed','and','best','a','the','of','in','detail','masterpiece','with','at','up','by','very','perfect','to','is','on','quality','realistic',]
data_list = []
# 상위 100개의 아이템을 출력합니다.
n = 20
count = 0
i = 0
while count < n and i < len(sorted_frequency):
sf = sorted_frequency[i]
if sf[0] in no_dic:
i += 1
continue
try:
float(sf[0])
except:
data_list.append(sf)
count += 1
i += 1
tf-idf 알고리즘을 도입하는데 detail, best, a 와 같은 자주 사용하지만 검색에 큰 영향을 끼치지 못하는 단어를 dictionary에서 제외하고자 한다.
Touch background to close