รู้จัก Firebase Realtime Database ตั้งแต่ Zero จนเป็น Hero

Firebase Realtime Database เป็น NoSQL cloud database ที่เก็บข้อมูลในรูปแบบของ JSON และมีการ sync ข้อมูลแบบ realtime กับทุก devices ที่เชื่อมต่อแบบอัตโนมัติในเสี้ยววินาที รองรับการทำงานเมื่อ offline(ข้อมูลจะถูกเก็บไว้ใน local จนกระทั่งกลับมา online ก็จะทำการ sync ข้อมูลให้อัตโนมัติ) รวมถึงมี Security Rules ให้เราสามารถออกแบบเงื่อนไขการเข้าถึงข้อมูลทั้งการ read และ write ได้ดังใจ ทั้ง Android, iOS และ Web

วิดีโอแนะนำการทำงานของ Firebase Realtime Database

ในการพัฒนา Firebase Realtime Database ขอแยกออกเป็น 5 parts ดังนี้

  1. การ Set up Firebase และ Realtime Database SDK
  2. การเขียนข้อมูล
  3. การอ่านข้อมูล
  4. การเปิดใช้งานโหมด Offline
  5. Security & Rules

เมื่อพร้อมแล้ว…ก็เปิด Android Studio ขึ้นมา โดยจะสร้างโปรเจคใหม่ หรือจะใช้โปรเจคเดิมก็ได้

Part 1 การ Set up Firebase และ Realtime Database SDK

ถ้าสร้างโปรเจคใหม่ ให้ไปดูการ Set up Firebase ที่บทความนี้ก่อน

เมื่อ Set up Firebase เรียบร้อยแล้ว ก็ให้เพิ่ม Realtime Database SDK ใน build.gradle ของ app-level แล้วกด Sync ก็เป็นอันจบส่วนที่ 1 ละ

dependencies {
     compile 'com.google.firebase:firebase-database:10.0.1'
}

การเข้าถึงข้อมูลสำหรับ Firebase Realtime Database ทั้ง read และ write โดยปกติ เราจะต้องทำการ Authentication ผ่าน Firebase Authentication ซะก่อน แต่เพื่อให้เราสามารถเข้าใจบทความนี้ได้โดยไม่ต้องอ่าน Firebase Authentication เราจะมาทำให้มันเข้าถึงได้แบบ public กัน โดยให้เข้าไปที่ Firebase Console เข้าไปที่โปรเจค จากนั้นเลือกเมนู Database แล้วเลือก tab ที่ชื่อว่า RULES จะพบหน้าตาของประมาณนี้

Default rules in Firebase Realtime Database

ด้านขวามือจะมี simulator ให้ลองทดสอบ rules ที่เราสร้างขึ้น ทั้งแบบ public หรือแบบ authentication แล้วก็ดี ดังนั้น ลองกด RUN แบบ default rules ก่อนเลย ผลปรากฎว่าถ้าไม่ได้ authentication ก็จะไม่สามารถเข้าถึงข้อมูลได้นั่นเอง ดังรูป

Run default rules ด้วย simulator

เอาหละ ต่อไปเรามาปรับให้มันเป็น public กันดีกว่า โดยให้เปลี่ยน rules ตามนี้

Run public rules ด้วย simulator

เมื่อกด RUN ดูก็พบว่า ไม่ต้อง Authentication ก็สามารถเข้าถึงข้อมูลได้ละ เมื่อเสร็จแล้วก็กดปุ่ม PUBLISH ด้วย เป็นอันจบ Part 1


Part 2 การเขียนข้อมูล (Write)

เริ่มด้วยการประกาศตัวแปร DatabaseReference รับค่า Instance และอ้างถึง path ที่เราต้องการใน database

DatabaseReference mRootRef = FirebaseDatabase.getInstance().getReference();

จากนั้นก็อ้างอิงไปที่ path ที่เราต้องการจะจัดการข้อมูล ตัวอย่างคือ users และ messages

DatabaseReference mUsersRef = mRootRef.child("users");
DatabaseReference mMessagesRef = mRootRef.child("messages");

การเขียนข้อมูล (Write)

การ write, update หรือ delete ข้อมูลใน Firebase Realtime Database จะรองรับค่าหลายประเภททั้ง String, Long, Double, Boolean, Map<String, Object> และ List<Object> โดยการ write จะมีด้วยกัน 4 รูปแบบดังนี้

1. setValue() เป็นการ write หรือ update ข้อมูล ไปยัง path ที่เราอ้างถึงได้ เช่น users/<user-id>/<username>

mUsersRef.child("id-12345").setValue("Jirawatee");

เราสามารถดูผลลัพธ์แบบ realtime ได้ที่ Firebase Console โดยไปที่เมนู Database แล้วเลือก tab แรก คือ Data เราก็จะเห็นข้อมูลทั้งหมดแบบทุกลมหายใจละ

ตัวอย่างการ setValue()

2. push() เป็นการเพิ่มชุดของข้อมูล ในที่นี้ผมจะสร้าง model object ชื่อ FriendlyMessage ซึ่งจะบรรจุ text และ username ไว้ โดยการ push นั้น Firebase จะสร้าง unique key ของชุดข้อมูลนั้นๆ เพื่อใช้อ้างอิงต่อไปได้ เช่น messages/<message-id>/<data-model>

FriendlyMessage friendlyMessage = new FriendlyMessage("Hello World!", "Jirawatee");
mMessageRef.push().setValue(friendlyMessage);
ตัวอย่างการ push()

3. updateChildren() เป็นการ write หรือ update ข้อมูลบางส่วน(บาง key) ตาม path ที่เราอ้างถึง โดยไม่ต้องทำการ replace ข้อมูลทั้งชุด และสามารถทำพร้อมๆกันได้หลาย object

ตัวอย่างจะเป็นการสร้าง post ใหม่ขึ้นมา โดยจะ write ข้อมูลไป 2 ที่คือ
/user-messages/Jirawatee/$postid และ /messages/$postid

// push เป็นการ generate $postid ของ object ชื่อ posts ออกมาก่อนเพื่อใช้ใน // /user-posts/$userid/$postid 
String key = mMessagesRef.push().getKey();
HashMap<String, Object> postValues = new HashMap<>();
postValues.put("username", "Jirawatee");
postValues.put("text", "Hello World!");

Map<String, Object> childUpdates = new HashMap<>();
childUpdates.put("/messages/" + key, postValues);
childUpdates.put("/user-messages/Jirawatee/" + key, postValues);

rootRef.updateChildren(childUpdates);

สีที่แสดงใน console จะบอกสถานะแตกต่างกันดังนี้
สีเหลือง: แสดงการอัพเดท
สีเขียว: แสดงการเพิ่ม
สีแดง: แสดงการลบ
สีน้ำเงิน: แสดงการเคลื่อนย้าย

write ข้อมูลเข้าไปทั้ง 2 objects

ในกรณีที่ต้องการอัพเดทข้อมูลบางส่วน ก็สามารถทำได้พร้อมๆกันได้ โดยจะต้องรู้ username และ message-id เป็นตัวระบุในแต่ละ object

update ข้อมูลบางส่วนทั้ง 2 objects

4. runTransaction() เป็นการอัพเดทข้อมูล ที่มี concurrent เยอะๆ ที่อาจเกิดชนกัน เกิดข้อผิดพลาดได้ ตัวอย่างเช่น การกด like และกด unlike ที่โพสเดียวกัน เวลาเดียวกัน จะต้องมีการนับยอด like ตลอดเวลา ว่าช่วงเวลานั้นเป็นเท่าไร

postRef.runTransaction(new Transaction.Handler() {
     @Override
     public Transaction.Result doTransaction(MutableData mutable) {
        Post p = mutable.getValue(Post.class);
        if (p == null) {
           return Transaction.success(mutable);
        }

        if (p.stars.containsKey(getUid())) {
           // Unlike the post and remove self from likes
           p.starCount = p.starCount - 1;
           p.stars.remove(getUid());
        } else {
           // Like the post and add self to likes
           p.starCount = p.starCount + 1;
           p.stars.put(getUid(), true);
        }

        // Set value and report transaction success
        mutable.setValue(p);
        return Transaction.success(mutable);
      }

      @Override
      public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
          if (databaseError != null) {
             databaseError.getMessage()
             Log.w(TAG, databaseError.getMessage());
          } else {
             Log.d(TAG, "Transaction successful");
          }
      }
});

การลบข้อมูล (Delete)

การลบข้อมูลนั้น ให้เราระบุ path ที่เราต้องการจะลบ จากนั้นก็เรียกคำสั่ง removeValue() ตัวอย่างเช่น ต้องการลบข้อความทั้งหมดใน object ชื่อ messages

mMessageRef.removeValue();

นอกจากนั้นเรายังสามารถลบข้อมูล ได้ด้วยการส่งค่า null ไปที่ setValue(null) และสามารถใช้ค่า null กับเทคนิค updateChildren() เพื่อลบข้อมูลหลายๆ object ได้ด้วย

// ลบแบบ setValue()
mMessageRef.setValue(null);
// ลบแบบ updateChildren();
childUpdates.put("/messages/", null);

Part 3 การอ่านข้อมูล (Read)

เริ่มด้วยการประกาศตัวแปร DatabaseReference รับค่า Instance และอ้างถึง path ที่เราต้องการใน database

DatabaseReference mRootRef = FirebaseDatabase.getInstance().getReference();

การอ่านข้อมูลใน Firebase Realtime Database จะมี 2 ประเภทแยกตาม Listener ดังนี้

1. ValueEventListener

จะอ่านข้อมูลตั้งแต่เริ่ม และ จะอ่านข้อมูลทุกครั้งที่มีการเปลี่ยนแปลงของข้อมูลทั้งหมดภายใต้ path ที่เราอ้างถึง วิธีการคือใช้ object ที่เราอ้างถึงมา addValueEventListener โดยจะมี callback 2 แบบ

  • onDataChange จะถูกเรียกตอนเริ่ม และถูกเรียกทุกครั้งที่ข้อมูลภายใต้ path ที่เราอ้างถึงมีการเปลี่ยนแปลง
  • onCancelled จะถูกเรียกเมื่อไม่สามารถอ่านข้อมูลจาก database ได้
mRootRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        String value = dataSnapshot.getValue(String.class);
        mTextView.setText(value);
    }

    @Override
    public void onCancelled(DatabaseError error) {
        mTextView.setText("Failed: " + databaseError.getMessage());
    }
});

ข้อควรระวังของ ValueEventListener คือไม่ควรอ้างถึง root เนื่องจากมันจะคอยดูข้อมูลทั้งหมดของ database ซึ่งมันอาจมีขนาดใหญ่ และ เปลือง bandwidth โดยใช่เหตุ เราจึงควรใช้วิธีนี้แบบเจาะจงเฉพาะ หรืออ่านข้อมูลทั้งหมดครั้งแรก แล้ว removeEventListener() ออกไป

2. ChildEventListener

จะคอยรับข้อมูลจาก การเพิ่ม, การเปลี่ยนแปลง, การลบ และ การย้าย เฉพาะของ child ที่เราอ้างถึง วิธีการคือใช้ object ที่เราอ้างถึงมา addChildEventListener โดยจะมี callback 5 แบบ

  • onChildAdded() จะถูกเรียกเมื่อมีการเพิ่มชุดข้อมูลเข้ามาใน child
  • onChildChanged() จะถูกเรียกเมื่อข้อมูลใน child มีการเปลี่ยนแปลง
  • onChildRemoved() จะถูกเรียกเมื่อข้อมูลใน child ถูกลบ
  • onChildMoved() จะถูกเรียกเมื่อมีการเรียงลำดับของข้อมูลใน child เกิดขึ้น
  • onCancelled() จะถูกเรียกเมื่อโหลดข้อมูลจาก child ไม่สำเร็จ

ตัวอย่างการ addChildEventListener ไปกับการ comment ซึ่งเหมาะมากกับ RecyclerView

ChildEventListener childEventListener = new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
       // A new comment has been added, add it to the displayed list
       Comment comment = dataSnapshot.getValue(Comment.class);
       // ...
    }

    @Override
    public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
        // A comment has changed, use the key
        // to determine if we are displaying this
        // comment and if so displayed the changed comment.
        Comment newComment = dataSnapshot.getValue(Comment.class);
        String commentKey = dataSnapshot.getKey();
        // ...
    }

    @Override
    public void onChildRemoved(DataSnapshot dataSnapshot) {
        // A comment has changed, use the key
        // to determine if we are displaying this
        // comment and if so remove it.
        String commentKey = dataSnapshot.getKey();
        // ...
    }

    @Override
    public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
        // A comment has changed position,
        // use the key to determine if we are
        // displaying this comment and if so move it.
        Comment movedComment = dataSnapshot.getValue(Comment.class);
        String commentKey = dataSnapshot.getKey();
        // ...
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        Toast.makeText(mContext, "Failed to load comments.",
                Toast.LENGTH_SHORT).show();
    }
};
ref.addChildEventListener(childEventListener);

การเพิ่ม listener เข้าไปหลายตัว นั่นก็แปลว่าจะต้องมีการ call เกิดขึ้นมากมายตามแต่ event ซึ่ง หากเราไม่ได้ใช้ หรือออกจากหน้าดังกล่าว ก็ควรจะถอด listener เหล่านั้นออกไปด้วย เพื่อการใช้ bandwidth แบบคุ้มค่ามากที่สุดโดยสามารถถอดออกได้ด้วยคำสั่ง removeEventListener()

@Override
protected void onStop() {
   super.onStop();
   if (mValueEventListener != null) {
      mRootRef.removeEventListener(mValueEventListener);
   }
}

การอ่านข้อมูลเพียงครั้งเดียว

บางครั้งเราอาจต้องการอ่านข้อมูลแค่ครั้งเดียวและก็ไม่สนใจมันอีก Firebase ได้เตรียม addListenerForSingleValueEvent() ที่เมื่อได้ callback แล้วก็จะ remove listener ทิ้งอัตโนมัติ ตัวอย่างเช่น ดึงข้อมูลผู้ใช้ก่อนทำการโพส ว่าเขาผ่านการ sign-in มาหรือยัง หรือยังขาดข้อมูลอะไรที่ต้องการ หากครบถ้วนก็โพสได้ แต่หากไม่ครบอาจพาไปหน้า sign-in หรือพาไปหน้ากรอกข้อมูลให้ครบถ้วน

mDatabase.child("users").child(userId).addListenerForSingleValueEvent(new ValueEventListener() {
   @Override
   public void onDataChange(DataSnapshot dataSnapshot) {
      User user = dataSnapshot.getValue(User.class);
      if (user == null) {
         Toast.makeText(NewPostActivity.this, "Error: could not fetch user.", Toast.LENGTH_LONG).show();
      } else {
         writeNewPost(userId, user.username, title, body);
      }
      finish();
   }

   @Override
   public void onCancelled(DatabaseError databaseError) {
      Log.e(TAG, databaseError.getMessage());
   }
});

การเรียงลำดับและการกรองข้อมูล (Sorting and Filtering)

การ query ข้อมูล ของ Firebase Realtime Database นั้น รองรับการ sort และ filter ได้ อย่างก็ดีการ sort และ filter สามารถทำให้การ query นั้นช้าได้ ทางที่ดีควรศึกษาเรื่องการทำ index (.indexOn) ไปด้วยจะทำให้การ query นั้นมีประสิทธิภาพมากขึ้น

การ Sort ข้อมูล มี 3 รูปแบบ ดังนี้ (Ordering Function)

  • orderByChild() เป็นการเรียงลำดับ value ของ child key ที่ถูกเลือก (การใช้งานคล้าย WHERE ใน SQL)
  • orderByKey() เป็นการเรียงลำดับ child key (ใช้เรียง PK เหมาะกับแสดงข้อมูลแบบมี limit หรือแสดง pagination)
  • orderByValue() เป็นการเรียงลำดับ child value (เหมาะกับการเรียงค่าตัวเลข)
// ตัวอย่างการเรียงลำดับโพสของฉันที่ได้รับคะแนนโหวตมากที่สุด
dbReference.child("user-posts").child(getUid()).orderByChild("starCount");

