2015-06-07 18 views
24

Tôi đã đọc tài liệu Firebase trên Stucturing Data. Lưu trữ dữ liệu rẻ, nhưng thời gian của người dùng thì không. Chúng ta nên tối ưu hóa để có được hoạt động, và viết ở nhiều nơi.Cách ghi dữ liệu không chuẩn hóa trong Firebase

Vì vậy, sau đó tôi có thể lưu trữ một danh sách nút và một nút danh sách-index, với một số dữ liệu trùng lặp giữa hai, tại rất ít nhất tên danh sách.

Tôi đang sử dụng ES6 và lời hứa trong ứng dụng javascript của mình để xử lý luồng không đồng bộ, chủ yếu là tìm nạp khóa ref từ firebase sau lần đẩy dữ liệu đầu tiên.

let addIndexPromise = new Promise((resolve, reject) => { 
    let newRef = ref.child('list-index').push(newItem); 
    resolve(newRef.key()); // ignore reject() for brevity 
}); 
addIndexPromise.then(key => { 
    ref.child('list').child(key).set(newItem); 
}); 

Làm thế nào để đảm bảo rằng dữ liệu vẫn đồng bộ trong tất cả các nơi, biết ứng dụng của tôi chỉ chạy trên máy khách?

Để kiểm tra tính chính xác, tôi đặt setTimeout trong lời hứa và tắt trình duyệt trước khi giải quyết, và cơ sở dữ liệu của tôi không còn phù hợp nữa, với chỉ mục bổ sung được lưu mà không có danh sách tương ứng.

Bạn có lời khuyên nào không?

+3

* Tôi sẽ viết một câu trả lời thích hợp bên dưới. * Câu đầu tiên đó làm tôi hạnh phúc. Bạn có thể lặp lại nó tại bữa tiệc sinh nhật không? Đặc biệt là khi bạn bè của dev có mặt. :-) –

+0

điều này dường như có liên quan, cũng .. https://stackoverflow.com/questions/47334382 – Fattie

Trả lời

47

Câu hỏi hay. Tôi biết ba cách tiếp cận này, mà tôi sẽ liệt kê dưới đây.

Tôi sẽ lấy một ví dụ hơi khác cho ví dụ này, chủ yếu là vì nó cho phép tôi sử dụng các thuật ngữ cụ thể hơn trong phần giải thích.

Giả sử chúng tôi có một ứng dụng trò chuyện, nơi chúng tôi lưu trữ hai thực thể: tin nhắn và người dùng. Trong màn hình hiển thị thông báo, chúng tôi cũng hiển thị tên người dùng. Vì vậy, để giảm thiểu số lần đọc, chúng tôi cũng lưu trữ tên người dùng với từng tin nhắn trò chuyện.

users 
    so:209103 
    name: "Frank van Puffelen" 
    location: "San Francisco, CA" 
    questionCount: 12 
    so:3648524 
    name: "legolandbridge" 
    location: "London, Prague, Barcelona" 
    questionCount: 4 
messages 
    -Jabhsay3487 
    message: "How to write denormalized data in Firebase" 
    user: so:3648524 
    username: "legolandbridge" 
    -Jabhsay3591 
    message: "Great question." 
    user: so:209103 
    username: "Frank van Puffelen" 
    -Jabhsay3595 
    message: "I know of three approaches, which I'll list below." 
    user: so:209103 
    username: "Frank van Puffelen" 

Vì vậy, chúng tôi lưu trữ bản sao chính tiểu sử của người dùng trong nút users. Trong thông báo, chúng tôi lưu trữ uid (vì vậy: 209103 và như vậy: 3648524) để chúng tôi có thể tra cứu người dùng. Nhưng chúng tôi cũng lưu trữ tên của người dùng trong thư để chúng tôi không phải tìm kiếm điều này cho từng người dùng khi chúng tôi muốn hiển thị danh sách thư.

Vì vậy, bây giờ điều gì xảy ra khi tôi truy cập trang Tiểu sử trên dịch vụ trò chuyện và đổi tên của tôi từ "Frank van Puffelen" thành "puf".

Cập nhật giao dịch

