Giter Club home page Giter Club logo

til's Issues

@ModelAttribute

public class Foo {
    private String name;
    private int sequence;

    // getter, setter 생략
}
@RequestMapping(value = "/foo", method=RequestMethod.GET)
public void show(@RequestParam("sequence") int sequence, @ModelAttribute("foo") Foo foo, Model model) {
    
}

http://localhost:8080/foo?name=Gildong&sequence=327

위와 같이 호출을 하면 foo 객체에 요청 파라미터의 값이 세팅이 된다.

@ModelAttribute는 아래와 같은 역할을 한다.

  • @ModelAttribute 애노테이션이 붙은 객체를 자동으로 생성한다. 단 @ModelAttribute가 지정되는 클래스는 자바빈 규약을 지켜야한다.
  • 생성된 객체에 HTTP 요청을 통해 넘어온 값들을 자동으로 바인딩한다. 위의 예제에서는 name, sequence 값을 foo 객체에 바인딩한다.
  • @ModelAttribute 애노테이션이 붙은 객체가 자동으로 Model 객체에 추가된다. 따라서 foo 객체가 .jsp 뷰단까지 전달이 된다.
  • Model 객체에 추가될 때 @ModelAttribute 애노테이션의 value 값을 이름으로 사용한다.

참고

dockerfile 명령어 정리

Dockerfile

  • 이미지를 만들기 위해 Dockerfile이라는 파일에 자체 DSL 언어를 이용해서 이미지 생성 과정을 적는다.
  • 애플리케이션을 도커 이미지로 만드는 과정을 도커라이징이라고 한다.
  • 빌드 명령어를 실행한 디렉터리의 파일들을 빌드 컨텍스트라고 한다. 이 파일들은 도커 서버로 전송된다.
  • 도커 빌드는 임시 컨테이너 생성 > 명령어 수행 > 이미지로 저장 > 임시 컨테이너 삭제 > 새로 만든 이미지 기반 임시 컨테이너 생성 > 명령어 수행 > 이미지로 저장 > ... 의 과정을 계속해서 반복한다. 명령어를 실행할 떄마다 이미지 레이어를 저장하고 다시 빌드할 때 Dockerfile이 변경되지 않았다면 기존에 저장된 이미지를 그대로 캐시처럼 사용한다.
# 이미지 빌드하기
$ docker build -t [tag name] [Directory of Dockerfile]

$ docker build -t app .

Using Avro in KSQL - VALUE_AVRO_SCHEMA_FULL_NAME

VALUE_AVRO_SCHEMA_FULL_NAME

  • 디폴트로 ksqldb를 통해 등록된 Avro 스키마는 동일한 이름과 동일한 네임 스페이스를 갖는다.
    • name : KsqlDataSourceSchema
    • namespace : io.confluent.ksql.avro_schemas
  • WITH 절에 VALUE_AVRO_SCHEMA_FULL_NAME 속성을 통해 name과 namespace를 재정의할 수 있다.
  • 예를 들어 VALUE_AVRO_SCHEMA_FULL_NAME값을 com.mycompany.MySchema로 했으면 name은 MySchema이고, namespace는 com.mycompany가 된다.

출처

Impala - CASE WHEN THEN

CASE WHEN THEN

문법

CASE WHEN a THEN b [WHEN c THEN d]... [ELSE e] END

CASE a WHEN b THEN c [WHEN d THEN e]... [ELSE f] END

예제

select case x when 1 then 'one' 
 when 2 then 'two' 
 when 0 then 'zero' 
 else 'out of range' 
 end
 from t1;
select case 
 when dayname(now()) in ('Saturday','Sunday') then 'result undefined on weekends' 
 when x > y then 'x greater than y' 
 when x = y then 'x and y are equal' 
 when x is null or y is null then 'one of the columns is null' 
 else null 
 end 
 from t1;

where 조건에서도 아래와 같이 사용가능

select *
from t1
where case x when 1 then true
else false
end

uuid_generate_v1()

  • uuid_generate_v1() : UUID를 생성할 때 사용할 수 있는 함수로 맥 어드레스와 타임스탬프를 기반으로 생성된다.

    • UUID는 식별자를 만든 컴퓨터의 ID와 식별자를 만든 시간을 나타내므로 특정 보안에 민감한 응용 프로그램에 적합하지 않을 수 있다.
  • https://www.postgresql.org/docs/9.4/uuid-ossp.html

@ElementCollection Primary Key

@ElementCollection Primary Key

  • @ElementCollection 애노테이션을 사용해서 Primary Key 설정을 해주고 싶은 경우엔 아래와 같이 @column에 nullable을 false로 해주면된다. 그러면 JOIN 컬럼과 엘레먼트 컬럼을 이용해서 Primary Key를 만든다.
@Column(name = "STRINGS", nullable = false)
@ElementCollection
private Set<String> strings;
  • 만약에 @ElementCollection 에 추가된 타입이 identifier를 가진다면 @OneToMany를 사용하는게 더 적절하진 않은지 고민해봐야 한다.

참고

Facet 쿼리

Facet 쿼리란?

  • 검색 결과에 대한 분류. 검색 결과를 세분화하여 검색할 수 있도록 분류를 만드는 것

mixed content

HTTPS 프로토콜을 사용하는 URL이 안전하지 않은 HTTP 연결을 통해 이미지를 Link할 때 발생하는 에러다.

SSL 인증서를 성공적으로 획득한후 웹사이트가 보안 연결을 통해 로드되지만 페이지의 이미지가 여전히 HTTP를 통해 로드되는 경우 오류가 발생한다.

https://sitechecker.pro/site-audit-issues/https-page-links-http-image/

spring.jpa 프로퍼티

spring.jpa 관련 프로퍼티

  • spring.jpa.database : 타겟 데이터베이스로 코드에선 Enum을 관리된다. 디폴트는 자동으로 감지된다.
  • spring.jpa.database-platform : 타겟 데이터베이스 이름, spring.jpa.database는 Enum으로 관리되기 때문에 Enum에 원하는 값이 없는 경우 대체로 사용이 가능하다.
  • spring.jpa.generate-ddl : 시작시에 스키마를 초기화 할지 여부 (기본값 : false)
  • spring.jpa.mapping-resources : persistence.xmlmapping-file과 같다.
  • spring.jpa.open-in-view : OpenEntityManagerInViewInterceptor를 등록한다. (기본값 true)
  • spring.jpa.properties.* : JPA 프로바이더에게 추가적으로 제공할 native 설정
  • spring.jpa.show-sql : SQL문을 로깅할지 말지 결정 (기본값 : false)