การ Filter ข้อมูล มี 5 รูปแบบ ดังนี้ (Querying Function)

  • limitToFirst() การระบุจำนวน item ซึ่งจะเรียงลำดับจากแถวแรก
  • limitToLast() การระบุจำนวน item ซึ่งจะเรียงลำดับจากแถวสุดท้าย
  • startAt() จะดึงจำนวน item ที่มากกว่า หรือ เท่ากับ ที่ระบุ key หรือ value โดยขึ้นอยู่กับการ order-by
  • endAt() จะดึงจำนวน item ที่น้อยกว่า หรือ เท่ากับ ที่ระบุใน key หรือ value โดยขึ้นอยู่กับการ order-by
  • equalTo() จะดึงจำนวน item ที่เท่ากับที่ระบุใน key หรือ value โดยขึ้นอยู่กับการ order-by
// ตัวอย่างการดึงข้อมูลโพส 100 อันล่าสุด
databaseReference.child("posts").limitToFirst(100);

Part 4 การเปิดใช้งานโหมด offline

ลักษณะการทำงาน offline (Persistence Behavior)

Firebase Realtime Database มีการจัดการเรื่องการเชื่อมต่อให้แล้ว เมื่อไม่สามารถเชื่อมต่ออินเตอร์เน็ต หรืออยู่ในโหมด offline เรายังสามารถใช้งานแอพได้ โดยตัว Firebase จะทำการ cache ข้อมูลไว้ทุกการกระทำ และจะทำการ sync ข้อมูลให้อัตโนมัติเมื่อเรากลับเข้าสู่โหมด online แม้ว่าเราจะปิดแล้วเปิดแอพใหม่ก็ตาม มันยอดมากเลย แค่ประกาศใช้งานง่ายๆเพียงบรรทัดเดียว

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

*******************************************************************
ปัญหาที่หลายคนประกาศ 
.setPersistenceEnabled แล้วเมื่อออกและเข้ามาใหม่ปรากฏว่า crash โดยแสดง error ประมาณนี้

com.google.firebase.database.DatabaseException: Calls to setPersistenceEnabled() must be made before any other usage of FirebaseDatabase instance

ผู้เขียนแนะนำให้แก้ปัญหานี้โดย
1. สร้าง class ที่ extends Application แล้วไปประกาศ
setPersistenceEnabled(true)ที่นั่น

class MyApp extends android.app.Application 

@Override
public void onCreate() {
    super.onCreate();
    FirebaseDatabase.getInstance().setPersistenceEnabled(true);
}

2. ใน AndroidManifest ให้ไปประกาศ class ในข้อ 1 ใน <application> ซะ

android:name="com.example.MyApp"

ผู้เขียนทดสอบแล้ว ประกาศที่เดียว จบ
*******************************************************************

แอบ sync ข้อมูลแบบลับๆ (Keeping Data Fresh)

Firebase Realtime Database จะ sync และเก็บข้อมูลใน local ของ client เฉพาะข้อมูลที่ตัว listener ทำงานอยู่ แต่เราสามารถจะ sync ข้อมูลส่วนอื่นที่ยังไม่ได้ active ได้ โดยให้อ้างถึง path ที่ต้องการ sync ตัวอย่างเช่น

DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
scoresRef.keepSynced(true);

และโดยทั่วไปเราจะเก็บข้อมูลลง cache ได้ไม่เกิน 10MB แต่หากแอพมีข้อมูลที่ต้องการ cache มากกว่านั้น Firebase Realtime Database จะ purge cache ออก แต่ข้อมูลที่เก็บแบบ keepSynced จะไม่ถูก purge นะเออ

การ Query ข้อมูลในขณะที่ Offline

เมื่อแอพ sync ข้อมูลตอน online มาไว้ในเครื่องแล้ว และเมื่อแอพเข้าสู่โหมด offline เราก็ยังสามารถ query ข้อมูลที่ sync มาแล้วได้ ตัวอย่างเช่น

// ตอน online ได้ผลลัพธ์ 4 แถว จากการ query
scoresRef.orderByValue().limitToLast(4)
// เมื่อ offline หากต้องการ query ผลลัพธ์ 2 แถว ก็ทำได้
scoresRef.orderByValue().limitToLast(2)

การ run ตัว Transactions ขณะ Offline

จริงๆแล้วก็เหมือนกับกรณี offline ปกติ คือเมื่อ run ตัว transaction ในขณะ offline ข้อมูลต่างๆก็จะถูกเข้าคิวไว้ แต่หากมีการปิดแล้วเปิดแอพใหม่คิวจะไม่ถูกเก็บไว้ โดยเราควรแจ้งผู้ใช้ให้ทราบว่า transaction ไม่สำเร็จในกรณีที่ปิดแล้วเปิดแอพอีกครั้ง


Part 5 Security & Rules

Firebase Realtime Database Rules เป็นการกำหนดการเข้าถึง database ทั้งการ read, write และการทำ index โดย rules ทั้งหมดจะอยู่บน Firebase server แล้วเราสามารถปรับเปลี่ยนและมีผลทันทีได้ตลอดเวลา ซึ่งมี syntax เป็น Javascript-like โดยจะแบ่ง rules ออกเป็น 4 ประเภทดังนี้

  1. .read คือ การอนุญาตให้อ่าน
  2. .write คือ การอนุญาตให้เขียน
  3. .validate คือ การตรวจสอบข้อมูลที่เข้ามา
  4. .indexOn คือ การทำ index เพื่อความรวดเร็วในการเรียงลำดับ และการ query

โดยการกำหนด rules ต่างๆให้เข้าไปที่ Firebase Console เลือกโปรเจค จากนั้นเลือกเมนู Database แล้วเลือก tab ชื่อ RULES

สำหรับรายละเอียด Security & Rules นั้นมีมากมาย เช่น การกำหนด rules สำหรับการ read และ write ของแต่ละ path ที่ยึดตามโครงสร้างของ database, การกำหนด rules สำหรับการ validate การเพิ่มข้อมูล หรือ อัพเดทข้อมูล และการให้เข้าถึงข้อมูลตัวเองของผู้ใช้คนนั้นๆ เป็นต้น แนะนำให้ไปศึกษาเพิ่มเติมตามลิงค์นี้ครับ https://firebase.google.com/docs/database/security/ รับรองว่ามันไม่ยาก แค่มันไม่ง่ายแค่นั้นเอง


เคล็ดไม่ลับ

รับ Callback หลังจาก setValue() หรือ updateChildren()

เราสามารถ add completion callback ได้ทั้ง setValue() และ updateChildren() ได้ เผื่อใครอยากจะ handle กรณีที่ไม่สำเร็จ ดังตัวอย่างด้านล่าง

mUsersRef.child("id-12345").setValue("Jirawatee", new DatabaseReference.CompletionListener() {
   @Override
   public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
      if (databaseError != null) {
         Log.w(TAG, "setValue() failure");
      } else {
         Log.d(TAG, "setValue() successful");
      }
   }
});

การ Minify

หากทำการ minify กับโปรเจค ที่มีการใช้งาน model object กับฟังก์ชัน DataSnapshot.getValue(Class) หรือ DatabaseReference.setValue(Object) เราจำเป็นต้องเพิ่ม code ด้านล่างนี้ในไฟล์ proguard-rules.pro ด้วยนะ

# Add this global rule
-keepattributes Signature

# This rule will properly ProGuard all the model classes in
# the package com.yourcompany.models. Modify to fit the structure
# of your app.
-keepclassmembers class com.yourcompany.models.** {
  *;
}

Best practice ในการออกแบบโครงสร้างของข้อมูล

เราควรหลีกเลี่ยงการซ้อนกันของ data เพราะ Firebase Realtime Database อนุญาตให้เราออกแบบ data ซ้อนกันได้ไม่เกิน 32 ชั้น เพราะหากเรา fetch ข้อมูลจาก database ที่มีข้อมูลซ้อนกันเยอะ เราอาจต้อง fetch ข้อมูลจำนวนมาก ทำให้เปลือง bandwidth ได้ ควรออกแบบโครงสร้างแบบ flat เพื่อขนาด data ที่เล็กและเร็วในการ fetch ซึ่งประสิทธิภาพที่สูงกว่านั้น เราจะต้องแลกด้วยการไม่ทำ normalize ข้อมูล หรือเรียกว่า denormalize นั่นเอง ขอแค่อย่าลืมว่า เมื่อมีการอัพเดทหรือลบข้อมูลก็ต้องทำให้ครบทุกที่ที่เราเพิ่มข้อมูลเหล่านั้นเข้าไปด้วย

จากตัวอย่างเรามีการ add key มากกว่า 1 ที่ โดยสามารถแยกการ query ออกเป็น 3 มุมมองดังนี้

  1. Query ข้อมูลของ user ทั้งหมดจาก users
  2. Query ข้อมูลของ content ทั้งหมดจาก posts
  3. Query ข้อมูล content ของ user แต่ละคนจาก user-posts

ทั้ง 3 แบบเราสามารถ query ข้อมูลได้อย่างมีอย่างรวดเร็ว และยังสามารถทำ offset และ limit ของ data ได้ด้วย

ประเมินค่าใช้จ่าย

Firebase Realtime Database นั้นเป็นบริการที่ฟรีแบบจำกัด โดยจะมีเรื่องให้พิจารณาด้วยกัน 4 เรื่องคือ

  1. จำนวนการใช้งานพร้อมๆกัน (Connection)
  2. พื้นที่ที่เก็บข้อมูล (Storage)
  3. ขนาดของการส่งรับข้อมูล (Bandwidth)
  4. การสำรองข้อมูล (Backup)

โดยสถิติการใช้งานทั้งหมดเราสามารถเช็คได้ที่ Firebase Console โดยเข้าไปที่เมนู Database แล้วเลือก tab ชื่อ USAGE

และในหน้า Pricing ของ Firebase เราสามารถจะประมาณการใช้งานได้ว่าใช้เท่าไรคิดเงินเท่าไร ตัวอย่าง ผมจะจำลองการใช้งานของแพ็คเกจ SPARK ซึ่งเป็นแพ็คเกจฟรี โดย storage 1 GB เก็บข้อมูลได้ประมาณ 20 ล้านข้อความ และ bandwidth 10 GB จะ read และ write ได้ประมาณ 200 ล้านข้อความ เรียกได้ว่าแค่แพ็คเกจฟรี ก็ใช้งานได้เยอะเลยทีเดียว

หากแอพของเรามีข้อมูลเยอะ transaction เยอะ ก็เลือกใช้ตามแพ็คที่มีค่าใช้จ่ายที่เหมาะสมต่อไป ดูจากราคาแล้วเรียกว่าคุ้มค่ากับความเสถียร, ความปลอดภัย และเวลาในการพัฒนาแน่นอน

ตัวอย่างพร้อมแล้วใน GitHub

ตัวอย่างมีทั้งแบบการใช้งานแบบ authentication กับ public เลยครับ โดยตัว public จะเป็นตัว basic และตัว authentication จะ advance หน่อย มีตัวอย่างทั้งแอพ quickstart ของ Firebase ที่เอามา modify และตัวอย่าง chat app ด้วย โหลดเลย


ทิ้งไว้กลางทาง…เอ้ย ทิ้งท้าย

เป็นอย่างไรบ้างครับกับ Firebase Realtime Database ดูท่าทางน่าจะทำให้ชีวิตหลายคนดีขึ้นมาเลยทีเดียว ก่อนจะเอาขึ้น Production อย่าลืมไปตั้ง Security Rules ให้ต้อง Authentication ก่อนเข้าถึง database นะครับ ไม่งั้นเดี๋ยวคนอื่นแอบมาเขียน database เราไม่รู้ด้วย สำหรับบทความนี้ก็ทิ้งห่างจากบทความที่แล้วไปครึ่งเดือนเท่านั้นเอง ไม่นานเล้ยยย(เสียงสูง) เอาเป็นว่าบทความต่อไปก็จะเป็นเรื่องของ Firebase Storage ซึ่งจะไม่ทิ้งช่วงนานแน่นอน เราจะทำตามสัญญา ขอเวลาอีกไม่นาน…สำหรับวันนี้ต้องลาไปก่อน ราตรีสวัสดิ์พี่น้องชาวไทย

ref:https://developers.ascendcorp.com/%E0%B8%A3%E0%B8%B9%E0%B9%89%E0%B8%88%E0%B8%B1%E0%B8%81-firebase-realtime-database-%E0%B8%95%E0%B8%B1%E0%B9%89%E0%B8%87%E0%B9%81%E0%B8%95%E0%B9%88-zero-%E0%B8%88%E0%B8%99%E0%B9%80%E0%B8%9B%E0%B9%87%E0%B8%99-hero-5d09210e6fd6

How to Optimize MySQL

CentOS
There is a default my.cnf that comes with mysql (4+5) that will make mysql run a bit quicker if you have 2+ gig of ram

cp -f /usr/share/mysql/my-large.cnf /etc/my.cnf

There is also my-huge.cnf, or my-medium.cnf depending on your hardware setup.   Check the contents of these my*.cnf files for the one that’s right for you.

*NOTE 1* the log-bin option is enabled  by default.  This will quickly use a lot of disk space.  It’s recommended to comment out the log-bin line from your /etc/my.cnf, if it exists.

*NOTE 2* Take note of your old /etc/my.cnf file.  If you have innodb_file_per_table=1 make sure the new my.cnf you install also has this setting.  Similarly, if your old one does not have innodb_file_per_table=1 enabled, then your new my.cnf should also not have it enabled.   If the new my.cnf has a different setting for innodb_file_per_table, then it may corrupt your data.

Be sure to make full backups of your .sql files before doing any changes to your my.cnf.

Debian/FreeBSD
We don’t currently have optimized my.cnf files for these OSs.
The /etc/my.cnf will rely on the internal defaults in the mysqld binaries.


 

CentOS and MySQL 5.6

New MySQL installs might not have any included my-*.cnf files.
We’ve added a few from MySQL 5.5 which seem to work with 5.6, eg:

cp /etc/my.cnf /etc/my.cnf.old
wget -O /etc/my.cnf http://files.directadmin.com/services/all/mysql/my-huge-5.5.cnf

 


MySQL 4.x – Depreciated
If you’ve got mysql 4 (and not mysql 5), then you can use the following code in your /etc/my.cnf:

Referenced from the Forum

vi /etc/my.cnf [ENTER]

Press ‘i’ to enter insert mode, then paste:

[mysqld]
local-infile=0
skip-locking
query_cache_limit=1M
query_cache_size=32M
query_cache_type=1
max_connections=500
interactive_timeout=100
wait_timeout=100
connect_timeout=10
thread_cache_size=128
key_buffer=16M
join_buffer=1M
max_allowed_packet=16M
table_cache=1024
record_buffer=1M
sort_buffer_size=2M
read_buffer_size=2M
max_connect_errors=10
# Try number of CPU’s*2 for thread_concurrency
thread_concurrency=2
myisam_sort_buffer_size=64M
server-id=1

[safe_mysqld]
err-log=/var/log/mysqld.log
open_files_limit=8192

[mysqldump]
quick
max_allowed_packet=16M

[mysql]
no-auto-rehash
#safe-updates

[isamchk]
key_buffer=64M
sort_buffer=64M
read_buffer=16M
write_buffer=16M

[myisamchk]
key_buffer=64M
sort_buffer=64M
read_buffer=16M
write_buffer=16M

[mysqlhotcopy]
interactive-timeout

Press ctrl-c to exit insert mode.  Then press shift-Z shift-Z to save and quit.   Restart mysqld:
Redhat:

/sbin/service mysqld restart

FreeBSD:

/usr/local/etc/rc.d/mysqld restart

MySQL vs. MongoDB: Choosing a Data Management Solution

Table Of Contents

1. Introduction
2. The dominance of RDBM systems
3. A new era of NoSQL movement
4. Tables vs Documents vs Graphs vs Key/Values
5. MySQL and MongoDB: Conscious Decision
5.1. Enforced Schema vs Schemaless
5.2. Normalization vs Duplication
5.3. Relations vs References
5.4. Transactions vs Atomic Updates
5.5. SQL vs JSON
5.6. Stored Procedures vs Scripting
5.7. GROUP BY vs Aggregations
5.8. Clustering and Sharding/Partitioning
5.9. Full text search
5.10. Development
5.11. Monitoring & Diagnostics
5.12. Security
5.13. Trade-offs, not battles
5.15. MySQL or MongoDB?
5.16. MySQL and MongoDB: Nowadays
6. Conclusions

