1. Create

  • URL : <domain>/<version>/user
  • Method : POST
  • Request Header
    • Content-Type: application/json
  • Request Body (format = JsonObject)
    • Userinfo(userid, username, password, email, phone_number, address,  ... ) - JsonObject
    • secretHash HMAC(signing_key(clientSecret, sha256)[clientId + username]) - String
  • Response

    • If success, 
    • If failed, response is HTTP status code with customed error message

 

2. Read

  • URL : <domain>/<version>/user/me
  • Method : GET or POST
  • Request Header :
    • Authorization: Bearer <YOUR_ACCESS_TOKEN>
  • Response
    • If success, response is HTTP status code with userinfo that matched with access token.
    • If failed, response is HTTP status code with customed error message.

2.1. Read by List

  • URL : <domain>/<version>/users
  • Method : GET or POST
  • Request Header : 
    • Authorization: Bearer <YOUR_ACCESS_TOKEN>
    • Content-Type: application/json
  • Request Body
    • Query Parameters - JsonObject

 

 

 

3. Update(덮어쓰기? 부분업데이트?)

  • URL : <domain>/<version>/user/me
  • Method : PUT
  • Request Header
    • Authorization: Bearer <YOUR_ACCESS_TOKEN>
    • Content-Type: application/json
  • Request Body
    • Updated UserInfo - JsonObject
  • Response
    • If Success, return requester's id
    • If failed, response is HTTP status code with customed error message.

 

 

 

4. Delete

  • URL : <domain>/<version>/user/me
  • Method : DELETE
  • Request Header
    • Authorization: Bearer <YOUR_ACCESS_TOKEN>
  • Response
    • If Success, return customed message that user delete is completed.
    • If failed, return HTTP status code with customed error message.

 

 

 

 

 

5. User Table

  • DB : mysql
  • Storage Engine : InnoDB (Transaction이 많이 일어나기 때문에 MyISAM보다 InnoDB가 유리)
  • Schema : portal_user 
  • 향후 잘못되었다 판단한 부분은 빨간색으로 표시

 

Column Type Nullable PK 설명
id varchar(255)    O 사용자 고유 식별자
name varchar(50)     사용자 이름
email varchar(100)     사용자 이메일
authorities varchar(255) → ????     권한 → 역할의 조합이므로, 테이블을 따로 빼서 관리한다.
password varchar(255)     암호화된 비밀번호
role varchar(30) → ????     역할 → 권한에 종속되는 개념이므로, 정규화가 필요함.
last_logined_date datetime     마지막 로그인된 날짜 및 시간
account_create_date datetime     계정 생성 날짜
account_last_update_date datetime     마지막 계정 정보 수정 날짜 → 로그성임. 
credential_last_update_date datetime     비밀번호 마지막 변경 날짜 → 바로 밑 항목과 비슷한 성격이다.
비밀번호 만료일을 가지고 계산할 수 있는 항목임.
credential_expire_date datetime     비밀번호 만료일 → last update를 이 컬럼으로 대체 할 수 있음.
is_account_enabled bit(1)     계정 활성화 여부
is_account_locked bit(1)     계정 잠금 여부
is_credential_expired bit(1)     비밀번호 만료 여부 → 이 컬럼이 필요한가?
is_account_expired bit(1)     계정 만료 여부 (spring security isEnabled()와 매칭되는 column)

 

 

 

5.1. DB Table 설계시, 참고자료

https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/core/userdetails/UserDetails.java

 

 

5.2. 찾은 문제점

  • Spring Security의 Default UserDetails를 보고 필드를 설계를 하였으나, 설계한 필드들이 "왜"쓰이는 필드들인지 이해를 하지 못하였다.
  • 오버헤드가 걸리지 않는 선에서 충분히 credential expire date에 대해 판별할 수 있지만, credential_expire_date와 is_credential_expired와 같은 중복케이스가 첫 설계시 존재했다.
  • 데이터에 비해 불필요하게 크기를 잡았다. 불필요한 데이터 크기 설계는 자제하도록 하자.
  • mysql 데이터 타입을 잘 모르다 보니 효율적인 데이터 타입에 대한 선택을 하지 못했다.
  • 정규화를 하지 못했다.

 

 

 

6. 20200423 개선된 DB 설계

 

6.1. User Schema

Column Type Nullable PK 설명
id varchar(20)    O 사용자 고유 식별자
name varchar(10)     사용자 이름
email varchar(30)     사용자 이메일
password varchar(255)     암호화된 비밀번호
last_logined_date datetime O   마지막 로그인된 날짜 및 시간
account_create_date datetime     계정 생성 날짜
account_last_update_date datetime     마지막 계정 정보 수정 날짜
credential_expire_date datetime     비밀번호 만료일
is_account_enabled bit(1)     계정 활성화 여부
is_account_locked bit(1)     계정 잠금 여부
is_account_expired bit(1)     계정 만료 여부

 

 

6.2. Authority Schema(복합 PK)