spring.jpa.hibernate

  • spring.jpa.hibernate.ddl-auto : hibernate.hbm2ddl.auto 프로퍼티의 숏컷이다.
    • 임베디드 데이터베이스를 사용하고 스키마 관리자가 detect되지 않은 경우의 기본값 : create-drop
    • 그 외 : none
  • spring.jpa.hibernate.naming.implicit-strategy : implicit naming strategy의 full name
  • spring.jpa.hibernate.naming.physical-strategy : physical naming strategy의 full name

Hibernate는 2개의 명명 전략(hibernate.physical_naming_strategy,hibernate.implicit_naming_strategy)을 이용해서 객체 모델의 이름을 상응하는 데이터베이스 이름으로 매핑한다.

  • spring.jpa.hibernate.use-new-id-generator-mappings : Hibernate의 새로운 IdentifierGenerator를 사용할지 여부. hibernate.id.new_generator_mappings의 숏컷으로 해당값을 설정하지 않으면 true로 설정된다.

참고 : https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.data.spring.jpa.database

CREATE TABLE

문법

CREATE TABLE [IF NOT EXISTS] table_name (
   column1 datatype(length) column_contraint,
   column2 datatype(length) column_contraint,
   column3 datatype(length) column_contraint,
   table_constraints
)
  • IF NOT EXISTS 옵션을 사용하면 존재하지 않는 경우에만 새 테이블을 생성할 수 있다.
  • IF NOT EXISTS 옵션을 사용하고 테이블이 이미 존재하는 경우 PostgreSQL은 오류 대신 알림을 발행하고 새 테이블 생성을 건너뛴다.

제약 조건

  • NOT NULL : 값이 널이 될 수 없다
  • UNIQUE
  • PRIMARY KEY : 테이블은 오직 하나의 Primary Key만 가질 수 있다.
  • CHECK : 데이터가 boolean expression을 만족해야 한다.
  • FOREIGN KEY

예제

CREATE TABLE accounts (
	user_id serial PRIMARY KEY,
	username VARCHAR ( 50 ) UNIQUE NOT NULL,
	password VARCHAR ( 50 ) NOT NULL,
	email VARCHAR ( 255 ) UNIQUE NOT NULL,
	created_on TIMESTAMP NOT NULL,
        last_login TIMESTAMP 
);

CREATE TABLE account_roles (
  user_id INT NOT NULL,
  role_id INT NOT NULL,
  grant_date TIMESTAMP,
  PRIMARY KEY (user_id, role_id),
  FOREIGN KEY (role_id) REFERENCES roles (role_id),
  FOREIGN KEY (user_id) REFERENCES accounts (user_id)
);
  • account_role의 경우 Primary Key가 두 개의 컬럼으로 구성이 된다. 그러므로 Primary Key를 table constraint로 정의해야 한다.

Spark 설정

  • spark.driver.extraJavaOptions : 드라이버에게 추가로 전달할 추가 JVM 옵션. 이 옵션을 사용해서 최대 힙 크기(-Xmx)를 설정할 수 없다. 최대 힙 크기는 클러스터 모드의 경우 spark.driver.memory로 클라이언트 모드의 경우 --driver-memory 옵션으로 지정할 수 있다.
  • spark.executor.extraJavaOptions : 익스큐터에게 추가로 전달할 추가 JVM 옵션. 이 옵션을 사용해서 최대 힙 크기(-Xmx)를 설정할 수 없다. 익스큐터의 최대 힙 크기는 spark.executor.memory로 설정할 수 있다.

PostgreSQL Upsert Using INSERT ON CONFLICT statement

Introduction to the PostgreSQL upsert

  • upsert : 테이블에 새로운 행을 추가할 때, 이미 데이터가 있으면 업데이트한다. 데이터가 없는 경우엔 새로운 행을 추가한다.
  • PostgresSQL에서 upsert를 사용하려면 INSERT ON CONFLICT 문을 사용하면 된다.
INSERT INTO table_name(column_list) 
VALUES(value_list)
ON CONFLICT target action;

target은 다음 중 하나가 될 수 있다.

  • (column_name) : 컬럼 이름 (특정 컬럼의 값을 기준으로 체크한다.)
  • ON CONSTRAINT constraint_name : 여기서 제약 조건 이름은 UNIQUE 제약 조건의 이름일 수 있다. (constraint 기준으로 체크한다.)
  • WHERE predicate

action은 다음중 하나가 될 수 있다.

  • DO NOTHING : 아무것도 하지 않는다.
  • DO UPDATE SET column_1 = value_1, ... WHERE condition : 테이블에 있는 필드를 업데이트 한다.

ON CONFLICT 구문은 PostgreSQL 9.5 이상 버전에서만 사용가능하다.

예제

  • 아래 예제는 고객의 이름이 있는 경우, 아무것도 하지 않는다.
INSERT INTO customers (NAME, email)
VALUES('Microsoft','[email protected]') 
ON CONFLICT ON CONSTRAINT customers_name_key 
DO NOTHING;

INSERT INTO customers (name, email)
VALUES('Microsoft','[email protected]') 
ON CONFLICT (name) 
DO NOTHING;
  • 아래 예제는 고객이 이미 존재하는 경우, 이전 이메일과 새로운 이메일을 합친다.
  • 아래 예에서 EXCLUDED는 새로 입력된 값을 접근할 때 사용한다.
INSERT INTO customers (name, email)
VALUES('Microsoft','[email protected]') 
ON CONFLICT (name) 
DO 
   UPDATE SET email = EXCLUDED.email || ';' || customers.email;

출처 및 참고

Content-Type

ContentType

  • request에 실어 보내는 데이터(body)의 타입 정보를 표현
  • Text타입
    • text/css
    • text/javascript
    • text/html
    • text/plain
  • File
    • multipart/form-data
  • Application
    • application/json
    • application/x-www-form-urlencoded