1. Introduction

It would be fair to say that as IT professionals we are living in the golden age of data management era. As our software systems become more complex and more distributed, driven by myriads of scalability and availability requirements, the traditional solutions used for years began to fall behind.

The goal of this tutorial is to provide a fair overview of the two data management solutions: mature and widely used relation database represented by MySQL and a new kid on the block, MongoDB, which in turn represents a new generation of data store. By no means is it a battle with a clear winner at the end. Instead, the intention is to help developers to make a choice and find a right fit for their applications by going through the features of MySQL and MongoDB, discussing the different guarantees and limitations each one has.

2. The dominance of RDBM systems

The history and evolution of data management software is not a particularly exciting topic to spend time on. For a quite some time relational database management systems took a leadership and for years the choice was obvious, either MySQL,PostreSQL or Oracle, just to name a few. SQL (and its vendor-specific dialects) was the de-facto standard for querying relational data stores and by definition every backend software developer had to learn and understand a least some basics of it. It worked surprisingly well until recently, when a whole family of new data management systems has emerged, the phenomenon also known as NoSQL (or Not Only SQL) movement.

3. A new era of NoSQL movement

The challenges which modern software systems are facing nowadays with respect to data volumes and requests/transactions throughput requirements revealed that relational data stores often become a bottleneck, thus imposing the limits on overall system scalability. Traditionally, the way to solve this problem was just buying a bigger box (so called vertical scalability), however at some point the price to pay becomes very, very high, making the whole system ridiculously expensive and impractical.

The industry was actively looking into a cheaper ways to build complex distributed systems, making use of horizontal scalability instead. It also meant to come up with alternatives to relation data stores, which could scale horizontally as well. That the moment, when NoSQL movement has begun.

4. Tables vs Documents vs Graphs vs Key/Values

A Relational data model represents all data in terms of tuples and relations (better known as tables). The structured data fits this model very well and for a long time no other viable alternative existed. With the NoSQL movement, many alternative data models were developed, giving a birth to a whole bunch of specialized data storage systems.

The NoSQL solutions could be classified in a few different categories. Document data stores are designed for storing, querying and managing documents (semi-structured data). The more mature representatives of this category includeCouchDB, Couchbase, MongoDB, OrientDB and HyperDex. Key/Value data stores are designed for storing, querying and managing associative arrays (also known as dictionaries or hashes).

The most widely used representatives of this category include DynamoDB, FoundationDB, HyperDex, MemcacheDB, Redis,Riak, Aerospike and OrientDB. Graph data stores are designed for efficiently storing and operating graph structures. The well-known representatives of this category include Neo4J, InfiniteGraph, GraphBase and OrientDB. Last but not least, wide-column data stores are taking a hybrid approach (combining some characteristics of key/value data stores and traditionalrelational data stores). The most advanced representatives of this category include Accumulo, Cassandra and HBase.

Please notice that the list of different NoSQL data stores presented above is far from being complete, it just contains the most well-known and widely used names, but there are a lot more out there.

5. MySQL and MongoDB: Conscious Decision

Enough with the introduction, so let us move on to more practical things. In this part of the tutorial we are going to take a look on all aspects of application development process using MySQL and MongoDB, also spending some time talking about deployment and monitoring. The goal is to discuss the trade-offs and design decisions that each of those data stores make and analyze how they are changing the way we develop applications. Also, another purpose this tutorial serves is to assist in making a decision when MongoDB may be a better choice than MySQL (and vice versa), taking into account application architecture, data access patterns and storage requirements.

As we already now, MongoDB is a document data store. It stores JSON-style documents which are grouped into collections. At the top of MongoDB data model hierarchy is a database (please refer to official documentation in order to get the comprehensive details). The current production ready version of MongoDB is 3.0.4.

From other side, MySQL is relational data store. The data is stored in tables which contains columns. The tables are grouped into database (please refer to official documentation in order to get the comprehensive details). The current production ready version of MySQL is 5.6.25.

MySQL supports multiple storage engines, each of them serves own purpose and has some features not available in other storage engines. Along this tutorial we assume that InnoDB storage engine is used as it is the default and most general-purpose one which is recommended for use except for specialized use cases.

5.1. Enforced Schema vs Schemaless

MySQL as a relation data store requires a strict schema for its data model: all tables should be created with columns defined. Only then the data could be stored and queried using the SQL language. It somewhat complicates the development and deployment process as every time the modification of data model is required, the schema should be changed first and then data migrated. Here is an example of typical steps required to create new database, new table and insert one row into it using MySQL shell and SQL language:

1 CREATE DATABASE tutorial;
2 USE tutorial;
3 CREATE TABLE chapters (
4     id INT PRIMARY KEY AUTO_INCREMENT,
5     title VARCHAR(255) NOT NULL
6 );
7 INSERT INTO chapters (title) VALUES ("MySQL and MongoDB: trade-offs, not battles");

In contrast, MongoDB does not impose any schema on the documents being stored in the collection. It becomes the responsibility of application to deal with that, the only thing MongoDB restricts is the supported data types. It significantly speeds up the development process as MongoDB could be used right away to store JSON documents of any shape.

The structure of the documents could evolve and change over time, and different generations of the documents may coexist in the same collection. It is up to application developer to decide how to perform data cleanup or migration if necessary. Let us take a look on how the same steps of creating new database, new collection and inserting one document into it could be done using MongoDB shell:

1 use tutorial
2
3 db.chapters.insert({
4     "title": "MySQL and MongoDB: trade-offs, not battles"
5 })

It is worth to mention that upcoming 5.7 version of MySQL (which is currently in release candidate phase) introduces the native JSON support. With the introduction of this new JSON data type it would be possible to combine enforced schema and schemaless data in MySQL.

5.2. Normalization vs Duplication

Normalization in the world of relational databases is the process of organizing tables to minimize data redundancy. It involves decomposing a table into less redundant (and smaller) tables without losing data. It also assumes defining foreign keys in the old tables referencing the primary keys of the new ones. For example, this is the way to create two tables inMySQL, where the books table references the authors table:

01 CREATE TABLE authors (
02     id INT PRIMARY KEY AUTO_INCREMENT,
03     name VARCHAR(255) NOT NULL
04 );
05 CREATE TABLE books (
06     id INT PRIMARY KEY AUTO_INCREMENT,
07     title VARCHAR(255) NOT NULL,
08     author_id INT NOT NULL,
09     FOREIGN KEY (author_id) REFERENCES authors(id)
10 );
11 INSERT INTO authors (name) VALUES ("Michael Kofler");
12 INSERT INTO books (title, author_id) VALUES ("The Definitive Guide to MySQL 5", 1);

The normalization is obsolete for MongoDB. Instead, careful data modeling and certain level of data duplication is the way to get most out of document data store. It also becomes the responsibility of the application to decide the level of data repetition and to keep all data fragments in sync in case of modifications. For example:

1 db.books.insert( {
2     "title": "The Definitive Guide to MySQL 5",
3     "author": {
4         "name": "Michael Kofler"
5     }
6 })

5.3. Relations vs References

In the normalized database, a JOIN clause from the SQL is usually used to combine records from two or more MySQLtables (there are other techniques as well but by and large they essentially lead to the same results). For example:

1 SELECT * FROM books b INNER JOIN authors a ON a.id = b.author_id;

MongoDB supports document references but does not support joins: to resolve the reference, the additional query (or queries) should be executed (leading to infamous in relational world N+1 query problem). For example, in the snippet below we are inserting the reference to authors collection from the books collection instead of embedding author details:

01 db.authors.insert({
02     "name": "Michael Kofler",
03     "_id": 1
04 })
05 db.books.insert({
06     "title": "The Definitive Guide to MySQL 5",
07     "author": {
08         "$ref": "authors",
09         "$id": 1
10     }
11 })

The necessary round-trips are the price to pay for the flexibility of going schemaless. However, as we already mentioned in section Normalization vs Duplication, certain level of data duplication and careful analysis of data access patterns may mitigate the problem.

5.4. Transactions vs Atomic Updates

A transaction is a logical unit of work performed by data management system against its data. The importance of transaction is encapsulated into its foundational properties: transaction must be atomic, consistent, isolated anddurable (also known as ACID model). MySQL is a fully transactional relation data store which makes it a perfect choice for the mission-critical applications that are not able to tolerate data lost or inconsistency. However, transactions are not free: they significantly limit the horizontal scalability of the data stores, and MySQL is not an exception.

MongoDB does not support transactional guarantees. Instead, it supports atomic updates on the single document level. It means when a single update operation modifies multiple documents, the modification of each document is atomic, but the operation as a whole is not atomic and other operations may interleave. It worth mentioning that it is possible to implementtransaction-like semantic on top of MongoDB (using the two-phase commits technique) but it requires quite complex implementations.

5.5. SQL vs JSON

SQL is a special-purpose programming language designed for managing data held in a relational database management system. It is the only query language supported by MySQL, enriched with a few vendor-specific extensions. Despite its simplicity, SQL is a very powerful language which essentially consists of two parts: a data definition language (DDL) and adata manipulation language (DML). We have seen quite a few examples already, but let us take a look on the simplest and probably most useful example of selecting rows from the table:

1 SELECT * FROM books;
2 SELECT * FROM books WHERE title LIKE "%MySQL%";

In MongoDB, everything is a document, represented as JSON. MongoDB does not have a dedicated language to manage the documents and takes very different approach instead. Document manipulation or querying is described using very richset of operators, composed with each other using JSON structure, as any other document. This unified representation is quite powerful, expressive and easy to understand, plus it does not require to learn yet another programming language. Most of the operators are self-explanatory and intuitive, for example:

1 db.books.find();
2 db.books.find({
3     "title": {
4         "$regex": "MySQL"
5     }
6 });

5.6. Stored Procedures vs Scripting

A stored procedure is a special kind of routine available on the server side of relational data store. Stored procedures could be implemented in different programming languages however most of the time they are written using SQL or its vendor-specific dialect, which is the case with MySQL. Typically, stored procedures help increase the performance of the applications by executing some logic directly on the server, requiring less information to be sent between the server and the client. For example, here is a very simple MySQL stored procedure:

1 DELIMITER //
2 CREATE PROCEDURE find_books
3 (IN pattern CHAR(255))
4 BEGIN
5     SELECT COUNT(*) FROM books WHERE title LIKE pattern;
6 END //
7 DELIMITER ;
8
9 CALL find_books("%MySQL%");

MongoDB supports server-side scripting in the form of executing JavaScript code on the server. Those server-side scripts may resemble stored procedures but the implementation goes beyond just simple scripting as it supports map/reduceparadigm, firstly populated by Google, and widely adopted for big data analytics. Essentially, by supplying the script tomap/reduce command, it becomes possible parallelize the processing of a large and very large datasets across manyMongoDB data store instances. Let us take a look on very naïve map/reduce script example which counts how many books have “MySQL” string in their title:

01 db.runCommand( {
02     mapReduce: "books",
03     map: function() {
04         if (this.title.indexOf("MySQL") >= 0) {
05             emit("MySQL", 1);       
06         }
07     },
08     reduce: function(key, counters) {
09         count = 0;
10
11         for (var index = 0; index < counters.length; ++index) {
12             count += counters[index];                   
13         }
14
15         return count;
16     },
17     out: { inline: 1 }
18 } )

Compared to the MySQL stored procedure example, the MongoDB one looks overly verbose and complicated. However once the concepts of the map/reduce paradigm become more clear, the example is going to look quite simple, opening a whole new frontier of applying many data processing, exploration and analysis techniques.

5.7. GROUP BY vs Aggregations

In data management domain, an aggregate function is a function where the values of multiple data records are grouped together depending on certain aggregation criteria, as such producing a single value of more significant meaning or measurement. The SQL language defines a set of specific clauses for performing aggregations: GROUP BY and HAVING. The set of standard grouping functions among many others include AVG(), COUNT(), MAX(), MIN(), STD(), SUM(). Let us take a look on GROUP BY example in MySQL shell:

01 CREATE TABLE orders (
02     id INT PRIMARY KEY AUTO_INCREMENT,
03     publisher VARCHAR(255) NOT NULL,
04     product VARCHAR(255) NOT NULL,
05     price NUMERIC (15,2) NOT NULL
06 );
07 INSERT INTO orders (product, publisher, price) VALUES
08     ("The Definitive Guide to MySQL 5", "APress", 37.36),
09     ("MySQL Cookbook", "O’Reilly", 49.65),
10     ("Learning MySQL", "O’Reilly", 29.48);
11 SELECT publisher, SUM(price) FROM orders GROUP BY publisher;

MongoDB offers a specialized set of aggregations operations which process documents and return computed (or aggregated) results. Aggregation operations group values from multiple documents together, and can perform a variety of operations on the grouped data to return a single result. Aggregations in MongoDB could be done in two ways: usingaggregation pipeline or single purpose aggregation methods and commands.

Let us take a look on the same example we have prepared to demonstrate MySQL aggregations, this time using MongoDB’saggregation pipeline:

01 db.books.insert( {
02     "title": "The Definitive Guide to MySQL 5",
03     "publisher": "APress",
04     "price": 37.36
05 })
06 db.books.insert( {
07     "title": "MySQL Cookbook",
08     "publisher": "O’Reilly",
09     "price": 49.65
10 })
11 db.books.insert( {
12     "title": "Learning MySQL",
13     "publisher": "O’Reilly",
14     "price": 29.48
15 })
16 db.books.aggregate([
17     { $match: {} },
18     { $group: {
19         "_id": "$publisher",
20         "price": {
21             "$sum": "$price"
22         }
23     }}
24 ]);

The map/reduce command which we discussed in Stored Procedures vs Scripting section could also be considered as a way to do aggregations.

5.8. Clustering and Sharding/Partitioning

Nowadays, the data volumes are growing exponentially and the single physical instance of data store often is not able to persist and manage such a mass of data at acceptable pace. Clustering is technology that allows to bring many individual computing instances to work together. With respect to data storages, clustering goes side by side with data sharding/partitioning, which is a technique to split large amount of data across multiple data store instances.

For quite a while, MySQL has been available in the MySQL Cluster edition which claims to be a write-scalable, real-time,ACID-compliant transactional data store. MySQL Cluster is built on top of distributed, multi-master architecture, which supports auto-sharding/partitioning and scales horizontally to serve read and write intensive workloads.

Historically, MySQL Cluster has a reputation of being too complex, quite hard to configure, monitor and maintain. In contrast to standalone MySQL deployments, the data schema should be designed in such a way so to take data sharding / partitioning into account, otherwise the performance of the data store will suffer a lot. Lastly, MySQL Cluster has many limitations comparing to the regular MySQL distribution.

MongoDB document data store supports sharding/partitioning out of the box using concept of sharded clusters. The strong side of MongoDB sharding/partitioning is that it has simple and easy configuration. It scales horizontally pretty well but there are quite a few limitations to be aware of.

5.8.1. Replication

Replication is an essential technique to keep data safe (by replicating it across many data store instances) and in many cases to improve scalability and fault-tolerance of the applications dealing with this data. MySQL supports traditionalmaster/slave replication, which by default is asynchronous but semi-synchronous and delayed replication modes are also possible.

MongoDB approaches replication by introducing replica sets. Basically, it is master/slave replication but MongoDB uses a little bit different terminology. The master, called the primary, receives all write operations and slaves, calledsecondaries, apply operations from the primary. The one of the greatest features supported by replica sets is automatic failover: when a primary does not communicate with the other members of the replica set, the replica set will attempt to select another member to become the new primary.

To be fair, it is possible to configure MySQL master/slave replication with automatic failover as well but there are some concerns and discussions about this feature in MySQL community.

5.9. Full text search

Since a long time MySQL has support for full-text indexing and searching that is implemented using special type of the index. It is important to mention that full-text indexing support became available on the InnoDB storage engine only sinceMySQL 5.6 release branch.