Column Type Nullable PK FK 설명
id varchar(20)   O User.id User Table로부터의 외래키
role varchar(10)   O   User에게 부여된 역할

0. Table of Content

 

 

1. 개발 환경

  • STS 3.9.7
  • Spring Boot 2.2.6
  • Java 1.8

 

 

2. Dependency

plugins {
	id 'org.springframework.boot' version '2.2.6.RELEASE'
	id 'io.spring.dependency-management' version '1.0.9.RELEASE'
	id 'java'
}

group = 'com.jeonghyeong'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
	compile group: 'com.sun.mail', name: 'javax.mail', version: '1.6.2'
	
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
}

test {
	useJUnitPlatform()
}

 

AWS에서 제공하는 SDK를 사용해도 무방하나, SMTP를 사용하는데 있어서 가장 큰 요소로 작용한 것은 의존성의 용량이다.

AWS에서 제공하는 SDK와 SMTP를 이용하기 위한 의존성의 크기를 비교해보았다.

 

 

2.1. AWS SDK for SES Dependency

aws-java-sdk-ses의 의존성을 조사해본 결과 다음과 같았고, 이 의존성에 대한 용량을 파악해보기로 하였다.

 

aws-java-sdk-core + aws-java-sdk-ses + jmespath-java = 160B * 3 = 480B

 

 

 

2.2. SMTP를 이용하기 위한 Dependency

javax.mail의 의존성을 조사해본 결과 다음과 같았고, 이 의존성에 대한 용량을 파악해보기로 하였다.

 

javax.mail + javax.activation = 320B

 

의존성 용량에 대해서는 큰 차이는 없지만, 엄격하게 생각한다면 AWS-SDK를 사용하는 것 보다는 SMTP를 사용하는 방법이 컴파일 하는 시간을 단축시킬 수 있을 것이다.

 

 

 

 

 

3. Hands On Lab

SES를 처음 이용하게 되면 SES Sandbox가 설정되어 있다. Sandbox가 설정이 되어있으면, 다음과 같은 제약사항이 따른다.

	1. 하루 보낼 수 있는 제한이 200개로 제한
	2. 초당 발송 이메일 1개
	3. 등록된 이메일로만 이메일 전송 가능
Sandbox를 해제하기 위해서는 AWS Support Center에 case를 open하여 sandbox를 해제하여야 한다.

자세한 절차는 직접 콘솔에 들어가서 확인 후, 가이드에 따라서 절차를 진행하는 것을 권장한다.

 

 

 

 

먼저, 다음과 같이 SES console에서 이메일을 등록하고, 이메일 인증을 거쳐야 Amazon SES를 이용가능하다.

 

 

 

 

이메일 인증 절차를 진행하게 되면, 등록한 이메일 주소로 다음과 같은 메일이 발송된다. 메일에 첨부된 verification request link에 대해 http request를 보내면 최종적으로

SES console의 email verification status가 verified로 바뀐다. 이제 등록한 이메일을 사용할 수 있게 되었다.

 

 

 

SMTP를 이용하기 위해서는 별도의 IAM Credential이 필요하다. 다음 화면에서 발급 절차를 진행한 후, 발급된 credential를 잘 보관하길 바란다.

 

 

 

 

download한 credential정보와 Amazon SES SMTP Server Specification을 다음과 같이 spring boot의 application.properties에 기입한다.

#SES Credentials
aws.ses.username=<YOUR_SMS_CREDENTIAL_USERNAME>
aws.ses.password=<YOUR_SMS_CREDENTIAL_PASSWORD>


#Amazon SES SMTP Server Info
aws.ses.host=email-smtp.ap-south-1.amazonaws.com
aws.ses.port=587

 

 

 

이제, Email의 정보를 담을 DTO 클래스를 다음과 같이 생성한다.

package com.jeonghyeong.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor

public class Email {
	
	private String title;
	private String content;
	private String sender;
	private String receiver;

}

 

 

 

 

실질적인 테스트 이메일 발송 워크플로우를 EmailService.java에 기입 후, @Service annotation을 기입한다.

package com.jeonghyeong.service;

import java.io.UnsupportedEncodingException;
import java.util.Properties;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.jeonghyeong.dto.Email;


@Service
public class EmailService {
	
	@Value("${aws.ses.port}")
	private String smtpPort;
	
	@Value("${aws.ses.host}")
	private String sesHost;
	
	@Value("${aws.ses.username}")
	private String sesUsername;
	
	@Value("${aws.ses.password}")
	private String sesPassword;
	