application/x-www-form-urlencoded

  • form에서 디폴트로 사용된다.
  • key-value&key=value... 형태로 전송된다.
  • W3에 잇는 문서에 따르면 반드시 인코딩 되어야한다. 대부분의 브라우저에서는 해당 content-type에 대해 자동으로 인코딩하도록 구현되어있다.
  • 라이브러리 또는 프레임워크를 사용해서 HTTP 요청을 보낼 경우, 인코딩이 되는지 확인을 해야한다.

FetchJoin vs Join

FetchJoin vs Join

  • Fetch Join: Fetch Join은 JPA에서만 사용되는 용어이다.
    • 조회의 주체가 되는 엔티티 이외에 Fetch Join이 걸린 연관 엔티티도 함께 SELECT 하여 모두 영속화
    • 주로 N+1 문제를 해결하기 위해서 사용됌
  • Join: Entity에 Join을 걸어도 실제 쿼리에서 SELECT하는 엔티티는 오직 JPQL에서 조회하는 주체가 되는 엔티티만 영속화한다.
    • 주로 연관 엔티티가 검색조건에 필요한 경우에 사용

출처

Kakfa Prefered

파티션 리더

  • 설정을 통해 미리 지정된 리더 파티션을 *preferred leader라고 한다.
  • 브로커가 다운되어 리더가 죽은 경우, 리더 역할을 담당할 팔로워 파티션을 ISR에서 선출(election)하게 된다. 리더 재선출은 자동적으로 이뤄진다.
  • 브로커가 다시 클러스터에 합류되더다로, 복구된 브로커에 있던 파티션들은 모두 팔로워 파티션 역할로 변경된다. 이러한 역할 변경이 발생하면 초기에 지정한 파티션 복제 설정과 다른 파티션 복제 구성 상태가 된다.
  • 이런 경우 카프카 매니저에서 Preferred Leaderfalse로 나타난다. 이는 카프카 매니저나 CLI 통해 복구가 가능하다.

Preferred Leader가 false이면 Producer, Consumer에 영향을 주는가?

Preferred Leader가 false라고 하더라도, 파티션에 리더가 할당만 되어 있다면 Producer나 Consumer에 영향을 주진 않는것으로 보인다.

출처

gradle - transitive dependency

관련글

  • https://docs.gradle.org/current/userguide/dependency_downgrade_and_exclude.html#sec:excluding-transitive-deps
  • https://www.crocus.co.kr/1587

Impala - rlike

Impala에서 rlike를 이용하면 정규표현식을 조건문에 넣을 수 있다.

-- Find all customers whose first name starts with 'J', followed by 0 or more of any character.
select c_first_name, c_last_name from customer where c_first_name rlike '^J.*';

-- Find 'Macdonald', where the first 'a' is optional and the 'D' can be upper- or lowercase.
-- The ^...$ are required, to match the start and end of the value.
select c_first_name, c_last_name from customer where c_last_name rlike '^Ma?c[Dd]onald$';

-- Match multiple character sequences, either 'Mac' or 'Mc'.
select c_first_name, c_last_name from customer where c_last_name rlike '^(Mac|Mc)donald$';

-- Find names starting with 'S', then one or more vowels, then 'r', then any other characters.
-- Matches 'Searcy', 'Sorenson', 'Sauer'.
select c_first_name, c_last_name from customer where c_last_name rlike '^S[aeiou]+r.*$';

-- Find names that end with 2 or more vowels: letters from the set a,e,i,o,u.
select c_first_name, c_last_name from customer where c_last_name rlike '.*[aeiou]{2,}$';

-- You can use letter ranges in the [] blocks, for example to find names starting with A, B, or C.
select c_first_name, c_last_name from customer where c_last_name rlike '^[A-C].*';

-- If you are not sure about case, leading/trailing spaces, and so on, you can process the
-- column using string functions first.
select c_first_name, c_last_name from customer where lower(trim(c_last_name)) rlike '^de.*';
  • rilke 대신에 REGEXP를 사용해도 된다.
  • 값이 정규표현식과 일치하는지 테스트한다.

출처

ksql.service.id

ksql.service.id

  • ksql 서버의 서비스 ID
  • 만약에 여러대의 ksqlDB 서버가 동일한 서비스 ID로 동일한 카프카 클러스터에 접속을 한다면, 그들은 KSQL DB 클러스터를 구성하고 워크로드를 공유한다.
  • 여러대의 KSQL 서버가 동일한 카프카 클러스터에 연결되지만, 서로 다른 ksql.service.id 가진다면 별도의 ksqlDB 클러스터를 형성한다.
  • 기본적으로 ksqlDB 서버의 서비스 ID는 default_ 이다.
  • 서비스 ID는 ksqlDB에서 생성한 내부 토픽의 prefix로도 사용된다.
  • ksql.service.id로 기본값을 사용하는 경우, ksql의 내부 토픽의 prefix는 _confluent-ksql-default_가 된다.

중요
관례에 따라 ksql.service.id 프로퍼티는 -_ 와 같은 구분 문자로 끝나야한다. 이렇게 하> 면 내부 토픽의 이름을 더 쉽게 읽을 수 있다.

출처

엔티티 작성시 주의사항

엔티티 작성시 주의사항

equals(), hashcode() 재정의

  • 엔티티는 식별자(@id)를 가지를 객체로써 equals(), hashcode()를 재정의해줘야 동일성과 동등성이 보장될 수 있다.
  • 재정의시 @Id 필드는 필수로 넣어주면 좋다.

toString() 재정의

  • 연관관계 필드들은 제외하고 정의해야 한다.
  • 양방향 관계라면 특히 조심해야 한다.

kotlin 기준 엔티티 작성 주의사항

all open 플러그인 잘쓰기

// allopen 확장, @Component 등 특정 스프링 어노테이션에 대해 allopen 플러그인을 적용.
kotlin("plugin.spring") version kotlinVersion
// noarg 확장, @Entity, @Embeddable, @MappedSuperclass 어노테이션에 대해 noarg 플러그인 적용.
kotlin("plugin.jpa") version kotlinVersion
  • 코틀린을 사용하여 JPA, 스프링을 개발할 때 기본적으로 위와 같은 두 플러그인을 설정하게 된다.
  • JPA의 지연 로딩을 제대로 사용하려면 all open 플러그인의 커스텀이 필요하다.
    • 지연 로딩은 프록시 객체를 만들 수 있어야 하는데, 그러려면 엔티티 클래스에 open 키워드를 클래스와 필드에 추가해줘야한다.