Interestingly, full-text search in MySQL is possible using natural language search (phrase search), boolean search (terms search) where the words to search for might be marked as “must be present” or “must be absent”, and query expansion search (a slight modification of a natural language search). However, at the moment full-text indexing is not supported in clustered MySQL deployments (see please Clustering and Sharding/Partitioning section for a brief discussion about MySQL Cluster). Let us take a look on quick example:

01 CREATE TABLE products (
02     id INT PRIMARY KEY AUTO_INCREMENT,
03     product VARCHAR(255) NOT NULL,
04     description VARCHAR(255) NOT NULL,
05
06     FULLTEXT product_description (product, description)
07 );
08 INSERT INTO products (product, description) VALUES
09     ("The Definitive Guide to MySQL 5", "This book shows readers how to connect to MySQL via all of the major APIs, including PHP, Perl, Java, JSP, and C#"),
10     ("MySQL Cookbook", "Ideal for beginners and professional database and web developers"),
11     ("Learning MySQL", "Book travels far into MySQL's subtleties, including complex queries and joins");
12 SELECT * FROM products
13     WHERE MATCH (product, description)
14     AGAINST ('database guide' IN NATURAL LANGUAGE MODE);

Full-text search support was introduced in MongoDB not so long ago. Similarly to MySQL, it is implemented using special type of the index on the string content (or array of strings). MongoDB also supports phrase search, terms search and boolean search as a combinations of those. It is easy to use and elegantly implemented feature but not without limitations. Unfortunately, at the moment MongoDB gives no control to specify the subset of fields to perform full-text search against: it is always matches all the fields included in the full-text index. Let us take a look on MongoDB full-text search in action:

01 db.products.insert({
02     "product": "The Definitive Guide to MySQL 5",
03     "description": "This book shows readers how to connect to MySQL via all of the major APIs, including PHP, Perl, Java, JSP, and C#"
04 })
05 db.products.insert({
06     "product": "MySQL Cookbook",
07     "description": "Ideal for beginners and professional database and web developers"
08 })
09 db.products.insert({
10     "product": "Learning MySQL",
11     "description": "Book travels far into MySQL's subtleties, including complex queries and joins"
12 })
13 db.products.createIndex({
14     product: "text",
15     description: "text"
16 })
17 db.products.find({
18     $text: {
19         $search: "database guide"
20     }
21 })

5.10. Development

When it comes to development, MySQL provides a wide set of connectors which could be used to communicate with the server from most of the mainstream programming languages of your choice. With respect to Java development, MySQLincludes a JDBC driver which conforms to the JDBC 3.0 and JDBC 4.0 specifications.

Although it is quite rare to encounter the code which uses straight JDBC interfaces to communicate with relation data store (many frameworks have been built to facilitate that), it is good to know what is involved. Let us take a look on following code snippet which connects to MySQL and executes a single SELECT statement:

01 try {
02     connection = DriverManager.getConnection("jdbc:mysql://localhost/tutorial");
03
04     Statement statement = null;
05     ResultSet resultSet = null;
06
07     try {
08         statement = connection.createStatement();
09         resultSet = statement.executeQuery("SELECT * FROM books");
10                 
11         while (resultSet.next()) {
12             // Now do something with the ResultSet ....
13         }
14     } catch (SQLException ex) {
15         // Log exception
16     } finally {
17         if (resultSet != null) {
18         try { resultSet.close(); } catch ( SQLException ex) { /* ignore */ }
19         }
20
21         if (statement != null) {
22         try { statement.close(); } catch ( SQLException ex) { /* ignore */ }
23         }
24     }
25 } catch (SQLException ex) {
26     // Log exception
27 } finally {
28     if (connection != null ) {
29         try { connection.close(); } catch( SQLException ex ) { /* ignore */ }
30     }
31 }

It looks quite verbose and full of exception handling. MongoDB in turn has a rich ecosystem of drivers, which aside from mainstream languages includes drivers for Erlang, Go, Node.js and Scala. Having a freedom to come up with own specifications, MongoDB is in the process of development of the next generation drivers and quickly adapting the existing implementations to the latest trends in the industry (the good example of that is ReactiveMongo: Reactive Scala Driver for MongoDB).

As an example, let us take a look on one of the way to connect to MongoDB using Java and Morphia driver (assuming we have Book and Author data model classes):

1 final MongoClient client = new MongoClient( "localhost", 27017 );
2 final Datastore dataStore = morphia
3     .map( Book.class, Author.class )
4     .createDatastore( client, "tutorial" );
5
6 final Query< Book > query = dataStore.createQuery( Book.class ).
7 final List< Book > books =  query.asList();       

Historically, JSON support in Java was quite good because of rich set of the community supported libraries, thus making conversions of JSON documents to Java classes (and back) no brainer.

It would be unfair not to mention that Java community has developed quite a lot of different object-relational mappingframeworks (ORMs) to abstract from low-level JDBC interfaces, which essentially allow to write the code as concise asMongoDB’s example. However, it is important to understand that they add quite a lot of complexity into existing applications.

5.10.1. Deployment

Both MySQL and MongoDB are available on most of the major operating systems. In most cases MySQL is being installed from platform-specific packages and requires privileged access to the system. Although the downloadable archives are also available, depending on the operating system, configuration and edition (for example, MySQL Cluster), the installation may get quite complex and non-intuitive (however may not require privileged access to the system).

Contrary, MongoDB in most cases is distributed as downloadable archive which could be unpacked and used right away. The sensible defaults play the great role here by requiring minimum configuration, just run the MongoDB server and start filling data store with documents.

It will be fair to mention that with containerization and Docker, the landscape of deployment procedures (we so get used to) is changing very rapidly. Moving from traditional package-based distributions to preconfigured containers flatten the installable packages and download-unpack-run models: everything becomes an image, run as isolated container, available in milliseconds.

Not to mention that many cloud providers (AWS, Cloud Foundry, Open Shift, just to name a few) have software-as-a-service offerings for MySQL and/or MongoDB, taking care of all configuration, infrastructure and scalability details.

5.11. Monitoring & Diagnostics

Clearly, monitoring is a critical component of any data store administration procedure. There is quite a number of commercial solutions for monitoring data store of your choice but let us discuss the ones available as part of the MySQL andMongoDB distributions.

MySQL has a several ways to perform monitoring and diagnostics. First of all, it is SHOW ENGINE <ENGINE> STATUScommand. Secondly, MySQL supports performance schema which is a feature for monitoring MySQL server execution at a low level. And lastly, MySQL supports DTrace probes (not supported by every operating system) which are designed to provide information about the execution of queries within MySQL server and the different areas of the system being utilized during that process.

MongoDB distribution includes a number of command line utilities that allow to quickly retrieve statistics about the performance and activity of each data store instance. Not only that, every running MongoDB instance also provides a web interface that exposes diagnostic and monitoring information through a simple web page. And lastly, MongoDB includes a number of commands to get the insights about the state of the database by connecting to it through the shell.

5.12. Security

MySQL uses privilege-based security model. The primary function of the MySQL’s privilege system is to authenticate a user and to associate that user with privileges on a particular database (such as CREATE, DROP, SELECT, INSERT, UPDATE,DELETE and others).

As of now, there are few things that are not supported by MySQL privilege system: inability to explicitly specify that a given user should be denied access and inability to specify that a user has privileges to create or drop tables in a database but not to create or drop the database itself. On the transport layer, MySQL supports secure (encrypted) connections between clients and the server using the SSL (Secure Sockets Layer) using the TLS v1.0 protocol.

MongoDB security features include authentication, authorization and auditing. Its foundation is a role-based access control with flexible set of privileges. It worth to mention that MongoDB provides a basis for user-defined roles by defining a set of build-in roles. It is also possible to use TLS/SSL (Transport Layer Security/Secure Sockets Layer) to encrypt all of MongoDB’s network traffic to ensure that it is only readable by the intended client.

5.13. Trade-offs, not battles

As we have seen, MySQL and MongoDB both serve same common goal: be a comprehensive data management solution for its users. However, the ways each of them approaches this goal are very, very different. From one side, we see quite mature and battle-tested relational data store. From other side, we met young but quickly maturing document data store, so suitable for certain classes of modern web applications.

As was stated several times along this tutorial, it is not a battle and we are not looking for a winner. Rather, we are looking for solution which better fits to the needs of your applications. Even more, heterogeneous data storage infrastructure becomes rather a norm than an exception nowadays: for example, MongoDB may perfectly fit into analytics and reporting demands, whereas MySQL may store billing transactions. Luckily, with the help of this tutorial you will be able to pick the right tool to do the job.

In many respects it would be fair to say that MongoDB was built for web: embracing JSON, very short learning curve, foundation of rapid development, new features added every release. MySQL is battle-tested, conservative and old fashion, however things are changing very fast as relation data stores try to adapt themselves to meet modern application needs.

5.15. MySQL or MongoDB?

A lot have been already said about MySQL and MongoDB. It would be good to finish the discussion with the set of recommendations when MongoDB is preferred than MySQL and vice-versa.

First of all, if your data is critical to your business, very likely MySQL is a safer choice: ACID properties are there for a reason. But every application is different. Content management systems, logs management, analytics, forums and blogs, events stores, product catalogs, inventory management, those kinds of applications may benefit from MongoDB as a data store.

Schemaless data model is an enabler for rapid development: just introduce new properties as you go without a need to perform schema evolutions and data migration. Arguably, but MongoDB’s style of manipulating documents and running queries is much more developer-friendly (moreover, it does not require any language to learn, like SQL). ConfiguringMongoDB‘s replica sets and sharded clusters is really easy and fast, comparing to MySQL Cluster configuration (and management).

Modern architectural patterns are promoting to split application’s read and write paths (for example, Command and Query Responsibility Segregation or just CQRS). With that, applications could be designed in a way where write path is served byMySQL but MongoDB could be a data storage for read path, where data pieces are composed into meaningful documents.

5.16. MySQL and MongoDB: Nowadays

It is worth mentioning that two companies, Twitter and Facebook, successfully operate some of the largest MySQLdeployments out there. They happily share their experience though the numerous blog posts: MySQL at Twitter, Another look at MySQL at Twitter and incubating Mysos, How Twitter Stores 250 Million Tweets a Day Using MySQL, WebScaleSQL: A collaboration to build upon the MySQL upstream, and more.

From other side, adoption of MongoDB is growing every day with more and more companies deploying it at a larger and larger scale. For example, eBay shared How We Built eBay’s First Node.js Application, while Craigslist followed up withMongoDB at Craigslist: 1 Year Later. No doubts, we are going to see more wide adoption in the future.

6. Conclusions

In this short tutorial we tried to take a look on two very popular data stores, traditional relation database represented byMySQL and document data store represented by MongoDB. We briefly walked through most important features and data guarantees each of those has, trying to assess how applications scalability and availability might be affected. We have omitted a lot of low-level intrinsic details as both MySQL and MongoDB worth several books alone. Instead, we did a high-level overview, picking the most interesting and important areas.

 

ref:https://www.javacodegeeks.com/2015/07/mysql-vs-mongodb.html#transactions_vs_atomic

Updating FreeBSD with Make World

When a new version of FreeBSD is released, or there have been a number of security updates released it is necessary to update FreeBSD with Make World. The process may seem very complex if you have never done one before, but overall it is very straight forward and painless.

Step 1. Getting the new source

To see how to download the most recent source see the Cvsup Tutorial.

Step 2. Building the new world

This step is not necessary since the world will be automatically built when you do the install, if it has not been built already. The reason for running this command seperately is so that it can run while the machine is running normally instead of running while the machine is in single user mode. Running it seperately also allows you to fix any errors that occured before installing the world.

# cd /usr/src
# make buildworld

The time buildworld takes varies greatly depending on the speed of the machine. On a 90mhz machine it could take up to 24 hours, where as on a 3ghz machine the time would be under 45 minutes.

If you experience problems during the buildworld you need to run the follow commands to clean up so you can start over

# cd /usr/obj
# chflags -R noschg *
# rm -rf *

Step 3. Recompiling the Kernel

The kernel needs to be using the same source as the new world or strange things will happen. To do this see the building a new kernel tutorial

Step 4. Installing the new world

The new world should be installed with as little else running as possible, so booting into single user mode is the best way. To do this reboot and either choose boot into single user mode from the menu in 5.x or hit space during the 10 second count down at boot and type

boot -s

Hit enter to choose /bin/sh as your shell and then enter the following commands to mount the needed drives and add swap space.

# mount -u /
# mount -a -t ufs
# swapon -a

Now you are ready to install the new world.

# cd /usr/src
# make installworld

Step 5. Merging the config file

Some updates to the source require updates to the config files in /etc. It is a good idea to back up your /etc directory. Once you have done this run mergemaster

# mergemaster -v

Mergemaster will compare the new etc files to what you have in /etc. It will present any differences to you and ask if you want to merge, skip, or just install the new file. For the most part you should install the new version of every file unless it is a file that you know you have editted such as make.conf, rc.conf, or master.passwd.

After you have finished merging the etc files you can reboot back into multi-user mode.

Performing a Make World remotely.

Although it is not recommended a make world can be done remotely. You just need to kill every unnecessary process and be really careful since if there is a problem the machine will not come back online after the reboot.
ref:http://www.freebsdmadeeasy.com/tutorials/freebsd/performing-a-make-world-in-freebsd.php

Recompiling The Kernel In FreeBSD

Unless you have built your own custom kernel in FreeBSD, you are using the GENERIC kernel. The GENERIC kernel contains everything you need to get the machine up and running the first time and covers a wide range of hardware. Some common reasons for rebuilding the kernel are:

  • To speed it up by taking out unused modules
  • Add support for new hardware
  • Update the kernel with the new source during a Make World

Creating the Kernel Config

The first step to building a new kernel is to copy the GENERIC config file to your own. This new config file is generally given the hostname of the machine.

# cd /usr/src/sys/i386/conf
# cp GENERIC NEWKERNEL

Once you have does this open the new kernel config and begin hashing out modules that are not needed. For example, if you are creating this kernel for a PIII you do not need the I486_CPU or the I485_CPU in your kernel and you can take them out like this:

machine i386
#cpu I486_CPU
#cpu I586_CPU
cpu I686_CPU
ident GENERIC

You should also change the ident to the name of the file so that when booting up it will show that it is booting your kernel and not the GENERIC one.

machine i386
#cpu I486_CPU
#cpu I586_CPU
cpu I686_CPU
ident MIDNIGHT

On machines without SCSI controllers everything in the SCSI section can be hashed out, the same is true with the RAID, USB, and Firewire sections. If something needed is removed the machine may not be able to reboot with the new kernel and the previous one will need to be loaded instead. Notes on what the different modules in the config file are responsible for can be found in /usr/src/sys/i386/conf/NOTES

Building your new kernel is traditional way

# cd /usr/sbin/config NEWKERNEL
# cd ../../compile/NEWKERNEL
# make depend
# make
# make install

To save time the last three commands can be done as

# make depend && make && make install && reboot

Building your new kernel the new way

Recently FreeBSD has a new way to build and install the new kernel and it can be done as so

# cd /usr/src
# make buildkernel KERNCONF=NEWKERNEL
# make installkernel KERNCONF=NEWKERNEL

Here KERNCONF is just a variable that refers to the name of the kernel config file you want to use. To store the name so you no longer need the last part of these commands you can put the following into you /etc/make.conf to save it.

KERNCONF=NEWKERNEL

Rebooting

Once you successfully built and installed the kernel you will need to reboot the machine

# reboot

Errors

If the machine reboots successfully but gives errors it may be because the kernel was compiled with source code that is newer than that of the world. To rebuild with the new source code read about performing a make world

A NAT Router Firewall IPSec Gateway with FreeBSD 5.1-RELEASE

A typical setup for home users and small businesses is to have a single machine connected to the internet as a router that serves as gateway for the private network behind it. Obviously, this router has to “hide” the whole net behind its own external address, which can even be dynamically assigned via an ISP’s DHCP service.

This article describes the steps necessary for setting this up on a machine with two network cards running FreeBSD 5.1-RELEASE. It is largely equivalent to the setup described for the same machine running Linux in another article here. Additionally, the FreeBSD Router serves as IPSec gateway, encrypting traffic to networks behind other routers on the internet.

Warning!