Thực hiện cập nhật giao dịch là bản cập nhật có thể xuất hiện trong hầu hết các nhà phát triển. Chúng tôi luôn muốn có username trong tin nhắn để khớp với name trong cấu hình tương ứng.

Sử dụng đa viết (gia tăng đối với 20.150.925)

Kể từ khi căn cứ hỏa lực 2.3 (cho JavaScript) và 2.4 (dành cho Android và iOS), bạn có thể đạt được thông tin cập nhật nguyên tử khá dễ dàng bằng cách sử dụng một bản cập nhật đa duy nhất:

function renameUser(ref, uid, name) { 
    var updates = {}; // all paths to be updated and their new values 
    updates['users/'+uid+'/name'] = name; 
    var query = ref.child('messages').orderByChild('user').equalTo(uid); 
    query.once('value', function(snapshot) { 
    snapshot.forEach(function(messageSnapshot) { 
     updates['messages/'+messageSnapshot.key()+'/username'] = name; 
    }) 
    ref.update(updates); 
    }); 
} 

Điều này sẽ gửi một lệnh cập nhật tới Firebase cập nhật tên người dùng trong tiểu sử của họ và trong mỗi thư.

trước cách tiếp cận nguyên tử

Vì vậy, khi sự thay đổi người sử dụng là name trong hồ sơ của họ:

var ref = new Firebase('https://mychat.firebaseio.com/'); 
var uid = "so:209103"; 
var nameInProfileRef = ref.child('users').child(uid).child('name'); 
nameInProfileRef.transaction(function(currentName) { 
    return "puf"; 
}, function(error, committed, snapshot) { 
    if (error) { 
    console.log('Transaction failed abnormally!', error); 
    } else if (!committed) { 
    console.log('Transaction aborted by our code.'); 
    } else { 
    console.log('Name updated in profile, now update it in the messages'); 
    var query = ref.child('messages').orderByChild('user').equalTo(uid); 
    query.on('child_added', function(messageSnapshot) { 
     messageSnapshot.ref().update({ username: "puf" }); 
    }); 
    } 
    console.log("Wilma's data: ", snapshot.val()); 
}, false /* don't apply the change locally */); 

Khá liên quan và người đọc tinh ý sẽ nhận thấy rằng tôi lừa gạt trong việc xử lý các thông điệp. Cheat đầu tiên là tôi không bao giờ gọi off cho người nghe, nhưng tôi cũng không sử dụng một giao dịch.

Nếu chúng ta muốn một cách an toàn làm kiểu này hoạt động từ khách hàng, chúng tôi cần:

quy tắc an ninh
  1. đảm bảo các tên trong cả hai trận đấu nơi. Nhưng các quy tắc cần cho phép đủ linh hoạt để chúng tạm thời khác nhau trong khi chúng tôi thay đổi tên. Vì vậy, điều này biến thành một chương trình cam kết hai pha khá đau đớn.
    1. thay đổi tất cả username lĩnh vực cho các tin nhắn bằng so:209103 để null (một số giá trị kỳ diệu)
    2. thay đổi các name về thành viên so:209103 để 'PUF'
    3. thay đổi các username trong mỗi tin nhắn của so:209103 đó là null để puf.
    4. truy vấn đó yêu cầu and trong hai điều kiện mà truy vấn Firebase không hỗ trợ. Vì vậy, chúng tôi sẽ kết thúc với một tài sản bổ sung uid_plus_name (với giá trị so:209103_puf) mà chúng tôi có thể truy vấn.
  2. mã phía máy khách xử lý tất cả các chuyển tiếp này một cách giao dịch.

Cách tiếp cận này làm tôi đau đầu. Và thường có nghĩa là tôi đang làm điều gì sai. Nhưng ngay cả khi đó là cách tiếp cận đúng đắn, với một cái đầu làm tôi đau đớn hơn thì tôi có nhiều khả năng mắc lỗi mã hóa hơn. Vì vậy, tôi thích tìm một giải pháp đơn giản hơn.

nhất cuối cùng

Cập nhật (20.150.925): căn cứ hỏa lực phát hành một tính năng cho phép ghi nguyên tử để nhiều đường. Điều này hoạt động tương tự như cách tiếp cận bên dưới, nhưng với một lệnh duy nhất. Xem phần cập nhật ở trên để đọc cách hoạt động của phần này.