allOpen {
    annotation("javax.persistence.Entity")
    annotation("javax.persistence.MappedSuperclass")
    annotation("javax.persistence.Embeddable")
}
  • 위 설정을 추가하여 지연 로딩이 가능하게 즉, 프록시 객체 생성이 가능하게 설정할 수 있다.

출처

DataFrameNaFunctions

DataFrameNaFunctions

  • df.na를 호출하면 DataFrameNaFunctions 를 리턴하며 null 또는 NAN 값을 처리하는데 사용할 수 있다.

drop

  • drop() : 널 또는 NAN 값을 하나라도 포함하고 있는 로우는 삭제해서 새로운 데이터프레임을 리턴한다.

참고 : https://sparkbyexamples.com/spark/spark-dataframe-drop-rows-with-null-values/

fill

  • fill 함수를 사용하면 널이나 NAN 값을 지정한 값으로 대체할 수 있다.
  • 아래와 같이 사용하면 String 널값을 빈문자열로 대체하고 numeric 컬럼의 널값 또는 NaN값은 0으로 대체한다.
df.na.fill("").na.fill(0)

참고

Kubernates - 컨테이너 진단하기

컨테이너 진단하기

컨테이너가 실행된 후에는 kubelet가 컨테이너를 주기적으로 진단한다. 이 때 필요한 프로브는 다음 두 가지가 있다.

  • livenessProbe : 컨테이너가 실행되었는지 확인한다. 이 진단이 실패하면 kubelet은 컨테이너를 종료시키고, 재시작 정책에 따라서 컨테이너를 재시작한다. 컨테이너에 liveness Probe를 어떻게 할 지 명시되지 않았다면 기본 상태 값은 Success 이다.
  • readinessProbe : 컨테이너가 실행된 후 실제로 서비스 요청에 응답할 수 있는지 진단한다. 이 진단이 실패하면 엔드포인트 컨트롤러는 해당 파드에 연결된 모드 서비스를 대상으로 엔드포인트 정보를 제거한다. 첫 번째 readinessProbe를 하기 전까지의 기본 상태 값은 Failure이다. readinessProbe를 지원하지 않는 컨테이너라면 기본 상태 값은 Success이다.

컨테이너 진단은 컨테이너가 구현한 핸들러를 kubelet이 호출해서 실행한다. 핸들러에는 세 가지가 있다.

  • ExecAction : 컨테이너 안에서 지정된 명령을 실행하고 종료 코드가 0일때 Success라고 진단한다.
  • TCPSocketAction : 컨테이너 안에 지정된 IP와 포트로 TCP 상태를 확인하고 포트가 열려 있으면 Success라고 진단한다.
  • HttpGetAction : 컨테이너 안에 지정된 IP, 포트, 경로로 HTTP Get 요청을 보낸다. 응답 상태 코드가 200에서 400 사이면 Success라고 진단한다.

진단 결과도 세 가지가 있다.

  • Success : 컨테이너가 진단에 성공
  • Failure : 컨테이너가 진단에 실패
  • Unknown : 진단 자체가 실패해서 컨테이너 상태를 알 수 없음

참고

JPA - N+1 문제

N+1 문제

즉시 로딩과 N+1

  • N+1 문제는 JPA를 개발할 때 가장 주의해야 할 문제이다.
@Entity
@Table(name = "MEMBER")
public class Member {
    @Id @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
    private List<Order> orders = new ArrayList<Order>();
}
@Entity
@Table(name = "ORDER")
public class Order {
    @Id @GeneratedValue
    private Long id;

    @ManyToOne
    private Member member;
}

특정 회원 하나를 em.find() 메소드로 조회하면 즉시 로딩으로 설정한 주문 정보도 함께 조회한다. 이 때 실행된 SQL을 보면 조인을 사용해서 한 번의 SQL로 회원과 주문 정보를 함께 조회한다.
하지만 JPQL을 사용할 때 문제가 발생한다.

em.createQuery("select m from Member m", Member.class).getResultList();

JPQL을 실행하면 즉시 로딩과 지연 로딩에 대해서 전혀 신경 쓰지 않고 JPQL만 사용해서 SQL을 생성한다. 이 때 N+1 문제가 발생한다.

SELECT * FROM MEMBER
SELECT * FROM ORDER WHERE MEMBER_ID = 1
SELECT * FROM ORDER WHERE MEMBER_ID = 2
SELECT * FROM ORDER WHERE MEMBER_ID = 3
SELECT * FROM ORDER WHERE MEMBER_ID = 4
... 

회원 조회 SQL로 N명의 회원 엔티티를 조회하면, 각각의 회원 엔티티와 연관된 주문 콜렉션을 즉시 조회하기 위해 추가로 N번의 SQL을 실행한다. 이처럼 처음 실행한 SQL의 결과 수 만큼 추가로 SQL을 실행하는 것을 N+1 문제라 한다.

지연 로딩과 N+1

@Entity
@Table(name = "MEMBER")
public class Member {
    @Id @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = "member", fetch = FetchType.LAZY)
    private List<Order> orders = new ArrayList<Order>();
}
  • 지연로딩으로 설정하면 JPQL에서는 N+1 문제가 발생하진 않는다. 지연 로딩이므로 데이터베이스에서 회원만 조회한다.
  • 문제는 비즈니스 로직에서 모든 회원에 대해 연관된 주문 컬렉션을 사용할 때 발생한다.
    • 회원이 5명이면 회원에 따른 주문도 5번 조회된다.

N+1 문제 해결

페치 조인 사용

  • JPQL에서 N+1 문제를 해결하기 위한 가장 일반적인 방법은 페치 조인을 사용하는 것이다.
select m from Member m join fetch m.orders

하이버네이트 @BatchSize

@Entity
@Table(name = "MEMBER")
public class Member {
    @Id @GeneratedValue
    private Long id;
    