	public void send(Email email) throws MessagingException, UnsupportedEncodingException {
		
		
        Properties props = System.getProperties();
    	props.put("mail.transport.protocol", "smtp");
    	props.put("mail.smtp.port", smtpPort); 
    	props.put("mail.smtp.starttls.enable", "true");
    	props.put("mail.smtp.auth", "true");
    	
    	Session session = Session.getDefaultInstance(props);

    	
    	System.out.println(email.getSender());
        MimeMessage msg = new MimeMessage(session);
        msg.setFrom(new InternetAddress(email.getSender(),"<EMAIL_SENDER_NICKNAME>"));
        msg.setRecipient(Message.RecipientType.TO, new InternetAddress(email.getReceiver()));
        msg.setSubject(email.getTitle());
        msg.setContent(email.getContent(),"text/html");
        
        //msg.setHeader("X-SES-CONFIGURATION-SET", "ConfigSet");
        
        Transport transport = session.getTransport();
        
        try{
            transport.connect(sesHost, sesUsername, sesPassword);	
            transport.sendMessage(msg, msg.getAllRecipients());

        }
        catch (Exception ex) {
            System.out.println("Error message: " + ex.getMessage());
        }
        finally{
            transport.close();
        }
	}
}

 

 

 

 

 

Service를 외부에 노출시키는 Rest Controller를 다음과 같이 작성해준다.

package com.jeonghyeong.controller;

import java.io.UnsupportedEncodingException;
import javax.mail.MessagingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.jeonghyeong.dto.Email;
import com.jeonghyeong.service.EmailService;

@RestController 
public class EmailController {
	
	@Autowired
	private EmailService emailService;
	
	
	@RequestMapping("/email")
	public String emailSender(@RequestParam("title") String title, @RequestParam("content") String content, @RequestParam("sender")String sender,
			@RequestParam("receiver")String receiver) throws UnsupportedEncodingException, MessagingException {
		Email email = new Email(title, content, sender, receiver);
		
		try {
			emailService.send(email);
		}catch (Exception e) {
			System.out.println(e);
		}
			
		return "Sending Email is success, Please Check your Email.";
	}
}

 

 

위 코드를 작성한 뒤,

localhost:8080/email?title=<YOUR_EMAIL_TITLE>&content=<YOUR_EMAIL_CONTENT>&sender=<REGISTERED_EMAIL_ADDRESS_IN_SES>&receiver=<EMAIL_RECEIVER_ADDRESS>

를 호출하면 다음과 같은 이메일을 받을 수 있다.

 

 

 

 

4. Source Code

 

1. Secondary Index를 사용하는 이유

 

다음과 같은 은행 계좌를 관리하는 테이블을 생각해보자.

 

OriginCountry가 Germany인 모든 데이터를 불러온다고 가정하자. Query를 통해서 불러오기 위해선 AccountID와 CreationDate을 알고 있는 상황이어야 하지만

실제 Query상황에서는 이를 알 수 있는 방법이 없기 때문에 scan을 사용해야한다. scan을 사용하면 모든 데이터에 대해 검색을 실행하기 때문에 그만큼 실행속도가 느리다.

 

 

 

 

그러나 GSI를 사용하게 되면 다음과 같이 Primary Key를 원하는대로 바꿔서 테이블을 새로 생성 할 수 있다.

 

GSI Table(우측 테이블)을 생성하면서 기존 OriginCountry 속성을 Partition Key로 설정하였다.

기존 테이블에서 scan으로 처리하였던 요청을 query의 "KeyCondition" 함수를 이용하여 "OriginCountry eq Germany" 조건을 이용하게 되면 모두다 읽어버리는 scan보다 Read Capacity가 적게 들게된다.

 

출처 : https://www.youtube.com/watch?v=ihMOlb8EZKE

 

 

 

2. Secondary Index의 제약사항

 

다음 링크를 참조 바랍니다.

https://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/Limits.html#limits-api [DynamoDB의 제한 값]

 

 

 

 

3. Secondary Index 설계 원칙

3.1. 인덱스의 수를 최소한으로 유지한다.

  • GSI의 경우, 별도의 용량단위를 부여받기 때문에, 원본 테이블의 sync를 맞추기 위해서는 추가로 용량을 소모하며 이를 줄이는 것이 비용, Storage, I/O 관점에서 효율적이다.
  • 그러므로, 자주 query가 이루어지지 않는 속성에 대해서는 Secondary Index를 생성하지 않는다

 

3.2. LSI 이용시 index에 없는 속성을 projection하지 않는다.

  • LSI의 경우, LSI테이블에 존재하지 않고 원본 테이블에 존재하는 속성에 대해서 쿼리가 가능하다.
  • 다음 다이어그램은 위 경우를 모식화한 것이다.

 

  • LSI에 존재하지 않는 속성에 대해 query를 하게 되면 원본 테이블에서 해당 속성에 대해 찾게 된다.
  • 그 결과, 그만큼 지연시간이 걸리며, row전체를 조회한 다음 projection을 실행하기 때문에 불필요한 데이터에 RCU를 소모하게 된다.
  • 결론적으로, LSI 역시 GSI처럼 필요없는 속성은 인덱스에 포함해서는 안된다.

 

3.3. Sparse Index의 활용

  • Dense Index와 Sparse Index에 대해서는 3.3.1과 3.3.2를 참고한다.
  • 밑의 인덱스 개념을 한마디로 쉽게 설명하면 다음문장과 같다.
  • "테이블에서 Partition Key다음으로 유일하고 특징을 나타내는 속성을 GSI의 Partition Key로 설정하여 Partition을 상황에 맞게 효율적으로 커스트마이징 한다."

 

