Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Realm은 어떻게 효율적인 데이터베이스를 만들었나?

Realm은 어떻게 효율적인 데이터베이스를 만들었나?

64c1a3841da6d254ac11d9d842e04ce4?s=128

Leonardo YongUk Kim

November 22, 2017
Tweet

Transcript

  1. Realm은 어떻게 효율적인 
 데이터베이스를 만들었나? Leonardo YongUk Kim Java

    team
  2. (SFFO%BP 03.-JUF 42-JUF 3FBMN

  3. 첫번째 비결
 Zero copy

  4. Zero copy
 “최대한 복제를 미루는 것입니다.”

  5. 왜 Zero copy인가? public class City { private String name;

    private long votes; public String getName() { return name; } public void setName(String name) { this.name = name; } public long getVotes() { return votes; } public void setVotes(long votes) { this.votes = votes; } } 제로 카피 없는 세상을 생각해 봅시다.
  6. 왜 Zero copy인가? public class City { private String name;

    private long votes; public String getName() { return name; } public void setName(String name) { this.name = name; } public long getVotes() { return votes; } public void setVotes(long votes) { this.votes = votes; } } ORM이나 JSON 파서가 채워줘야 해요. ORM이나 JSON 파서가 채워줘야 해요.
  7. 왜 Zero copy인가? public class City { private String name;

    private long votes; public String getName() { return name; } public void setName(String name) { this.name = name; } public long getVotes() { return votes; } public void setVotes(long votes) { this.votes = votes; } } public List<City> hydrate() { List<City> results = new ArrayList<>(); while (hasNextItem) { City c = new City(); c.setName(name); c.setVotes(votes); results.add(c); } return results; } 너가 뭘 쓸지 몰라서 다 준비했어요.
  8. 왜 Zero copy인가? public List<City> hydrate() { List<City> results =

    new ArrayList<>(); while (hasNextItem) { City c = new City(); c.setName(name); c.setVotes(votes); /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ results.add(c); } return results; } 너가 뭘 쓸지 몰라서 다 준비했어요. 수화라고 번역합니다. 객체의 필드를 채우는 일입니다.
  9. 왜 Zero copy인가? public List<City> hydrate() { List<City> results =

    new ArrayList<>(); while (hasNextItem) { City c = new City(); c.setName(String.valueOf(name)); c.setVotes(Integer.parseInt(votes)); /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ /* invoke other setters */ results.add(c); } return results; } 어쩌면 매번 변환이 필요할지 몰라Yo~!
  10. 왜 Zero copy인가? public class City { private String name;

    private long votes; public String getName() { return name; } public void setName(String name) { this.name = name; } public long getVotes() { return votes; } public void setVotes(long votes) { this.votes = votes; } } 만약에 사용자가 getName만 필요하면? votes값의 변환과 설정은 필요없는 것 아닌가?
  11. 왜 Zero copy인가? public class City { private String name;

    private long votes; private final int COLUMN_NAME = 0; public String getName() { return (String) getRow().getSting(COLUMN_NAME); } public void setName(String name) { getRow().setString(COLUMN_NAME, name); } public long getVotes() { return votes; } public void setVotes(long votes) { this.votes = votes; } } row에서 해당 column만 이제 가져옵시다. name을 채우지 맙시다. row의 해당 column에 기록합니다. tࢎਊೞ૑ঋਸ૑ݽܰח೦ݾਸਤೠ੘সਸ୭؀ೠٍ۽޷ܖ੗u
  12. 보일러플레이트

  13. 보일러플레이트 public class City { private String name; public String

    getName() { return name; } public void setName(String name) { this.name = name; } } public class City { private final int COLUMN_NAME = 0; public String getName() { return (String) getRow().getSting(COLUMN_NAME); } public void setName(String name) { getRow().setString(COLUMN_NAME, name); } } 프로그래머에게 직관적인 코드
  14. 보일러플레이트 public class City { private String name; public String

    getName() { return name; } public void setName(String name) { this.name = name; } } public class LazyCity { private final int COLUMN_NAME = 0; public String getName() { return (String) getRow().getSting(COLUMN_NAME); } public void setName(String name) { getRow().setString(COLUMN_NAME, name); } } 프로그래머에게 비관적인 코드 꼭 이렇게 짜야하나요?
  15. 보일러플레이트 public class City extends RealmObject { private String name;

    public String getName() { return name; } public void setName(String name) { this.name = name; } } public class CityRealmProxy extends City{ private final int COLUMN_NAME = 0; public String getName() { return (String) getRow().getSting(COLUMN_NAME); } public void setName(String name) { getRow().setString(COLUMN_NAME, name); } } 반복적인 작업은 기계에게 맡겨야 합니다. t"OOPUBUJPO1SPDFTTJOH5PPM "15 u 생성된 클래스는 모두 RealmProxy가 붙습니다. Realm 객체는 RealmObject를 상속 받습니다.
  16. 반복적인 작업은 기계에게 맡겨야 합니다. t"OOPUBUJPO1SPDFTTJOH5PPM "15 u tয֢ప੉࣌೐۽ࣁयో਷য֢ప੉࣌ਵ۽ࠗఠ࢜۽਍ё୓ܳࢤࢿ೤פ׮u

  17. Man vs Code City CityRealmProxy 사용자의 원본 객체 (RealmObject) 기계가

    생성한 객체 (RealmProxy) 어노테이션 프로세싱 툴 (APT)
  18. Man vs Code 3FBMN੄"15ח"CTUSBDU1SPDFTTPS࢚ܳࣘೠ3FBMN1SPDFTTPS۽ҳഅؾפ׮ AbstractProcessor
 +process(annotations, roundEnv) RealmProcessor RealmObject RealmProxy

    RealmObject RealmProxy RealmObject RealmProxy
  19. Man vs Code writer.emitAnnotation("Override") .beginMethod( "void", // return type "copy",

    // method name EnumSet.of(Modifier.PROTECTED, Modifier.FINAL), // modifiers "ColumnInfo", "rawSrc", "ColumnInfo", "rawDst"); // parameters writer.emitStatement("final %1$s src = (%1$s) rawSrc", columnInfoClassName()); writer.emitStatement("final %1$s dst = (%1$s) rawDst", columnInfoClassName()); for (VariableElement variableElement : metadata.getFields()) { writer.emitStatement("dst.%1$s = src.%1$s", columnIndexVarName(variableElement)); } writer.endMethod(); t݃ߨ਷হणפ׮+BWB௏٘ܳࢤࢿೞחѪ਷Ҋాझ۞਍ੌੑפ׮u
  20. Man vs Code t݃ߨ਷হणפ׮+BWB௏٘ܳࢤࢿೞחѪ਷Ҋాझ۞਍ੌੑפ׮u  4USJOH#VJMEFS
  5FNQMBUFFOHJOFT  "QBDIF7FMPDJUZ

    ֙݅ীࠗഝ   "QBDIF'SFF.BLFS  1FCCMF5FNQMBUF&OHJOF  5IZNFMFBG5FNQMBUF&OHJOF
  $PEFHFOFSBUPST  +BWB1PFU  +BWB8SJUFS ѐߊ઺ױ+BWB1PFU੉റࣘ੘   KDPEFNPEFM ֙҃ࠗఠѐߊ઺ױ 당신은 용자 코드도 찍어내면 그만? 내가 코드를 짜는 건지 코드가 날 짜는 건지. 튜토리얼이 많은 애가 개발이 잘 안돼. 그래도 스퀘어가 제일 낫지 않나?
  21. APT가 해법인가?

  22. APT가 해법인가? public class City extends RealmObject { private String

    name; public String getName() { return name; } public void setName(String name) { this.name = name; } } public class CityRealmProxy extends City { private final int COLUMN_NAME = 0; public String getName() { return (String) getRow().getSting(COLUMN_NAME); } public void setName(String name) { getRow().setString(COLUMN_NAME, name); } } "15חযڃݫࢲ٘оযڃ৉ೡੋ૑੉೧ೞ૑ޅ೤פ׮
  23. APT가 해법인가? public class City extends RealmObject { private String

    name; public String getName() { return name; } public void setName(String name) { this.name = name; } } public class CityRealmProxy extends City { private final int COLUMN_NAME = 0; public String getName() { return (String) getRow().getSting(COLUMN_NAME); } public void setName(String name) { getRow().setString(COLUMN_NAME, name); } } ঐޗ੸ੋஶ߮࣌ਸо੿೤פ׮
 ҳߡ੹੄3FBMNীࢲח೦࢚಴ળ੸ੋ੉ܴ੄HFUUFS৬TFUUFSܳъઁ೤פ׮ name이란 필드에 대해 getName과 setName을 쓰지 않을까요?
  24. APT가 해법인가? public class City extends RealmObject { private String

    name; public String getName() { return name; } public void setName(String name) { this.name = name; } } public class CityRealmProxy extends City { private final int COLUMN_NAME = 0; public String getName() { return (String) getRow().getSting(COLUMN_NAME); } public void setName(String name) { getRow().setString(COLUMN_NAME, name); } } ࠗݽоOBNF೙٘৬যڃ࢚ഐ੘ਊਸೞҊ੓ਸ૑৘࢚ೡࣻহणפ׮
 ҳߡ੹੄3FBMN਷ழझథز੘ਸӘ૑ೞҊࠗݽ௿ېझ੄ز੘ਸޖद೮णפ׮ City의 getName은 어떤 부가 작업을 하고 있을까요? City의 setName은 어떤 부가 작업을 하고 있을까요?
  25. 바이트 코드 뒤집기

  26. 바이트 코드 뒤집기 City name CityRealmProxy CityRealmProxyInterface
 
 +realmGet$name: String

    +realmSet$name(name) ZeroCopy관련 객체를 위한 인터페이스 name 필드에 접근하는 모든 코드 대신 realmGet$name과 realmSet$name이 호출되도록 변조합니다. realmGet$name과 realmSet$name를 오버라이드해서 Zero copy 부분을 구현합니다. "15۽௏٘ࢤࢿ ੋఠಕ੉झҳഅ
  27. 바이트 코드 뒤집기 public class CityRealmProxy extends City implements CityRealmProxyInterface

    { private final int COLUMN_NAME = 0; public String realmGet$name() { return (String) getRow().getSting(COLUMN_NAME); } public void realmSet$Name(String name) { getRow().setString(COLUMN_NAME, name); } } 내부용 게터와 세터는 RealmProxy 아래 생성됩니다.
  28. 바이트 코드 뒤집기 public class City implements CityRealmProxyInterface { private

    String name; public String getName() { return (String) realmGet$name(); } public void setName(String name) { realmSet$name(name); } public String realmGet$name() { return name; } public void realmSet$Name(String name) { this.name = name; } } t߄੉౟௏٘о߸ઑػ$JUZё୓ੑפ׮u public class City extends RealmObject { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
  29. 바이트 코드 뒤집기 public class City implements CityRealmProxyInterface { public

    String name; public String realmGet$name() { return name; } public void realmSet$Name(String name) { this.name = name; } } t੉ઁחHFUUFS৬TFUUFSоহযبؾפ׮u public class City extends RealmObject { public String name; } name 필드에 대한 접근은 모두 게터와 세터 호출로 변경됩니다.
  30. 바이트 코드 뒤집기 City CIty 사용자가 생성한 객체 바이트 코드

    변조된 객체 Transformer t5SBOTGPSNFSח࠽٘ػѾҗܳ߸ઑ೤פ׮u
  31. 바이트 코드 뒤집기 class RealmTransformer extends Transform { @Override void

    transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException { … } } 이 트랜스포머가 필드에 대한 접근들을 모두 Realm의 게터와 세터로 변조합니다.
  32. 바이트 코드 뒤집기 t݃ߨ਷হणפ׮߄੉౟௏٘ܳ߸ઑೞחѪ਷Ҋాझ۞਍ੌੑפ׮u  "4.  #$&-  $(-*#

     +BWBTTJTU  "TQFDU+ 중간 정도 난이도의 도구. Realm이 사용.
  33. 다른 DB도 Zero copy?

  34. 다른 DB도 Zero copy? SQLite 시스템 적인 경계 Object Object

    Object Object Object Object ORM
  35. 다른 DB도 Zero copy? SQLite 시스템 적인 경계 Object Object

    Object Object Object Object ORM 이 영역까지만 lazy하게 미룰 수 있음.
  36. 다른 DB도 Zero copy? SQLite 시스템 적인 경계 Object Object

    Object Object Object Object ORM 경계를 넘어서 lazy한 처리는 어려움.
  37. 더 많은 Zero copy 가능성 realm.executeTransaction(new Realm.Transaction() { @Override public

    void execute(Realm realm) { Person person = realm.createObject(Person.class); person.setId(1); person.setName("Young Person"); person.setAge(14); } }); final RealmResults<Person> people = realm.where(Person.class).findAll(); final Person person = people.first(); final String name = person.name; t3FBMN਷ࢤпࠁ׮؊ѱਵܵפ׮u
  38. 더 많은 Zero copy 가능성 realm.executeTransaction(new Realm.Transaction() { @Override public

    void execute(Realm realm) { Person person = realm.createObject(Person.class); person.setId(1); person.setName("Young Person"); person.setAge(14); } }); final RealmResults<Person> people = realm.where(Person.class).findAll(); final Person person = people.first(); final String name = person.name; Realm 은 오프셋도 리미트도 없습니다.
  39. 더 많은 Zero copy 가능성 realm.executeTransaction(new Realm.Transaction() { @Override public

    void execute(Realm realm) { Person person = realm.createObject(Person.class); person.setId(1); person.setName("Young Person"); person.setAge(14); } }); final RealmResults<Person> people = realm.where(Person.class).findAll(); final Person person = people.first(); final String name = person.name; 이 시점에서는 실제 데이터를 받아오지 않습니다. 실제 데이터를 가지고 있지 않기 때문에 특수한 리스트 구조를 가지고 있습니다.
  40. 더 많은 Zero copy 가능성 realm.executeTransaction(new Realm.Transaction() { @Override public

    void execute(Realm realm) { Person person = realm.createObject(Person.class); person.setId(1); person.setName("Young Person"); person.setAge(14); } }); final RealmResults<Person> people = realm.where(Person.class).findAll(); final Person person = people.first(); final String name = person.name; 이 시점에 첫 번째 사람에 대한 메타 데이터만 가지고 옵니다.
  41. 더 많은 Zero copy 가능성 realm.executeTransaction(new Realm.Transaction() { @Override public

    void execute(Realm realm) { Person person = realm.createObject(Person.class); person.setId(1); person.setName("Young Person"); person.setAge(14); } }); final RealmResults<Person> people = realm.where(Person.class).findAll(); final Person person = people.first(); final String name = person.name; 이 시점에 첫 번째 사람의 정보 중 name만 접근합니다.
  42. 더 많은 Zero copy 가능성 3FBMN਷MB[Zೞѱز੘ೞӝਤ೧ౠ߹ೠ௿ېझܳо૘פ׮  3FBMN0CKFDU  3FBMN3FTVMUT

     3FBMN-JTU %#ূ૓੄૑ਗٸޙী੹୓੸ਵ۽ѱਸ۞૕ࣻ੓णפ׮
  43. 조금 더 빨라지기 위해

  44. 조금 더 빨라지기 위해 final RealmResults<Person> people = realm.where(Person.class).findAll(); for

    (Person person : people) { final String name = person.name; } 객체의 전체 필드가 필요한 경우는 드뭅니다. ݽ߄ੌؘ੉ఠ߬੉झ੄ౠ૚ਸࢤп೧ࠇद׮
  45. 조금 더 빨라지기 위해      

     3PX  ੹ా੸ੋ3PX਋ࢶ#5SFF 리프가 10번째 Row까지 가진다는 것을 의미 OBNF WPUFT ಁ٬ 리프는 보통 연속된 Array Row에 속한 모든 Column
  46. 조금 더 빨라지기 위해      

     3PX  োࣘػOBNFѨ࢝ OBNF WPUFT ಁ٬ 인접한 데이터는 name이 아니다.
  47. 조금 더 빨라지기 위해 நदܳࢤп೧ࠇद׮ ഥ੸઺ OBNF WPUFT ಁ٬ OBNF

    WPUFT ಁ٬ OBNF WPUFT ಁ٬ OBNF WPUFT ಁ٬ 캐시 라인 캐시 히트 캐시 미스
  48. 조금 더 빨라지기 위해 ҳઑܳஸۢӝળਵ۽߄Բযࠇद׮ OBNF OBNF OBNF OBNF OBNF

    OBNF OBNF OBNF OBNF OBNF OBNF OBNF OBNF OBNF OBNF OBNF 캐시 라인
  49. 조금 더 빨라지기 위해 ҳઑܳஸۢӝળਵ۽߄Բযࠇद׮ ഥ੸઺ OBNF OBNF OBNF OBNF

    OBNF OBNF OBNF OBNF OBNF OBNF OBNF OBNF OBNF OBNF OBNF OBNF 캐시 라인 캐시 히트 캐시 미스
  50. 조금 더 빨라지기 위해    3FBMN੄$PMVNO਋ࢶ#5SFF OBNF OBNF

    OBNF OBNF 리프가 4번째 컬럼까지 가진다는 것을 의미 동질적인 데이터이기에 예측가능한 사이즈
  51.    3FBMN੄੹୓౟ܻ OBNF OBNF OBNF OBNF OBNF WPUFT

    1FSTPO Group (DB와 같은 개념) Table Column B-tree    WPUF WPUFT WPUFT WPUFT Root
  52. Root가 있는 이유?

  53. Root가 있는 이유?    OBNF OBNF OBNF OBNF

    OBNF WPUFT 1FSTPO    WPUF WPUFT WPUFT WPUFT Root 전체 Group을 하나의 스냅샷으로 인지.
  54. Root가 있는 이유? 현재 버전을 포인팅 Root 현재 버전 삭제될

    과거 버전 t.VMUJ7FSTJPO$PODVSSFODZ$POUSPMu Git, 현대적인 DBMS에서 사용.
  55. Root가 있는 이유? Root 다른 사용자는 현재 버전에 Lock 없이

    접근. 새 버전을 작성중. ੍ӝח঱ઁա࠺ߓఋ੸ੑפ׮
  56. Root가 있는 이유? Root 새로 작성된 버전으로 옮겨가는 것만 락이

    필요합니다. Root ॳӝ݅ߓఋ੸ੑפ׮ 다른 사용자는 여전히 이전 버전에 접근 중. 루트를 이동
  57. Root가 있는 이유? .77$חബਯਸਤ೧ࢎਊࢿਸੌࠗ൞ࢤ೤פ׮u  ੍ӝח-PDL੉೙ਃೞ૑ঋਵݴ঱ઁաоמ೤פ׮  ॳӝח-PDL੉೙ਃ೤פ׮  ё୓חযڃद੼ਸଵҊೞҊ੓ӝٸޙী׮ܲझۨ٘۽੹׳ؼࣻহणפ׮

     ׮ܲझۨ٘ীࢲё୓ܳଵҊೞחѪ਷૊द੉ܖয૘פ׮ 프로그래머에게 직관적이지는 않습니다.
  58. 이전 버전을 읽을 가능성?

  59. 이전 버전을 읽을 가능성? Root 다른 사용자는 현재 버전에 Lock

    없이 접근. 새 버전을 작성중. t׮ܲࢎਊ੗חৈ੹൤ҳߡ੹ਸ੽Ӕೞѱغ૑ঋաਃ u
  60. 이전 버전을 읽을 가능성? Person 객체 (Leonardo)
 v1 Person 객체

    (Leonardo) v1 झۨ٘" झۨ٘# Person 객체 (Leonardo)
 v2 1. 스레드 A에서 Person 객체 업데이트. 2. 업데이트 내역이 전파. Person 객체 (Leonardo)
 v2 3. 스레드 B의 라이브 객체가 자동 업데이트. 3. 스레드 B의 객체의 리스너에게 모두 업데이트 내용을 통보.
  61. 추가적인 최적화

  62. 추가적인 최적화    OBNF OBNF OBNF OBNF OBNF

    WPUFT 1FSTPO 배열은 얼마나 많은 공간을 차지할까?    WPUF WPUFT WPUFT WPUFT Root
  63. 추가적인 최적화     votes가 최대 1인 경우

    t୭؀ч੉ੋ҃਋ѐ੄WPUFTחCJUTܳର૑೤פ׮u Boolean 타입이 가장 효율적으로 저장됩니다.
  64. 추가적인 최적화     최대 값이 2로 변경되었습니다.

    t୭؀ч੉ੋ҃਋ѐ੄WPUFTחCJUTܳର૑೤פ׮u
  65. 추가적인 최적화     최대 값이 5로 변경되었습니다.

    t୭؀ч੉ੋ҃਋ѐ੄WPUFTחCJUTܳର૑೤פ׮u
  66. 추가적인 최적화    ֢٘੄पઁҳઑ OBNF OBNF OBNF OBNF

    OBNF WPUFT 1FSTPO    WPUF WPUFT WPUFT WPUFT Root    개별 노드는 3개의 배열을 사용합니다. 유연하지만 오버헤드가 있습니다.
  67. 추가적인 최적화 OBNF WPUFT 1FSTPO WPUF WPUFT WPUFT WPUFT 

      만약에 데이터가 뒤로만 추가된다면 더 최적화를 할 수 있습니다. 모든 노드가 다 차있다면 처음과 마지막 인덱스만 있으면 됩니다.
  68. 추가적인 최적화 OBNF WPUFT 1FSTPO WPUF WPUFT WPUFT WPUFT 

     물론 이러한 꿈은 삽입과 삭제가 이뤄지면 물거품이 됩니다. 모든 노드가 다 차있다면 처음과 마지막 인덱스만 있으면 됩니다.
  69. 흥미로운가요?

  70. 기회는 여러분에게 열려있습니다. t3FBMN਷য়೑ࣗझੑפ׮u 3FBMN+BWB
 IUUQTHJUIVCDPNSFBMNSFBMNKBWB
 3FBMN0CKFDUJWF$4XJGU
 IUUQTHJUIVCDPNSFBMNSFBMNDPDPB
 3FBMN9BNBSJO
 IUUQTHJUIVCDPNSFBMNSFBMNEPUOFU


    3FBMN3FBDUJWF/BUJWF/PEFKT
 IUUQTHJUIVCDPNSFBMNSFBMNKT 3FBMN0CKFDU4UPSFҕాݽٕ
 IUUQTHJUIVCDPNSFBMNSFBMNPCKFDUTUPSF
 3FBMN$PSF೨बূ૓
 IUUQTHJUIVCDPNSFBMNSFBMNDPSF
  71. Realm을 바로 사용하실 수 있습니다. 3FBMN+BWBޙࢲ
 IUUQTSFBMNJPEPDTKBWBMBUFTU
 3FBMN4XJGUޙࢲ
 IUUQTSFBMNJPEPDTTXJGUMBUFTU
 3FBMN0CKFDUJWF$ޙࢲ


    IUUQTSFBMNJPEPDTPCKDMBUFTU 3FBMN3FBDU/BUJWFޙࢲ
 IUUQTSFBMNJPEPDTKBWBTDSJQUMBUFTU
 3FBMN9BNBSJOޙࢲ
 IUUQTSFBMNJPEPDTYBNBSJOMBUFTU
  72. 최신 모바일 기술은 Realm에서 IUUQTSFBMNJP

  73. Thank you