    @BatchSize(size = 5)
    @OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
    private List<Order> orders = new ArrayList<Order>();
}
  • @BatchSize 애노테이션을 사용하면 연관된 엔티티를 조회할 때 지정한 size 만큼 SQL의 IN 절을 사용해서 조회한다. 만약에 조회한 회원이 10명인데 size=5로 지정하면 2번의 SQL만 추가로 실행한다.
  • 즉시 로딩으로 설정하고, 조회한 회원이 10명이라면 2번의 SQL로 주문 데이터를 조회한다.
  • 지연 로딩으로 설정하면 지연 로딩된 엔티티를 최초 사용하는 시점에 SQL을 실행해서 5건의 데이터를 미리 로딩한다. 그리고 6번째 데이터를 사용하면 SQL을 추가로 실행한다.

하이버네이트 @Fetch(FetchMode.SUBSELECT)

@Entity
@Table(name = "MEMBER")
public class Member {
    @Id @GeneratedValue
    private Long id;
    
    @Fetch(FetchMode.SUBSELECT)
    @OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
    private List<Order> orders = new ArrayList<Order>();
}
  • @Fetch(FetchMode.SUBSELECT)를 사용하면 연관된 엔티티를 조회할 때 서브 쿼리를 사용해서 N+1 문제를 해결한다.
  • 주문 데이터를 가져올 때 아래와 같은 SQL을 실행한다.
SELECT O FROM ORDERS O 
WHERE O.MEMBER_ID IN (SELECT M.ID FROM MEMBER M WHERE M.ID > 10) 

출처

  • 자바 ORM 표준 - JPA 프로그래밍 (김영한 지음)

참고하면 좋은글

CI/CD

CI (Continuous Integration)

CI

  • 어플리케이션의 새로운 코드 변경 사항이 정기적으로 빌드 및 테스트 되어 공유 레포지토리에 통합히는 것을 의미
  • CI의 핵심 목표
    • 버그를 신속하게 찾아 해결
    • 소프트웨어의 품질 개선
    • 새로운 업데이트의 검증 및 릴리즈의 시간을 단축

CD (Continuous Delivery & Continuous Deployment)

CD

  • Continuous Delivery : 공유 레포지토리로 자동으로 Release
  • Continuous Deployment : Production 레벨까지 자동으로 deploy

CI&CD

출처

JVM Options

-Xss

  • 사용예 : java -Xss1M
  • 자바 애플리케이션에서 각 스레드는 스택을 가진다.
  • 스택 영역의 최대 크기는 -Xss 옵션으로 조정할 수 있다.
  • 다만 아래와 같은 상황이 발생할 수 있으므로, 값을 잘 선택해야한다.
    • StackOverflowError : 스택의 크기가 제한을 넘어간 경우 (-Xss 크기를 증가시킨다.)
    • OutOfMemoryError : 각 스레드가 너무 큰 스택을 가지고 있는 경우 OutOfMemoryError가 발생할 수 있다. (-Xss 크기를 감소시킨다.)
  • 스택 사이즈의 기본값은 VM마다 다르며 버전에 따라도 다르다.

참고

async, defer

관련 글

  • https://jae04099.tistory.com/entry/HTML-script-%ED%83%9C%EA%B7%B8%EB%8A%94-%EC%96%B4%EB%94%94%EC%97%90-%EC%9C%84%EC%B9%98-%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C

내용 요약

  • script 파일을 다운로드(fetching), 실행(execution) 과정을 거쳐 실행된다.
  • HTML은 파싱 도중 script 태그를 만나게 되면 중간에 파싱을 멈춘다.
  • HTML문서는 하향식으로 읽기 때문에 head 태그 내부에 script를 위치시키면 body 내부의 UI는 script 태그를 읽은 후에 사용자에게 보여주게 된다.
  • async는 HTML 파일을 파싱하다가 script 태그를 만나면, 파싱을 중단하지 않고 script를 다운로드 한다. 다운로드 완료 후 script를 실행할 때 HTML 파싱이 멈추게 된다.
  • defer는 async와 마찬가지로 HTML을 파싱하다가 script 태그를 만나면 스크립트를 다운로드한다. 차이점은 HTML 파싱 완료 이후에 script를 실행시킨다는 점이다.

Hikari 관련 설정

HikariCP 관련 설정

  • autoCommit: 커넥션이 풀에서 반환될 때, auto-commit을 할지 여부를 결정한다. (기본값: true)
  • connectionTimeout: 풀에서 커넥션을 가져올 때의 timeout 설정 (기본값: 30,000 - 30초)
  • idleTimeout: 커넥션이 풀에서 idle 상태로 있을 수 있는 최대 시간을 설정한다. 이 설정은 minimumIdlemaximumPoolSize 보다 작은 경우에만 적용이 된다. 이 설정을 0으로 한다면 idle 상태의 커넥션이 풀에서 제거되지 않는다. 기본값은 600,000 (10분)이다.
  • keepaliveTime: 데이터베이스 또는 네트워크 인프라에 의해 timeout 되는것을 방지하기 위해서 HikariCP가 Ping을 날리는 주기를 결정한다. 이 설정값은 maxLifetime 값보단 작아야한다. 커넥션이 "keepalive" 시간이 되면, 커넥션은 풀에서 꺼내져서 Ping을 날린 후에 다시 커넥션에 반환된다. ping은 JDBC4 isValid() 메소드 호출 또는 connectionTestQuery 실행 중 하나이다. 이 값의 최소값은 30,000(30초)이며 분 범위의 값이 가장 바람직하다. 기본값은 0으로 disable 시킨다.
  • maxLifetime: 커넥션이 풀에서 존재할 수 있는 최대시간. 현재 사용중인 커넥션은 중간에 절대로 폐기되지 않으며, 사용중이지 않을 때만 풀에서 제거된다. 대량의 커넥션 제거를 방지하기 위해 커넥션 별로 약간의 오차를 둔다. 데이터베이스 또는 인프라에서 부과하는 커넥션 time limit 보다 수 초 정도 짧게 설정하는 것을 강력히 권장한다. 이 값을 0으로 설정하면 최대 수명이 없음을 나타내며 이 경우 idleTimeout 설정에 따라 커넥션이 종료된다. 최소값은 30,000(30초)이며 기본값은 180,000 (30분)이다.
  • connectionTestQuery: 드리어버가 JDBC4를 지원하는 경우 이 속성을 설정하지 않는것이 좋다. Connection.isValid 메소드를 지원하지 않는 레거시 드라이버를 위한 설정이다. 이 쿼리는 커넥션이 풀에서 꺼내어 질 때, 커넥션과 데이터베이스 연결이 살아있는지를 확인하기 위해서 실행된다.
  • validationTimeout: 이 속성은 커넥션이 살아있는지 테스트될 때 사용되는 timeout 값이다. 기본값은 5,000으로 connectionTimeout 보다는 작아야한다.
  • minimumIdle: Hikari는 최소한 이 설정값 이상의 idle 커넥션을 유지하려고 노력한다. 만약에 idle 커넥션의 수가 이 설정값 아래로 떨어지고, 커넥션의 총 개수가 maximumPoolSize 보다 작은 경우엔 Hikari는 빠르게 커넥션을 추가하려고 노력한다. 가능하면 이 설정값을 사용하지 않고 HikariCP가 고정된 크기의 커넥션 풀을 사용하도록 하는것을 권장한다. 기본값은 maximumPoolSize 과 같다.
  • maximumPoolSize: 풀에서 유지할 수 있는 최대 커넥션 수(사용중인 커넥션 + idle 커넥션 모두 포함) 합리적인 값은 실행 환경에 따라 결정된다. 풀이 maximumPoolSize에 도달하고, idle한 커넥션이 없는 상태에서 getConnection() 메소드가 호출되면 해당 메소드는 connectTimeout 시간동안 block 된다.
  • poolName: 유저가 정의한 커넥션 풀의 이름을 지정한다. 로깅 및 JMX management console에서 풀을 식별하는데 사용될 수 있다.
  • leak-detection-threshold:

참고

Transform - Drop

Drop

Description

  • 메시지의 키 또는 밸류를 널로 설정한다.
    • 키 : io.confluent.connect.transforms.Drop$Key
    • 밸류 : io.confluent.connect.transforms.Drop$Value

예제

transforms=dropKeyExample, dropValueAndForceOptionalSchemaExample

# 메시지에서 키를 삭제한다. 
transforms.dropKeyExample.type=io.confluent.connect.transforms.Drop$Key

# 메시지에서 밸류를 삭제한다. 그리고 밸류에 대한 스키마가 옵션날이 아닌 경우, 옵션날로 변경한다.
transforms.dropValueAndForceOptionalSchemaExample.type=io.confluent.connect.transforms.Drop$Value
transforms.dropValueAndForceOptionalSchemaExample.schema.behavior=force_optiona

Record contents using Drop$Key with schema.behavior set to nullify

transforms.dropValueAndForceOptionalSchemaExample.type=io.confluent.connect.transforms.Drop$Key
transforms.dropValueAndForceOptionalSchemaExample.schema.behavior=nullify
  • Before: key: 24, schema: {"type": "integer"}
  • After: key: null, schema: null

키와 스키마 모두 널이 된다.

Record contents using Drop$Key with schema.behavior set to retain

transforms.dropValueAndForceOptionalSchemaExample.type=io.confluent.connect.transforms.Drop$Key
transforms.dropValueAndForceOptionalSchemaExample.schema.behavior=retain
  • Before: key: 24, schema: {"type": "integer"}
  • After: key: null, schema: {"type": "integer"}

키는 널이 되지만 스키마는 변경되지 않는다.

Record contents using Drop$Key with schema.behavior set to validate where the schema is not optional

transforms.dropValueAndForceOptionalSchemaExample.type=io.confluent.connect.transforms.Drop$Key
transforms.dropValueAndForceOptionalSchemaExample.schema.behavior=validate
  • Before: key: 24, schema: {"type": "integer"}
  • After: 스키마에서 키는 널이 될 수 없기 때문에 에러가 발생한다.

Record contents using Drop$Key with schema.behavior set to validate, where the schema is optional

transforms.dropValueAndForceOptionalSchemaExample.type=io.confluent.connect.transforms.Drop$Key
transforms.dropValueAndForceOptionalSchemaExample.schema.behavior=validate
  • Before: key: 24, schema: {"type": "integer", "optional": true}
  • After: key: null, schema: {"type": "integer", "optional": true}

키는 널이 되고 스키마는 변경되지 않는다.

Record contents using Drop$Key with schema.behavior set to force_optional, where the schema is not optional

transforms.dropValueAndForceOptionalSchemaExample.type=io.confluent.connect.transforms.Drop$Key
transforms.dropValueAndForceOptionalSchemaExample.schema.behavior=force_optional
  • Before: key: 24, schema: {"type": "integer"}
  • After: key: null, schema: {"type": "integer", "optional": "true"}

키는 널이되고 스키마에서 Optional은 true가 된다.

Properties

  • schema.behavior : 널이 아닌 스키마를 처리하는 방법
    • nullify : 스키마가 널이 된다.
    • retain : 스키마가 변경되지 않는다.
    • validate : optional인 경우엔 해당 스키마가 그대로 사용된다. optional이 아닌 경우엔 에러가 발생한다.
    • force_optional : 스키마는 강제로 optional: true가 된다.

코틀린 + 하이버네이트

코틀린에서 하이버네이트를 사용할 수 있을까?

출처

data class 사용시 주의사항

  • data class는 equals, hashcode, toString 메서드를 자동으로 구현해주는데, 이는 재정의 하는게 좋다.
  • toString : 양방향 매핑, lazy loading에 주의해서 구현하는게 좋다.
  • equals, hashCode : 엔티티의 ID를 이용해서 구현하는게 권장된다.

프록시 객체 사용

  • 엔티티 클래스는 final로 선언되서는 안된다.
  • 엔티티의 메서드나 인스턴스 변수는 final로 선언되서는 안된다.
  • 엔티티 클래스를 final로 선언하면 lazy loading을 위한 프록시를 생성할 수 없다.

코틀린의 클래스와 프로퍼티, 함수는 기본적으로 final이며 상속이 불가능하다.

  • 이를 해결하기 위해선 allopen 플러그인을 사용하면 된다.
  • 하지만 data classopen이 되지 않는다. 따라서 Lazy Loading을 사용하기 위해서는 data class를 사용할 수 없다.

일반 클래스 사용하기

