미리보기 기능이 있으면 올린 파일이 어떻게 보이는지 미리 알 수 있어서 좋고, Drag Drop기능이 있으면 파일을 보다 편하게 업로드 할 수 있고, 이미지 복사하여 붙여넣기 기능이 있다면 더욱 편하게 업로드 할 수 있을 것입니다.
이렇게 Image혹은 파일을 업로드 하는 것을 우리는 일상속에서 많이 찾아볼 수 있습니다.
그리하여 이번에는 Image를 업로드 할 때 많이 접할 수 있는 미리보기, Drag Drop, Copy Paste기능을 정리하여 보겠습니다.
Preview
이미지 미리보기는 요즘 이미지 업로드라면 대부분이 가지고 있는 기능이라 생각합니다.
사실 이미지를 업로드 할때 미리보기를 구현하는 것은 매우 쉬운일입니다.
이미지를 업로드하게 된다면 이미지 파일을 업로드 할 것이고, 우리는 그 이미지를 그냥 화면에 뿌려주기만 하면 되는 것이기 때문입니다.
위와 같이 이미지를 업로드 할경우 우리는 event를 통해 file객체를 얻을 수 있습니다.
이때 file객체를 읽어 화면에 보여주기 위해서는 FileReader Object를 사용하였습니다. 이 FileReader는 비동기로 작동하는 File을 읽을 수 있도록 해주는 기능을 제공해줍니다.
FileReader는 file를 읽어들이게되면 읽을 수 있는 정보를 result에 담아주게 되는데 이를 저장하였다가 <img/> 태그의 src속성에 넣어주면 위 Gif처럼 이미지를 미리보여줄 수 있습니다.
// 파일을 읽어 result데이터를 뽑아내는 코드
const reader = new FileReader();
reader.readAsDataURL(image);
reader.onloadend = () => {
if(reader.result && typeof reader.result === 'string') setImageReaderPath(reader.result);
}
Drag Drop
사용자가 편하게 이미지를 업로드 시킬 수 있게 도와주는 기능인 Drag Drop기능입니다. 이 기능은 우선 어떤 목적을 가지고 사용할 것인지가 중요할 것 같습니다.
드래그 할 수 있는 영역을 지정해 두고, 해당 필드에만 업로드 할 수 있게 하거나, 원하는 영역을 잡아 해당 영역을 드래그 드랍이 가능한 영역으로 잡는 것 입니다.
똑같은 것 아닌가??라고 생각하실 수 있지만, 코드 상으로 들어가게 되면 조금 달라진다는 것을 알 수 있습니다.
만약 드래그할 수 있는 영역을 따로 지정하여둔다면 코드 상에서 특별한 어려움 없이 해당 영역에만 이벤트 처리를 하면 됩니다.
다만 드래그를 할 수 있는 영역을 지정하지 않고, 전체영역과 같이 영역을 선택한다면, leave event를 조심해야할 것 입니다. 이유는 DragLeave이벤트는 본인 태그를 벗어나버릴 경우 호출되며, 이는 본인 태그의 자식태그로 마우스가 들어가게 되면 해당 이벤트가 발생하여 저와 같이 화면에 회색화면을 띄워주고 싶다 할 경우 문제가 생길 수 있기 때문입니다.
위와 같이 clipboardData의 item들을 꺼내와 그중 image type인 데이터들만 뽑아내게 됩니다. 이때 저는 file형태로 관리를 하고 있어 file형태로 변형하여 state관리를 하였고, 미리보기 기능을 위해 FileReader를 사용하여 미리보기 데이터를 관리하였습니다.
>는 여러가지 토큰을 일치시켜줄 수 있는 것으로 이는 몇가지 조건을 맞춰야 사용할 수 있습니다.
하나 이상의 토큰과 일치
제목 끝에만 명시가능
위 조건을 만족할 경우 사용하여 토큰을 매칭시킬 수 있습니다.
와일드 카드는 혼합이 가능하며 *는 한 제목에 여러번 작성할 수 있습니다. ex) *.*.east.>
Core NATS
NATS를 구성하는 모델들은 다음과 같습니다.
Publish-Subscribe
Request-Reply
Queue Groups
1. Publish-Subscribe
Publish와 Subscribe는 각각 메시지를 발행하는 송신자와 메시지를 수신하는 수신자로 봐도 좋을 것 같습니다.
Publish가 message를 발행할경우 Publisher와 매핑되는 토큰을 가진 Subscriber들이 message들을 읽어 처리하는 식입니다.
주고 받을 수 있는 Message는 아래의 필드들로 구성되어있습니다.
과목
패이로드(byte Array)
헤더 필드의 수
reply(optional)
Message사이즈는 max_payload설정으로 설정이 가능하고, 기본적으로 1MB가 할당되어있습니다. 최대 사이즈 64MB까지 설정할 수 있지만 무작정 메모리 크기를 늘리기 보다는 꼭 필요한 만큼만 사이즈를 늘려 관리하는 것을 권장하고 있습니다.
2. Request-Reply
요청과 응답 부분입니다. 사용자 즉 Client가 서버에 요청을 보낼경우 서버는 보인이 처리해야하는 로직들을 처리 후 Message를 Publish합니다.
이때, 동기방식으로 Message처리 후 응답을 기다렸다 Client에게 응답을 하는 것이 아닌 서버에서 처리할 부분이 완료될경우 Message는 Publish한 후 Client에게 응답을 하고, 나머지 Message를 받아 처리하는 부분은 Background로 처리합니다.
3. Queue Groups
NATS의 로드밸런싱 기능입니다. 이는 분산 queue기능을 통해 처리하며 이것은 동일한 그룹 내에서 한번만 처리되도록 보장하기 위한 기능입니다.
Queue Groups는 다음과 같은 특징을 가지고 있습니다.
Load Balancing
Message Delivery Guaratees
Scalability
Fallover
위와 같은 특징들은 모두 Message를 동일한 Queue Group내에서 한번만 처리되도록 하기 위해 분산 처리, 전달 보장, 장애 복구의 기능을 통해 메세지가 처리되도록 보장하고 있습니다. 또한 위와 같이 분산처리를 할 경우 K8s 환경에서 개발을 할경우 Pod를 얼마나 늘리든지 상관없이 얼마든지 확장 강능하다는 강점을 가지고 있습니다.
JetStream
NATS server에는 NATS의 기능보다 확장된 기능들을 제공하는 built-in distributed persistence system이 존재하는데 그것이 JetStream입니다.
JetStream에서 제공하는 기능은 다음과 같습니다.
Streaming
Replay policies
Retention policies and limits
Limits
Retention policy
Subhect mapping transformations
Persistent and Consistent distributed storage
Stream replication factor
Mirroring and Sourcing between streams
De-coupled flow control
Exactly once semantics
Consumers
Fast push consumers
Horizontaliy scalable pull consumers with batching
Cosumer acknowledgments
Key Value Store
이러한 기능들은 Option이므로 사용하실 때 필요한 기능들을 찾아 적용하시는 것이 좋을 것 입니다.
또한 NATS는 가볍고 빨라야하기 때문에 Event의 Message 즉 Payload가 적정선 이상으로 커지는 것을 경계하는 것으로 보입니다. Message를 설계를 할 때 Message의 크기를 8MB로 제한하고 설계한다면 NATS의 성능을 제대로 활용할 것 같습니다.
우리가 개발을 하다보면 Redis라는 용어를 한번쯤 들어보았을 것이고, RedisTemplate라는 것을 한번은 코드상에서 봤을 수 있습니다.
개발을 하면서 데이터를 저장할 때 쓴다고 한 Mysql, Mongodb등 기존 데이터 베이스를 두고 왜 Redis라는 Database를 또 사용하는지, 그리고 어떻게 사용하는지에 대해 다뤄보겠습니다.
Redis란?
Redis는 REmote Dictionary Server의 약자이며, Mysql과 같이 서버와는 별개로 저장이 되지 않고 메모리상에 저장되는 In memory구조로 구성되어있습니다.
데이터는 Key Value로 관리가 되고 있으며, 다양한 구조 집합을 제공함으로 다양한 구조를 Value로 저장할 수 있으며 최대 512MB의 데이터가 저장가능합니다.
Key로 데이터를 관리하기때문에 조회에 있어 가장 빠른 성능을 자랑하며, 빠른 조회 성능을 요구하는 캐싱, 세션 관리 등에 사용을 하고 있습니다.
Redis는 In memory 구조로 key value형태로 데이터를 저장 하고 있으며, key로 데이터를 관리하기때문에 조회에 있어 빠른 성능을 자랑하며, 현재 가장 인기 있는 key value store입니다.
Key Value로 처리하면서 Redis는 Row로 저장되는 것 보다는 Column을 저장한다는 것으로 보이고, 이때문에 많은 양의 데이터를 저장하고 지속적으로 관리하는 기존 데이터베이스와 사용용도는 다르다고 필자는 생각합니다.
또한 InMemory구조로인한 데이터를 지속적으로 백업해주어야하는 문제가 있으며, 이를 해결하여 사용하기보다는 기존의 다른 데이터베이스와 함께 사용하면서 용도에 맞춰 사용하는 것이 가장 현명한거 같다 생각합니다.
Redis는 요청에 의했을 때는 Single Thread 개념을 가져오지만, background에서는 Multi Thread를 지원하는 만큼 데이터를 조회하는데에 있어 빠른 성능을 강점으로 가져가는 것 같습니다.
이처럼 Redis는 java의 Map과 같이 Key Value로 데이터를 저장하며, inmemory구조지만 Redis를 사용하는 상황은 분산 시스템에 있습니다. 분산시스템에서 동일한 key value의 저장소를 공유해야만 하는 상황이라면 Redis를 사용하여 해당 Issue를 해결하는 방법도 좋은 방법이 될 수 있습니다.
사용법
설치
우선 필자는 Database관리를 Docker로 하고 있기 때문에 Container로 만들어 관리를 하도록 하겠습니다.
1. Redis Image Pull
docker pull redis
Docker에서 지원해주는 Redis Image를 다운받아줍니다.
2. Redis Container run
docker run --name test-redis -p 6379:6379 redis
redis이미지를 토대로 container를 생성하여 실행하여줍니다. port는 redis기본 port인 6379로 설정하였습니다.
3. redis접속
docker exec -it test-redis /bin/bash
위 명령어를 통해 실행중인 container에 접속해주도록 합니다.
redis-cli
위 명령어 입력을 완료하면 container로 실행중인 redis안에 접속할 수 있습니다.
4. CRD
set {key} {value}
get {key}
del {key}
위 명령어와 같이 set을 통해 데이터를 등록할 수 있습니다. key는 식별자 역할을 함으로써 수정할 때에도 동일하게 set명령어를 사용할 경우 해당 key의 value를 변경할 수 있습니다.
이처럼 Redis자체를 사용하는 것은 쉽게 사용할 수 있습니다. 그럼 이제 서버에서 어떻게 사용하는지를 알아보겠습니다.
JAVA에서 Redis사용법
spring:
data:
redis:
host: localhost
port: 6379
Application.yml설정입니다. Redis기본 port가 6379이기 때문에 따로 port가 겹치지 않아 6379Port로 설정하여 사용하였습니다.
@Configuration
public class RedisConfig {
@Value("${spring.data.redis.host}")
private String redisHost;
@Value("${spring.data.redis.port}")
private int redisPort;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
}
@Bean
public RedisTemplate<String, String> redisTemplate() {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
RedisConfig설정입니다. 연결을 위해 redisHost와 redisPort값을 받아 Redis저장소에 연결 즉 connect를 하고, 이후 RedisTemplate를 정의함으로써 보다 편하게 사용할 수 있게 정의하였습니다.
@Repository
@Transactional
@RequiredArgsConstructor
public class TestStore {
//
private final RedisTemplate<String, String> redisTemplate;
public void create(String key, String data) {
ValueOperations<String, String> opreation = redisTemplate.opsForValue();
opreation.set(key, data);
}
public void update(String key, String data) {
ValueOperations<String, String> opreation = redisTemplate.opsForValue();
opreation.set(key, data);
}
public String load(String key) {
ValueOperations<String, String> opreation = redisTemplate.opsForValue();
return opreation.get(key);
}
public void remove(String key) {
redisTemplate.delete(key);
}
}
TestStore입니다. RedisTemplate를 통해 데이터를 CRUD를 하는 작업을 담당하게 구현하였습니다.