3.3.1. Dense index

  • 하나의 레코드에 하나의 포인터가 할당된다.
  • 데이터의 모든 키(Partition Key, Sort Key, 일반키 등...)가 인덱스에서 표현이 가능한 형태이다.
  • 이러한 형태는 Primary Key를 이용하여 1회의 Disk I/O 만으로 원하는 레코드를 찾을 수 있는 장점이 있다.

 

 

 

 

 

 

3.3.2. Sparse index

  • Dense Index처럼 모든 키를 가지고 있기 되면 용량이 커진다.
  • 용량이 커지는 단점을 보완하기 위해 데이터 블록마다 하나의 key-pointer쌍을 가지게 하였다.
  • 이 같은 설계를 통해 여러개의 데이터를 하나의 블록으로 합침으로써 Dense Index보다 레코드의 개수를 줄일 수 있다.
  • 레코드의 개수가 줄어든 만큼 Disk I/O는 줄어들지만, 블록안의 속성에 대해 검색할 경우에는 추가적인 disk I/O를 요구할 수 있다.

 

 

 

 

 

 

 

4. 참고문헌

 

1. @Controller

 

전통적인 Spring MVC Controller는 View 기술을 이용하여 화면을 리턴하는 방식이다.

json, xml형태의 객체를 반환하기 위해서는 @ResponseBody를 사용하여 json, xml형태의 객체를 리턴 할 수 있다.

@Controller의 workflow는 다음과 같다.

 

 

위 workflow의 순서를 간략히 설명하면 다음과 같다.

  1. Client는 URL로 서비스에 Request를 보낸다.
  2. 해당 요청을 처리할 수 있는 Handler를 찾아 Mapping 하기 위해 Dispatcher Servlet가 인터셉트 한다.
  3. Mapping 된 Handler가 존재하는 Controller에서 해당 요청을 처리하고, Model and View 객체를 반환한다.
  4. Dispatcher Servlet는 반환된 객체를 client에게 전달한다.

 

 

2. @RestController

 

View를 통해서 객체를 반환하지 않으며, 이 annotation을 통해 Controller에서 직접적으로 client에게 객체를 반환할 수 있게 되었다.

또한, 위 공식 문서의 첫 문장을 보면 다음과 같은 description이 있다.

"@RestController는 @Controller와 @ResponseBody가 annotated된 편리한 anntation이다." => @RestController = @Controller + @ResponseBody

즉, @ResponseBody의 성질에 의해서 HTTP응답에 대해 return 값을 자동으로 변환까지 해준다.

Workflow는 다음과 같다. 위 @Controller과 비교해보자.

 

 

 

 

 

 

3. 참고 문헌

1. DynamoDB Partitioning 원리

  • DynamoDB는 자체 내부 Hash Function이 있다.
  • Partition Key값을 파라미터로 계산된 Hash Value를 기준으로 DynamoDB Table 내부의 파티션이 결정되어 데이터는 적재된다.
  • Primary Key가 복합키인 경우에도 단일 Partition Key로 이루어진 경우와 같은 방식으로 partition key hash값을 계산하여 partition에 적재한다.
  • 그러나 같은 partition key를 가진 데이터들은 물리적으로 가깝게 설계되며, Sort Key 값으로 정렬된다.

 

 

 

 

2. Partition Key 설계시 고려사항

 

2-1. 분산된 워크로드

  • Partition Key는 테이블에서 데이터가 저장되는 논리적 및 물리적 파티션을 결정하는 요소이다.
  • DynamoDB는 결정된 파티션들에게 프로비저닝된 Capacity Unit을 균일하게 분배한다.
  • 즉, Partition Key의 분산도를 고려하지 않고 설계시, 요청에 대해 효율적인 Capacity Unit을 사용할 수 없게 된다.
  • 물론 DynamoDB는 관리형 서비스이기 때문에 어느정도 조절은 해 주지만, 최적화를 위해서는 위 관리형 서비스에 의존하지 않고, 직접 튜닝하는 것이 적절하다.

 

 

 