allopen 플러그인

  • 엔터티의 클래스, 프로퍼티와 함수를 open 해야한다.
  • allopen 플러그인은 자동으로 모든 클래스를 open 시켜준다.

noarg 플러그인

  • 인자가 없는 생성자가 필요하다.
  • noarg 플러그인을 사용한다.

kassava 라이브러리

  • toString(), equals(), hashCode() 구현을 위해 사용할 라이브러리
  • equals(), hashCode() 에서는 id 프로퍼티만 확인하게 변경하는게 좋다.

Verification and Validation in Testing

Verification and Validation in Testing

Verification과 Validation은 비슷해 보이지만, 서로 다른 의미를 가진다. 그리고 이 둘의 차이를 이해하는 것은 중요하다.
이 글에서는 두 용어가 테스트 영역에서 어떤 의미로 사용되는지 설명한다.

Verification vs Validation

소프트웨어 테스트에는 다양한 방법과 프로세스가 존재한다. Verfication과 Validation은 소프트웨어 테스트에서 동일한 그룹의 일부이다. (V-Model 이라고도 불린다.)

What is Verification?

Verification의 정의는 다음과 같다.

  • A system test to prove that it meets all its specified requirements at a particular stage of its development.

개발의 특정 단계에서 모든 요구사항을 충족하는지 증명하기 위한 시스템 테스트

Verification은 개발 초기 단계에서 지정한 조건을 충족하는지 테스트하는 것이다. Verification은 Validation과 달리 소프트웨어가 개발 중일 때 수행되는 내부 프로세스에 가깝다.

What is Validation?

validation의 정의는 다음과 같다.

  • An activity that ensures that an end product stakeholder’s true needs and expectations are met.

이해 관계자의 진정한 요구와 기대가 충족되도록 하는 활동이다.

validation은 제품의 일부 또는 전체 애플리케이션이 완성되면 수행한다. Validation 관련된 이슈는 사용자와 개발자 간의 커뮤니케이션이 부적절하거나 부족할 때 발생한다. 개발자는 validation을 통해 제품이 사용자의 요구사항을 충족하는지 확인해야 한다.

Verification과 Validation은 소프트웨어 테스트에서 사용되는 키워드이다. 두 용어 모두 V&V(Verification and Validity) 의 일부이며 아래 작업을 달성하려고 한다.

  • 개발자 입장에서 제품을 확인하라 : 제품이 요구사항을 준수하는지 확인한다.
  • 소비자 입장에서 제품을 확인하라 : 고객의 용도에 맞는 제품인지 확인한다.

출처

Kafka Broker 모니터링

  • Memory Usage : Kafka should run entirely on RAM. JVM heap size shouldn't be bigger than your available RAM. That is to avoid swapping
  • Swap Usage : Watch for swap usage, as it will degrade performance on Kafka and lead to operations timing out
  • Network bandwidth : Kafka servers can incur a high network usage. Keep an eye on this, especially if you notice any performance degradation. Also look out for dropped packet errors.
  • Disk usage : Make sure you always have free space for new data, temporary files, snapshot or backups
  • Disk IO : Kafka partitions are stored asynchronously as a sequential write ahead log. Thus, disk reads and writes in Kafka are sequential, with very few random seeks.

Kafka Server 동작중에 발생하는 메트릭 정보들

  • UnderReplicatedPartitions : 복제가 안된 파티션의 개수 => 항상 0이어야한다.
  • OfflinePartitionsCount : 리더가 없는 파티션의 개수 => 파티션을 읽거나 쓰기가 불가능
  • ActiveControllerCount : 현재 동작중인 컨트롤러 브로커의 개수  => 반드시 1개여야함 (클러스터내의 컨트롤러는 반드시 1개임)
  • UncleanLeaderElectionsPerSec : unclean한 리더선출이 얼마나 일어나는지 => 0 이어야함
  • TotalTimeMs : 하나의 요청을 처리하는데 소요된 전체 시간
    • 구간별로 분할하여 시간 측정 가능
  • IsrShrinkPerSec : ISR이 줄어드는 비율 => 브로커가 다운되는 경우 ISR이 줄어들수 있다.
  • IsrExpandPerSec : 브로커가 다운후에 다시 살아난 경우 리더를 따라잡기 위해 노력하며 따라잡은 경우 ISR로 된다.
  • BytesInPerSec, BytesOutPerSec

PostgreSQL 데이터 타입

VARCHAR(n)

  • 가변길이 문자열
  • 여기서 n은 byte가 아니라 length임
  • VARCHAR(n)은 최대 n개의 문자를 저장할 수 있다.
  • n개 이상의 문자를 저장하려고 하면 에러가 발생한다.
    • 초과 문자가 모두 공백인 경우에는, 공백을 최대 길이(n)로 자르고 저장한다.
  • VARCHAR 데이터 유형에 n 정수를 지정하지 않으면 TEXT 처럼 동작한다. (TEXT는 제한없이 문자열을 저장할 수 있다.)
  • 다른 데이터베이스와는 달리 VARCHAR, CHAR, TEXT 간에 성능 차이가 없다고 한다.

CHAR(n)

  • 고정된 길이의 문자열

TEXT

  • 길이 제한이 없는 문자열

출처

데이터베이스 공백 VS 널

  • 오라클을 제외한 다른 대부분의 DB에선 공백과 널을 다르게 인식함
  • 값이 공백인 부분과 값이 없음은 엄연히 다름
-- 공백
SELECT *
FROM MY_TABLE
WHERE COLUMN1 = ''

-- 널비교
SELECT *
FROM MY_TABLE
WHERE COLUMN1 IS NULL
  • length를 통해서 컬럼값의 길이를 구할 때도 공백이 경우는 0을 리턴하는 반면 null인 경우는 null을 리턴한다.
  • 출처 : https://ytubelog.tistory.com/12

Spark SQL 함수

공식문서

substring

  • substring(str, pos[, len]) : 부분 문자열을 리턴한다. pos에서 시작해서 len 만큼의 길이의 부분 문자열을 리턴한다. 주의할 점은 pos가 1부터 시작한다는 것이다.
> SELECT substring('Spark SQL', 5);
 k SQL
> SELECT substring('Spark SQL', -3);
 SQL
