./room.go:39:6: main redeclared in this block
* Mac
* vscode
* Golang 1.20
상황
go build 하는 도중 에러가 발생했습니다.
해결
하나의 폴더 아래 main이라는 함수를 2개를 사용해서 발생한 에러입니다.
main 함수를 하나만 가지도록 이름을 수정한 후 go build를 다시 하니 정상 작동 했습니다.
* Mac
* vscode
* Golang 1.20
go build 하는 도중 에러가 발생했습니다.
하나의 폴더 아래 main이라는 함수를 2개를 사용해서 발생한 에러입니다.
main 함수를 하나만 가지도록 이름을 수정한 후 go build를 다시 하니 정상 작동 했습니다.
* Mac
* vscode
* Golang 1.20
go mod init 한 후 go build 했을 때 발생했습니다.
go build 하기 전에 gopls 설정을 했기 때문에 거기서 발생한 문제 일수 있어 들어가 봤더니 필요 없는 구문이 적혀있어 제거 후 VSCode를 재실행 했습니다.
재실행 후 잘 작동합니다.
./room.go:39:6: main redeclared in this block (0) | 2023.08.09 |
---|---|
package command-line-arguments is not a main package (0) | 2023.08.09 |
package is not in GOROOT (0) | 2023.07.26 |
./room.go:39:6: main redeclared in this block (0) | 2023.07.26 |
gopls was not able to find modules in your workspace.When outside of GOPATH, gopls needs to know which modules you are working on ~ (2) | 2023.07.26 |
* Mac
* vscode
* Golang 1.20
vscode에서 go run main.go를 실행하는데 발생한 에러입니다.
1. vscode 설정 수정
*이 방법은 인터넷에서 가장 많이 찾은 방법이지만 현재 2023.07.26 기준으로 (제가 했을 때)안됩니다.*
먼저 settings에 들어가서 gopls을 검색 후 settings.json에 들어갑니다.
들어간 후 “gopls” : { “"build.expandWorkspaceToModule": true,”} 입력해주면 됩니다.
* 저는 Invalid settings: gopls setting "experimentalWorkspaceModule" is deprecated 발생하여 작동하지 않습니다. 뜨면서 해결되지 않았습니다.
2. Golang이 directory를 인식하지 못하는걸 인식할 수 있도록 수정
최상의 directory(root 폴더)에 go.work를 생성해준 후 go work use <폴더 위치>를 통해 go.work에 폴더 위치를 추가해줍니다.
저는 2번으로만으로 해결되었습니다.
./room.go:39:6: main redeclared in this block (0) | 2023.08.09 |
---|---|
package command-line-arguments is not a main package (0) | 2023.08.09 |
package is not in GOROOT (0) | 2023.07.26 |
./room.go:39:6: main redeclared in this block (0) | 2023.07.26 |
current directory is contained in a module that is not one of the workspace modules listed in go.work. You can add the module to the workspace using: (0) | 2023.07.26 |
EXISTS는 먼저 쿼리를 조회해서 하나의 레코드를 가지고 오고 나서 서브쿼리를 실행하여 서브쿼리에 대한 결과가 존재하는지를 확인해서 있다면 레코드를 출력해줍니다.
여기서 가장 중요한 것은 서브쿼리에 대한 결과가 NULL이여도 결과가 존재한 것이기 때문에 결과가 True가 되어 레코드를 출력하게 됩니다.
테스트 해볼 사이트: https://sqltest.net/
SQL Script
CREATE TABLE mysql_test_a (
id VARCHAR(30),
name VARCHAR(30) NOT NULL
);
INSERT INTO `mysql_test_a` (`id`, `name`)
VALUES ('1', 'abab');
INSERT INTO `mysql_test_a` (`id`, `name`)
VALUES ('2', 'John');
INSERT INTO `mysql_test_a` (`id`, `name`)
VALUES ('3', 'dd');
INSERT INTO `mysql_test_a` (`id`, `name`)
VALUES ('4', 'bb');
INSERT INTO `mysql_test_a` (`id`, `name`)
VALUES ('5', 'aa');
INSERT INTO `mysql_test_a` (`id`, `name`)
VALUES ('2', 'omokoe');
INSERT INTO `mysql_test_a` (`id`, `name`)
VALUES ('6', 'John');
SQL Query
SELECT * FROM mysql_test_a a
where exists (
select 1 from mysql_test_a b
where a.id = b.id
and b.name = 'John'
);
결과
'2', 'John'
'2', 'omokoe'
'6', 'John'
왜 '2', 'omokoe' 가 결과 값에 포함되어 있을까?
앞서 말했드이 먼저 쿼리를 조회하고 나서 서브쿼리를 조회 하는데, 서브쿼리 b 전체 행을 조회합니다. b 테이블에 id 값이 2인데 name에 값이 John인 레코드가 있으면 return True가 되고 레코드에 출력되는 것입니다. 그래서 '6', 'John'도 출력되는 것입니다.
MySQL 계정에 DB 권한 부여 (0) | 2024.09.05 |
---|---|
MySQL Query 최대 용량 (1) | 2024.09.05 |
Join 속도 개선 (0) | 2023.05.17 |
시세 플랫폼은 실시간 시세 데이터를 안전하고 빠르게 처리해야합니다. 거래소 데이터를 받아 가공한 후 내부 서비스들에게 제공하거나 과거 데이터를 누적 또는 여러 정보를 합성하여 제공하기도 합니다.
그리고 최우선 목표로 낮은 지연시간과 빠른 장애복구가 있습니다.
시세 플랫폼은 총 3가지 파트로 구성되어 있습니다.
수신부는 거래소가 제공하는 시세 데이터를 UDP 멀티캐스트 그룹에 접속해서 읽어오는 일을 합니다. 그리고 처리부에게 데이터를 전송할 때 수신 시각을 Header에 포함하여 처리부에서 총 처리 시간을 측정하는데 사용합니다.
처리부는 비즈니스 로직이 모여있는 곳입니다. 처리 결과를 Redis에 저장하거나 실시간 정보를 서비스들에게 바로 전달합니다. 비즈니스 로직 중에는 Blocking I/O가 있기 때문에 처리 시간에 가장 많은 영향을 주는 곳이기도 합니다.
조회부는 REST API를 서비스들에게 제공합니다.
이 중 처리부는 코드 변경이 빈번하게 발생하여 장애 발생 확률이 높습니다. 만약 처리부에서 장애가 발생하고 장애가 복구 되어도 API를 사용하는 서비스들은 여전히 장애를 겪게 됩니다. 왜냐하면 장애 시간동안 데이터가 유실되었거나 오염되었을 수도 있기 때문입니다.
<문제 해결>
이러한 문제를 해결하기 위해 처리부와 Redis를 두 개의 그룹으로 만들어서 처리할 수 있습니다.
평상시에는 처리부A에만 할당하다가 장애가 발생할 시 처리부B로 전환하는 방법입니다. 이 방법의 장점으로는 처리부 A와 처리부 B를 서로 번갈아 배포하면서 둘 간의 차이를 비교할 수 있고, 배포의 문제가 있다면 트래픽 전환만으로 빠르게 롤백할 수 있어 배포 부담감을 많이 줄일 수 있습니다.
시스템 장애를 대비해 각 처리부를 2개로 늘리고 ZooKeeper Group을 통해 Leader를 선출하도록 했습니다. 이 경우 중복 데이터 발생하는 것을 막기 위해 오직 각 그룹에 Leader만 데이터를 처리하도록 하였습니다.
이런 처리도 결국 수신부가 각 처리부의 갯수만큼 데이터를 보내야 하기 때문에 수신부의 성능 저하를 야기하게 됩니다.
이 문제를 해결하기 위해 수신부와 처리부 사이에 메세지 브로커를 사용하여 해결할 수 있었습니다. 메세지 브로커를 사용하므로서 수신부와 처리부를 Decoupling 할 수 있고 수신부는 데이터를 한 번만 전송해도 되게 됩니다.
하지만 시세 플랫폼에서 가장 중요한 것이 ‘낮은 지연시간‘인데 메세지 브로커로 인해 지연시간이 늘어나게 됩니다. 그렇기에 메세지 브로커로 어떤걸 사용할지가 중요합니다.
메세지 브로커의 후보는 아래와 같습니다.
수신부에 속도가 개선되어도 처리부에서 속도가 느리다면 지연시간이 늘어날 수 있습니다.
처리부에서는 TCP Socket으로부터 데이터를 읽는 일과 비즈니스를 처리하는 일을 합니다. 이 때 처리부가 데이터를 읽는 속도가 지연시간에 큰 영향을 줍니다. 그렇기 때문에 데이터를 읽는 Thread와 비즈니스 처리 Thread를 따로 두어 처리해야 합니다.
이를 위해 Spring Data Redis에서 제공하는 ReactiveRedisTemplate을 사용했습니다. Spring Data Redis에서 기본 제공하는 Lattuce를 사용하여 네트워크 라이브러리인 Natty를 사용하고 Natty의 Channel은 Socket을 추상화한 레이어로서 커넥션이 맺어진 이후 EventLoop에 등록됩니다. EventLoop는 무한 루프를 돌면서 수신 버퍼의 데이터를 읽는 역활을 합니다.
EventLoop은 운영체제에 따라 Nio, Epoll, KQueue등 여러 방식을 지원합니다.
NioEventLoop는 실행되면(run) 버퍼에 데이터가 있는지 확인하고(select), 데이터를 읽고(read) 변환하여(decode) 결과를 통보(notify) 합니다. 그렇기에 NioEventLoop가 비즈니스 로직을 처리하지 않는 것이 중요합니다.
비즈니스 처리에는 Blocking I/O가 포함되어 있기 때문에 처리 성능을 높이기 위해 다음과 같이 멀티 스레딩을 사용해야 합니다. 하지만 이 경우 비즈니스 처리의 순서가 역전될 수 있다는 문제가 있습니다.
<문제 해결>
이 문제를 방지하기 위해 멀티 스레딩 대신 EventLoopGroup을 사용하여 해결할 수 있습니다.
EventLoop는 Queue를 이용하여 순서를 보장하고 하나의 Thread만 사용하기 때문에 동기화가 필요 없다는 장점이 있습니다. EventLoop를 사용해도 어떤 EventLoop에서 처리해야 할지 알아야 순서를 보장할 수 있기 때문에 미리 종목 코드를 알아야하고 이를 위해서 JSON을 객체로 변환해야 했습니다.
문제는 이 변환 작업이 트랙픽 양이 증가함에 따라서 NioEventLoop의 CPU 자원을 많이 사용하여 지연의 원인이 된다는 것입니다.
<문제 해결>
이 문제를 해결하기 위해 Redis Pub/Sub에서 제공하는 Channel을 사용했습니다.
수신부가 데이터를 보낼 때 처리부의 EventLoop 개수만큼 Channel을 나누어 보내고, 처리부는 이 Channel 명을 보고 해당하는 EventLoop를 찾는 것입니다. 이를 통해 수신부에서 발송한 데이터의 순서를 처리부에서 그래도 유지할 수 있고, NioEventLoop에서 더 이상 객체 변환을 하지 않아도 되기 때문에 성능이 향상된것을 확인 할 수 있습니다.
Reference
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class SampleService {
public void performTask() {
log.debug("Debug message");
log.info("Info message");
log.warn("Warning message");
log.error("Error message");
}
}
Reference
[error]Spring-docs + WebSecurityConfig (0) | 2023.06.11 |
---|---|
[error]Sequence "MEMBER_SEQ" not found; SQL statement: (0) | 2023.06.04 |
Fault Tolerant System은 하드웨어나 소프트웨어의 결함이 발생하더라도 규정된 기능을 수행할 수 있는 시스템을 말합니다. 그렇기 때문에 컴퓨터 부품 또는 모듈이 제거 또는 재장착을 하는 동안 시스템은 기능을 수행하고 있습니다.
Fault Tolerant System 단계
1. try-with-resources
try (InputStream in = new FileInputStream(inFile); // try에 자원 객체를 전달하면 finally 블록으로 종료 처리를 하지 않아도 try 코드 블록이 끝나면 자동으로 자원을 종료해주는 기능.
OutputStream out = new FileOutputStream(outFile)) {
...
} catch(IOException ex) {
...
}
2. multicatch
try (InputStream in = new FileInputStream(inFile); // try에 자원 객체를 전달하면 finally 블록으로 종료 처리를 하지 않아도 try 코드 블록이 끝나면 자동으로 자원을 종료해주는 기능.
OutputStream out = new FileOutputStream(outFile)) {
...
} catch(IOException ex) {
...
}
3. switch-case에 문자열 지원
String str = "비밀";
switch(str) {
case "진실": ...
break;
case "비밀": ...
break;
...
default: ...
break;
}
4. Fork/Join
// 기본
long total = 0;
for(int loop = from; loop <= to; loop++) {
total += loop;
}
// Fork/Join
public class ForkJoinSample {
static final ForkJoinPool mainPool = new ForkJoinPool();
public static void main(String[] args) {
long from = 0;
long to = 10;
GetSum sum = new GetSum(from, to);
// 계산을 수행하는 객체를 넘겨주어 작업을 실행하고 결과를 받음
Long result = mainPool.invoke(sum);
System.out.println("total:" + result);
}
}
public clas GetSum extends RecursiveTask<Long> {
long from, to;
public GetSum(long from, long to) {
this.from = from;
this.to = to;
}
public Long compute() {
long gap = to - from;
// 작업 단위가 작을 경우
if(gap <= 3) {
long result = 0;
for(int loop = from; loop <= to; loop++) {
result += loop;
}
return result;
}
// 작업 단위가 클 경우
// 두 개의 작업으로 나누어 동시에 실행시키고, 두 작업이 끝날 때 까지 결과를 기다림
long middle = (from + to) / 2;
GetSum prev = new GetSum(from, middle);
prev.fork();
GetSum post = new GetSum(middle + 1, to);
// compute() 함수는 람다식을 통해서 기존의 값에 어떻게 연산을 할지 지정할 수 있습니다.
return post.compute() + prev.join();
}
}
5. ECC 암호화 기능 제공
JDK, JRE, JVM이란? (0) | 2023.06.15 |
---|---|
[ERROR]cannot find symbol (0) | 2023.05.29 |
[ERROR]Illegal modifier for the interface field Observer.name; only public, static & final are permitted (0) | 2023.05.29 |
AOP이란? (0) | 2023.05.11 |