다음 데이터는 실제로 필자가 DynamoDB를 학습하며 설계하였던 테이블이다. (나중에 이 테이블을 보니 최악이었다 :(  ) 

 

 

위 테이블의 경우, 사내 회의실 예약 시스템에 대한 정보를 구현하기 위해 설계한 내역이며, partition key가 INFO_TYPE이며, sort key가 HASH_VALUE로 설계되었다. 

현재 위 테이블의 Partition Key의 값은 단 2개로 나뉘어져 있다. 위 DynamoDB Partition 아키텍처는 다음과 같이 그림으로 나타낼 수 있다.

 

 

 

 

 

위 아키텍처를 염두에 두고 생각해보자. 회의실의 개수는 건물에 한정적이고, 이 한정적인 회의실에서는 수많은 직원들이 회의실을 사용한다.

그러므로 상대적으로 회의실 정보보다 예약 정보가 훨씬 많을 것이고, 데이터에 대한 접근도 예약정보가 훨씬 많을 것이다.

그 결과, 같은 Capacity Unit을 제공받은 partition이지만, I/O요청이 압도적으로 많은 "res" partition key를 가진 파티션은 그만큼 비효율적인 요청처리를 하게 된다.

 

 

 

 

다음 아키텍처는 위 아키텍처를 조금이나마 개선한 아키텍처이다. (극단적인 예시이므로 참고만 해주길 바란다.)

위같이 설계를 바꾼 이유는 다음과 같다.

  • 회의실에 대한 예약은 현재 HASH_VALUE값 자체를 이용하며, [회의날짜]#[회의실]#[회의시작시간]#[회의종료시간] 의 format을 가진다.
  • 시간의 흐름 중에서 뽑은 하나의 시간 표본은 유일하다.
  • HASH_VALUE도 시간 값 이므로, 유일한 값이다.
  • 위 HASH_VALUE를 Partition Key로 설계하면 Partition의 분포도가 좋아진다.
  • 그러므로 한 파티션에 대한 I/O 병목현상이 개선될 수 있다.

 

 

 

 

 

 

2-2. Partition Key에 난수 추가 (Sharding)

  • Sharding은 Partitioning의 한 부분이며, "Horizontal Partitioning"과 같은 의미이다.

  • 기존 설계한 Partition Key에 난수를 추가하게 되면, Hash Function에 의해 결과값이 다양해진다.
  • 그 결과로 DynamoDB 내부의 논리적 및 물리적 파티션의 분포는 분산된다.
  • 파티션이 분산됨에 따라 병렬처리를 개선할 수 있다.

 

2-3. Partition Key를 이용한 효율적인 쓰기 작업 분산

  • DynamoDB는 기본적으로 파티션의 크기와는 상관 없이 균일한 Capacity Unit을 할당받는다.
  • DynamoDB는 관리형 서비스이기 때문에 어느정도 파티션간 Capacity Unit을 프로비저닝 해주지만, 이에 의존한 key설계는 올바르지 못하다.
  • 그러므로, 쓰기 작업에 대해서도 타겟 파티션을 분산시켜 정의할 필요가 있다.

 

 

case1. partition key의 정렬 순서대로 데이터를 쓰는 경우

 

  •  위와 같이 쓰기작업을 정의할 시, 한 파티션의 Capacity Unit을 집중적으로 쓰기 때문에 병목현상이 발생 할 수 있다.

표 출처 : AWS DynamoDB 설명서

 

 

case2. partition key의 순서와 무관하게 분산시켜 데이터를 쓰는 경우

표 출처 : AWS DynamoDB 설명서

  • 위와 같이 쓰기 작업을 개선할 시, 매 작업마다 다른 파티션의 Capacity Unit을 사용하기 때문에 case1 보다 Capacity Unit의 사용률에 대해 보다 유연하게 대처 가능하다.

 

 

 

 

 

3. 참고문헌

금일 개발환경 구축을 위해 개발 팀원들에게 Amazon Linux에서 새로운 계정을 발급하여 배포하였다.

 

 

 

팀원이 사용하는 계정에서는 문제가 없었지만, 패키지를 설치하고 관리하는 admin역할을 담당하는 팀장님의 계정에 문제가 있었다. 그 문제는 다음과 같다.

 

 

다음과 같이 sudo 권한 획득을 요구한 유저가 sudoer 파일에 없다는 에러 메시지가 출력이 되었다.

위와 같은 에러메시지를 해석하기 위해 sudoer라는 파일에 대해 찾아보았다.

 

 

 

 

Path : /etc/sudoers (Amazon Linux2 기준)

Role : Sudoers allows particular users to run various commands as the root user, without needing the root password.

      (Sudoers는 특정 유저에게 여러 커맨드를 root password를 사용하지 않고 root user처럼 사용할 수 있게 허용한다.)

 

 

 

 

 

위 파일 내부에서 다음과 같이 특정 유저에게 sudo 권할을 부여할 수 있는 명령을 찾았다.

 

 

 

 

 

Allow root to run any commands any command anywhere와 밑의 root ALL=(ALL) ALL를 보았을 때, 이와 같은 권한을 다른 유저에게 주면 sudo를 언제든 사용할 수 있기 때문에 나는 다음과 같이 testuser에 대해서도 root와 같이 어떠한 커맨드든 어디서든 sudo 권한으로 실행할 수 있게 권한을 인가하였다.

 

 

 

 

 

위와 같이 권한을 testuser에게 준 결과, 다음과 같이 sudo 권한을 준 명령어가 잘 작동함을 볼 수 있었다.

 

'IT > Security' 카테고리의 다른 글

[Linux] user not in the sudoers file.  (0) 2020.01.06

오늘 파이썬 코드 작업 도중 git에 올려야 하는 코드를 제외하고 commit메시지를 작성하였다. 

 

commit을 취소 하기 위해

 

git reset --hard HEAD^

 

를 입력하여 commit을 취소하고 다시 git add 를 실행하는데 Nothing to Commit이라고

 

출력이 되었다. 다시 깃허브 명령어를 보니 --soft 옵션이 아니고 --hard옵션으로 실행되어

 

commit이 취소되는 동시에 해당 파일들은 예전 코드로 변경이 되어 있었다.

 

흘려본적 없는 식은땀이 났고, 고객사에 제출해야하는데 코드가 모두 날아가서 매우 당황스러웠다.

 

git-scm을 찾아서 되돌릴 방법을 찾아 본 결과 획기적인(그 당시의 나에게만) 명령어가 있었다.

 

그 명령어는 다음과 같다.

SYNOPSIS
git reflog <subcommand> <options>




SUBCOMMAND & OPTIONS
git reflog [show] [log-options] [<ref>]
git reflog expire [--expire=<time>] [--expire-unreachable=<time>]
	[--rewrite] [--updateref] [--stale-fix]
	[--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>…​]
git reflog delete [--rewrite] [--updateref]
	[--dry-run | -n] [--verbose] ref@{specifier}…​
git reflog exists <ref>

 

 

위 documentation을 보고 git reflog을 입력하니 다음과 같이 메시지가 출력이 되었다.

 

다행히 내가 날려먹은 커밋 기록인 "debug on amazon linux_20191126"이 존재하였고,

 

해당 HEAD로 파일들을 reset시켜 기록을 되돌려 놓을 수 있었다. 

 

 

이참에 git reset의 --hard, --soft, --mixed옵션에 대해서 한번 다시 공부하기로 하였다.

 

옵션을 표로 정리해 보았다.

 

  index staged / unstaged 작업 중이던 파일
git reset --hard 해당 index 취소 unstaged로 변경 작업 취소(유실)

git reset --mixed

(Default option)

해당 index 취소 unstaged로 변경 작업 보존
git reset --soft 유지 staged 상태 유지 작업 보존

 

 

이 표로 정리하고 나니, 내가 식은 땀을 흘렸던 상황이었을 때 git reset -mixed를 사용하여 

 

commit을 취소하고, 작업은 살리는 쪽으로 했어야 했었다.

 

다시는 이런 실수 안하겠지...

'IT > Git' 카테고리의 다른 글

[Github] git reset 작업 취소 방법  (0) 2019.11.26

어제 학습한 servlet에 대해 의문점이 생겼다.

 

Chrome에서 servlet을 1회 최초 호출하고, Safari에서 servlet을 1회 호출한 결과는 위 사진과 같이 나왔다.

2번 호출 했으니, init 매서드가 2번 호출 될 것이라고 생각했었는데 아니었다.

 

 

 

그 이유는 다음과 같다.

서블릿의 동작과정을 한눈에 보기 쉽게 다이어그램으로 정리해보았다.

 

 

 

 

Servlet는 최초 호출이 되면 해당 selvlet은 메모리에 적재됩니다. 그 이후로 다른곳에서도 중복 호출 하게 되면, 똑같이 Servlet를 불러오는 것이 아니라, 메모리에 적재된 Servlet을 재사용해서 사용자와의 인터페이스를 제공한다고 한다.  Servlet는 메모리에 이미 init()이 실행된 채로 메모리에 적재되어 있기 때문에 또 다시 같은 Servlet을 쓰지 않고도 일반 호출보다 더 빠르게 인터페이스를 제공 할 수 있게 된다.

 

 

다시 한번 정리하면, Servlet는 메모리에 적재된 Servlet을 재사용하기 때문제 일반 웹보다 훨씬 빠르고 효율적으로 인터페이스를 제공 할 수 있도록 도움을 준다.

1. why we use noSQL

  • 유연성 : rdb와 비교하여, 유연한 스키마를 제공하고 있기 때문에 데이터베이스를 반정형 또는 비정형적인 데이터를 취급하는데 유리하다. 이러한 스키마는 빠르고 반복적인 개발을 유도하게 된다.

  • 확장성 : noSQL 데이터베이스는 고가의 스펙이 뛰어난 서버 대신, 분산형 클러스터를 이용하기 때문에, 서버 운영에 대한 부담 절감과 더 적은 스트리지를 기반으로 운영 할 수 있게 됨에 따라 더 많은 데이터를 담을 수 있게 되었다.

  • 고성능 : 특정 비정형 데이터(document, key-value, graph … ) 및 엑세스 패턴에 대해 특화시킬 수 있기 때문에 특정 워크로드를 유사하게 받아들이는 것이 아닌 그대로 받아들일 수 있게 된다. 이에 따라 유사한 환경을 제공하는 rdb보다 더 뛰어난 성능을 보일 수 있다.

 

 

2. noSQL 데이터 유형

  • key-value : 이 데이터 유형은 rdb와 비교하여 분할성이 크기 때문에 큰 범위로써의 확장을 가능하게 한다. 이로 인해 이 데이터는 게이밍, 광고 및 iot와 같은 사례에서 뛰어난 성능을 발휘한다.

  • document : 어플리케이션 상 데이터는 객체 또는 애플리케이션 코드에서 데이터는 종종 객체 또는json으로 표현된다. 이 형식이 개발자들에게 효율적이며 직관적이기 때문이다. 이 형식을 기반으로하는 데이터베이스를 사용하면 코드 상의 데이터의 형식과 동일한 형식으로 맞출 수 있기 때문에 보다 손쉽게 데이터를 저장하고 query작업을 할 수 있다.

  • graph : SNS와 추천 엔진 등 데이터 간 연결성이 큰 어플리케이션을 쉽게 해주는 데이터이다.

  • in memory : 게이밍과 광고 기술 애플리케이션에는 밀리초의 응답 시간을 필요로 하며, 언제라도 수신 트래픽이 급등할 수 있는 리더보드, 세션 스토어, 실시간 분석을 요구하는 어플리케이션에 특화되어있다.

  • search : 많은 앱에서 로그를 수집하는 환경에서 특화되어있다.

 

 

 

3. RDS vs noSQL

  SQL Databases NOSQL Databases

스키마

구조와 데이터 모델은 사전에 정의되기 때문에 데이터 입력시 레코드에서 사전에 필터링 된 데이터를 입력해야한다. 새로운 유형의 데이터가 저장되기 위해서는 전체 데이터베이스가 변경되어야 한다.

상황에 따라 다르다. 데이터의 분류가 필요없이 즉시 새로운 정보를 추가 할 수 있으며, sql과 달리 다른 필요한 데이터를 같이 저장 할 수 있다.

확장성

수직적이다.

수요를 처리하기 위해서는 더 좋 은 성능의 서버를 이용해야한다.여러 SQL데이터베이스에다 데이터를 저장 할 수 있지만, 이에 따른 추가적인 작업이 일반적으로 필요하다.

수평적이다.

용량을 더 늘리기 위해서는 데이터베이스 관리자가 단숨히 서버나 클라우드 인스턴스를 늘리기만 하면 된다. noSQL데이터베이스는 자동적으로 데이터를 필요한만큼 분산시킨다.

성능

일반적으로 동작하고 있는 시스템에 따라 상이하다. 또한 최고 성능을 위해서 쿼리, 인덱스 및 테이블 구조를 자주 튜닝해야한다.

일반적으로 기본 하드웨어의 클러스터 크기, 네트워크 성능, DB를 호출하는 어플리케이션의 기능에 비례한다.

데이터 호출

ANSI SQL에서 유래한 다양한 명령문으로 데이터를 불러온다

객체를 기반으로 한 API Calling을 데이터를 불러온다.

일관성

강한 일관성

제품마다 상이하다.

예를 들어 MongoDB의 경우는 강한 일관성, Cassandra는 최종 일관성을 기반으로 한다. dynamoDB는 둘다 지원

 

 

최종일관성 : 데이터를 조회 하였을 때, 그 상태의 값 그대로 불러 올 수 있다.

강력한(즉시) 일관성 : 데이터를 조회 할 때, 해당 데이터가 writing상태이면, locking상태가 되어 데이터를 불러올 수 없음.

사진 출처 : https://medium.com/system-design-blog/eventual-consistency-vs-strong-consistency-b4de1f92534d

1. 테이블

  • 데이터 레코드의 집합

 

2. 항목(Item)

  • 테이블에 있는 하나의 레코드 자체를 의미

 

3. 속성(Attribute)

  • 기본적인 데이터 요소로 더 이상 나뉠 수 없는 것, 항목의 조각

  • 속성에 대한 내포 속성은 32 깊이 까지 허용

  • 대부분의 속성은 스칼라(하나의 값만 가질 수 있음)다.

 

 

 

4. primary key

DynamoDB의 PK는 두가지로 구성이 되어 있다.

출처 : https://aws.amazon.com/ko/blogs/database/choosing-the-right-dynamodb-partition-key

4.1 Partition Key

  • 하나의 속성으로 구성되는 단순 기본 키

  • Partition Key의 설계에 따라 데이터의 분산도가 달라진다.
  • Partition Key 선택 기준 (ex. 고객ID, 디바이스 ID, 모델 번호 …)

    • 고유 값이 가장 많은 속성

    • 균일한 비율로 무작위로 요청되는 속성

 

4.2. Sort Key (Range Key)

  • Partition Key와의 조합으로 복합 Primary Key를 구성할 수 있으며, 이를 이용하여 보다 유연하게 DynamoDB에 대한 요청을 처리할 수 있다.

  • Sort Key 선택 기준

    • 1:n, M:N 관계 모델링

    • 효율적 / 선택적 조회

    • 범위 조회

 

 

 

5. Secondary Index

  • Primary Key(Hash Key)만으로 DynamoDB에서 원하는 데이터에 대한 요청을 처리하는 것에 대해 비효율적인 엑세스 패턴이 발생할 수 있다.
  • 이를 방지하고자 테이블에 대해 다른 엑세스 패턴을 설계할 수 있는 Secondary Index개념이 존재하며, RDB개념에서 "View"개념과 비슷하다.
  • Secondary Index에는 GSI(Global Secondary Index) LSI(Local Secondary Index)가 존재한다.
  • GSI의 default limit 개수는 20이다(Support Center를 통해 증가 요청 가능).
  • LSI의 default limit 개수는 5이다.

 

 

GSI(Global Secondary Index)

LSI(Local Secondary Index)

키 속성

  • 기본 테이블과 파티션키, 정렬키 모두 다르게 하여 새로운 primary key를 설정 할 수 있다.

  • 그러나 Hash Key의 항목은 무조건 생성한 GSI의 attribute로 포함되어야 한다.
  • 기본 테이블과 동일한 파티션 키가 있지만, 정렬 키가 다른 인덱스다.

파티션 키 당

인덱싱 크기 제한

  • 크기 제한 없음

  • Partition Key값마다 인덱싱 된 모든 항목의 전체 크기는 10GB 이하.

읽기 일관성

  • 최종 일관성만을 지원한다.

  • 최종 일관성, 강력한 일관성을 지원한다.

프로비저닝된 처리량 소비

  • 읽기 및 쓰기에 대해 기본 테이블과는 다른 별도의 용량단위가 존재한다.

  • 기본 테이블 업데이트 시 발생되는 인덱스 업데이트에도 인덱스 용량단위를 사용한다.

 

  • 기본 테이블의 용량단위를 소비한다.

  • 기본 테이블에 쓰기를 실행할 경우, 기본 테이블의 용량단위를 쓰기 때문에 Capacity Unit이 상대적으로 GSI보다 높게 나올 수 있다.
프로젝션
  • GSI에 존재하는 attribute에 대해서만 projection을 수행할 수 있다,
  • LSI에 존재하지 않고 기본테이블에 존재하는 attrubute에 대해 query 또는 scan할 경우, 기본 테이블에서 해당 속성을 자동으로 가져와 결과를 반환한다.

 

 

6. WCU, RCU

  • AWS의 기본 사상은 "쓰는만큼만 지불"하는 개념이다. 이 개념은 DynamoDB에도 그대로 적용되며, 읽기 및 쓰기하는 용량에 따라서 과금의 정도가 다르다.
  • 읽기에 대한 용량을 RCU(Read Capacity Unit)라 부르고, 쓰기에 대한 용량을 WCU(Write Capacity Unit)라고 부른다.
  • 프리티어로 25 WCU, 25 RCU가 제공된다.
  • ex) 테이블 25개 생성, 각 테이블 별로 1 RCU, 1 WCU의 프로비저닝 된 용량을 할당 -> 총 25 RCU, 25 WCU Free Tier Limit을 넘지 않음
  • ex) 테이블 2개 생성, 각 테이블 별로 15 RCU, 15 WCU의 프로비저닝 된 용량을 할당 -> 총 30 RCU, 30 WCU Free Tier를 제외한 나머지 5 RCU 및 5WCU에 대해 과금

 