> SELECT substring('Spark SQL', 5, 1);
 k

concat

  • concat(col1, col2, ..., colN) : col1, col1를 합쳐서 리턴한다.
> SELECT concat('Spark', 'SQL');
 SparkSQL
> SELECT concat(array(1, 2, 3), array(4, 5), array(6));
 [1,2,3,4,5,6]
  • concat에서 주의해야 할 점은 아래와 같이 파라매터 값에 널이 있는 경우 concat의 결과는 널이 된다는 것이다.
scala> spark.sql("SELECT CONCAT('hello', 'world')").show()
+--------------------+
|concat(hello, world)|
+--------------------+
|          helloworld|
+--------------------+


scala> spark.sql("SELECT CONCAT('hello', 'world', null)").show()
+------------------------------------------+
|concat(hello, world, CAST(NULL AS STRING))|
+------------------------------------------+
|                                      null|
+------------------------------------------+

concat_ws

  • concat_ws(sep, [str | array(str)]+) : sep로 구분된 합쳐진 문자열을 리턴한다.
> SELECT concat_ws(' ', 'Spark', 'SQL');
  Spark SQL
  • concat_ws의 경우 파라매터의 널이 있어도 널을 리턴하진 않는다.
scala> spark.sql("SELECT CONCAT_WS(' ', 'hello', 'world')").show()
+--------------------------+
|concat_ws( , hello, world)|
+--------------------------+
|               hello world|
+--------------------------+


scala> spark.sql("SELECT CONCAT_WS(' ', 'hello', null)").show()
+-------------------------+
|concat_ws( , hello, NULL)|
+-------------------------+
|                    hello|
+-------------------------+

scala> spark.sql("SELECT CONCAT_WS(' ', null, 'world')").show()
+-------------------------+
|concat_ws( , NULL, world)|
+-------------------------+
|                    world|
+-------------------------+


scala> spark.sql("SELECT CONCAT_WS(' ', null, null)").show()
+------------------------+
|concat_ws( , NULL, NULL)|
+------------------------+
|                        |
+------------------------+

== vs ===

Referential Equality

  • ===는 동일한 객체를 가르키는지 여부를 판단하고 싶을 때 사용한다. (자바에서 ==와 같다.)
  • 참조 타입의 주소값 비교
val a = Integer(10)
val b = Integer(10)

a === b // false
  • a와 b는 메모리의 서로 다른 위치를 가르키므로 false를 리턴한다.

Structural Equality

  • ==는 두 값이 동일한지 체크한다.
  • ==는 내부적으로 equals를 호출한다.
val a = Integer(10)
val b = Integer(10)

a == b // false

Array Equality

val hobbies = arrayOf("Hiking, Chess")
val hobbies2 = arrayOf("Hiking, Chess")

assertTrue(hobbies contentEquals hobbies2)
  • contentEquals 를 사용하면 Array의 값이 같은지 확인할 수 있다.

Data Class

  • data class를 활용하면 Kotlin 언어 수준에서 알아서 equals/hashCode 정의
  • data class 생성자 밖에 정의한 값은 hashCode와 equals에 포함하지 않는다.
@Test
fun test() {
    val somethingItemOne = SomethingResponse(
        name = "aaa",
        age = 50,
        isSelected = false
    )
    val somethingItemTwo = SomethingResponse(
        name = "aaa",
        age = 50,
        isSelected = false
    )
    println("somethingItemOne == somethingItemTwo ${somethingItemOne == somethingItemTwo}")
    println("somethingItemOne.hashCode ${somethingItemOne.hashCode()}")
    println("somethingItemTwo.hashCode ${somethingItemTwo.hashCode()}")
}

결과

somethingItemOne == somethingItemTwo true
somethingItemOne.hashCode 92566031
somethingItemTwo.hashCode 92566031
  • somethingItemOne === somethingItemTwo 결과는 false이다.
  • equals가 동일하기 때문에 hashCode도 동일한 결과가 나온다.

자바 vs 코틀린

Java

  • equals : 값이 동일한지 체크한다.
  • == : 메모리상 동일한 객체인지 체크한다.

kotlin

  • == : 값이 동일한지 체크한다.
  • === : 메모리상 동일한지 체크한다.

출처

KSQL - Show Stream, Describe

내용 정리

SHOW Streams

SHOW | LIST STREAMS [EXTENDED];
  • 정의된 Stream 목록을 보여준다.

예제

-- See the list of streams currently registered:
SHOW STREAMS;

-- See extended information about currently registered streams:
LIST STREAMS EXTENDED; 

DESCRIBE

DESCRIBE (stream_name|table_name|STREAMS|TABLES) [EXTENDED];
  • DESCRIBE : Stream과 Table의 컬럼과 타입을 보여준다. 그 외에도 Stream과 Table에 대한 관련 세부 정보를 보여준다.
  • DESCRIBE EXTENDED : 카프카 토픽 세부 사항 및 더 다양한 정보를 보여준다.

예제

> DESCRIBE ip_sum;
 Field   | Type
-----------------------------------------
 IP      | VARCHAR(STRING)  (primary key)
 KBYTES  | BIGINT
-----------------------------------------
For runtime statistics and query details run: DESCRIBE <Stream,Table> EXTENDED
> DESCRIBE ip_sum EXTENDED;
Type                 : TABLE
Timestamp field      : Not set - using <ROWTIME>
Key format           : KAFKA
Value format         : JSON
Kafka output topic   : IP_SUM (partitions: 4, replication: 1)

 Field   | Type
-----------------------------------------
 IP      | VARCHAR(STRING)  (primary key)
 KBYTES  | BIGINT
-----------------------------------------

Queries that write into this TABLE
-----------------------------------
id:CTAS_IP_SUM - CREATE TABLE IP_SUM as SELECT ip,  sum(bytes)/1024 as kbytes FROM CLICKSTREAM window SESSION (300 second) GROUP BY ip EMIT CHANGES;

For query topology and execution plan run: EXPLAIN <QueryId>; for more information

Local runtime statistics
------------------------
messages-per-sec:      4.41   total-messages:       486     last-message: 12/14/17 4:32:23 PM GMT
  failed-messages:         0      last-failed:       n/a
(Statistics of the local ksqlDB server interaction with the Kafka topic IP_SUM)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.