This article contains just the steps that it took me to set up the router. Neither does it cover the whole underlying concepts of networking, security, IPSec, and the state of the world as such, nor does it necessarily protect your machines from security risks (even though it is designed to do so). I might have overlooked something, might have opened a security hole in my configuration. I might be an absolute nincompoop. So do not simply read the text and follow the steps. Follow the links in it, try Google and freebsd.org to understand for yourself what it is you’re doing. If you find any flaw in my configuration, please let me know (address at bottom).

You’ve been warned.

  1. Preparing the system

    1.1. Install FreeBSD 5.1-RELEASE. I used the ISO image here. Do read the Handbook!. Make sure to install the kernel sources and the ports collection.

    1.2. Recompile the kernel. How to do this is described in detail in the Handbook!. Because stock 5.1-RELEASE comes without firewall and IPSec support in the kernel, we have to compile that in. Those are the necessary options:

        # Firewall support added 
        options         IPFIREWALL
        # Divert support added (necessary for natd)
        options         IPDIVERT
        # IPSec support added
        options         IPSEC
        options         IPSEC_ESP
        options         IPSEC_DEBUG
    

    Compile, install and reboot.

  2. Set up the firewall
    (This setup uses ipfw, a dynamically assigned external address and a local net of 192.168.1.0/24.)

    2.1. Enable all necessary services and functions in /etc/rc.conf (adopt addresses and interface names for your setup):

        # use DHCP for external interface
        ifconfig_ep0="DHCP"
        # static address for internal interface
        ifconfig_ep1="inet 192.168.1.1 netmask 255.255.255.0 \
    	broadcast 192.168.1.255
    
        # enable IP forwarding
        gateway_enable="YES"
    
        # enable firewall
        firewall_enable="YES"
        # set path to custom firewall config
        firewall_type="/etc/fw/rc.firewall.rules"
        # be non-verbose? set to YES after testing
        firewall_quiet="NO"
    
        # enable natd, the NAT daemon
        natd_enable="YES"
        # which is the interface to the internet that we hide behind?
        natd_interface="ep0"
        # flags for natd
        natd_flags="-f /etc/fw/natd.conf"
    

    2.2 Edit /etc/fw/rc.firewall.rules (or whatever you set firewall_type to)

        # be quiet and flush all rules on start
        -q flush
        
        # allow local traffic, deny RFC 1918 addresses on the outside
        add 00100 allow ip from any to any via lo0
        add 00110 deny ip from any to 127.0.0.0/8
        add 00120 deny ip from any to any not verrevpath in
        add 00301 deny ip from 10.0.0.0/8 to any in via ep0
        add 00302 deny ip from 172.16.0.0/12 to any in via ep0
        add 00303 deny ip from 192.168.0.0/16 to any in via ep0
                                                                                        
        # check if incoming packets belong to a natted session, allow through if yes
        add 01000 divert natd ip from any to me in via ep0
        add 01001 check-state
        
        # allow some traffic from the local net to the router 
        # SSH
        add 04000 allow tcp from 192.168.1.0/24 to me dst-port 22 in via ep1 setup keep-state
        # ICMP
        add 04001 allow icmp from 192.168.1.0/24 to me in via ep1
        # NTP
        add 04002 allow tcp from 192.168.1.0/24 to me dst-port 123 in via ep1 setup keep-state
        add 04003 allow udp from 192.168.1.0/24 to me dst-port 123 in via ep1 keep-state
        # DNS
        add 04006 allow udp from 192.168.1.0/24 to me dst-port 53 in via ep1
        
        # drop everything else
        add 04009 deny ip from 192.168.1.0/24 to me
             
        # pass outgoing packets (to be natted) on to a special NAT rule
        add 04109 skipto 61000 ip from 192.168.1.0/24 to any in via ep1 keep-state
                                                                                        
        # allow all outgoing traffic from the router (maybe you should be more restrictive)
        add 05010 allow ip from me to any out keep-state
        
        # drop everything that has come so far. This means it doesn't belong to an 
        established connection, don't log the most noisy scans.
        add 59998 deny icmp from any to me
        add 59999 deny ip from any to me dst-port 135,137-139,445,4665
        add 60000 deny log tcp from any to any established
        add 60000 deny log ip from any to any
        
        # this is the NAT rule. Only outgoing packets from the local net will come here.
        # First, nat them, then pass them on (again, you may choose to be more restrictive)
        add 61000 divert natd ip from 192.168.1.0/24 to any out via ep0
        add 61001 allow ip from any to any
    

    2.3 Edit /etc/fw/natd.conf (or whatever you set natd_flags -f to)

        unregistered_only
        interface ep0
        use_sockets
        dynamic
        # dyamically open fw for ftp, irc
        punch_fw 2000:50
    

    2.4. Start the firewall

    Run /etc/rc.d/ipfw start. If you have the DHCP client running, you should have an external address and your firewall router will be functional. Else start it with /etc/rc.d/dhclient start or reboot. Rebooting would be a good idea at this point anyway.
    Time to get yourself a nice cold beer. You’ve deserved it!

    2.5. Tuning

    It may be useful to set some connection related parameters in the kernel. I did this in /etc/sysctl.conf (you can of course call sysctl directly).

     
        security.bsd.see_other_uids=0
        net.inet.ip.fw.dyn_ack_lifetime=3600
        net.inet.ip.fw.dyn_udp_lifetime=10
        net.inet.ip.fw.dyn_buckets=1024
    

    This sets the session lifetime for TCP sessions without any traffic to 1 hour, for UDP to 10 seconds.

  3. Set up IPSec

    3.1. Get and install racoon

    (Also take a look at http://www.x-itec.de/projects/tuts/ipsec-howto.txt)

    Go to /usr/ports/security/racoon.
    Enter make all install clean. This will install racoon and clean up afterwards. Racoon is used for managing the key exchange needed for IPSec. The encryption itself is done in the kernel.

    3.2. Enable racoon

    Add the following line to /etc/rc.conf

        racoon_enable="YES"
    

    This will start racoon at boot time by running /usr/local/etc/rc.d/racoon.sh. I simply edited this file and set my options there: it now calls/usr/local/sbin/racoon -f /etc/ipsec/racoon.conf -l /var/log/racoon. This means that config is in /etc/ipsec, logs go to /var/log.

    3.3. Configure racoon

    Now we have to edit racoon’s config file. In my case this is /etc/ipsec/racoon.conf, per default it is /usr/local/etc/racoon/racoon.conf.

    Very good documentation has been written on how to configure racoon. Google knows it, and you might also take a look athttp://www.kame.net/newsletter/20001119/. Below is just what I changed in /etc/ipsec/racoon.conf.

        [....]
        path certificate "/etc/ipsec/cert" ;
        [....]
        log info;
        [....]
    
        # this is the other gateway's address
        remote aaa.bbb.ccc.ddd 
    {
    	# it's freeswan, so it doesn't support aggressive mode
            exchange_mode main,aggressive; 
            doi ipsec_doi;
            situation identity_only;
                                                                                    
            my_identifier asn1dn ;
    	# Subject of other gateway's certificate
            peers_identifier asn1dn "C=XY/O=XY Org/CN=xy.org.org";
    	# my own X.509 certificate and key
            certificate_type x509 "mycert.crt" "mykey.key";
     
            nonce_size 16;
            lifetime time 1 min;    # sec,min,hour
            initial_contact on;
            support_mip6 on;
            proposal_check obey;    # obey, strict or claim
     
            proposal {
                    encryption_algorithm 3des;
                    hash_algorithm sha1;
                    authentication_method rsasig ;
                    dh_group 2 ;
            }
    }
    

    3.4. Install the certificates

    Also take a look at http://www.kame.net/newsletter/20001119b/

    Copy the following files to /etc/ipsec/cert/ (or whatever you set path certificate to above):

    • Your certificate (named "mycert.crt" above)
    • Your private key (mykey.key; make this -rw------- root)
    • The other gateway’s CA’s public certificate.

    Then make a link to the other gateway’s CA’s certificate (I assume in the following example that the certificate file is named ca.pem). This link must be named after the hash value of the file itself. You can create it with

        # ln -s ca.pem `openssl x509 -noout -hash -in ca.pem`.0
    

    3.5. Tell the kernel to use IPSec

    You should have read the above links before you proceed.

    Now you must tell the kernel to use IPSec when communicating with the other gateway. You do this by creating a file which is then used by /etc/rc.d/ipsecon startup. I put the file in /etc/ipsec/ipsec.conf and entered the following in /etc/rc.conf

        ipsec_enable="YES"
        ipsec_file="/etc/ipsec/ipsec.conf"
    

    /etc/ipsec/ipsec.conf contains the parameters for the setkey (8) command that adds, updates, dumps, or flushes Security Association Database (SAD) entries as well as Security Policy Database (SPD) entries in the kernel.

        # First, flush all SAD and SPD
                                                                                    
        flush;
        spdflush;
                                                                                    
        # Then, set up SPD for the local net and the net behind the other gateway
        # www.xxx.yyy.zzz is my own external address
        # aaa.bbb.ccc.ddd is the other gateway's address as given in
        # /etc/ipsec/racoon.conf
    
        spdadd 192.168.1.0/24 192.168.100.0/24 any -P out ipsec \
            esp/tunnel/www.xxx.yyy.zzz-aaa.bbb.ccc.ddd/require ;
        spdadd 192.168.100.0/24 192.168.1.0/24 any -P in ipsec \
            esp/tunnel/aaa.bbb.ccc.ddd-www.xxx.yyy.zzz/require ;
    

    This tells the kernel to use the ESP tunnel between www.xxx.yyy.zzz (the router) and aaa.bbb.ccc.ddd (the other gateway) when routing between the subnets 192.168.1.0/24 (local) and 192.168.100.0/24 (remote).

    This is the only place in the whole configuration where my own external IP address is used. Since this is a dynamically assigned address which can change, I had to provide some mechanism to restart IPSec when the address changes. I did this by configuring dhclient to run a script which rewrites/etc/ipsec/ipsec.conf and restarts /etc/rc.d/ipsec. I found it to be the easiest way to name the script /etc/dhclient-exit-hooks, as the ISC dhclient will look for this file everytime it has to update anything (see dhclient-script(8)). Somebody is likely to come up with a better solution.

    3.6. Amend the firewall rules for IPSec

    Add the following lines to your firewall config (/etc/fw/rc.firewall.rules in this example)

        add 05020 allow udp from any 500 to me dst-port 500 in via ep0 keep-state
        add 05021 allow esp from any to me in via ep0 keep-state
        add 05022 allow ah from any to me in via ep0 keep-state
    

    This will allow racoon to exchange keys on UDP port 500 and the kernel to talk via ESP and AH with the outside world (AH is actually not needed in my setup, but I keep it there for completeness and portability).

    3.7. Start all required services and enjoy

    You may want to add a DHCP, NTP, and DNS server to your router (I did, as the above firewall rules imply). I will not cover this here, though. The configuration files are about the same as in the Linux setup article.

    Now start all services manually with their respective scripts in /etc/rc.d/ or simply reboot. This should do.

    In case you’re also in charge of the other IPSec gateway, here are its /etc/ipsec.conf and /etc/ipsec.secrets. In my case it was RedHat Linux, Kernel 2.4.18-3, and the super-freeswan-1.99.8 patch from http://www.freeswan.ca/.
    ref : http://lugbe.ch/lostfound/contrib/freebsd_router/

VLAN คืออะไร ?

โดยปกติแล้ว ถ้าเรารู้จัก LAN ก็จะสามารถทำความเข้าใจกับ VLAN ได้ง่ายขึ้น ซึ่ง LAN ก็หมายถึงการเชื่อมต่ออุปกรณ์ Network ต่างๆ เข้าด้วยกันภายใน location เดียวกัน อาจจะเป็นตึกเดียวกัน ชั้นเดียวกัน หรือภายในพื้นที่เดียวก็ได้
LANอุปกรณ์ที่เชื่อมต่อภายใน LAN เดียวกัน ก็จะสามารถติดต่อสื่อสารกันได้ และ อุปกรณ์ทั้งหมดก็จะอยู่ใน Broadcast Domain เดียวกัน
Broadcast Domain คือ ขอบเขตหรือบริเวณที่ Broadcast Traffic สามารถส่งกระจายไปถึงได้ ถูกแบ่งได้จากอินเตอร์เฟสของอุปกรณ์ L3 ขึ้นไป
ถ้ามีการใช้งานมากๆ บน LAN ก็จะทำให้ traffic แบบ broadcast ยิ่งมีมากขึ้นไปด้วย จะส่งผลให้ Network ทำงานได้ช้าลง นอกจากจะทำให้ Network ทำงานช้าแล้ว ยังส่งผลต่ออุปกรณ์ต่างๆด้วย ผมเคยเจอปัญหาของลูกค้าในการใช้งาน LAN วงใหญ่ๆ แล้ว Printer ใช้งานไม่ได้ ปรากฎว่า มี broadcast เยอะมากๆ จน Printer ประมวลผลไม่ทัน
ถ้าเราต้องการจะแยก Network ออกจากกัน หรือพูดง่ายๆ คือ แบ่ง broadcast domain ออกเป็น 2 วง หรือ หลายๆ วง ก็สามารถทำได้โดยแยก Switch ไปอีกตัวนึงเลย ตามรูปด้านล่าง
broadcast domainจากรูปเราก็จะสามารถแยกวง หรือ แยก broadcast domain ออกจากกันได้แล้ว แต่ก็เปลือง Switch ไปด้วยใช่ไหมครับ !! VLAN จะเข้ามาช่วยได้ครับ โดยให้ผลเหมือนกับการแบ่ง Switch ออกจากกันเลย แต่ใช้ Switch เพียงตัวเดียวก็สามารถทำได้

VLAN หรือ Virtual LAN เป็นความสามารถของอุปกรณ์สวิตช์ที่สามารถกำหนดขอบเขตของ Broadcast Domain บน Layer 2 หมายความว่า บน Switch 1 ตัว สามารถแยก broadcast domain ได้หลายๆ วง หรือ แยก subnet ได้นั่นเอง
broadcast doamin-2

ประโยชน์ของการทำ VLAN หลักๆมีอะไรบ้าง ?

  • ลดจำนวน broadcast traffic ลงในเครือข่าย
  • ลดความเสี่ยง ป้องกันการ flooding ภายใน network ให้จำกัดภายใน VLAN เดียว
  • เพิ่มความปลอดภัย เพราะแต่ละ VLAN ไม่สามารถสื่อสารกันได้
  • มีความยืดหยุ่นในการใช้งาน เพียงแค่เปลี่ยน config บน port ของ switch ให้อยู่ภายใน VLAN กำหนด โดยไม่ต้องไปย้ายสาย

ลองมาดูวิธีการตั้งค่า VLAN กันครับ

1. สร้าง VLAN บน Switch ที่ต้องการ
Switch# configure terminal
Switch(config)# vlan 10
!! สร้าง VLAN หมายเลข 10
Switch(config-vlan)# name Sales
!! ตั้งชื่อ VLAN หมายเลข 10 เป็น Sales
.
เลขของ VLAN สามารถระบุได้ตาม range ได้ดังนี้vlan-rangeNote : บน Cisco Switch ทุกตัว จะมี default เป็น VLAN 1 และพอร์ตทุกพอร์ตก็จะเป็นสมาชิกของ VLAN 1
ตรวจสอบการสร้าง VLAN ด้วยคำสั่ง “show vlan brief”
show vlan-1
ลองดูรูปด้านล่าง จะเห็นว่า บน Switch มีการสร้าง VLAN 10 ขึ้นมาแล้ว และตอนนี้พอร์ตของ Switch ทุกพอร์ตจะเป็นสมาชิกของ VLAN 1 อยู่ โดย default หมายความว่า ถ้าเราเอา devices มาต่อที่พอร์ต devices เครื่องนั้นก็จะอยู่ใน VLAN 1 ทันที ถ้าเราต้องการจะให้ devices นั้นอยู่ใน VLAN 10 เราจะต้องตั้งค่าพอร์ตนั้นให้เป็นสมาชิกของ VLAN 10 ลองดูที่ขั้นตอนถัดไปครับ
create-vlan-switch
2. นำพอร์ตของ Switch เข้ามาเป็นสมาชิกของ VLAN
Switch# configure terminal
Switch(config)# interface Fa0/1
Switch(config-if)# switchport mode access
Switch(config-if)# switchport access vlan 10
ตรวจสอบด้วยคำสั่ง “show vlan brief”
show vlan-2
จะสังเกตุว่าพอร์ต Fa0/1 จะถูกย้ายเข้ามาเป็นสมาชิกของ VLAN 10 แล้ว
จากรูปด้านล่าง ตอนนี้เรานำพอร์ต Fa0/1 ไปเป็นสมาชิก VLAN 10 แล้ว หมายความว่า ถ้านำ devices ใดๆ มาต่อกับพอร์ต Fa0/1 ก็จะอยู่ใน VLAN 10 และก็จะติดต่อกับเครื่องที่นำมาต่อกับพอร์ต Fa0/2 , Fa0/3 , Fa0/4 ที่อยู่ใน VLAN 1 ไม่ได้แล้วครับ
assign-vlan-switchจบแล้วครับ สำหรับเรื่อง VLAN หวังว่าน่าจะเป็นประโยชน์กับผู้ที่สนใจครับ เจอกันใหม่ในบทความหน้านะคร้าบบบ
ref:http://netprime-system.com/vlan/

เทคนิคการคำนวณ IP Address

IP Address หรือ Internet Protocol Address มีความสำคัญอย่างไร และเกี่ยวข้องอะไรกับ
เราบ้าง ปัจจุบันคงไม่ต้องกล่าวถึงแล้ว IP Address เป็นหมายเลขที่ใช้กำหนดให้กับเครื่องคอมพิวเตอร์ หรือ
อุปกรณ์ Network ต่างๆ เช่น Router, Switch , Firewall , IP Camera , IP Phone , Access
point , เป็นต้น และอีกไม่นานอุปกรณ์ไฟฟ้าหรืออุปกรณ์สื่อสารทุกประเภทที่จะออกวางจำหน่ายจะมีIP
Address ติดมาด้วยจากโรงงานเลยทีเดียว IP Address ที่ใช้ในปัจจุบันนั้นจะเป็นชนิดที่เรียกว่า IPv4
(IP version 4) ซึ่งไม่เพียงพอต่อการใช้งาน จึงมีการพัฒนาเป็น IPv6 (IP version 6) เพื่อรองรับ
อุปกรณ์และเทคโนโลยีใหม่ๆที่ต้องใช้IP Address ในการติดต่อสื่อสาร และในเมืองไทยเองก็มีการใช้IPv6
ในหลายหน่วยงานแล้ว หน่วยงานที่จัดสรร IP Address ให้ในแถบ Asia Pacific คือAPNIC ผู้
ให้บริการ Internet หรือ ISP จะขอ IP จาก APNIC แล้วนำมาแจกจ่ายให้แก่ลูกค้าของ ISP นั้นๆอีกต่อไป
สำหรับผู้ที่จะสอบใบ Certificate ค่ายต่างๆ เช่น CCNA , CCNP , LPI , Security + , CWNA
เป็นต้น ล้วนแล้วแต่จะต้องมีความรเู้ กี่ยวกับ IP Address ทั้งสิ้น โดยเฉพาะ IPv4 จะต้องคำนวณได้อย่าง
แม่นยำและรวดเร็วIPv4
IPv4 ประกอบด้วยเลขฐานสอง 32 bits (4 bytes ,( 8bits=1byte)) แบ่งเป็น 4 กลุ่ม กลุ่มละ 8 bits แต่
ละกลุ่มนั้นจะคั่นด้วย . ( Dot )
กรณีตัวเลขน้อยสุดหรือเป็น เลข 0 ทั้งหมด  00000000 . 00000000 . 00000000 . 00000000
กรณีตัวเลขมากสุดหรือเป็น เลข 1 ทั้งหมด  11111111 . 11111111 . 11111111 . 11111111
เมื่อแปลงเป็นเลขฐาน 10 จะได้
กรณีตัวเลขน้อยสุดหรือเป็น เลข 0 ทั้งหมด  0.0.0.0
กรณีตัวเลขมากสุดหรือเป็น เลข 1 ทั้งหมด  255.255.255.255
ดังนั้น IPv4 จะมีตัวเลขที่เป็นไปได้ ตั้งแต่ 0.0.0.0 – 255.255.255.255

ก่อนการคำนวณเรื่อง IP เพื่อความรวดเร็ว ให้เขียนตามด้านล่างนี้
3-12-2554 8-33-03.png

IPv4 จะมีตัวเลขที่เป็นไปได้ทั้งหมดคือตั้งแต่ 0.0.0.0 – 255.255.555.555
สามารถแบ่ง IPv4 ได้เป็น 5 แบบ หรือ 5 Class ตามด้านล่าง โดยวิธีการแบ่งจะอ้างอิงจาก byte ที่1 ดังนี้
class A  byte ที่1 ตัวเลขบิตแรก จะเป็น 0
class B  byte ที่1 ตัวเลขบิตแรกจะเป็น 1 บิตที่2 จะเป็น 0
class C  byte ที่1 ตัวเลข 2 บิตแรก จะเป็น 1 บิตที่3 จะเป็น 0
class D  byte ที่1ตัวเลข 3 บิตแรก จะเป็น 1 บิตที่4 จะเป็น 0
class E  byte ที่1 ตัวเลข 4 บิตแรกจะเป็น 1
ดังนั้นจะได้ผลตามรูปด้านล่าง

3-12-2554 8-16-17.png

จะได้IP ในแต่ละ Class ดังนี้
Class A จะเริ่มต้นตั้งแต่ 0.0.0.0 ถึง 127.255.255.255
Class B จะเริ่มต้นตั้งแต่ 128.0.0.0 ถึง 191.255.255.255
Class C จะเริ่มต้นตั้งแต่ 192.0.0.0 ถึง 223.255.255.255
Class D จะเริ่มต้นตั้งแต่ 224.0.0.0 ถึง 239.255.255.255
Class E จะเริ่มต้นตั้งแต่ 240.0.0.0 ถึง 255.255.255.255
IP ที่สามารถนำไป Set ให้อุปกรณ์หรือ Host ได้จะมีอยู่3 Class คือ Class A, B และ C ส่วน IP Class
D จะสงวนไว้ใช้สำหรับงาน multicast applications และ IP Class E จะสงวนไว้สำหรับงานวิจัย หรือ
ไว้ใช้ในอนาคต
IPv4 ยังแบ่งเป็น 2 ประเภท คือ Public IP ( IP จริง ) และ Private IP ( IP ปลอม )
Public IP ( IP จริง ) คือ IP ที่สามารถ set ให้อุปกรณ์network เช่น Server หรือ Router แล้ว
สามารถติดต่อสื่อสารกับ Public IP ( IP จริง ) ด้วยกัน หรือออกสู่Network Internet ได้ทันที
Private IP ( IP ปลอม ) สามารถนำมา ใช้set ให้กับ PC หรืออุปกรณ์ในออฟฟิตได้แต่ไม่สามารถออกสู่
Public IP หรือออก Internet ได้ ต้องมีอุปกรณ์ Gateway เช่น Router ,Server หรือModem
DSL เปิด Service NAT ( Network Address Translation ) ไว้ จึงจะสามารถออกสู่Internet ได้
Private IP จะมีเฉพาะ Class A,B และ C ดังนี้
Class A : 10.x.x.x ( 10.0.0.0 – 10.255.255.255 )
Class B : 172.16.x.x – 172.31.x.x ( 172.16.0.0 – 172.31.255.255 )
Class C : 192.168.x.x ( 192.168.0.0 – 192.168.255.255 )
การคำนวณ IPv4
เมื่อเราได้IP Address มา 1 ชุด สิ่งที่จะต้องบอกได้จาก IP Address ที่ได้มาคือ
Subnet Mask คือ IP Address อะไร
Network IP คือ IP Address อะไร
Broadcast IP คือ IP Address อะไร
Range host IP ที่สามารถนำมาใช้งานได้ มีIP อะไรบ้าง
จำนวน Subnets , จำนวน hosts / Subnet
Subnet Mask ทำหน้าที่แบ่ง network ออกเป็นส่วนย่อยๆ ลักษณะคล้ายกับ IP Address คือ
ประกอบด้วยตัวเลข 4 ตัวคั่นด้วยจุด เช่น 255.255.255.0 วิธีการที่จะบอกว่า computer แต่ละเครื่องจะอยู่
ใน network วงเดียวกัน (หรืออยู่ใน subnet เดียวกัน) หรือไม่นั้นบอกได้ด้วยค่า Subnet Mask

วิธีการหา Subnet Mask
/30 หมายถึง mask 30 bits แรก
/27 หมายถึง mask 27 bits แรก
/20 หมายถึง mask 20 bits แรก
ให้ทำการแปลง mask bit ที่กำหนดให้ เป็นค่า Subnet Mask
วิธีการคือ bits ที่อยู่หน้าตัวmask ให้แทนด้วยเลข 1 bits ที่อยู่หลังให้แทนด้วยเลข 0
Ex /30
/30  11111111 . 11111111 . 11111111 . 111111/00

3-12-2554 8-34-57.png

จะได้ค่า Subnet Mask
/30  255.255.255.252
11111111 . 11111111 . 11111111 . 111111/00
ให้ใช้ตารางช่วยจะทำให้เร็วขึ้น โดย ถ้าเป็น 1 จำนวน 8 ตัวจะได้255
ถ้าเป็น 1 จำนวน 6 ตัวจะคือ 252 หรือจะใช้วิธีนับจาก 24 bits แรกซึ่งเป็น 1 ทั้งหมดอยู่แล้ว นับต่อมาจะได้
bits ที่30 เป็น 252 พอดี
Ex /27
/27  11111111 . 11111111 . 11111111 . 111/00000
จะได้ค่า Subnet Mask
/27  255.255.255.224

Ex /20
/20  11111111 . 11111111 . 1111/0000 . 00000000
จะได้ค่า Subnet Mask
/20  255.255.240.0
ตัวอย่าง Subnet Mask ต่างๆ มีดังนี้
Mask ที่เป็นค่า default ของ IP Class ต่างๆมีดังนี้
Class A = Mask 8 bits = 255 . 0 . 0 . 0
Class B = Mask 16 bits = 255 . 255 . 0 . 0
Class C = Mask 24 bits = 255 . 255 . 255 . 0
Subnet mask ทั่วไป
Mask 10 = 255 . 192 . 0 . 0 Mask 21 = 255 . 255 . 248 . 0
Mask 11 = 255 . 224 . 0 . 0 Mask 22 = 255 . 255 . 252 . 0
Mask 12 = 255 . 240 . 0 . 0 Mask 23 = 255 . 255 . 254 . 0
Mask 13 = 255 . 248 . 0 . 0 Mask 25 = 255 . 255 . 255 . 128
Mask 14 = 255 . 252 . 0 . 0 Mask 26 = 255 . 255 . 255 . 192
Mask 15 = 255 . 254 . 0 . 0 Mask 27 = 255 . 255 . 255 . 224
Mask 17 = 255 . 255 . 128 . 0 Mask 28 = 255 . 255 . 255 . 240
Mask 18 = 255 . 255 . 192 . 0 Mask 29 = 255 . 255 . 255 . 248
Mask 19 = 255 . 255 . 224 . 0 Mask 30 = 255 . 255 . 255 . 252
Mask 20 = 255 . 255 . 240 . 0 Mask 31 = 255 . 255 . 255 . 254

หมายเหตุ เพื่อให้การแปลงตัวเลขจากเลขฐานสอง เป็นฐานสิบเร็วขึ้นให้ดูจากด้านล่าง เช่นถ้าเป็น เลข 1
ทั้งหมดจะได้เลข ฐานสิบคือ 255 ถ้าเป็นเลข 1 จำนวน 4 ตัวจะคือ 240 ถ้าเป็นเลข 0 ทั้งหมด จะได้เลข 0

หลังจากได้Subnet Mask แล้ว ขั้นตอนต่อไปคือการหา Network IP และ Broadcast IP
Network IP คือ IP ตัวแรกของ Subnet ปกติจะเอาไว้ประกาศเรื่องของ Routing จะไม่สามารถนำมา
Set ให้แก่อุปกรณ์หรือเครื่อง PC ได้

ให้แก่อุปกรณ์หรือเครื่อง PC ได้เช่นกัน
Ex.1 192.168.22.50/30
จากโจทย์ /30 เมื่อแปลงเป็น Subnet Mask จะได้255.255.255.252
ให้ดูจากที่เขียนไว้ด้านบนนะครับ ถ้าเป็น 1 หมดทั้ง 8 ตัวจะได้255 ( แปลงจากฐานสองเป็นฐานสิบ )
เป็น 1 ทั้งหมด 6 ตัวจะได้252 ดังนั้นจึงได้subnet mask เป็น 255.255.255.252
ต่อไป หาว่า จำนวน IP ต่อ Subnet มีจำนวนเท่าไหร่ จากค่า Subnet Mask ที่ให้มา
ดูที่2 bit ที่เหลือ ที่เป็นอะไรก็ได้นั้น ตัวเลขที่เป็นไปได้หมดคือ 00 , 01 , 10 , 11 มี4 ตัว
และเมื่อนำ00 , 01 , 10 , 11 แปลงเป็นฐานสิบจะได้
00 แปลงเป็นฐานสิบจะได้ 0
01 แปลงเป็นฐานสิบจะได้ 1
10 แปลงเป็นฐานสิบจะได้ 2
11 แปลงเป็นฐานสิบจะได้ 3
สรุปคือ จำนวน IP ต่อ Subnet เมื่อ Subnet Mask คือ 255.255.255.252 คือ 4 ตัว นั่นเอง
หรือใช้วิธีลัดดูจากที่เขียนไว้ ตัวเลขที่อยู่บน 252 คือ 4 ตามด้านล่างครับ

ดังนั้นถ้า /30 จำนวน IP ในแต่ละ subnet ที่จะเป็นไปได้ดูเฉพาะกลุ่มสุดท้าย
คือ 0-3 , 4-7 , 8-11 , _ _ _ , 252-255 หรือเขียนในรูป IPv4 จะได้
192.168.22.0 – 192.168.22.3
192.168.22.4 – 192.168.22.7
192.168.22.8 – 192.168.22.11
———–
192.168.22.48 – 192.168.22.51
———
192.168.22.252 – 192.168.22.255

หมายเหตุ 3 กลุ่มแรกเหมือนเดิมเนื่องจากผลของการ and ระหว่าง bit เนื่องจาก 3 กลุ่มแรกเป็น bit 1
ทั้งหมดทำการ add กับเลขใดก็จะได้ตัวเดิม 3 กลุ่มแรกจึงได้เลขฐาน 10 ตัวเดิม
โดย IP Address ตัวแรกของแต่ละ subnet จะเรียกว่า Network IP และ IP Address ตัวสุดท้ายของแต่
ละ subnet จะเรียกว่า Broadcast IP ดังนั้น
จากโจทย์192.168.22.50/30
1. Network IP คือ IP Address อะไร
ตอบ 192.168.22.48
2. Broadcast IP คือ IP Address อะไร
ตอบ 192.168.22.51
3. Range hosts IP ที่สามารถนำมาใช้งานได้ หรือ จำนวน hosts Per Subnet
ตอบ 192.168.22.49 – 192.168.22.50 นำIP มา set เป็น host ได้2 IP
วิธีการหา Network IP นอกเหนือจากการเขียนตามด้านบนแล้วยังหาได้โดย
วิธีการปกติทำได้โดยการนำเอา Subnet Mask มา AND กับ IP Address ที่ให้มา ผลที่ได้จะเป็น
Network IP วิธีนี้หนังสือหลายเล่มมีอธิบายแล้ว
วิธีการหาร นำIP จากโจทย์ที่ให้มา ตั้งหารด้วยจำนวน IP ที่มีได้ใน Subnet เช่น
192.168.22.50/30 ให้นำเอาตัวเลข 50 หารด้วย 4 ดังด้านล่าง

3-12-2554 8-37-45.png

เมื่อได้Netwok IP แล้ว ก็จะได้คำตอบเช่นเดียวกับด้านบน เนื่องจากเรารู้อยู่แล้วว่า /30 ใน 1 subnet จะมี
จำนวน IP ทั้งหมด 4 ตัวจากตาราง ดังที่ได้กล่าวมาแล้ว

Ex.2 192 .168.5.33/27 which IP address should be assigned to the PC host ?
A.192.168.5.5
B.192.168.5.32
C. 192.168.5.40
D. 192.168.5.63
E. 192.168.5.75
จากโจทย์/27 จะหมายถึง
11111111 . 11111111 . 11111111 . 111/XXXX X = mask 27 bit แรก ต้องเป็นเลข 1 ส่วน 5
bit หลัง เป็นอะไรก็ได้
/27 เมื่อแปลงเป็นเลขฐานสิบจะได้255 . 255 . 255 . 224

หรือจะคิดแบบลัด ตามตาราง ดูบรรทัดที่4 จะหมายถึงผลบวกของ bit ใน 8 bit สุดท้ายครับ 111 ก็คือ
128+64+32 = 224
เมื่อ ได้Subnet Mask แล้ว เราก็จะรู้ว่ามีจำนวน IP ต่อ Subnet เท่ากับ 32 หรือจะดูจากที่เขียนไว้ด้านบน
ของ 224 ก็คือ 32 นั่นเอง
จากโจทย์192 .168.5.33/27 จะใช้วิธีไหนก็ได้หาตัว Network มาให้ได้ก่อน
192.168.5.33/27 หมายถึง 192.168.5.32 – 192.168.5.63
โดย IP ตัวแรกจะเป็น Network IP ( 192.168.5.32 ) และ IP ตัวสุดท้ายจะเป็น Broadcast IP (
192.168.5.63 ) ซึ่งไม่สามารถใช้set ให้แก่PC ได้ ดังนั้นจะเหลือ IP ที่สามารถ Set ให้แก่PC ได้คือ
192.168.5.33 – 192.168.5.62
คำตอบจึงเป็นข้อ C. 192.168.5.40
Ex.3 IP 10.10.10.0/13 เป็น IP ที่นำไป set ให้host ได้หรือไม่
IP ที่สามารถนำไป set ให้host ได้หรือนำไปใช้งานได้ จะต้องไม่ตรงกับ Network IP หรือ
Broadcast IP
วิธีการคิดก่อนอื่นเราต้องทำการแปลง /13 หรือmask 13 bit ให้เป็น subnet mask

11111111 . 11111/XXX . XXXXXXXX . XXXXXXXX = mask 13 bit
แรก ต้องเป็นเลข 1 ส่วน bit ที่เหลือเป็นอะไรก็ได้
/13 เมื่อแปลงเป็นเลขฐานสิบจะได้ 255 . 248 . 0 . 0
จากโจทย์ เขียนใหม่ได้ดังนี้IP 10.10.10.0 subnet mask 255.248.0.0
ขั้น ต่อไปเราจะมาหาช่วง IP จาก subnet mask ที่หามาได้255.248.0.0
หลักที่1 จะมีค่าคงที่คือเลข 10 หลักที่3 และหลักที่4 นั้น ตัวเลขที่เป็นไปได้คือ 0 – 255
ส่วนหลักที่2 นั้น เราต้องมาคำนวณ โดยเว้นไว้ก่อน เขียนช่วง IP จะได้ดังนี้คือ
10 . X . 0 . 0 – 10 . X . 255 . 255

ถ้า เราพิจารณาเฉพาะ 248 (ดูเฉพาะตัวเลขกลุ่มที่2 ) ถ้าดูจากรูปด้านบน บรรทัดที่3 ซึ่งจะหมายถึง IP ที่มี
ได้ทั้งหมด ก็คือ 8 ตัว คือ 0-7 , 8-15 , 16- 23 , _ _ _ , 248-255 หรือเขียนเต็มๆจะได้
10 . 0 . 0 . 0 – 10 . 7 . 255 . 255
10 . 8 . 0 . 0 – 10 . 15 . 255 . 255 ————> จากโจทย์10.10.10.0 จะอยู่ในช่วงนี้
10 . 16 . 0 . 0 – 10 . 23 . 255 . 255
————
10 . 248 . 0 . 0 – 10 . 255 . 255 . 255
จากโจทย  10.10.10.0/13 ก็จะคือ IP ในช่วง 10 .8 . 0 . 0 – 10 . 15 . 255 . 255
1. Network IP คือ IP Address อะไร
ตอบ 10 . 8 . 0 . 0
2. Broadcast IP คือ IP Address อะไร
ตอบ 10 . 15 . 255 . 255
3. Range host IP ที่สามารถนำมาใช้งานได้
ตอบ 10 . 8 . 0 . 1 – 10 . 15 . 255 . 254 ดังนั้น IP 10.10.10.0/13 จึงนำมาใช้งานได้ถือว่า
เป็นHost ตัวนึง

การหาจำนวน Subnet และ จำนวน hosts / Subnet
การหาจำนวน hosts ต่อ Subnet จากค่า Subnet Mask ที่ให้มา จะใช้ สูตร
2n – 2
โดย n คือจำนวน bits ที่อยู่หลังตัวMask ส่วนเลข 2 ที่ลบออกไปคือ Network IP และ Broadcast IP

Ex.1 /30 11111111 . 11111111 . 11111111 . 111111/00
หรือ 255.255.255.252 จะได้
จำนวน hosts/Subnet = 2n – 2 = 22 – 2 = 4 – 2 = 2

Ex.2 /20 11111111 . 11111111 . 1111/0000 . 00000000
หรือ 255.255.240.0
จำนวน hosts/Subnet = 2n – 2 = 212- 2 = 4096 – 2 = 4094

การหาจำนวน Subnet จากค่า Subnet Mask ที่ให้มา ปัจจุบันใช้สูตร
2n ไม่ต้องลบ 2 เนื่องจากว่า ปัจจุบันทุก Subnet สามารถใช้ได้ทั้งหมด และใน router cisco เองมีการ
เพิ่ม IP Subnet Zero ไว้อยู่แล้ว
โดย n คือจำนวน bits ที่อยู่หน้าตัวMask ถึงตำแน่ง . (dot) ที่ใกล้ที่สุดหรือตำแหน่งที่ระบุไว้

Ex.3 /30 11111111 . 11111111 . 11111111 . 111111/00
หรือ 255.255.255.252 จะได้
จำนวน Subnet = 2n = 26 = 64

Ex.4 /20 11111111 . 11111111 . 1111/0000 . 00000000
หรือ 255.255.240.0
จำนวน Subnet = 2n = 24 = 16

Ex.5 จากเดิม /20 แบ่งเป็น /27 จะได้กี่Subnet อันนี้ระบุMask ต้นทางมาจะได้
11111111 . 11111111 . 1111/1111 . 111/00000
จำนวน Subnet = 2n = 27 = 128

คำศัพท์ที่ควรรู้
Classful และClassless
Classful จะสนใจ Class ของ IP เป็นหลักจะไม่สนใจตัวMask ดูตัวเลข IP ว่าอยู่Class ไหน เช่น อยู่
Class A ,B หรือ C ตามนี้
Class A ( 0.0.0.0 – 127.255.255.255 )
Class B ( 128.0.0.0 – 191.255.255.255 )
Class C (192.0.0.0 – 223.255.255.255 )
ในการใช้IP Address ช่วงแรกๆจะเป็นแบบ Classful ซึ่ง Classful จะ มีค่า default subnet mask
ดังนี้
A /8 255.0.0.0
B /16 255.255.0.0
C /24 255.255.255.0
ดังนั้นถ้าเราใช้หลักการของ Classful ก็ไม่สามารถแบ่ง Subnet ได้แตกต่างจากค่า Default Subnet Mask
ตัวอย่าง routing protocols : ที่เป็นแบบClassful
• RIP Version 1 (RIPv1)
• IGRP
ส่วน Classless จะตรงข้ามกับ Classful คือจะไม่สนใจ Class ของ IP แต่จะสนใจตัวMask เป็น
หลัก อย่างเช่นที่คำนวณตามตัวอย่างที่ผ่านมา โดยจะเป็นไปตามหลักการของ Classless Inter-
Domain Routing (CIDR) ดังนั้น ตัวMask จะเป็นอะไรก็ได้ไม่สนใจว่า IP อยู่Class ไหน
ตัวอย่าง routing protocols : ที่เป็นแบบClassless ได้แก่
• RIP Version 2 (RIPv2)
• EIGRP
• OSPF
• IS-IS
Variable Length Subnet Masks ( VLSM )
จากหลักการ เครือข่ายที่เราใช้งานกันอยู่ ไม่จำเป็นจะต้องมีขนาดเท่ากันเสมอไป (ไม่จำเป็นต้องมี ตัวMask
เท่ากัน ) เช่น การเชื่อมต่อแบบจุดต่อจุด (Point-to-Point) ต้องการแค่2 IP ก็เพียงพอ ดังนั้นก็ควร
Mask 30 bit ( /30 ) หรือใช้subnet mask เป็น 255.255.255.252 หรือการเชื่อต่อใน
LAN ที่มีเครื่องเพียง 20 เครื่อง ก็ควรmask 27 bit ( /27 ) หรือ ใช้subnet mask เป็น
255.255.255.224 เป็นต้น ดังตัวอย่างในรูปด้านล่าง ใช้หลักการของVLSM จะเห็นว่าแต่ละ
subnet จะมีตัวmask ต่างกันและmask bit ตามความเหมาะสมทำให้ประหยัด IP หรือใช้IP ได้
อย่างมีประสิทธิภาพ

Default Subnet mask ของแต่ล่ะ Class ดั้งนี้
• Class A จะมี Subnet mask เป็น 255.0.0.0 หรือเลขฐานสองดัง้นี้
11111111.00000000.00000000.00000000
(รวมเลข 1 ให้หมด ก็จะได้เท่ากับ 255)

• Class B จะมี Subnet mask เป็น 255.255.0.0 หรือเลขฐานสองดัง้นี้
11111111.11111111.00000000.00000000

• Class C จะมี Subnet mask เป็น 255.255.255.0 หรือเลขฐานสองดัง้นี้
11111111.11111111.11111111.00000000

credit : http://forums.dp-server.com/topics/%7Bdp-server%7D-67-1-1.html

New Delphi Seattle MongoDB Sample

I created some more Delphi 10 Seattle samples to show off MongoDB and FireDAC functionality: LocalSQL, Indexing & Geospatial.

FireDAC MongoDB NoSQL

The first one queries some data from MongoDB allowing you to specify the match, sort and projection, then it stores the results in a DataSet. At that point you can use LocalSQL to write a SQL query against the result set. While FireDAC gives you full native support for MongoDB, it also puts the SQL back into NoSQL.

MongoDB FireDAC LocalSQL

Indexing is used to improve your query performance. It is really easy to work with MongoDB queries with FireDAC.

MongoDB FireDAC Indexes

And one of the cool features of MongoDB is that you can do spatial queries. Here is an example that shows how to create a Spatial index and then do a spatial query with FireDAC. This uses the restaurant data that is included with the shipping samples, so make sure you load the restaurant data first.

Geospatial MongoDB FireDAC

If you missed my previous post I had a MongoDB FireDAC and C++Builder sample.

[You can download my new samples here.]

see more..
https://forums.embarcadero.com/thread.jspa?messageID=714438    — discut insert data to MongoDB via Firedac

 

Why You Should Never Use MongoDB

Disclaimer: I do not build database engines. I build web applications. I run 4-6 different projects every year, so I build a lot of web applications. I see apps with different requirements and different data storage needs. I’ve deployed most of the data stores you’ve heard about, and a few that you probably haven’t.

I’ve picked the wrong one a few times. This is a story about one of those times — why we picked it originally, how we discovered it was wrong, and how we recovered. It all happened on an open source project called Diaspora.

The project

Diaspora is a distributed social network with a long history. Waaaaay back in early 2010, four undergraduates from New York University made a Kickstarter video asking for $10,000 to spend the summer building a distributed alternative to Facebook. They sent it out to friends and family, and hoped for the best.

But they hit a nerve. There had just been another Facebook privacy scandal, and when the dust settled on their Kickstarter, they had raised over $200,000 from 6400 different people for a software project that didn’t yet have a single line of code written.

Diaspora was the first Kickstarter project to vastly overrun its goal. As a result, they got written up in the New York Times – which turned into a bit of a scandal, because the chalkboard in the backdrop of the team photo had a dirty joke written on it, and no one noticed until it was actually printed. In the NEW YORK TIMES. The fallout from that was actually how I first heard about the project.

As a result of their Kickstarter success, the guys left school and came out to San Francisco to start writing code. They ended up in my office. I was working at Pivotal Labs at the time, and one of the guys’ older brothers also worked there, so Pivotal offered them free desk space, internet, and, of course, access to the beer fridge. I worked with official clients during the day, then hung out with them after work and contributed code on weekends.

They ended up staying at Pivotal for more than two years. By the end of that first summer, though, they already had a minimal but working (for some definition) implementation of a distributed social network built in Ruby on Rails and backed by MongoDB.

That’s a lot of buzzwords. Let’s break it down.

“Distributed social network”

If you’ve seen the Social Network, you know everything you need to know about Facebook. It’s a web app, it runs on a single logical server, and it lets you stay in touch with people. Once you log in, Diaspora’s interface looks structurally similar to Facebook’s:

A screenshot of the Diaspora interface

A screenshot of the Diaspora user interface

There’s a feed in the middle showing all your friends’ posts, and some other random stuff along the sides that no one has ever looked at. The main technical difference between Diaspora and Facebook is invisible to end users: it’s the “distributed” part.

The Diaspora infrastructure is not located behind a single web address. There are hundreds of independent Diaspora servers. The code is open source, so if you want to, you can stand up your own server. Each server, called a pod, has its own database and its own set of users, and will interoperate with all the other Diaspora pods that each have their own database and set of users.

The Diaspora Ecosystem

Pods of different sizes communicate with each other, without a central hub.

Each pod communicates with the others through an HTTP-based API. Once you set up an account on a pod, it’ll be pretty boring until you follow some other people. You can follow other users on your pod, and you can also follow people who are users on other pods. When someone you follow on another pod posts an update, here’s what happens:

1. The update goes into the author’s pod’s database.

2. Your pod is notified over the API.

3. The update is saved in your pod’s database.

4. You look at your activity feed and see that post mixed in with posts from the other people you follow.

Comments work the same way. On any single post, some comments might be from people on the same pod as the post’s author, and some might be from people on other pods. Everyone who has permission to see the post sees all the comments, just as you would expect if everyone were on a single logical server.

Who cares?

There are technical and legal advantages to this architecture. The main technical advantage is fault tolerance.

Here is a very important fault tolerant system that every office should have.

If any one of the pods goes down, it doesn’t bring the others down. The system survives, and even expects, network partitioning. There are some interesting political implications to that — for example, if you’re in a country that shuts down outgoing internet to prevent access to Facebook and Twitter, your pod running locally still connects you to other people within your country, even though nothing outside is accessible.

The main legal advantage is server independence. Each pod is a legally separate entity, governed by the laws of wherever it’s set up. Each pod also sets their own terms of service. On most of them, you can post content without giving up your rights to it, unlike on Facebook. Diaspora is free software both in the “gratis” and the “libre” sense of the term, and most of the people who run pods care deeply about that sort of thing.

So that’s the architecture of the system. Let’s look at the architecture within a single pod.

It’s a Rails app.

Each pod is a Ruby on Rails web application backed by a database, originally MongoDB. In some ways the codebase is a ‘typical’ Rails app — it has both a visual and programmatic UI, some Ruby code, and a database. But in other ways it is anything but typical.

The internal structure of one Diaspora pod

The visual UI is of course how website users interact with Diaspora. The API is used by various Diaspora mobile clients — that part’s pretty typical — but it’s also used for “federation,” which is the technical name for inter-pod communication. (I asked where the Romulans’ access point was once, and got a bunch of blank looks. Sigh.) So the distributed nature of the system adds layers to the codebase that aren’t present in a typical app.

And of course, MongoDB is an atypical choice for data storage. The vast majority of Rails applications are backed by PostgreSQL or (less often these days) MySQL.

So that’s the code. Let’s consider what kind of data we’re storing.

I Do Not Think That Word Means What You Think That Means

“Social data” is information about our network of friends, their friends, and their activity. Conceptually, we do think about it as a network — an undirected graph in which we are in the center, and our friends radiate out around us.

Photos all from rubyfriends.com. Thanks Matt Rogers, Steve Klabnik, Nell Shamrell, Katrina Owen, Sam Livingston-Grey, Josh Susser, Akshay Khole, Pradyumna Dandwate, and Hephzibah Watharkar for contributing to #rubyfriends!

When we store social data, we’re storing that graph topology, as well as the activity that moves along those edges.

For quite a few years now, the received wisdom has been that social data is not relational, and that if you store it in a relational database, you’re doing it wrong.

But what are the alternatives? Some folks say graph databases are more natural, but I’m not going to cover those here, since graph databases are too niche to be put into production. Other folks say that document databases are perfect for social data, and those aremainstream enough to actually be used. So let’s look at why people think social data fits more naturally in MongoDB than in PostgreSQL.

How MongoDB Stores Data

MongoDB is a document-oriented database. Instead of storing your data in tables made out of individual rows, like a relational database does, it stores your data in collections made out of individual documents. In MongoDB, a document is a big JSON blob with no particular format or schema.

Let’s say you have a set of relationships like this that you need to model. This is quite similar to a project that come through Pivotal that used MongoDB, and was the best use case I’ve ever seen for a document database.

At the root, we have a set of TV shows. Each show has many seasons, each season has many episodes, and each episode has many reviews and many cast members. When users come into this site, typically they go directly to the page for a particular TV show. On that page they see all the seasons and all the episodes and all the reviews and all the cast members from that show, all on one page. So from the application perspective, when the user visits a page, we want to retrieve all of the information connected to that TV show.

There are a number of ways you could model this data. In a typical relational store, each of these boxes would be a table. You’d have atv_shows table, a seasons table with a foreign key into tv_shows, an episodes table with a foreign key into seasons, andreviews and cast_members tables with foreign keys into episodes. So to get all the information for a TV show, you’re looking at a five-table join.

We could also model this data as a set of nested hashes. The set of information about a particular TV show is one big nested key/value data structure. Inside a TV show, there’s an array of seasons, each of which is also a hash. Within each season, an array of episodes, each of which is a hash, and so on. This is how MongoDB models the data. Each TV show is a document that contains all the information we need for one show.

Here’s an example document for one TV show, Babylon 5.

It’s got some title metadata, and then it’s got an array of seasons. Each season is itself a hash with metadata and an array of episodes. In turn, each episode has some metadata and arrays for both reviews and cast members.

It’s basically a huge fractal data structure.

Sets of sets of sets of sets. Tasty fractals.

All of the data we need for a TV show is under one document, so it’s very fast to retrieve all this information at once, even if the document is very large. There’s a TV show here in the US called “General Hospital” that has aired over 12,000 episodes over the course of 50+ seasons. On my laptop, PostgreSQL takes about a minute to get denormalized data for 12,000 episodes, while retrieval of the equivalent document by ID in MongoDB takes a fraction of a second.

So in many ways, this application presented the ideal use case for a document store.

Ok. But what about social data?

Right. When you come to a social networking site, there’s only one important part of the page: your activity stream. The activity stream query gets all of the posts from the people you follow, ordered by most recent. Each of those posts have nested information within them, such as photos, likes, reshares, and comments.

The nested structure of activity stream data looks very similar to what we were looking at with the TV shows.

Users have friends, friends have posts, posts have comments and likes, each comment has one commenter and each like has one liker. Relationship-wise, it’s not a whole lot more complicated than TV shows. And just like with TV shows, we want to pull all this data at once, right after the user logs in. Furthermore, in a relational store, with the data fully normalized, it would be a seven-table join to get everything out.

Seven-table joins. Ugh. Suddenly storing each user’s activity stream as one big denormalized nested data structure, rather than doing that join every time, seems pretty attractive.

In 2010, when the Diaspora team was making this decision, Etsy’s articles about using document stores were quite influential, although they’ve since publicly moved away from MongoDB for data storage. Likewise, at the time, Facebook’s Cassandra was also stirring up a lot of conversation about leaving relational databases. Diaspora chose MongoDB for their social data in this zeitgeist. It was not an unreasonable choice at the time, given the information they had.

What could possibly go wrong?

There is a really important difference between Diaspora’s social data and the Mongo-ideal TV show data that no one noticed at first.

With TV shows, each box in the relationship diagram is a different type. TV shows are different from seasons are different from episodes are different from reviews are different from cast members. None of them is even a sub-type of another type.

But with social data, some of the boxes in the relationship diagram are the same type. In fact, all of these green boxes are the same type — they are all Diaspora users.

A user has friends, and each friend may themselves be a user. Or, they may not, because it’s a distributed system. (That’s a whole layer of complexity that I’m just skipping for today.) In the same way, commenters and likers may also be users.

This type duplication makes it way harder to denormalize an activity stream into a single document. That’s because in different places in your document, you may be referring to the same concept — in this case, the same user. The user who liked that post in your activity stream may also be the user who commented on a different post.

Duplicate data Duplicate data

We can represent this in MongoDB in a couple of different ways. Duplication is any easy option. All the information for that friend is copied and saved to the like on the first post, and then a separate copy is saved to the comment on the second post. The advantage is that all the data is present everywhere you need it, and you can still pull the whole activity stream back as a single document.

Here’s what this kind of fully denormalized stream document looks like.

Here we have copies of user data inlined. This is Joe’s stream, and it has a copy of his user data, including his name and URL, at the top level. His stream, just underneath, contains Jane’s post. Joe has liked Jane’s post, so under likes for Jane’s post, we have a separate copy of Joe’s data.

You can see why this is attractive: all the data you need is already located where you need it.

You can also see why this is dangerous. Updating a user’s data means walking through all the activity streams that they appear in to change the data in all those different places. This is very error-prone, and often leads to inconsistent data and mysterious errors, particularly when dealing with deletions.

Is there no hope?

There is another approach you can take to this problem in MongoDB, which will more familiar if you have a relational background. Instead of duplicating user data, you can store references to users in the activity stream documents.

With this approach, instead of inlining this user data wherever you need it, you give each user an ID. Once users have IDs, we store the user’s ID every place that we were previously inlining data. New IDs are in green below.

MongoDB actually uses BSON IDs, which are strings sort of like GUIDs, but to make these samples easier to read I’m just using integers.

This eliminates our duplication problem. When user data changes, there’s only one document that gets rewritten. However, we’ve created a new problem for ourselves. Because we’ve moved some data out of the activity streams, we can no longer construct an activity stream from a single document. This is less efficient and more complex. Constructing an activity stream now requires us to 1) retrieve the stream document, and then 2) retrieve all the user documents to fill in names and avatars.