6.1. RCU (Read Capacity Unit)

읽기의 경우, Default로 최종적 일관된 읽기가 적용된다.

  • 최종적 일관된 읽기의 경우, 1RCU = 8 KB / s

  • 강력한 일관된 읽기의 경우, 1RCU = 4 KB / s

6.2. WCU (Write Capacity Unit)

  • 1 WCU = 1 KB / s

  • 계산시, 1KB 단위로 올림하여 계산한다.
    ex) item 크기가 3.6KB 항목 하나를 쓸 경우, 4KB로 계산하며, 4WCU를 소비한다.

 

 

 

6.3. 참고 - 일관성

최종적 일관된 읽기(Eventual Consistent Read)

테이블을 읽을 때, 응답은 최근 완료된 쓰기 작업의 결과를 반영하지 않을 수 있다. 그러므로, 응답에는 부실 데이터가 일부 포함될 수 있다.

 

강력한 일관된 읽기(Strongly Consistent Read)

성공한 모든 이전 쓰기 작업의 업데이트를 반영하여 최신 데이터를 기반으로 응답을 반환한다. 그러나 DynamoDB에서 이를 적용하면 다음과 같은 단점을 수반한다.

- 강력한 일관된 읽기는 최종적 일관된 읽기보다 지연 시간이 더 길 수도 있습니다.

- 글로벌 보조 인덱스에서는 강력히 일관된 읽기가 지원되지 않습니다.

- 강력한 일관된 읽기(strongly consistent read)는 네트워크 지연 또는 중단이 발생한 경우에 사용이 어려울 수 있습니다. 이 경우 DynamoDB는 서버 오류(HTTP 500)를 반환할 수도 있습니다.

 

 

 

7. 참고 문헌

 

+ Recent posts