Cách tiếp cận thứ hai phụ thuộc vào việc chia nhỏ hành động của người dùng ("Tôi muốn đổi tên thành 'puf'") từ các tác động của hành động đó ("Chúng tôi cần cập nhật tên trong tiểu sử như vậy: 209103 và trong mọi thư có user = so:209103)

tôi muốn xử lý các đổi tên trong một kịch bản mà chúng tôi chạy trên một máy chủ phương pháp chính sẽ là một cái gì đó như thế này:..

function renameUser(ref, uid, name) { 
    ref.child('users').child(uid).update({ name: name }); 
    var query = ref.child('messages').orderByChild('user').equalTo(uid); 
    query.once('value', function(snapshot) { 
    snapshot.forEach(function(messageSnapshot) { 
     messageSnapshot.update({ username: name }); 
    }) 
    }); 
} 

một lần nữa tôi mất một vài phím tắt ở đây, chẳng hạn như sử dụng once('value' (nói chung là một ý tưởng tồi để có hiệu suất tối ưu với Firebase). e chi phí không có tất cả dữ liệu được cập nhật hoàn toàn cùng một lúc. Nhưng cuối cùng các tin nhắn sẽ được cập nhật để phù hợp với giá trị mới.

Không chăm sóc

Cách tiếp cận thứ ba là đơn giản nhất của tất cả: trong nhiều trường hợp bạn không thực sự cần phải cập nhật các dữ liệu trùng lặp ở tất cả. Trong ví dụ chúng tôi đã sử dụng ở đây, bạn có thể nói rằng mỗi tin nhắn đã ghi lại tên như tôi đã sử dụng vào thời điểm đó. Tôi đã không thay đổi tên của tôi cho đến bây giờ, do đó, nó có ý nghĩa rằng tin nhắn cũ cho thấy tên tôi được sử dụng tại thời điểm đó. Điều này áp dụng trong nhiều trường hợp trong đó dữ liệu thứ cấp là giao dịch trong tự nhiên. Nó không áp dụng ở khắp mọi nơi tất nhiên, nhưng nơi mà nó áp dụng "không quan tâm" là cách tiếp cận đơn giản nhất của tất cả.

Tóm tắt

Trong khi ở trên là những mô tả chỉ rộng như thế nào bạn có thể giải quyết vấn đề này và họ chắc chắn chưa hoàn chỉnh, tôi thấy rằng mỗi khi tôi cần phải quạt ra dữ liệu trùng lặp nó trở lại với một trong những cơ bản phương pháp tiếp cận.

+1

Cảm ơn bạn Frank vì câu trả lời tuyệt vời! Thật vậy, tôi đã đi theo phương pháp 'Không chăm sóc'. Tôi đã chuyển đổi các ops viết của mình để item xuất hiện trước chỉ mục, vì vậy tôi không mạo hiểm có một mục trong danh sách liên kết đến một nơi nào đó mà không có dữ liệu (nhưng bây giờ tôi có thể kết thúc với một mục danh sách mồ côi, một vấn đề lớn). – legolandbridge

+0

Câu trả lời tuyệt vời như vậy. Lời khuyên bạn đã vạch ra là siêu hữu ích và áp dụng cho nhiều trường hợp, không chỉ là firebase. –

+1

Cẩn thận tho đảm bảo trì hoãn nhiệm vụ máy chủ trong * tính nhất quán cuối cùng * và kiểm tra xem tên chưa được thay đổi chưa. – AJcodez

2

Để thêm vào câu trả lời tuyệt vời của Franks, tôi đã triển khai cách tiếp cận nhất quán cuối cùng với tập hợp Firebase Cloud Functions. Các hàm được kích hoạt bất cứ khi nào một giá trị chính (ví dụ tên người dùng) bị thay đổi, và sau đó truyền các thay đổi cho các trường không chuẩn hóa.

Nó không phải là nhanh như một giao dịch, nhưng đối với nhiều trường hợp nó không cần phải được.

+1

Tuyệt vời bổ sung Uffe. Chức năng đám mây là điều tuyệt vời cho điều đó, miễn là bạn không có yêu cầu thời gian thực hoặc ngoại tuyến nghiêm ngặt. –

Các vấn đề liên quan