What’s missing from MongoDB is a SQL-style join operation, which is the ability to write one query that mashes together the activity stream and all the users that the stream references. Because MongoDB doesn’t have this ability, you end up manually doing that mashup in your application code, instead.

Simple Denormalized Data

Let’s return to TV shows for a second. The set of relationships for a TV show don’t have a lot of complexity. Because all the boxes in the relationship diagram are different entities, the entire query can be denormalized into one document with no duplication and no references. In this document database, there are no links between documents. It requires no joins.

On a social network, however, nothing is that self-contained. Any time you see something that looks like a name or a picture, you expect to be able to click on it and go see that user, their profile, and their posts. A TV show application doesn’t work that way. If you’re on season 1 episode 1 of Babylon 5, you don’t expect to be able to click through to season 1 episode 1 of General Hospital.

Don’t. Link. The. Documents.

Once we started doing ugly MongoDB joins manually in the Diaspora code, we knew it was the first sign of trouble. It was a sign that our data was actually relational, that there was value to that structure, and that we were going against the basic concept of a document data store.

Whether you’re duplicating critical data (ugh), or using references and doing joins in your application code (double ugh), when you have links between documents, you’ve outgrown MongoDB. When the MongoDB folks say “documents,” in many ways, they mean things you can print out on a piece of paper and hold. A document may have internal structure — headings and subheadings and paragraphs and footers — but it doesn’t link to other documents. It’s a self-contained piece of semi-structured data.

If your data looks like that, you’ve got documents. Congratulations! It’s a good use case for Mongo. But if there’s value in the links between documents, then you don’t actually have documents. MongoDB is not the right solution for you. It’s certainly not the right solution for social data, where links between documents are actually the most critical data in the system.

So social data isn’t document-oriented. Does that mean it’s actually…relational?

That Word Again

When people say “social data isn’t relational,” that’s not actually what they mean. They mean one of these two things:

1. “Conceptually, social data is more of a graph than a set of tables.”

This is absolutely true. But there are actually very few concepts in the world that are naturally modeled as normalized tables. We use that structure because it’s efficient, because it avoids duplication, and because when it does get slow, we know how to fix it.

2. “It’s faster to get all the data from a social query when it’s denormalized into a single document.”

This is also absolutely true. When your social data is in a relational store, you need a many-table join to extract the activity stream for a particular user, and that gets slow as your tables get bigger. However, we have a well-understood solution to this problem. It’s called caching.

At the All Your Base Conf in Oxford earlier this year, where I gave the talk version of this post, Neha Narula had a great talk about caching that I recommend you watch once it’s posted. In any case, caching in front of a normalized data store is a complex but well-understood problem. I’ve seen projects cache denormalized activity stream data into a document database like MongoDB, which makes retrieving that data much faster. The only problem they have then is cache invalidation.

“There are only two hard problems in computer science: cache invalidation and naming things.”

Phil Karlton

It turns out cache invalidation is actually pretty hard. Phil Karlton wrote most of SSL version 3, X11, and OpenGL, so he knows a thing or two about computer science.

Cache Invalidation As A Service

But what is cache invalidation, and why is it so hard?

Cache invalidation is just knowing when a piece of your cached data is out of date, and needs to be updated or replaced. Here’s a typical example that I see every day in web applications. We have a backing store, typically PostgreSQL or MySQL, and then in front of that we have a caching layer, typically Memcached or Redis. Requests to read a user’s activity stream go to the cache rather than the database directly, which makes them very fast.

Typical cache and backing store setup

Application writes are more complicated. Let’s say a user with two followers writes a new post. The first thing that happens (part 1) is that the post data is copied into the backing store. Once that completes, a background job (part 2)  appends that post to the cached activity stream of both of the users who follow the author.

This pattern is quite common. Twitter holds recently-active users’ activity streams in an in-memory cache, which they append to when someone they follow posts something. Even smaller applications that employ some kind of activity stream will typically end up here (see: seven-table join).

Back to our example. When the author changes an existing post, the update process is essentially the same as for a create, except instead of appending to the cache, it updates an item that’s already there.

What happens if that step 2 background job fails partway through? Machines get rebooted, network cables get unplugged, applications restart. Instability is the only constant in our line of work. When that happens, you’ll end up with invalid data in your cache. Some copies of the post will have the old title, and some copies will have the new title. That’s a hard problem, but with a cache, there’s always the nuclear option.

Always an option >_<

You can always delete the entire activity stream record out of your cache and regenerate it from your consistent backing store. It may be slow, but at least it’s possible.

What if there is no backing store? What if you skip step 1? What if the cache is all you have?

When MongoDB is all you have, it’s a cache with no backing store behind it. It will become inconsistent. Not eventually consistent — just plain, flat-out inconsistent, for all time. At that point, you have no options. Not even a nuclear one. You have no way to regenerate the data in a consistent state.

When Diaspora decided to store social data in MongoDB, we were conflating a database with a cache. Databases and caches are very different things. They have very different ideas about permanence, transience, duplication, references, data integrity, and speed.

The Conversion

Once we figured out that we had accidentally chosen a cache for our database, what did we do about it?

Well, that’s the million dollar question. But I’ve already answered the billion-dollar question. In this post I’ve talked about how we used MongoDB vs. how it was designed to be used. I’ve talked about it as though all that information were obvious, and the Diaspora team just failed to research adequately before choosing.

But this stuff wasn’t obvious at all. The MongoDB docs tell you what it’s good at, without emphasizing what it’s not good at. That’s natural. All projects do that. But as a result, it took us about six months, a lot of user complaints, and a lot of investigation to figure out that we were using MongoDB the wrong way.

There was nothing to do but take the data out of MongoDB and move it to a relational store, dealing as best we could with the inconsistent data we uncovered along the way. The data conversion itself — export from MongoDB, import to MySQL — was straightforward. For the mechanical details, you can see my slides from All Your Base Conf 2013.

The Damage

We had eight months of production data, which turned into about 1.2 million rows in MySQL. We spent four pair-weeks developing the code for the conversion, and when we pulled the trigger, the main site had about two hours of downtime. That was more than acceptable for a project that was in pre-alpha. We could have reduced that downtime more, but we had budgeted for eight hours of downtime, so two actually seemed fantastic.

NOT BAD

Epilogue

Remember that TV show application? It was the perfect use case for MongoDB. Each show was one document, perfectly self-contained. No references to anything, no duplication, and no way for the data to become inconsistent.

About three months into development, it was still humming along nicely on MongoDB. One Monday, at the weekly planning meeting, the client told us about a new feature that one of their investors wanted: when they were looking at the actors in an episode of a show, they wanted to be able to click on an actor’s name and see that person’s entire television career. They wanted a chronological listing of all of the episodes of all the different shows that actor had ever been in.

We stored each show as a document in MongoDB containing all of its nested information, including cast members. If the same actor appeared in two different episodes, even of the same show, their information was stored in both places. We had no way to tell, aside from comparing the names, whether they were the same person. So to implement this feature, we needed to search through every document to find and de-duplicate instances of the actor that the user clicked on. Ugh. At a minimum, we needed to de-dup them once, and then maintain an external index of actor information, which would have the same invalidation issues as any other cache.

You See Where This Is Going

The client expected this feature to be trivial. If the data had been in a relational store, it would have been. As it was, we first tried to convince the PM they didn’t need it. After that failed, we offered some cheaper alternatives, such as linking to an IMDB search for the actor’s name. The company made money from advertising, though, so they wanted users to stay on their site rather than going off to IMDB.

This feature request eventually prompted the project’s conversion to PostgreSQL. After a lot more conversation with the client, we realized that the business saw lots of value in linking TV shows together. They envisioned seeing other shows a particular director had been involved with, and episodes of other shows that were released the same week this one was, among other things.

This was ultimately a communication problem rather than a technical problem. If these conversations had happened sooner, if we had taken the time to really understand how the client saw the data and what they wanted to do with it, we probably would have done the conversion earlier, when there was less data, and it was easier.

Always Be Learning

I learned something from that experience: MongoDB’s ideal use case is even narrower than our television data. The only thing it’s good at is storing arbitrary pieces of JSON. “Arbitrary,” in this context, means that you don’t care at all what’s inside that JSON. You don’t even look. There is no schema, not even an implicit schema, as there was in our TV show data. Each document is just a blob whose interior you make absolutely no assumptions about.

At RubyConf this weekend, I ran into Conrad Irwin, who suggested this use case. He’s used MongoDB to store arbitrary bits of JSON that come from customers through an API. That’s reasonable. The CAP theorem doesn’t matter when your data is meaningless. But in interesting applications, your data isn’t meaningless.

I’ve heard many people talk about dropping MongoDB in to their web application as a replacement for MySQL or PostgreSQL. There are no circumstances under which that is a good idea. Schema flexibility sounds like a great idea, but the only time it’s actually useful is when the structure of your data has no value. If you have an implicit schema — meaning, if there are things you are expecting in that JSON — then MongoDB is the wrong choice. I suggest taking a look at PostgreSQL’s hstore (now apparently faster than MongoDBanyway), and learning how to make schema changes. They really aren’t that hard, even in large tables.

Find The Value

When you’re picking a data store, the most important thing to understand is where in your data — and where in its connections — the business value lies. If you don’t know yet, which is perfectly reasonable, then choose something that won’t paint you into a corner. Pushing arbitrary JSON into your database sounds flexible, but true flexibility is easily adding the features your business needs.

Make the valuable things easy.

The End.